1. HMODULE GetModuleHandle( LPCTSTR lpModuleName // address of module name to return handle // for );
GetModuleHandle 实际上分为两个函数
GetModuleHandleA--处理 Ansi 字符串 GetModuleHandleW--处理 Unicode 字符串
其实真正干活的函数是,GetModuleHandleW (指NT以后的系统),GetModuleHandleA 只不过将用户输入的 Ansi 字符串转成 Unicode 字符串然后就直接调用 GetModuleHandleW,所以在 OD 中如果下断拦截这个函数,直接拦截 GetModuleHandleW 就可以了,保险一个都跑不掉。我看一些新手写 Hook 程序时往往不厌其烦的将每个函数都“沟”一下,其实大可不必,随着我的文章深入,你会发现一个基本原则--千条江河归大海,大部分的调用其实最后都是进入最底层的 ntoskrnl.exe、Hal.dll、和其他几个内核程序。而我们平常打交道最多的 Kernel32.dll 和 ntdll.dll 其实只是对底层函数进行了包装(当然对于某些ring3层的特定功能还是自己完成的),相当于一个接口,起一个中转和隔离的作用,将用户的函数调用进行预处理,比如检测用户参数的合法性,对用户参数进行重新排序(底层函数的参数排列顺序有时和用户层不同,这样可以防止你轻易的弄明白底层在干什么),将用户的调用分离拆解为若干个更为细小的调用,因为底层函数分类往往更细,这样比较灵活。 使用 GetModuleHandle 函数,在高级语言中可以直接写成: hMod = GetModuleHandle(lpModuleName); 编译器会根据参数的性质自行决定调用那一个函数,但在汇编中却不能这样使用,你必须明确的指明你要使用那一个函数。另外在OD中下断点,你也必须明确的指明对那一个函数下断,例如在OD中,进入CPU窗口,按Ctrl-G,然后输入GetModuleHandle,OD会告诉你 “未识的标识符”,你必须清楚的告诉 OD 你到底要在那一个函数下断,是 A 还是 W。按前面的说法其实你只要下 GetModuleHandleW 就可以了(当然有时候在A下断,比较容易知道程序是从那里调用的,如果程序使用的是A调用)。
俗话说,口说无凭。你怎么知道那两个还是最后变成了一个? 看雪老大也在qduwg的文章后说:
QUOTE: 不过这三个函数的定义只需要Google就能找到相关文档,因为太常用,不光是脱壳,编程是经常用的。 现在的强壳都不直接调用这些函数了,都自己实现。 其后跟贴更有"牛B王"--nbw 惊人的评论:
QUOTE: 可以参考以下MSDN。写壳都是自己实现。
以前自己能写个GetProcAddress就觉得很不错了,后来看高手写虚拟机,半小时模拟出来LoadLibrary,那个让偶惊啊。。。。 我没有他们那样的水平,自己不会写,人都说半瓶醋晃荡,醋瓶满了就不响了,我这个瓶子还没有满,所以你们有幸能看到这些垃圾文章(当然也是为了骗一点"加精",风光风光),等有一天我的瓶子也满了(最好能够做到),也就只会“啊...”了。
既然如此,那就只好看看老盖是怎么做的,这个函数在 Kernel32.dll 中,你可能会问,你怎么知道在Kernel32.dll中的?qduwg 老师在翻译文中可惜漏掉了可能他认为不重要的信息:
QuickInfo Windows NT: Requires version 3.1 or later. Windows: Requires Windows 95 or later. Windows CE: Unsupported. Header: Declared in winbase.h. Import Library: Use kernel32.lib. //这里指明了函数的归宿 Unicode: Implemented as Unicode and ANSI versions on Windows NT.
See Also Dynamic-Link Libraries Overview, Dynamic-Link Library Functions, FreeLibrary, GetModuleFileName, GetProcAddress, LoadLibrary,LoadResource
现在我们来看看 GetModuleHandleA 的具体操作:
77E80B1A ; HMODULE __stdcall GetModuleHandleA(LPCSTR lpModuleName) 77E80B1A public GetModuleHandleA 77E80B1A GetModuleHandleA proc near ; CODE XREF: sub_77E684C2+2p 77E80B1A ; CreateRemoteThread+FBp ... 77E80B1A 77E80B1A lpModuleName= dword ptr 8 77E80B1A 77E80B1A push ebp 77E80B1B mov ebp, esp 77E80B1D cmp [ebp+lpModuleName], 0 77E80B21 jnz short loc_77E80B31 ; lpModuleName 是有效的则转移 77E80B23 mov eax, large fs:18h ; TEB.NT_TIB.Self 77E80B29 mov eax, [eax+30h] ; TEB.Peb 77E80B2C mov eax, [eax+8] ; PEB.ImageBaseAddress 77E80B2F jmp short loc_77E80B45 77E80B31 77E80B31 loc_77E80B31: ; CODE XREF: GetModuleHandleA+7j 77E80B31 push [ebp+lpModuleName] 77E80B34 call @AnsiStrToUnicodeStr ; 将Ansi字符串转成Unicode字符串 77E80B39 test eax, eax 77E80B3B jz short loc_77E80B45 77E80B3D push dword ptr [eax+4] ; lpModuleName 77E80B40 call GetModuleHandleW ; 这里开始同流合污了。 77E80B45 77E80B45 loc_77E80B45: ; CODE XREF: GetModuleHandleA+15j 77E80B45 ; GetModuleHandleA+21j 77E80B45 pop ebp 77E80B46 retn 4 77E80B46 GetModuleHandleA endp 这些结果是在 IDA 中分析的,在使用IDA分析这些内核模块时请注意,一定要将那个符号文件.PDB和调试符号文件.DBG从老盖的网站上下载回来(注意要下载最新的版本),和你要分析的文件放在一个目录下,这样,IDA就会自动加载这些符号文件,分析出的结果可读性大为提高。另外值得注意的是 windows 的这些系统模块和符号文件的版本一定要相同,大多数的情况下,符号文件的版本会相对低一点,这样你可以到系统目录下去寻找过去版本的系统模块,windows在update时会将这些旧的系统模块保存在其他目录,具体的版本对于可执行模块来讲,比较简单,就是PE文件头中的TimeDateStamp,使用任何工具查看都行。 IMAGE_FILE_HEADER STRUCT Machine WORD ? NumberOfSections WORD ? TimeDateStamp DWORD ? //就是这里,不明白的话请看 qduwg 的翻译文章 PointerToSymbolTable DWORD ? NumberOfSymbols DWORD ? SizeOfOptionalHeader WORD ? Characteristics WORD ? IMAGE_FILE_HEADER ENDS
对于PDB文件比较麻烦,我没有现成的工具来查看这个 TimeDateStamp ,用任何十六进制编辑器打开这个.PDB文件在偏移 0x4404 的地方就是这个 TimeDateStamp 了,这两个TimeDateStamp一定要一样,否则宁愿不要使用。
1 2 下一页 |