首页
论坛
课程
招聘
[原创]小菜关于VC6release版本程序无法HOOK目标API的问题的探究
2013-4-30 15:01 6470

[原创]小菜关于VC6release版本程序无法HOOK目标API的问题的探究

2013-4-30 15:01
6470
《windows程序设计》中有一节是关于HOOK API的实现,它拦截主模块中对于目标API的调用并替换成自己的函数。关键代码如下:

//主函数
::MessageBox(NULL, "原函数", "HookDemo", 0);//覆盖前调用一次MessageBoxA
SetHook(::GetModuleHandle(NULL));//覆盖主模块对MessageBoxA的调用
::MessageBox(NULL, "原函数", "09HookDemo", 0);//再次调用MessageBoxA

//SetHook函数去保护并覆盖掉MessageBoxA的地址
DWORD dwOldProtect;
MEMORY_BASIC_INFORMATION mbi;
VirtualQuery(lpAddr,&mbi,sizeof(mbi));
VirtualProtect(lpAddr,sizeof(DWORD),PAGE_EXECUTE_READWRITE,&dwOldProtect);
DWORD* lpNewProc = (DWORD*)MyMessageBoxA;
::WriteProcessMemory(::GetCurrentProcess(),lpAddr, &lpNewProc, sizeof(DWORD), NULL);
VirtualProtect(lpAddr,sizeof(DWORD),dwOldProtect,0);

        此程序再VC6中编译成debug版本正常运行并能成功Hook掉MessageBoxA,但是编译成release版本后再调用SetHook函数后就无法实现对MessageBoxA的hook,vs2008ralease版貌似也有这种问题,现分析如下:
  Vc6debug版本的程序中的.idata节是可写的,我们可以不用VirtualProtect而直接用WriteProcessMemory覆盖掉目标API的地址,但是在release版本下此节被编译器优化成了只读属性,所以在release下必须要用VirtualProtect加上写属性才能写内存成功,这是《windows程序设计》这本书给出的答案。
  但是,这样在release下任然不能hook掉MessageBoxA函数,程序调用的还是SetHook函数调用前的MessageBoxA函数。。。。。
  怎么办呢?下面我将对两个版本的文件分别进行反编译,如下:
Debug版本汇编代码:
第一次调用MessageBoxA函数:

……
18:       // 调用原API函数
19:       ::MessageBox(NULL, "原函数", "HookDemo", 0);
004010C8   mov         esi,esp
004010CA   push        0
004010CC   push        offset string "HookDemo" (00422024)
004010D1   push        offset string "\xd4\xad\xba\xaf\xca\xfd" (0042201c)
004010D6   push        0
004010D8   call        dword ptr [__imp__MessageBoxA@16 (0042a2d4)]//这是第一次调用MessageBoxA函数
004010DE   cmp         esi,esp
004010E0   call        __chkesp (004013e0)
……

第二次调用MessageBoxA函数:

35:       // 挂钩后再调用
37:       ::MessageBox(NULL, "原函数", "HookDemo", 0);
004010FF   mov         esi,esp
00401101   push        0
00401103   push        offset string "HookDemo" (00422024)
00401108   push        offset string "\xd4\xad\xba\xaf\xca\xfd" (0042201c)
0040110D   push        0
0040110F   call        dword ptr [__imp__MessageBoxA@16 (0042a2d4)] //这是第一次调用MessageBoxA函数
00401115   cmp         esi,esp
00401117   call        __chkesp (004013e0)
……

        从上面对API的两次调用可以看出来,两次都是取得IAT中的函数地址0042a2d4然后再以此为地址取出真正MessageBoxA地址,这样的话IAT中的项再调用SetHook函数时已经被覆盖掉了,所以当然能够实现拦截。

下面再来看release版本程序的反编译代码:

