乱解 API 函数 -- API 绝密档案系列之二   通向高手之路
上一篇介绍了两个结构,今后你会发现,在系统程序中,对这两个结构的引用无所不在,理解这两个结构对正确理解系统程序有莫大的帮助。
这一篇加了副标题【通向高手之路】你可能要问,我是什么“手”,抱歉,我最多只能称“中手”,那么“中手”是什么意思? 是高手之下,低手之上的那只手? 错,“中手”--乃中国之手--乃国手也,哈哈。
那么我们为什么要研究这些底层的东西呢?这个问题提的好。一般来说有两个目的,第一是深刻的理解这些函数是如何工作的,在今后的开发中你也许可以借鉴这些底层的某些用法,来优化你的程序。其二就是弄一些自己的函数来取代这些函数从而达到某些特殊的目的。
对于如何用自己的代码来取代这些函数也有许多方法,最简单的就是将这些代码直接拷贝到你的程序中去,但这可能不是很好的办法,因为这些代码中,有许多函数是内部函数,不能直接调用,因而你不得不将这些所有的过程都搬到你的程序中。另一种方法就是在真正理解这些函数的基础上彻底改写。系统函数一般来讲都比较全面,各种情况都要考虑,所以比较繁琐,而你可能只是在某些特定的环境下使用这些函数,这就有可能简化这些函数,将你认为不需要的地方去掉,这样代码精炼,而且隐蔽性更好,如果更上一层楼,可以使用其他的方法达到相同的目的,这样就是最高境界了。
我在这里以 GetModuleHandle 函数为例,介绍了如何分析理解这些函数,Windows 从公开的到未公开的函数多如牛毛,不可能每一个的介绍,我没时间写,你也没有时间看,所以这里只是以这个函数为例,让你了解一下这些分析的过程,希望你能掌握这一思路和方法,自己去分析底层函数,这样你就有可能成为真正的高手。

首先我们来看看GetModuleHandleW的代码:(这里给出的是 TimeDateStamp=0x42A0669B 版)

代码:
77E80C27 ; Exported entry 320. GetModuleHandleW 77E80C27 ; ======== S U B R O U T I N E ==================== 77E80C27 ; HMODULE __stdcall GetModuleHandleW(LPCWSTR lpModuleName) 77E80C27       public _GetModuleHandleW@4 77E80C27 _GetModuleHandleW@4 proc near 77E80C27 UnicodeModuleName= dword ptr -10h 77E80C27 ModuleName= byte ptr -8 77E80C27 MemoryPointer= dword ptr -4 77E80C27 lpModuleName= dword ptr  8 77E80C27       push ebp 77E80C28       mov ebp, esp 77E80C2A       sub esp, 10h 77E80C2D       cmp [ebp+lpModuleName], 0 77E80C31       push esi 77E80C32       jnz short IfNotSelfModuleHandle 77E80C34       mov eax, large fs:18h               ; TEB.NT_TIB.Self 77E80C3A       mov eax, [eax+TEB.Peb] 77E80C3D       mov eax, [eax+PEB.ImageBaseAddress] 77E80C40       jmp short GetModHandleExit 77E80C42 IfNotSelfModuleHandle:                    ; SourceString 77E80C42       push [ebp+lpModuleName] 77E80C45       lea eax, [ebp+UnicodeModuleName] 77E80C48       push eax                            ; DestinationString 77E80C49       call ds:__imp__RtlInitUnicodeString@8 ; 将 CWSTR 转成 Unicode 77E80C4F       cmp _gDoDllRedirection, 0           ; Dll 重定向标志 77E80C56       jz short DontDllRedir           ;_gDoDllRedirection = TRUE 77E80C58       lea eax, [ebp+ModuleName] 77E80C5B       push eax                            ; returnName 77E80C5C       lea eax, [ebp+UnicodeModuleName] 77E80C5F       push eax                            ; lpModuleName 77E80C60       call _ComputeRedirectedDllName@8    ; 所谓重定向就是到用户程序的目录下加载同名的dll, 77E80C60                                           ; _ComputeRedirectedDllName函数将用户输入的ModuleName 77E80C60                                           ; 前面加上用户的目录(用户程序启动的目录)。 77E80C65       test eax, eax 77E80C67       jge short IfGetRedirDllName 77E80C69       push eax                            ; Status 77E80C6A       call _BaseSetLastNTError@4          ; BaseSetLastNTError(x) 77E80C6F       xor eax, eax 77E80C71       jmp short GetModHandleExit 77E80C73 IfGetRedirDllName: 77E80C73       lea eax, [ebp+ModuleName] 77E80C76       push eax                            ; lpModuleName 77E80C77       call _GetModuleHandleForUnicodeString@4 ; GetModuleHandleForUnicodeString(x) 77E80C7C       mov esi, eax 77E80C7E       mov eax, large fs:18h               ; TEB.NT_TIB.Self 77E80C84       push [ebp+MemoryPointer]            ; MemoryPointer 77E80C87       mov eax, [eax+TEB.Peb] 77E80C8A       push 0                              ; Flags 77E80C8C       push [eax+PEB.ProcessHeap]          ; HeapHandle 77E80C8F       call ds:__imp__RtlFreeHeap@12       ; __declspec(dllimport) RtlFreeHeap(x,x,x) 77E80C95       test esi, esi 77E80C97       jz short DontDllRedir 77E80C99       mov eax, esi 77E80C9B       jmp short GetModHandleExit          ;_gDoDllRedirection = FALSE 77E80C9D DontDllRedir: 77E80C9D       lea eax, [ebp+UnicodeModuleName] 77E80CA0       push eax                            ; lpModuleName 77E80CA1       call _GetModuleHandleForUnicodeString@4 ; GetModuleHandleForUnicodeString(x) 77E80CA6 GetModHandleExit: 77E80CA6       pop esi 77E80CA7       leave 77E80CA8       retn 4 77E80CA8 _GetModuleHandleW@4 endp


