首页
社区
课程
招聘
《Windows 核心编程》中的 LastMsgBoxInfo 示例程序是错误的!
发表于: 2006-1-19 12:01 8408

《Windows 核心编程》中的 LastMsgBoxInfo 示例程序是错误的!

2006-1-19 12:01
8408
《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开发者必定有着很重要的影响。看雪中有着很多高手,我想你们一定对这本经典书籍中的经典程序有着比较透彻地研究。你们能告诉我这个错误的最根本原因吗?我也很乐意与你一起讨论这个问题。

[课程]Linux pwn 探索篇!

收藏
免费 0
支持
分享
最新回复 (19)
雪    币: 201
活跃值: (11)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
2
由于看的是电子版,没有源码,但在Hook_GetProcAddress本身所在的dll的输入表没有修改时,可以直接调用GetProcAddress涵数

你的情况,应该是当Hook_GetProcAddress所在的dll输入节也被修改
2006-1-19 16:17
0
雪    币: 202
活跃值: (15)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
3
最初由 酷酷 发布
由于看的是电子版,没有源码,但在Hook_GetProcAddress本身所在的dll的输入表没有修改时,可以直接调用GetProcAddress涵数

你的情况,应该是当Hook_GetProcAddress所在的dll输入节也被修改


你连我的帖子都没看完就回复了吧?我在帖子中说得很清楚了,代码确实没有修改 LastMsgBoxInfoLib 模块的输入节。但从运行事实看,它的输入节又好像被修改了。我想知道造成这个结果的原因。

感谢你的热心回复!
2006-1-20 08:16
0
雪    币: 201
活跃值: (11)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
4
因为他枚举了所有加载到进程地址空间的模块,并修改了输入节,
勾子dll也在进程的地址空间,所以也被修改了
2006-1-20 10:10
0
雪    币: 202
活跃值: (15)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
5
最初由 酷酷 发布
因为他枚举了所有加载到进程地址空间的模块,并修改了输入节,
勾子dll也在进程的地址空间,所以也被修改了


好像是错了!

包含 Hook_GerProcAddress 的模块作并没有处理:

void CAPIHook::ReplaceIATEntryInAllMods(PCSTR pszCalleeModName,
   PROC pfnCurrent, PROC pfnNew, BOOL fExcludeAPIHookMod) {

   HMODULE hmodThisMod = fExcludeAPIHookMod
      ? ModuleFromAddress(ReplaceIATEntryInAllMods) : NULL;

   // Get the list of modules in this process
   CToolhelp th(TH32CS_SNAPMODULE, GetCurrentProcessId());

   MODULEENTRY32 me = { sizeof(me) };
   for (BOOL fOk = th.ModuleFirst(&me); fOk; fOk = th.ModuleNext(&me)) {

      // NOTE: We don't hook functions in our own module
      if (me.hModule != hmodThisMod) {

         // Hook this function in this module
         ReplaceIATEntryInOneMod(
            pszCalleeModName, pfnCurrent, pfnNew, me.hModule);
      }
   }
}

给最后一个参数传递TRUE,就不会修改 LastMsgBoxInfoLib 模块的 IAT。
2006-1-20 11:59
0
雪    币: 0
能力值: (RANK:10 )
在线值:
发帖
回帖
粉丝
6
你真牛

请文明用语! 原话己编辑纠正_by kanxue
2006-1-20 12:20
0
雪    币: 200
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
upm
7
你真牛

请文明用语! 原话己编辑纠正_by kanxue
2006-1-20 12:23
0
雪    币: 291
活跃值: (213)
能力值: ( LV12,RANK:210 )
在线值:
发帖
回帖
粉丝
8
还没看到22章,所以我不多说什么,给个调试建议
用OD源码调试,在IAT上下内存断点,看看到底是什么时候修改的
2006-1-20 12:32
0
雪    币: 201
活跃值: (11)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
9
虽然楼主的语气不是很友善,但抱着讨论的态度,再发表个人观点,如有不对的地方,还请凉解,谁让我水平差: 我觉得出现以上情况,最有可能的根源在下面两个地方
1.
HMODULE hmodThisMod = fExcludeAPIHookMod
      ? ModuleFromAddress(ReplaceIATEntryInAllMods) : NULL;
