zoukankan      html  css  js  c++  java
  • 应用调试(五)侵入式SWI


    title: 应用调试(五)侵入式SWI
    date: 2019/01/19 21:27:31
    toc: true

    应用调试(五)侵入式SWI

    场景应用

    1. 反汇编源程序,查看汇编代码,然后直接修改二进制的bin文件,替换其中一条A指令为B指令,B指令是swi跳转指令
    2. 构造一个SWI跳转,加入到内核中,这个SWI跳转可以用来打印变量等,然后完成原来A指令需要完成的事情
    3. 直接运行修改后的文件,也就是说程序执行到A指令时,先去执行内部的SWI跳转,然后执行A指令
    4. 整个过程实际上和我们调试的软件断点是很像的

    为什么需要这么调试?不直接修改APP程序更方便?

    这个应用我个人觉得应该是破解程序用的,不然修改APP多省事,或者直接弄个驱动能够操作内核,APP去调用这个驱动函数就好了.

    应该是有个APP,那么这个时候我们只有执行程序,没有源代码,我们就可以大概看下想破解什么的,,如果直接加入代码的话,有些地址相关的指令可能就不对的,这种直接替换的方式,也不会破坏地址空间.

    但是 破解应该有更好的工具,暂时没想到有什么绝佳的用处.

    测试程序

    这里写一个正常的测试程序如下,这里使用了sleep是因为直接快速打印,估计缓存区有优先级后两个不同的进程打印不一致

    #include <stdio.h>
    #include <unistd.h>
    int cnt = 0;
    void C(void)
    {
    	int i = 0;
    
    	while (1)
    	{
    		printf("Hello, cnt = %d, i = %d
    ", cnt, i);
    		cnt++;
    		i = i + 2;
    		sleep(5);
    	}
    }
    void B(void){C();}
    void A(void){B();}
    int main(int argc, char **argv)
    {
    	A();
    	return 0;
    }
    

    现在假设我们需要在i = i+ 2;这里设置断点,因为这个汇编语句简单,反汇编文件

    000084c0 <C>:
        84c0:	e1a0c00d 	mov	ip, sp
        84c4:	e92dd800 	stmdb	sp!, {fp, ip, lr, pc}
        84c8:	e24cb004 	sub	fp, ip, #4	; 0x4
        84cc:	e24dd004 	sub	sp, sp, #4	; 0x4
    ....
        84fc:	e5823000 	str	r3, [r2]
        8500:	e51b3010 	ldr	r3, [fp, #-16]		;这里获取局部变量i的值
        8504:	e2833002 	add	r3, r3, #2	; 0x2   ;这个就是i=i+2
    ...
    

    修改APP的bin

    修改为SWI指令,可以参考上一节的汇编,或者看下SWI的指令格式

    // 上一节的指令格式如下
    84b8:	ef900160 	swi	0x00900160
    

    mark

    所以 也就是修改二进制文件中的e2833002ef900160,注意下小端模式低字节在低地址也就是搜索02 30 83 e2替换为60 01 90 ef

    mark

    修改SWI

    在上一个小结的基础上修改sys_hello,在fs/read_write.c

    • 查看下全局变量cnt,我们可以在dis中查看cnt的地址

      000107c8 <cnt>:
         107c8:	00000000 	andeq	r0, r0, r0
      
    • 局部变量的值怎么看? 看到汇编i的运算,也就是存在[fp-16]

          8500:	e51b3010 	ldr	r3, [fp, #-16]					;这里获取局部变量i的值
          8504:	e2833002 	add	r3, r3, #2	; 0x2				;替换指令在这里
      

    接下来我们打印全局变量cnt和局部变量i,这里看下汇编知道是r3

    asmlinkage void sys_hello(char __user * buf, size_t count)
    {
    	int val;
    	struct pt_regs *regs;
    	
    	/* 1.输出一些调试信息 */
    	/* 这里我们输出应用程序中的cnt值,在反汇编文件test_sc.dis中搜cnt的cnt的地址为0x00010788 */
    	copy_from_user(&val, (const void __user *)0x000107c4,4);
    	printk("sys_hello : cnt = %d 
    ",val);
     
    	/* 2. 执行被替代的指令 */
    	regs = task_pt_regs(current);
    	regs->ARM_r3 += 2;
            /* 获得应用程序中C函数局部变量i的值 */
    	copy_from_user(&val,(const void __user *)(regs->ARM_fp - 16),4);
    	printk("sys_hello : i = %d 
    ",val);
     
    	/* 3. 返回 */
    }
    
    

    获得当前进程的寄存器

    上述的例子中需要获得寄存器的值,使用task_pt_regs(current)可以获得当前进程的寄存器值.当前进程就是发生swi前应用程序的进程。

    regs = task_pt_regs(current);
    
    #define task_pt_regs(p) 
    	((struct pt_regs *)(THREAD_START_SP + task_stack_page(p)) - 1)
    

    测试运行

    可以看到SWI中打印出来了全局变量cnt,因为函数是先打印cnt,然后cnt+1,所以SWIcntapp打印的大1,局部变量iapp打印后swi打印,然后+2,所以app的比swii一致

    # chmod +x test_sc_sleep_swi
    # ./test_sc_sleep_swi
    Hello, cnt = 0, i = 0
    sys_hello : cnt = 1
    sys_hello : i = 0
    ######################################
    # 系统调用cnt 比app的大1,与上一次的i值相同
    #####################################
    
    ###↓↓↓↓ 接下去是第二轮
    Hello, cnt = 1, i = 2
    sys_hello : cnt = 2
    sys_hello : i = 2
    Hello, cnt = 2, i = 4
    sys_hello : cnt = 3
    sys_hello : i = 4
    Hello, cnt = 3, i = 6
    sys_hello : cnt = 4
    sys_hello : i = 6
    

    恢复代码 进程间内存拷贝

    代码是放在内存里的,我们可以在执行一段时间后恢复这段代码,可以看到指令地址在0x8504

    这里使用了函数access_process_vm来将当前进程的一段内存内容拷贝到另一个进程的内存中

    // 8504:	e2833002 	add	r3, r3, #2	; 0x2				;替换指令在这里 
    static int cnt = 0;
    int ret;
    if (++cnt == 5)
    {
        copy_from_user(&val, (const void __user *)0x8504, 4);
        printk("[0x8504] code = 0x%x
    ", val);
        printk("regs->ARM_lr  = 0x%x
    ", regs->ARM_lr);
        val = 0xe2833002;   //原来正确的代码
        ret = access_process_vm(current, 0x8504, &val, 4, 1);
        printk("access_process_vm ret = %d
    ", ret);
        cnt = 0;
    }
    

    接着测试下是否生效,确实5次后就不再打印sys_hello

    # mount -t nfs -o nolock,vers=2 192.168.95.222:/home/book/stu /mnt
    # /mnt/code/test_sc_sleep_swi
    Hello, cnt = 0, i = 0
    sys_hello : cnt = 1
    sys_hello : i = 0
    Hello, cnt = 1, i = 2
    sys_hello : cnt = 2
    sys_hello : i = 2
    Hello, cnt = 2, i = 4
    sys_hello : cnt = 3
    sys_hello : i = 4
    Hello, cnt = 3, i = 6
    sys_hello : cnt = 4
    sys_hello : i = 6
    Hello, cnt = 4, i = 8
    sys_hello : cnt = 5
    sys_hello : i = 8
    [0x8504] code = 0xef900160
    regs->ARM_lr  = 0x84ec
    access_process_vm ret = 4
    #####下面不再打印sys_hello了
    
    Hello, cnt = 5, i = 10
    Hello, cnt = 6, i = 12
    
    

    TODO 更多参考文献

    下面的文章没怎么仔细看,先放在这里等以后水平上来了再瞅瞅,标记下 @Todo

    课堂笔记 应用调试:自制系统调用,并编写进程查看器

    浅析基于ARM的Linux下的系统调用的实现

    ARM Linux上的系统调用代码分析

    从glibc源码看系统调用原理

    Arm Linux系统调用流程详细解析

  • 相关阅读:
    用于创建和管理 Azure 虚拟机的常用 PowerShell 命令
    在 Azure Resource Manager 中为虚拟机设置密钥保管库
    使用 Azure 资源管理器向 Windows VM 应用策略
    Azure 门户中基于角色的访问控制入门
    为 Azure Resource Manager 中的虚拟机设置 WinRM 访问权限
    如何加密 Windows VM 上的虚拟磁盘
    适用于 Windows VM 的 Azure 示例基础结构演练
    Azure 中虚拟机的备份和还原选项
    1.1 基本算法和记号
    tomcat的class加载的优先顺序
  • 原文地址:https://www.cnblogs.com/zongzi10010/p/10293338.html
Copyright © 2011-2022 走看看