x86程序内存动态补丁拦截

这个算是之前关于外挂框架的一个继续,但主要是说程序的内存补丁的,适用于无法获取到 源代码而又想拦截或者调用目标程序中的相关指令或函数的方法。

一般情况下,我们使用长跳转指令0xe9(JMP XXXXXX)的方式跳转到我们自己的dll函数中去 执行代码,不同于很早很早之前的外挂教程使用的是消息通知的方法,这里其实也算是使用 跳转,但区别在于我们不用计算跳转的偏移。

先说下一般的跳转方式,这里是原始的一段代码的汇编语句:

0048DAEC  |. E8 BBEFFFFF    CALL test.0048CAAC
0048DAF1  |. 84C0           TEST AL,AL
0048DAF3  |. 74 61          JE SHORT test.0048DB56
0048DAF5  |. 8D4D FC        LEA ECX,DWORD PTR SS:[EBP-4]

我们发现,在调用0x0048DAEC的时候,我们想要的数据的地址保存在eax的寄存器中,因此, 我们需要将这个数据拦截下来并保存到其他地方。

因此,我们需要代码做如下的执行方式:

0048DAEC  |. E9 XXXXXX jmp hook.test
0048DAF1  |. 84C0           TEST AL,AL
0048DAF3  |. 74 61          JE SHORT test.0048DB56
0048DAF5  |. 8D4D FC        LEA ECX,DWORD PTR SS:[EBP-4]

XXXXXX 的值需要计算, 可以使用下面的代码来进行:

m_realProc = (DWORD)(test) - 0x0048DAED -5;

然后,将m_realProc的值写入0x0048DAED(即0x0048DAEC+1)的位置即可。 下面是test指令部分的操作(也是汇编代码)

    .code32
    .section .text
    .global _test

_test:
    .byte 0x60
   push %eax                    
    push $_keep_buf
    call _strcpy
    add $0x8, %esp
    .byte 0x61
    call $0x0048CAAC
    test %al, %al
    jmp $0x0048DAF3
    ret

上面的代码中,0x60表示pushad,0x61表示popad,用来保存和恢复寄存器的,上面的代码 并不能正确的执行,因为call和jmp都是相对地址的,在目标程序中,这个地址是有一定的 偏差的,然后,你需要对这两个位置重新做计算,上面的代码也并不能编译,语法错误, call和jmp并不允许这样子来写,只是一个示意。

操作实在麻烦,虽然调试正确之后就不用再弄了。但调试也是一种痛苦啊。问题的关键在于 无法直接进行绝对地址的操作,既然不能直接,那就间接的来了,我想到了call操作的方式, push eip,jmp;而ret其实就是pop eip,因此,对于jmp指令,我们可以使用push+ret来间接 实现,关键在于,使用push可以操作立即数。因此,原来的代码打过内存补丁之后,就变成 了下面的样子:

0048DAEC  |. 68 XXXXXXXX    push hook.test
0048DAF1  |. c3          ret
0048DAF2  |. 90           nop
0048DAF3  |. 74 61          JE SHORT test.0048DB56
0048DAF5  |. 8D4D FC        LEA ECX,DWORD PTR SS:[EBP-4]

写内存的差不多是7个字节(push 5字节+ret 1字节+nop 1字节),而关键的是,push后面 跟的是绝对地址。写入0x0048DAEC的内容可以这样子来生成:

char buf[7];
buf[0] = 0x68;
*((DWORD*)(buf+1)) = (DWORD)test;
buf[5] = 0xc3;
buf[6] = 0x90;

简单而且通用,可以封装成函数,直接调用函数即可。对于我们的拦截的代码,是否也可以 这样子来进行,当然没问题:

    .code32
    .section .text
    .global _test

_test:
    .byte 0x60
   push %eax                    
    push $_keep_buf
    call _strcpy
    add $0x8, %esp
    .byte 0x61
    push $jmp_addr
    push $0x0048CAAC
    ret
jmp_addr: 
    test %al, %al
    push $0x0048DAF3
    ret

原来的call 0x0048CAAC变成了push操作,并且在之前多了一次push,即push $jmp_addr, 这样的操作是因为call 0x0048CAAC之后还有指令,为了让0x0048CAAC函数返回时直接返回 正确的地址,多了一次push操作,这样函数返回之后,就自动跳转到了jmp_addr了。

这种写法的好处就是写patch可以不用去处心积虑的计算了,可以更安全,更快。