我这里没有源码,你在代码中校验下ModuleFromAddress(ReplaceIATEntryInAllMods)的返回值,因为这个涵数当VirtualQuery涵数执行失败时,返回的也是NULL值

2.还有在winmain涵数中

DWORD dwThreadId=0;
#ifdef _DEBUG
      HWND hwnd=FindWindow(NULL,TEXT("Untitled - Paint"));
      dwThreadId=GetWindowThreadProcessID(hwnd,NULL);
#endif
LastMsgBoxInfo_HookAllApps(TRUE,dwThreadID);
没有对dwThreadId做校验,如果GegWindowThreadProcessID失败,那么返回值就是NULL;
在后来的设置勾子时,就有可能是设的全局勾子

当系统给别的进程加载勾子dll时,由于被Hook了LoadLibrary涵数和GetProcAddress涵数,而转向勾子dll中的涵数,从而改掉了勾子dll的输入表
2006-1-20 16:21
0
雪    币: 202
活跃值: (15)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
10
最初由 酷酷 发布
1、虽然楼主的语气不是很友善


2、HMODULE hmodThisMod = fExcludeAPIHookMod
? ModuleFromAddress(ReplaceIATEntryInAllMods) : NULL;

3、2.还有在winmain涵数中

#ifdef _DEBUG
HWND hwnd=FindWindow(NULL,TEXT("Untitled - Paint"));
dwThreadId=GetWindowThreadProcessID(hwnd,NULL);
#endif

LastMsgBoxInfo_HookAllApps(TRUE,dwThreadID);
........


1、我没有任何的恶意。如果给你的心情造成了负面影响,我在此向你说一起对不起。

2、这个问题我刚调试了一下,ModuleFromAddress()每次调用都成功了,都返回了钩子模块的加载地址。

3、#ifdef ~ #endif 之间的代码完全可以注释掉。以这样的方式来调用:
LastMsgBoxInfo_HookAllApps(TRUE,0);
也是对的。如果第二个参数(dwThreadID)为0,那就表示要挂接系统中的所有线程。如果为这个参数指定一个值,那就只挂接指定的线程。

问题不是出在这里,谢谢你的热心回复!
2006-1-20 17:23
0
雪    币: 202
活跃值: (15)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
11
一点小发现:

一、进行一点修改后,在 Windows 2000 下可以正常运行。

Jeffrey 也许是为了统一,把挂接函数取了与系统 API 相同的名字:

// Used to trap when DLLs are newly loaded
static HMODULE WINAPI LoadLibraryA(PCSTR  pszModulePath) ;
static HMODULE WINAPI LoadLibraryW(PCWSTR pszModulePath) ;
static HMODULE WINAPI LoadLibraryExA(PCSTR pszModulePath, HANDLE hFile, DWORD dwFlags) ;
static HMODULE WINAPI LoadLibraryExW(PCWSTR pszModulePath, HANDLE hFile, DWORD dwFlags) ;

// Returns address of replacement function if hooked function is requested
static FARPROC WINAPI GetProcAddress(HMODULE hmod, PCSTR pszProcName) ;

我进行了一些修改,在挂接函数的名字前面加了一个 Hook 前缀:

        // Used to trap when DLLs are newly loaded
        static HMODULE WINAPI Hook_LoadLibraryA(PCSTR  pszModulePath) ;
        static HMODULE WINAPI Hook_LoadLibraryW(PCWSTR pszModulePath) ;
        static HMODULE WINAPI Hook_LoadLibraryExA(PCSTR pszModulePath,
                HANDLE hFile, DWORD dwFlags) ;
        static HMODULE WINAPI Hook_LoadLibraryExW(PCWSTR pszModulePath,
                HANDLE hFile, DWORD dwFlags) ;

        // Returns address of replacement function if hooked function is requested
        static FARPROC WINAPI Hook_GetProcAddress(HMODULE hmod, PCSTR pszProcName) ;

这样,在 Windows 2000 下就能完全正常的运行了。钩子函数所在DLL模块的IAT没有被修改。在挂接函数内对系统 API 的调用正确地跳转到了 kernel32.dll 中。

但是,这种方法在 Windows XP 下却行不通。即使为挂接函数加上 Hook 前缀问题依旧。