//这是OD反汇编后的代码
00401020  /$  56            PUSH ESI
00401021  |.  8B35 C0604000 MOV ESI,DWORD PTR DS:[<&USER32.MessageBo>;  USER32.MessageBoxA
//下面四个PUSH为MessageBoxA函数参数,由于是遵循_cedel调用约定,参数从右往左
00401027  |.  6A 00         PUSH 0          ; /Style = MB_OK|MB_APPLMODAL
00401029  |.  68 38704000   PUSH 09HookDe.00407038        ; |Title = "HookDemo"
0040102E  |.  68 30704000   PUSH 09HookDe.00407030        ; |Text = "原函数"
00401033  |.  6A 00         PUSH 0                        ; |hOwner = NULL
//这是第一次调用MessageBoxA函数
00401035  |.  FFD6          CALL ESI                       ; \MessageBoxA
00401037  |.  6A 00         PUSH 0                        ; /pModule = NULL
//取GetModuleHandleA地址
00401039  |.  FF15 00604000 CALL DWORD PTR DS:[<&KERNEL32.GetModuleH>; \GetModuleHandleA
0040103F  |.  50            PUSH EAX
//调用SetHook函数
00401040  |.  E8 3B000000   CALL 09HookDe.00401080
00401045  |.  83C4 04       ADD ESP,4
00401048  |.  6A 00         PUSH 0
0040104A  |.  68 38704000   PUSH 09HookDe.00407038       ;  ASCII "HookDemo"
0040104F  |.  68 30704000   PUSH 09HookDe.00407030
00401054  |.  6A 00         PUSH 0
//第二次调用MessageBoxA函数
00401056  |.  FFD6          CALL ESI
00401058  |.  5E            POP ESI
00401059  \.  C3            RETN
……

       可以看出来在第一次调用MessageBoxA时将导入项(IAT里的地址)存入了ESI寄存器中,而再SetHook函数中是覆盖的导入表中目标API中的地址,并没有取改变寄存器ESI中的值,而两次对MessageBoxA的调用都是call  ESI,所以在release版本下是不能HOOK的。因为在这种情况下.idata是只读的,所以编译器为了能够更快的调用API干脆在第一次调用时在找到导入项后就将其保存在了寄存器(通常是esi)中,所以尽管我们已经在导入表将MessageBoxA的地址覆盖掉了,但是SetHook执行之后任然会调用寄存器esi中保存的导入项。
     
        那么,我们如何能够在release下成功hook呢?既然寄存器可以保存函数地址,而且寄存器也就这么几个,那么为了强迫PE装载器能够去导入表查找MessageBoxA的地址,我们可以写一些无关紧要的API去占用寄存器的空间,例如:

  GetProcessHeap();GetCurrentProcess();GetCurrentThread();GetThreadLocale();
  GetProcessHeap();GetCurrentProcess();GetCurrentThread();GetThreadLocale();
  GetProcessHeap();GetCurrentProcess();GetCurrentThread();GetThreadLocale();
  GetProcessHeap();GetCurrentProcess();GetCurrentThread();GetThreadLocale();
  GetProcessHeap();GetCurrentProcess();GetCurrentThread();GetThreadLocale();

……

  再来用OD载入查看下它的反编译代码,如下:

//保存寄存器的值
00401020  /$  53            PUSH EBX
00401021  |.  55            PUSH EBP
00401022  |.  56            PUSH ESI
00401023  |.  57            PUSH EDI
00401024  |.  6A 00         PUSH 0               ; /Style = MB_OK|MB_APPLMODAL
00401026  |.  68 38704000   PUSH 09HookDe.00407038        ; |Title = "HookDemo"
0040102B  |.  68 30704000   PUSH 09HookDe.00407030        ; |Text = "原函数"
00401030  |.  6A 00         PUSH 0                        ; |hOwner = NULL
//调用第一次USER32.MessageBoxA
00401032  |.  FF15 CC604000 CALL DWORD PTR DS:[<&USER32.MessageBoxA>]     ; \MessageBoxA
///////////////////////////////////////以下为垃圾代码
00401038  |.  8B35 10604000 MOV ESI,DWORD PTR DS:[<&KERNEL32.GetProcessHe>;  kernel32.GetProcessHeap
0040103E  |.  FFD6          CALL ESI              ; [GetProcessHeap
00401040  |.  8B3D 0C604000 MOV EDI,DWORD PTR DS:[<&KERNEL32.GetCurrentPr>;  kernel32.GetCurrentProcess
00401046  |.  FFD7          CALL EDI             ; [GetCurrentProcess
00401048  |.  8B1D 08604000 MOV EBX,DWORD PTR DS:[<&KERNEL32.GetCurrentTh>;  kernel32.GetCurrentThread
0040104E  |.  FFD3          CALL EBX             ; [GetCurrentThread
00401050  |.  8B2D 04604000 MOV EBP,DWORD PTR DS:[<&KERNEL32.GetThreadLoc>;  kernel32.GetThreadLocale
00401056  |.  FFD5          CALL EBP             ; [GetThreadLocale
00401058  |.  FFD6          CALL ESI              ; [GetProcessHeap
0040105A  |.  FFD7          CALL EDI             ; [GetCurrentProcess
0040105C  |.  FFD3          CALL EBX             ; [GetCurrentThread
0040105E  |.  FFD5          CALL EBP                 ; [GetThreadLocale
00401060  |.  FFD6          CALL ESI                  ; [GetProcessHeap
00401062  |.  FFD7          CALL EDI                 ; [GetCurrentProcess
00401064  |.  FFD3          CALL EBX                 ; [GetCurrentThread
00401066  |.  FFD5          CALL EBP                 ; [GetThreadLocale
00401068  |.  FFD6          CALL ESI                  ; [GetProcessHeap
0040106A  |.  FFD7          CALL EDI                 ; [GetCurrentProcess
0040106C  |.  FFD3          CALL EBX                 ; [GetCurrentThread
0040106E  |.  FFD5          CALL EBP                 ; [GetThreadLocale
00401070  |.  FFD6          CALL ESI                  ; [GetProcessHeap
00401072  |.  FFD7          CALL EDI                  ; [GetCurrentProcess
00401074  |.  FFD3          CALL EBX                  ; [GetCurrentThread
00401076  |.  FFD5          CALL EBP                  ; [GetThreadLocale
//////////////////////////////////////////////////////////////////////////////
00401078  |.  6A 00         PUSH 0                    ; /pModule = NULL
0040107A  |.  FF15 00604000 CALL DWORD PTR DS:[<&KERNEL32.GetModuleHandle>; \GetModuleHandleA
00401080  |.  50            PUSH EAX
//SetHook
00401081  |.  E8 4A000000   CALL 09HookDe.004010D0
00401086  |.  83C4 04       ADD ESP,4
00401089  |.  6A 00         PUSH 0            ; /Style = MB_OK|MB_APPLMODAL
0040108B  |.  68 38704000   PUSH 09HookDe.00407038         ; |Title = "HookDemo"
00401090  |.  68 30704000   PUSH 09HookDe.00407030         ; |Text = "原函数"
00401095  |.  6A 00         PUSH 0                         ; |hOwner = NULL
//第二次调用MessageBoxA
00401097  |.  FF15 CC604000 CALL DWORD PTR DS:[<&USER32.MessageBoxA>]     ; \MessageBoxA
0040109D  |.  5F            POP EDI
0040109E  |.  5E            POP ESI
0040109F  |.  5D            POP EBP
004010A0  |.  5B            POP EBX
004010A1  \.  C3            RETN
……

        可以看到,此时由于寄存器不够用,PE装载器不得不查找导入表调用MessageBoxA函数,既然此时我们已经hook掉了MessageBoxA,程序当然调用的就是我们自定义的函数咯。

2021 KCTF 秋季赛 防守篇-征题倒计时(11月14日截止)!

收藏
点赞0
打赏
分享
最新回复 (9)
雪    币: 417
活跃值: 活跃值 (6487)
能力值: (RANK:350 )
在线值:
发帖
回帖
粉丝
kanxue 活跃值 8 2013-4-30 22:17
2
0
感谢分享,赠10KX,你现在可以购邀请码转正了。
雪    币: 67
活跃值: 活跃值 (10)
能力值: ( LV6,RANK:90 )
在线值:
发帖
回帖
粉丝
dycc 活跃值 1 2013-5-1 02:48
3
0
谢谢kanxue老大的鼓励,没想到还能得到你的关注啊,太激动了!!我会再接再厉的!!!
雪    币: 14
活跃值: 活跃值 (12)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
asmyth 活跃值 2013-5-1 03:32
4
0
唉,我也激动
雪    币: 67
活跃值: 活跃值 (10)
能力值: ( LV6,RANK:90 )
在线值:
发帖
回帖
粉丝
dycc 活跃值 1 2013-5-1 03:36
5
0
这么晚了,你激动啥呢?
雪    币: 8
活跃值: 活跃值 (10)
能力值: ( LV3,RANK:20 )
在线值:
发帖
回帖
粉丝
SinCoder 活跃值 2013-5-1 16:33
6
0
从来只用 inline hook  ..iat啥的  鸡肋啊
雪    币: 67
活跃值: 活跃值 (10)
能力值: ( LV6,RANK:90 )
在线值:
发帖
回帖
粉丝
dycc 活跃值 1 2013-5-1 19:51
7
0
inline hook太高端,本小菜还不懂,有待学习~
雪    币: 11
活跃值: 活跃值 (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
fragileeye 活跃值 2014-3-21 11:52
8
0
我用IAT hook的时候
call        __chkesp (004013e0)
崩溃,堆栈失衡,请问lz是否有过经验,怎么解决这个问题。
雪    币: 7506
活跃值: 活跃值 (298)
能力值: ( LV9,RANK:610 )
在线值:
发帖
回帖
粉丝
achillis 活跃值 15 2014-3-21 12:40
9
0
有这么麻烦吗? API的地址不管后面有没有保存到寄存器中,原始值肯定还是来源于IAT,所以IAT Hook不用考虑是debug还是release,如果你没做到,只能说明你Hook的时机有问题
雪    币: 36
活跃值: 活跃值 (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
wjlin 活跃值 2014-3-21 14:24
10
0
Vc6debug版本的程序中的.idata节是可写的,我们可以不用VirtualProtect而直接用WriteProcessMemory覆盖掉目标API的地址,但是在release版本下此节被编译器优化成了只读属性,所以在release下必须要用VirtualProtect加上写属性才能写内存成功,这是《windows程序设计》这本书给出的答案。

这个貌似有点不对
貌似 WriteProcessMemory写自己本身进程的内存是不需要使用VirtualProtect加写属性的

验证代码

#include "stdafx.h"
#include <windows.h>

int _tmain(int argc, _TCHAR* argv[])
{

        DWORD dwSize;
        LPVOID lpAddr;
        __asm
        {
        _A:
                mov ebx, _A;
                mov lpAddr, ebx;
                mov eax, _B;
                sub eax, ebx;
                mov dwSize, eax;
                jmp _B;
                nop;
                nop;
                nop;
                nop;
                nop;
                nop;
                nop;
                nop;
                nop;
                nop;
                nop;
                nop;
                nop;
                nop;
                nop;
                nop;
                nop;
                nop;
                nop;
                nop;
                nop;
        _B:
        }
        DWORD dwSizeNeed;

        char *pdata = new char[dwSize];
        memset(pdata, 0xCC, dwSize);
    // WriteProcessMemory 可以正常写
        WriteProcessMemory(INVALID_HANDLE_VALUE
                , lpAddr
                , pdata
                , dwSize
                , &dwSizeNeed
                );
        delete[] pdata;

    // 使用memset设置 直接崩溃
    memset(lpAddr, 0x90, dwSize);

    printf("hello world!");

        return 0;
}
游客
登录 | 注册 方可回帖
返回