这里有一个很有趣的问题,ModuleHandle 到底是什么东西,其实在第一篇中已经有了答案,ModuleHandle 是运行程序加载的基地址,即:
  ModuleHandle == ImageBaseAddress
这个ImageBaseAddress 和 PE头那个 ImageBaseAddress 有点不同,PE 头中的ImageBaseAddress 是由编译器生成的,这个地址可以由用户设定,例如在汇编程序的连接中,就可以通过如下的方法来设定 ImageBaseAddress:
LINK.EXE /base:0x500000 /SUBSYSTEM:WINDOWS /OUT:Prog.exe,Prog.obj,Prog.res
这里我们指定 ImageBase 为 0x500000,这个参数如果不设置,缺省值是 0x400000 (对exe文件,连接驱动程序这个参数不起作用)操作系统在加载用户程序时,将以这个参数为首选,如果该地址没有加载其他程序,用户程序将加载到这个地址,这时PE头中的 ImageBaseAddress 就和 ModuleHandle 相同,Win32 运行环境是每个进程独享 4GB(去掉操作系统所占实际为2GB空间),所以一般情况下,对于用户程序而言,ImageBaseAddress 总是和 ModuleHandle 相同,对于Dll和Sys而言,自己编写的模块,加载地址和 PE 头文件的地址多数是不同的,而老盖的系统模块则基本相同(加载到固定的地址)。

现在我们将分析上面的代码:
GetModuleHandleW 根据lpModuleName分两种方式来处理

1、lpModuleName = Null
这个我们在第一篇中已经讨论过,即简单的从 TEB -- PEB -- ImageBaseAddress 来获得当前进程的加载地址。具体代码如下:

代码:
77E80C34       mov eax, large fs:18h               ; TEB.NT_TIB.Self 77E80C3A       mov eax, [eax+TEB.Peb] 77E80C3D       mov eax, [eax+PEB.ImageBaseAddress]



2、lpModuleName != Null
当 lpModuleName 不为空时,又分为两种情况,其实在稍早一些的版本中,只有一种情况,新的版本才多出这一种。

代码:
77E80C4F       cmp _gDoDllRedirection, 0           ; Dll 重定向标志 77E80C56       jz short DontDllRedir