二、如果只为指定线程的消息队列安装消息钩子,那程序不管是在 Windows 2000 还是 XP 下,程序都能正常运行:

    LastMsgBoxInfo_HookAllApps(TRUE, dwThreadId) ;

如果为 dwThreadId 传递一个确定的值,也即只为指定线程的消息队列安装钩子,那程序就会正常运行。

// 假设 NOTEPAD.EXE 的主线程ID为 4319:

    LastMsgBoxInfo_HookAllApps(TRUE, 4319) ;

这个函数将为 NOTEPAD 进程的主线程安装一个消息钩子,当这个主线程所创建的窗口收到一个 Windows 消息时,系统会在调用 NOTEPAD 主窗口的窗口过程前会先调用我安装的钩子过程,这会导致系统把钩子过程所在的DLL加载到 NOTEPAD 的进程地址空间中。
DLL 一加载,在 DLL 模块中的C++全局对象将被构造,这将把除钩子过程所在的DLL模块之外的所有模块的IAT修改,以实现 HOOK API。运行过程与我们预想的完全一样,结果正确。

但是,在 Windows XP 下,如果想挂接系统中所有正在运行线程的消息队列:

    LastMsgBoxInfo_HookAllApps(TRUE, 0) ;

这会造成钩子过程所在DLL模块的IAT也被修改,在挂接函数内又会调用挂接,导致堆栈溢出错误进而终止进程。
2006-1-21 15:51
0
雪    币: 202
活跃值: (15)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
12
最初由 thebutterfly 发布
还没看到22章,所以我不多说什么,给个调试建议
用OD源码调试,在IAT上下内存断点,看看到底是什么时候修改的


偶不会用OD!
2006-1-22 13:51
0
雪    币: 200
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
13
看来当初应该学下c
2006-1-22 16:56
0
雪    币: 0
能力值: (RANK:10 )
在线值:
发帖
回帖
粉丝
14
连OD都不会用,你有什么资格说别人是错误的?
2006-1-22 17:10
0
雪    币: 202
活跃值: (15)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
15
最初由 孙行者 发布
连OD都不会用,你有什么资格说别人是错误的?


我正在学习OD。
我的代码中对钩子过程所在的模块作了特殊处理,所以不会修改它的IAT。
2006-1-22 20:52
0
雪    币: 200
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
16
这本书里有些举的代码例子不对。

有的函数在VC6的API里根本就没有。
2006-1-23 09:19
0
雪    币: 202
活跃值: (15)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
17
最初由 ax5491 发布
这本书里有些举的代码例子不对。

有的函数在VC6的API里根本就没有。


你没有安装最新的 Platform SDK,才会出现有些函数没有声明的问题。请到MS的官网去下载最新版的SDK。
2006-1-23 22:46
0
雪    币: 200
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
18
楼上的大侠,好像不是这样。

这本书写成时,还是98和NT的时代,看书中的例子就知道了。

或者我错了?目前的Platform SDK最新下载MS上好像没提供。
提供的都是专项的一些SDK。不是全套的Platform SDK;感觉像SDK的补丁。
2006-1-24 13:19
0
雪    币: 202
活跃值: (15)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
19
最初由 ax5491 发布
楼上的大侠,好像不是这样。

这本书写成时,还是98和NT的时代,看书中的例子就知道了。

或者我错了?目前的Platform SDK最新下载MS上好像没提供。
........


两种选择:

一、在这里下载最新的平台SDK。

http://www.microsoft.com/downloads/details.aspx?FamilyID=a55b6b43-e24f-4ea3-a93e-40c0ec4f68e5&DisplayLang=en

The Microsoft® Windows® Software Development Kit (SDK) provides the documentation, samples, header files, libraries, and tools you need to develop applications that run on Windows.

二、安装 Visual Studio 2005 Team Suit。它包含所有最新的东东。
我在猪猪乐园用BT上传了这个软件,需要的话可以去下载。
2006-1-25 12:11
0
雪    币: 200
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
20
这个问题到底还是没有解决是吧?我也期待着呢,高手们出来献身啊,嘿嘿~~~~~
2006-8-21 09:47
0
游客
登录 | 注册 方可回帖
返回
//