《Windows 核心编程》第22章 LastMsgBoxInfo 示例程序。
不知道论坛中的网友有没有认真研究过这个程序。昨晚我照着书把源代码一行一行的输入电脑,编译、运行,结果发现主窗口出现后一闪即逝。我以为自己输入过程中出了错,就把随书光盘中的程序拷到硬盘中编译、运行,问题依旧。
没办法,我只好一行一行的调试,结果发现问题出在对 GetProcAddress API的挂接上:
Jeffrey 原本是想把除开 LastMsgBoxInfoLib 之外的所有模块的输入节改写,那样任何模块中的代码如果想调用 GetProcAddress API 的话,就会跳到 LastMsgBoxInfoLib 模块中的 Hook_GetProcAddress 函数中,这个函数进行一些额外的处理后,再跳到 kernel32.dll 模块中调用系统的 GetProcAddress API。
可事实并不是这样,当某个模块(我无法确定是那个模块)中的代码调用了 GetProcAddress API时,因为输入节的改写,程序跳到 LastMsgBoxInfoLib 模块中的 Hook_GetProcAddress 函数中执行,在进行了一些额外的处理后,Hook_GetProcAddress 函数想要跳到 kernel32.dll 模块中调用原始的 GetProcAddress API,但实际上却又跳到了 Hook_GetProcAddress 函数中,这个函数又调用 kerne32.dll 模块中的 GetProcAddress API,但又跳到了 Hook_GetProcAddress 函数中......
结果因两个函数之间的互相调用,参数不断地压栈,最终因堆栈溢出错误而终止。
我仔细的调试了代码,发现程序确实没有对 LastMsgBoxInfoLib 模块的输入进行改写,也就是说在 LastMsgBoxInfoLib 中的代码如:Hook_GetProcAddress 在调用系统API GetProcAddress 时,应该跳到 kernel32.dll 模块中去,但为什么事实不是如此呢?
=========================================================================================
今天,我又继续进行调试:
在进入WinMain()之前,LastMsgBoxInfoLib 模块被加载到进程的地址空间中,C++全局对象先被C++启动代码构造,LoadLibraryA(), LoadLibraryW(), GetProcAddress()... API先后被挂接。在挂接这些API时,GetProcAddress API都被调用,它的入口地址在:7C80AC28处。
所有API被挂接后,进入 WinMain(),WinMain()中使用DialogBox()宏弹出作为主窗口的对话框。WM_INITDIALOG、WM_SIZE 消息先后被处理。
最终,有一个模块中的代码以如下形式调用了系统API GetProcAddress:
GetProcAddress( ntdll.dll的加载地址, "RtlDllShutdownInProgress" ) ;
因为这个模块的IAT被修改,这个调用就跳转到 LastMsgBoxInfoLib 模块的 Hook_GetProcAddress 函数中,挂接函数在进行了自己额外处理后,就想调用 kennel32.dll 模块中的 GetProcAddress API。意外情况发生了,LastMsgBoxInfoLib 模块的IAT似乎被修改,本想对 kennel32.dll 的 GetProcAddress API的调用竟然转到了 LastMsgBoxInfoLib 中的 Hook_GetProcAddress,至此,陷入两个函数的互相调用死循环,因函数参数的不停压栈,最终进程因堆栈溢出错误而终止。
=========================================================================================
//
// List 1 这是 Hook_GetProcAddress 中调用的一个自定义函数,目的是在挂接函数中调用系统API GetProcAddress
//
// NOTE: This function must NOT be inlined
FARPROC CAPIHook::GetProcAddressRaw(HMODULE hmod, PCSTR pszProcName)
{
10012170 push ebp
10012171 mov ebp,esp
10012173 sub esp,0C0h
10012179 push ebx
1001217A push esi
1001217B push edi
1001217C lea edi,[ebp-0C0h]
10012182 mov ecx,30h
10012187 mov eax,0CCCCCCCCh
1001218C rep stos dword ptr es:[edi]
return (::GetProcAddress(hmod, pszProcName)) ;
1001218E mov esi,esp
10012190 mov eax,dword ptr [pszProcName]
10012193 push eax
10012194 mov ecx,dword ptr [hmod]
10012197 push ecx
* 10012198 call dword ptr [__imp__GetProcAddress@8 (1001D288h)] // 这里,原意是想调用 kernel32.dll 中的 GetProcAddress API。问题没出现前,单步跟进后会跳转到 7C80AC28 处,这是 kernel32.dll 中 GerProcAddress 的入口地址。但问题出现后,单步跟进,请见 List 2
1001219E cmp esi,esp
100121A0 call @ILT+440(__RTC_CheckEsp) (100111BDh)
}
100121A5 pop edi
100121A6 pop esi
100121A7 pop ebx
100121A8 add esp,0C0h
100121AE cmp ebp,esp
100121B0 call @ILT+440(__RTC_CheckEsp) (100111BDh)
100121B5 mov esp,ebp
100121B7 pop ebp
100121B8 ret 8
=========================================================================================
//
// List 2 看样子这里应该是 LastMsgBoxInfoLib 的 IAT。因为 LastMsgBoxInfoLib 被加载到了 10000000 处。
//
...
......
100110DC jmp GetCurrentThreadId (10014D3Ch)
100110E1 jmp GetWindowsDirectoryW (10012BEAh)
* 100110E6 jmp CAPIHook::Hook_GetProcAddress (10012B30h) // 从 10012198 跳到了这里。这句本来是想对 kernel32.dll 中的系统API进行调用,但因IAT的修改,而错误的跳到了 LastMsgBoxInfoLib 模块的挂接函数中!
100110EB jmp _initterm (10014564h)
100110F0 jmp GetCurrentProcess (10012C20h)
......
...
错误原因:
导致这个错误出现的原因似乎被我发现了,因为 LastMsgBoxInfoLib 模块的 IAT 被修改,而让函数之间不断地互相调用引发了错误。
可是我仔细地检查了源代码,我的程序确实没有对 LastMsgBoxInfoLib 模块的 IAT 进行修改。可为什么在运行到一定时间后,LastMsgBoxInfoLib 的 IAT 却被修改了呢?
Petzold 的《Windows 程序设计》如果被誉为 Windows程序设计的圣经,我想这是对于初学者而说的。Jeffrey 的《Windows 核心编程》虽没有被寇上圣经的荣誉,但它对于大多数 Windows开发者必定有着很重要的影响。看雪中有着很多高手,我想你们一定对这本经典书籍中的经典程序有着比较透彻地研究。你们能告诉我这个错误的最根本原因吗?我也很乐意与你一起讨论这个问题。
[课程]FART 脱壳王!加量不加价!FART作者讲授!