即根据 _gDoDllRedirection 是否为零来分枝,_gDoDllRedirection 是一布尔变量,下面我们先看看这个 _gDoDllRedirection 是怎么来的。
对于用户程序,Win操作系统在加载用户程序时会根据输入表加载相应的模块,如果使用了 Kernel32.dll 中的函数,则Kernel32.dll 将被加载(如果Kernel32已经加载,则在用户空间会产生一个映射),在Kernel32加载时初始化了这个 _gDoDllRedirection 布尔变量。我们摘录部分代码分析,下面是 Kernel32.dll 初始化部分:

代码:
77E67A40 ; NTSTATUS __stdcall BaseDllInitialize(HINSTANCE hInstance,DWORD dwReason,LPVOID lpReserved) 77E67A40 _BaseDllInitialize@12 proc near 77E67A40 pObjectDirectory= dword ptr -210h 77E67A40 PEB   = dword ptr -10h 77E67A40 pConnectionInfo= dword ptr -0Ch 77E67A40 @ConnectionInfoSize= dword ptr -8 77E67A40 @ServerToServerCall= dword ptr -1 77E67A40 hInstance= dword ptr  8 77E67A40 dwReason= dword ptr  0Ch 77E67A40 lpReserved= dword ptr  10h 77E67A40 77E67A40       push ebp 77E67A41       mov ebp, esp 77E67A43       sub esp, 210h ... ... ... ... 77E67C66 loc_77E67C66: 77E67C66       push 104h                           ; nSize 77E67C6B       push offset _BaseDefaultPathBuffer  ; lpFilename 77E67C70       push esi                            ; hModule esi=Null 获取当前用户带路径的程序名 77E67C71       call _GetModuleFileNameW@12         ; GetModuleFileNameW(x,x,x) 77E67C76       movzx eax, ax                       ; 返回文件名的长度 77E67C79       mov esi, offset a_local             ; ".Local" 77E67C7E       lea edi, _BaseDefaultPathBuffer[eax*2] ;将指针移到文件名字符串的尾部 77E67C85       movsd                                ;添加 ".Local" 77E67C86       movsd 77E67C87       movsd 77E67C88       movsw 77E67C8A       mov esi, offset _BaseDefaultPathBuffer 77E67C8F       push esi                             ; FileName 77E67C90       call ds:__imp__RtlDoesFileExists_U@4 ; __declspec(dllimport) RtlDoesFileExists_U(x) 77E67C96       mov edi, src 77E67C9C       mov _gDoDllRedirection, al ... ... ... ...

 
首先是调用 GetModuleFileNameW 因为参数hModule=Null 返回用户程序名。然后再对用户程序名进行简单的处理,在尾巴添加 “.Local”,实际就是将所谓的DOSPathName 转变为 NtPathName,处理过的文件名如下

代码:
C:\MyDir\MyProc.exe.Local (Nt格式的文件名,注意,是 WSTR 字符串) 


然后调用 RtlDoesFileExists_U 函数,代码如下:

代码:
77F895D0 ; Exported entry 430. RtlDoesFileExists_U 77F895D0 ; ======== S U B R O U T I N E ==================== 77F895D0 ; BOOLEAN __stdcall RtlDoesFileExists_U(PWSTR FileName) 77F895D0       public _RtlDoesFileExists_U@4 77F895D0 _RtlDoesFileExists_U@4 proc near 77F895D0 FileName= dword ptr  4 77F895D0       push TRUE                                   ; AllowShare 77F895D2       push [esp+4+FileName]                       ; FileName 77F895D6       call _RtlDoesFileExists_UEx 77F895DB       retn 4 77F895DB _RtlDoesFileExists_U@4 endp

 
RtlDoesFileExists_U 直接调用 RtlDoesFileExists_UEx 代码如下:
下面是 RtlDoesFileExists_UEx 的相关部分:

代码:
77F89524 ; BOOLEAN __stdcall _RtlDoesFileExists_UEx(PWSTR FileName,BOOLEAN AllowShare) 77F89524 _RtlDoesFileExists_UEx proc near 77F89524 77F89524 FileInformation= _FILE_BASIC_INFORMATION ptr -54h 77F89524 var_30= dword ptr -30h 77F89524 ObjectAttributes= OBJECT_ATTRIBUTES ptr -2Ch 77F89524 DirectoryInfo= dword ptr -14h 77F89524 CurDir= dword ptr -10h 77F89524 Status= dword ptr -0Ch 77F89524 NtPathName= UNICODE_STRING ptr -8 77F89524 FileName= dword ptr  8 77F89524 AllowShare= byte ptr  0Ch 77F89524 77F89524       push ebp 77F89525       mov ebp, esp 77F89527       sub esp, 54h ... ... ... ... 77F89564       mov eax, [ebp+Status] 77F89567       mov [ebp+ObjectAttributes.Length], 18h      ; sizeof(OBJECT_ATTRIBUTES) 77F8956E       mov [ebp+ObjectAttributes.RootDirectory], eax 77F89571       lea eax, [ebp+NtPathName] 77F89574       mov [ebp+ObjectAttributes.ObjectName], eax 77F89577       lea eax, [ebp+FileInformation] 77F8957A       push eax                                    ; FileInformation 77F8957B       lea eax, [ebp+ObjectAttributes] 77F8957E       push eax                                    ; ObjectAttributes 77F8957F       mov [ebp+ObjectAttributes.Attributes], OBJ_CASE_INSENSITIVE 77F89586       mov [ebp+ObjectAttributes.SecurityDescriptor], ebx 77F89589       mov [ebp+ObjectAttributes.SecurityQualityOfService], ebx 77F8958C       call _ZwQueryAttributesFile@8               ; ZwQueryAttributesFile(x,x) 77F89591       mov esi, eax                                ;ZwQueryAttributesFile 返回结果为 ... ... ... ... 77F895AA       cmp esi, STATUS_SHARING_VIOLATION             77F895B0       jz short loc_77F895BE 77F895B2       cmp esi, STATUS_ACCESS_DENIED 77F895B8       jz short loc_77F895BE 77F895BA       xor al, al 77F895BC       jmp short loc_77F895C8 ... ...


RtlDoesFileExists_UEx 调用Ring0层函数 ZwQueryAttributesFile,ntdll.dll 包装了这个底层函数,具体如下:

代码:
77F8880C ; NTSTATUS __stdcall ZwQueryAttributesFile(POBJECT_ATTRIBUTES ObjectAttributes,PFILE_BASIC_INFORMATION FileInformation) 77F8880C           public _ZwQueryAttributesFile@8 77F8880C _ZwQueryAttributesFile@8 proc near 77F8880C 77F8880C ObjectAttributes= dword ptr  4 77F8880C FileInformation= dword ptr  8 77F8880C 77F8880C           mov   eax, 7Ah                          ; NtQueryAttributesFile 77F88811           lea   edx, [esp+ObjectAttributes] 77F88815           int   2Eh                               ; DOS 2+ internal - EXECUTE COMMAND 77F88815                                                   ; DS:SI -> counted CR-terminated command string 77F88817           retn  8 77F88817 _ZwQueryAttributesFile@8 endp



当 ZwQueryAttributesFile 返回结果为 STATUS_SHARING_VIOLATION 或 STATUS_ACCESS_DENIED 时 _gDoDllRedirection = 1 (TRUE),其余情况为 0。所以用户程序返回结果多数为 0 (FALSE)。

老盖为了防止用户直接调 int 2Eh (KiSystemService),每次 Updata 功能号都可能发生改变,另外在 ntoskrnl.exe 的输出表中,列出了许多函数,供驱动程序使用,但有些Ring3使用的函数,例如这个 ZwQueryAttributesFile 就没有列出在输出表中,属于ntoskrnl.exe的内部函数。在 IDA 中可以通过 Names 窗口,很容易的找到这个函数。如果想看 ZwQueryAttributesFile 的代码,还需要将前面的Zw换成Nt,即 NtQueryAttributesFile。凡是标有 Zw 字头的函数,多数是通过 int 2Eh (KiSystemService) 而进入 Nt 相应名称的函数,当然也有部分的 Nt 起头的函数通过 int 2Eh (KiSystemService) 来转向,大多数都是因为函数本身实现不在 ntoskrnl.exe 内部,而在其他模块。功能号为Byte,多数在内部实现,功能号为 Word 则多数在外部模块实现,所以跟踪一个函数,可能会跳两三个模块才能找到实现的代码。

2.1 _gDoDllRedirection = TRUE

代码:
77E80C58       lea eax, [ebp+ModuleName] 77E80C5B       push eax                            ; returnName 77E80C5C       lea eax, [ebp+UnicodeModuleName] 77E80C5F       push eax                            ; lpModuleName 77E80C60       call _ComputeRedirectedDllName@8    ; 所谓重定向就是到用户程序的目录下加载同名的dll, 77E80C60                                           ; _ComputeRedirectedDllName函数将用户输入的ModuleName 77E80C60                                           ; 前面加上用户的目录(用户程序启动的目录)。 77E80C65       test eax, eax 77E80C67       jge short IfGetRedirDllName 77E80C69       push eax                            ; Status 77E80C6A       call _BaseSetLastNTError@4          ; BaseSetLastNTError(x) 77E80C6F       xor eax, eax 77E80C71       jmp short GetModHandleExit 77E80C73 IfGetRedirDllName: 77E80C73       lea eax, [ebp+ModuleName] 77E80C76       push eax                            ; lpModuleName 77E80C77       call _GetModuleHandleForUnicodeString@4 ; GetModuleHandleForUnicodeString(x) 77E80C7C       mov esi, eax 77E80C7E       mov eax, large fs:18h               ; TEB.NT_TIB.Self 77E80C84       push [ebp+MemoryPointer]            ; MemoryPointer 77E80C87       mov eax, [eax+TEB.Peb] 77E80C8A       push 0                              ; Flags 77E80C8C       push [eax+PEB.ProcessHeap]          ; HeapHandle 77E80C8F       call ds:__imp__RtlFreeHeap@12       ; __declspec(dllimport) RtlFreeHeap(x,x,x) 77E80C95       test esi, esi 77E80C97       jz short DontDllRedir 77E80C99       mov eax, esi 77E80C9B       jmp short GetModHandleExit


_gDoDllRedirection = TRUE 首先调用 ComputeRedirectedDllName 函数,该函数通过调用 GetModuleFileNameW 获得用户程序的目录,其后是字符串处理,将用户的 lpModuleName 加在用户的目录之后。这里就不列出这段代码了。
然后调用 GetModuleHandleForUnicodeString,因为在 ComputeRedirectedDllName 函数中分配了堆栈,所以后面调用 RtlFreeHeap 释放堆栈。GetModuleHandleForUnicodeString 将在下面一起说明。

如果发生错误,将调用 BaseSetLastNTError ,代码如下:

代码:
77E68239 _BaseSetLastNTError@4 proc near 77E68239 Status= dword ptr  8 77E68239       push esi 77E6823A       push [esp+Status]                   ; Status 77E6823E       call ds:__imp__RtlNtStatusToDosError@4 ; __declspec(dllimport) RtlNtStatusToDosError(x) 77E68244       mov esi, eax 77E68246       push esi                            ; Status 77E68247       call _SetLastError@4                ; SetLastError(x) 77E6824C       mov eax, esi 77E6824E       pop esi 77E6824F       retn 4 77E6824F _BaseSetLastNTError@4 endp 77E68252 ; Exported entry 675. SetLastError 77E68252 ; ======== S U B R O U T I N E ==================== 77E68252 ; int __stdcall SetLastError(NTSTATUS Status) 77E68252       public _SetLastError@4 77E68252 _SetLastError@4 proc near 77E68252 Status= dword ptr  8 77E68252 77E68252       push ebp 77E68253       mov ebp, esp 77E68255       mov eax, large fs:18h 77E6825B       mov ecx, [ebp+Status] 77E6825E       mov [eax+TEB.LastErrorValue], ecx 77E68261       pop ebp 77E68262       retn 4 77E68262 _SetLastError@4 endp


内部函数 BaseSetLastNTError 调用 RtlNtStatusToDosError 将Nt的错误代码转换成Dos错误代码,然后调用 SetLastError,上面代码显示,SetLastError 就是将错误代码存入 TEB.LastErrorValue。

2.2 _gDoDllRedirection = FALSE

代码:
         ;_gDoDllRedirection = FALSE 77E80C9D DontDllRedir: 77E80C9D       lea eax, [ebp+UnicodeModuleName] 77E80CA0       push eax                            ; lpModuleName 77E80CA1       call _GetModuleHandleForUnicodeString@4 ; GetModuleHandleForUnicodeString(x)


不管走那条路,最后都是调用内部函数 GetModuleHandleForUnicodeString,代码如下:

代码:
77E80B49 ; BOOLEAN __stdcall GetModuleHandleForUnicodeString(LPCTSTR lpModuleName) 77E80B49 _GetModuleHandleForUnicodeString@4 proc near 77E80B49 77E80B49 TEB   = dword ptr -30h 77E80B49 var_2C= dword ptr -2Ch 77E80B49 pHModule= dword ptr -28h 77E80B49 NtStatus1= dword ptr -20h 77E80B49 MemoryPointer= dword ptr -1Ch 77E80B49 storeESP= dword ptr -18h 77E80B49 var_14= dword ptr -14h 77E80B49 var_10= dword ptr -10h 77E80B49 RetStatus= dword ptr -4 77E80B49 lpModuleName= dword ptr  8 77E80B49 77E80B49       push ebp 77E80B4A       mov ebp, esp 77E80B4C       push 0FFFFFFFFh 77E80B4E       push offset dword_77E62550 77E80B53       push offset __except_handler3 77E80B58       mov eax, large fs:0 77E80B5E       push eax 77E80B5F       mov large fs:0, esp 77E80B66       push ecx 77E80B67       push ecx 77E80B68       sub esp, 1Ch 77E80B6B       push ebx 77E80B6C       push esi 77E80B6D       push edi 77E80B6E       mov [ebp+storeESP], esp 77E80B71       lea eax, [ebp+pHModule] 77E80B74       push eax                            ; pHModule 77E80B75       push [ebp+lpModuleName]             ; ModuleFileName 77E80B78       push 0                              ; Unused 77E80B7A       push 1                              ; DllPath 77E80B7C       call _LdrGetDllHandle@16            ; LdrGetDllHandle(x,x,x,x) 77E80B81       test eax, eax 77E80B83       jge loc_77E80C13 77E80B89       call _GetEnvironmentStringsW@0      ; GetEnvironmentStringsW() 77E80B8E       mov esi, eax 77E80B90       test esi, esi 77E80B92       jz short loc_77E80BAB 77E80B94       push esi                            ; Environment 77E80B95       push FALSE                          ; BOOLEAN 77E80B97       call _BaseComputeProcessDllPath@8   ; BaseComputeProcessDllPath(x,x) 77E80B9C       mov ebx, eax 77E80B9E       mov [ebp+MemoryPointer], ebx 77E80BA1       push esi 77E80BA2       call _FreeEnvironmentStringsA@4     ; FreeEnvironmentStringsA(x) 77E80BA7       test ebx, ebx 77E80BA9       jnz short loc_77E80BB2 77E80BAB loc_77E80BAB: 77E80BAB       mov esi, STATUS_NO_MEMORY 77E80BB0       jmp short loc_77E80C05 77E80BB2 loc_77E80BB2: 77E80BB2       and [ebp+RetStatus], 0 77E80BB6       lea eax, [ebp+pHModule] 77E80BB9       push eax                            ; pHModule 77E80BBA       push [ebp+lpModuleName]             ; ModuleFileName 77E80BBD       push 0                              ; Unused 77E80BBF       push ebx                            ; DllPath 77E80BC0       call _LdrGetDllHandle@16            ; LdrGetDllHandle(x,x,x,x) 77E80BC5       mov esi, eax 77E80BC7       mov [ebp+NtStatus1], esi            ; NTSTATUS 77E80BCA       mov eax, large fs:18h 77E80BD0       mov [ebp+TEB], eax 77E80BD3       push ebx                            ; MemoryPointer 77E80BD4       jmp short loc_77E80BF3 77E80BD6 loc_77E80BD6: 77E80BD6       mov eax, [ebp+var_14] 77E80BD9       mov eax, [eax] 77E80BDB       mov eax, [eax] 77E80BDD       mov [ebp+var_2C], eax 77E80BE0       push TRUE 77E80BE2       pop eax 77E80BE3       retn 77E80BE4 77E80BE4 loc_77E80BE4: 77E80BE4       mov esp, [ebp+storeESP] 77E80BE7       mov esi, [ebp+var_2C] 77E80BEA       mov eax, large fs:18h 77E80BF0       push [ebp+MemoryPointer]            ; MemoryPointer 77E80BF3 loc_77E80BF3: 77E80BF3       push 0                              ; Flags 77E80BF5       mov eax, [eax+TEB.Peb] 77E80BF8       push [eax+PEB.ProcessHeap]          ; HeapHandle 77E80BFB       call ds:__imp__RtlFreeHeap@12       ; __declspec(dllimport) RtlFreeHeap(x,x,x) 77E80C01       or [ebp+RetStatus], 0FFFFFFFFh 77E80C05 loc_77E80C05: 77E80C05       test esi, esi 77E80C07       jge short loc_77E80C13 77E80C09       push esi                            ; Status 77E80C0A       call _BaseSetLastNTError@4          ; BaseSetLastNTError(x) 77E80C0F       xor eax, eax 77E80C11       jmp short loc_77E80C16 77E80C13 loc_77E80C13: 77E80C13       mov eax, [ebp+pHModule] 77E80C16 loc_77E80C16: 77E80C16       mov ecx, [ebp+var_10] 77E80C19       mov large fs:0, ecx 77E80C20       pop edi 77E80C21       pop esi 77E80C22       pop ebx 77E80C23       leave 77E80C24       retn 4 77E80C24 _GetModuleHandleForUnicodeString@4 endp


GetModuleHandleForUnicodeString 头部涉及一个 C 的异常处理过程,网上有许多这方面的文章,这里就不加解释了,(无非就是抄一遍),这是一篇英文的资料,写的非常详细,http://www.codeproject.com/cpp/exceptionhandler.asp,网上好像有中文的翻译版,你可以自己 google 一把。

GetModuleHandleForUnicodeString 实际就是调用 _LdrGetDllHandle 函数,该函数在 ntdll.dll 中,但最后通过调 NtOpenFile 等一系列的底层函数,完成,其过程非常复杂,代码超过 1000行,这里就不列出了。
如果调用失败,调用 GetEnvironmentStringsW 获得环境串,遍列环境串中的所有目录,重复调用 _LdrGetDllHandle,直到成功或失败。

后话:这一篇写的死气沉沉,何也,心情不好,也没有时间,在匆忙中草率完成,所以无法再笑解了,不哭解已经不错了。我的 House 终于找到租客,原本是好事,可惜每天忙着整理东西,然后想蚂蚁那样,一件一件的搬到 Basement 去,孤家寡人.每天楼上楼下没完没了的跑,都没有时间碰计算机,在过几天,连这个都没有了,估计要若干年后才能回来,我的电脑太重,没有办法搬到中国去,几年后也成为垃圾,只好送给邻居,一个鬼子好朋友。家里还有那么多的红木家具,都不知怎么搬下去,这里不同中国,周围没有一个中国人,最后也只好去找鬼子帮忙。还有7000多平方英尺的花园要种草,先卖 Top soil。然后铺上,然后平整,然后施肥,然后播Canada No1.的草种,这可是真正的苦力。这一切都要在交房前完成,只好和计算机拜拜了,如果还能抽点时间,准备写如何打造自己的 GetModuleHandle 和 GetProcAddress。
回到中国,买了计算机后,就又能泡制垃圾了。