延迟加载Dll的实现原理

延迟加载Dll的实现原理

一. 简介
延迟加载Dll是一个混合方式,它通过LoadLirary和GetProcAddress获得延迟加载函数的地址,然后直接转向对延迟加载函数的调用。
延迟加载不是操作系统的一个特征,它完全是通过链接器和运行库加入额外的代码和数据来实现的。同样地,无法在WINNT.H里找到关于延迟加载的更多参考,不过,可以在延迟装入数据(Delayload Data)和常规的输入数据之间看到一定的相似之处。
Datadirectory数组中的IMAGE_DIRECTORY_ENTRY_IMPORT条目指向延迟加载的数据。这是一个指向ImgDelayDescr结构数组的RVA,这个结构定义在VisualC++的DelayImp.h中,表1.1说明了它的内容。每一个被延迟加载的Dll都对应一个ImgDelayDescr结构。
 
表1.1 ImgDelayDescr 结构

大小 成员 描述
DWORD grAttrs 这个结构的属性。目前唯一被定义的旗标是dlattrRva,表明这个结构中的字段应该被认为是RVA,而不是虚地址
RVA rvaDLLName 指向一个被输入的DLL的名称的RVA。这个字符串被传递给LoadLibrary
RVA rvaHmod 指向一个HMODULE大小的内存位置的RVA。当延迟装入的DLL被装入内存后,它的模块句柄(hModule)被保存在这个地方
RVA rvaIAT 指向这个Dll的输入地址表的RVA,它与常规的IAT的格式相同
RVA rvaINT 指向这个DLL的输入名称表的RVA,它与常规的INT表格式相同
RVA rvaBoundIAT 可选的绑定IAT的RVA,指向这个DLL的输入地址表的绑定拷贝,它与常规的IAT表的格式相同,目前,这个IAT的拷贝并不是实际的绑定,但是这个特征可能会加到绑定程序的未来版本中
RVA rvaUnloadIAT 原始IAT的可选拷贝的RVA。它指向这个DLL的输入地址表的未绑定拷贝。它与常规的IAT表的格式相同,通常设为0
DWORD dwTimeStamp 延迟装入的输入DLL的时间/日期戳,通常设为0

 
ImgDelayDescr结构的关键在于它包括了对应了DLL的IAT和INT的地址,这些表在格式上与常规的输入表是相同的,唯一的区别是他们有运行库代码而不是由操作系统进行写入和读出。当第一次从一个延迟装入的DLL中调用一个API函数时,运行库调用LoadLibrary(如果需要),然后是GetProcAddress,最后得到的地址被存在延迟加载的IAT表中。这样以后每次调用这个API时都会直接到这里来。
在Visual C++6.0 中有延迟装入数据最初的原型。ImgDelayDescr中所有包含地址的域均是虚拟地址,而不是RVA。换句话说,它们包含延迟装入数据所在位置的实际地址。这些域是双字,是x86上一个指针的大小。现在向IA-64的快速移植正在被支持。很显然4个字节不足以装下一个完整的地址,在这一点上,微软做了件正确的事情,已将包含地址的字段改为RVA。表1-10中已经使用修正的结构定义和名称。
关于在ImgDelayDescr结构中使用RVA还是虚拟地址,现在仍有争论,在这个结构中有一个字段是旗标值。当grAttrs字段被设为1时,结构成员被当做RVA。这是惟一与Visual Studio.net和64位编译器一起出现的选项。如果在grAttrs中的这个位关掉,ImgDelayDescr字段将是虚拟地址。
 
二. 逆向分析
这里以urlmon.dll模块首次调用被设置为延迟加载的Wininet.dll文件中的InternetReadFile函数为例
 
BOOL InternetReadFile( HINTERNET hFile,  LPVOID lpBuffer,  DWORDdwNumberOfBytesToRead,  LPDWORD lpdwNumberOfBytesRead );
 
Parameters
 
hFile [in]       Handle returned from a previous call to InternetOpenUrlFtpOpenFile,GopherOpenFile, or HttpOpenRequest.
lpBuffer [out] Pointer to a buffer that receives the data.
dwNumberOfBytesToRead [in]     Number of bytes to be read.
lpdwNumberOfBytesRead [out]    Pointer to a variable that receives the number of bytes read. InternetReadFile sets this value to zero before doing any work or error checking.
 
第一次调用被延迟加载的HttpReadfile函数:
75C7BBAE    50              push    eax        ;参数lpdwNumberOfBytesRead [out]
75C7BBAF    8B45 08         mov     eax, dword ptr [ebp+8]
75C7BBB2    51              push    ecx               ;参数dwNumberOfBytesToRead [in]
75C7BBB3    03C7            add     eax, edi
75C7BBB5    50              push    eax                             ;参数lpBuffer [out]
75C7BBB6    FF76 6C         push    dword ptr [esi+6C]          ;参数hFile
75C7BBB9    33DB            xor     ebx, ebx
75C7BBBB    895D FC         mov     dword ptr [ebp-4], ebx
75C7BBBE    FF15 3020CD75   call    dword ptr [75CD2030]        ; InternetReadFile
(图一)
其中地址75CD2030 中保存的是一个函数指针,它指向0x75c7bb08,如图(二)
 
75CD2030 08 BB C7 75 47 8A 6A 76 A4 68 CC 75 B3 68 CC 75 uGv蘵砲蘵
75CD2040 C2 68 CC 75 B4 90 6A 76 DA 58 69 76 D1 68 CC 75 耯蘵磹jviv裩蘵
75CD2050 FC 73 69 76 CD 36 69 76 38 71 68 76 E0 68 CC 75 iv?iv8qhv鄅蘵
75CD2060 EF 68 CC 75 FE 68 CC 75 0D 69 CC 75 1C 69 CC 75 飄蘵蘵.ii
75CD2070 2B 69 CC 75 3A 69 CC 75 49 69 CC 75 58 69 CC 75 +i:iIiXi
75CD2080 67 69 CC 75 76 69 CC 75 85 69 CC 75 94 69 CC 75 givi蘵卛蘵攊蘵
75CD2090 A3 69 CC 75 B2 69 CC 75 C1 69 CC 75 78 33 6A 76 蘵瞚蘵羒蘵x3jv
75CD20A0 D0 69 CC 75 68 68 CC 75 A5 B1 68 76 69 62 69 76 衖蘵hh蘵ケhvibiv
75CD20B0 88 9A C7 75 00 00 00 00 00 00 00 00 00 00 00 00 垰莡............
(图二)
 
该函数指针指向的代码如(图三),offset InternetReadFile的值是75CD2030
.text:75C7BB08 B8 30 20 CD 75                    mov     eax, offset InternetReadFile
.text:75C7BB0D E9 DE 03 FF FF                    jmp     loc_75C6BEF0
(图三)
将该值赋值给eax寄存器后,跳转到地址75C6BEF0,所有延迟加载的Dll文件中的函数第一次调用是都jmp到这个地址,不同的只是eax寄存器中的值。
75C6BEF0处的代码如(图四)所示:
 
75C6BEF0    51              push    ecx              
75C6BEF1    52              push    edx
75C6BEF2    50              push    eax
75C6BEF3    68 30E8CC75     push    75CCE830       ; 指向ImgDelayDescr结构体
75C6BEF8    E8 88FDFFFF     call    75C6BC85       ; 有两个参数,获得延迟加载函
                                                    ; 数的地址,并将其值写到eax
                                                    ; 的值75CD2030中,idata段相                                                                                          ; 的位置处
75C6BEFD    5A              pop     edx
75C6BEFE    59              pop     ecx
75C6BEFF    FFE0            jmp     eax           ; 正式执行延迟加载的函数,此
                                                   ; 时函数返回到75c73a5
(图四)
在(图四)中压入栈中的寄存器的值分别为
ecx =6a               保存的是参数dwNumberOfBytesToRead [in]的值,为准备读出的Buffer的长度
edx =18e3ec
eax =75cd2030             将来获得InternetReadFile函数所在的地址,保存在这里
 
call    75C6BC85的参数有两个分别为:
push    eax   ;其中eax值是75cd2030 实际上是ImgDelayDescr. rvaIAT中数组中的InternetReadFile函数相应的元素,如图(二)中第一个DWORD的值
push    75CCE830 ; 两个值的含义是75CCE830指向ImgDelayDescr结构体数组中,InternetReadFile所在的Dll文件 Wininet.dll所对应的元素。
 
以下是call    75C6BC85函数的具体实现,所有延迟加载的Dll文件中的函数第一次调用是都调用这个函数,不同的只是函数的参数。
 
75C6BC85    8BC0            mov     eax, eax
75C6BC87    55              push    ebp
75C6BC88    8BEC            mov     ebp, esp
75C6BC8A    83EC 44         sub     esp, 44
75C6BC8D    53              push    ebx
75C6BC8E    B8 0000C675     mov     eax, 75C60000       ; eax是所加载dll在内存块中的基址
75C6BC93    56              push    esi
75C6BC94    8B75 08         mov     esi, dword ptr [ebp+8]   ; 该处存放75cce830
75C6BC97    8B56 08         mov     edx, dword ptr [esi+8]    ; rvaHmod
75C6BC9A    8B4E 04         mov     ecx, dword ptr [esi+4]    ; rvaDllName
75C6BC9D    8B5E 0C         mov     ebx, dword ptr [esi+C]   ; rvaIAT
75C6BCA0    03D0            add     edx, eax                   ; rvaHmod的相对地址
75C6BCA2    57              push    edi
75C6BCA3    8B7E 14         mov     edi, dword ptr [esi+14] ; rvaBoundIAT绑定地址
75C6BCA6    03F8            add     edi, eax       ; 获得绑定IAT表的相对地址
75C6BCA8    03C8            add     ecx, eax       ; 指向Dll字符串的地址-WININET.DLL
75C6BCAA    8955 E8      mov     dword ptr [ebp-18], edx   ; ImgDelayDescr.ravHmod 的地址值压入堆栈中
75C6BCAD    8B56 10         mov     edx, dword ptr [esi+10]          ; rvalINT
75C6BCB0    03D8            add     ebx, eax      ; rvalIAT的相对地址
75C6BCB2    03D0            add     edx, eax      ; rvalINT的相对地址
75C6BCB4    8B46 1C         mov     eax, dword ptr [esi+1C] ; 时间戳dwTimeStamp
75C6BCB7    8945 FC         mov     dword ptr [ebp-4], eax   ; 时间戳放到这里
75C6BCBA    8B45 0C         mov     eax, dword ptr [ebp+C] ; 第二个参数,用来保存所加载函数的地址
75C6BCBD    894D C8         mov     dword ptr [ebp-38], ecx ; 所加载的dll文件的名称
75C6BCC0    33C9            xor     ecx, ecx
75C6BCC2    897D F4         mov     dword ptr [ebp-C], edi           ; rvaBoundIAT绑定地址
75C6BCC5    8945 C4         mov     dword ptr [ebp-3C], eax    ; 用来存储dll 函数的地址
75C6BCC8    33C0            xor     eax, eax
75C6BCCA    F706 01000000   test    dword ptr [esi], 1    ; 判断ImgDelayDescr的似一个元素,是否是1
75C6BCD0    8D7D D0         lea     edi, dword ptr [ebp-30]     ; lpProcName     edi的值是13F998
75C6BCD3    C745 BC 2400000>mov     dword ptr [ebp-44], 24
75C6BCDA    8975 C0         mov     dword ptr [ebp-40], esi
75C6BCDD    894D CC         mov     dword ptr [ebp-34], ecx          ; 将一下个处清零
75C6BCE0    AB              stos    dword ptr es:[edi]
75C6BCE1    894D D4         mov     dword ptr [ebp-2C], ecx
75C6BCE4    894D D8         mov     dword ptr [ebp-28], ecx
75C6BCE7    894D DC         mov     dword ptr [ebp-24], ecx
75C6BCEA    0F84 05C40100   je      75C880F5
75C6BCF0    8B45 E8         mov     eax, dword ptr [ebp-18]          ; Target
75C6BCF3    8B38            mov     edi, dword ptr [eax]             ; Wininet所在的基地址76680000
75C6BCF5    8B45 0C         mov     eax, dword ptr [ebp+C]           ; 用于存放找到的延迟加载的函数的位置
75C6BCF8    2BC3            sub     eax, ebx                         ; ebx=75cd2014 eax=75cd2030 算出和首地址的距离
75C6BCFA    C1F8 02         sar     eax, 2                           ; 逻辑右移
75C6BCFD    C1E0 02         shl     eax, 2                           ; 算数左移,目的是使该值成为4的整数倍
75C6BD00    03D0            add     edx, eax                         ; edx==75cce91c
75C6BD02    8B12            mov     edx, dword ptr [edx]             ; 执行后edx的值是6eab0
75C6BD04    8945 08         mov     dword ptr [ebp+8], eax
75C6BD07    8BC2            mov     eax, edx
75C6BD09    C1E8 1F         shr     eax, 1F
75C6BD0C    F7D0            not     eax
75C6BD0E    83E0 01         and     eax, 1
75C6BD11    8945 CC         mov     dword ptr [ebp-34], eax          ; 这个值是1
75C6BD14   0F84 A1000000   je      75C6BDBB
75C6BD1A    8D82 0200C675   lea     eax, dword ptr [edx+75C60002]    ; 获得dll中函数的名称,基地址是75c60002
75C6BD20    8945 D0         mov     dword ptr [ebp-30], eax
75C6BD23    A1 6455CD75     mov     eax, dword ptr [75CD5564]
75C6BD28    33DB            xor     ebx, ebx
75C6BD2A    3BC1            cmp     eax, ecx
75C6BD2C    0F85 E2C30100   jnz     75C88114
75C6BD32    85FF            test    edi, edi                         ; 这里是所延迟加载的WININET.dll的基地址
75C6BD34    74 4C           je      short 75C6BD82
75C6BD36    A1 6455CD75     mov     eax, dword ptr [75CD5564]
75C6BD3B    85C0            test    eax, eax
75C6BD3D    897D D4         mov     dword ptr [ebp-2C], edi
75C6BD40    0F85 5DC40100   jnz     75C881A3
75C6BD46    85DB            test    ebx, ebx
75C6BD48    75 1D           jnz     short 75C6BD67
75C6BD4A    395E 14         cmp     dword ptr [esi+14], ebx
75C6BD4D    0F85 5FC40100   jnz     75C881B2
75C6BD53    FF75 D0         push    dword ptr [ebp-30]
75C6BD56    57              push    edi
75C6BD57    FF15 2412C675   call    dword ptr [<&KERNEL32.GetProcAdd>; 调用GetProcessAddress函数获得地址
75C6BD5D    8BD8            mov     ebx, eax
75C6BD5F    85DB            test    ebx, ebx
75C6BD61    0F84 90C40100   je      75C881F7
75C6BD67    8B45 0C         mov     eax, dword ptr [ebp+C]   ; 用来存放dll函数地址的指针,执行后eax=75cd2030
75C6BD6A    8918            mov     dword ptr [eax], ebx
75C6BD6C    A1 6455CD75     mov     eax, dword ptr [75CD5564]
75C6BD71    85C0            test    eax, eax
75C6BD73    0F85 C2C40100   jnz     75C8823B
75C6BD79    8BC3            mov     eax, ebx
75C6BD7B    5F              pop     edi
75C6BD7C    5E              pop     esi
75C6BD7D    5B              pop     ebx
75C6BD7E    C9              leave
75C6BD7F    C2 0800         retn    8
 
 
(图五)
 
这里的注释都很详细,函数的具体含义就不再解释了,最后的将获得的延迟加载的函数的地址被放入75cd2030和eax寄存器中,函数返回。
回到图四的函数调用75C6BEFF    FFE0            jmp     eax 去执行InternetReadFile函数,当第二次调用InternetReadFile函数时不在执行以上过程,直接去调用InternetReadFile函数。
 
 
 
75C6BD82    A1 6455CD75     mov     eax, dword ptr [75CD5564]
75C6BD87    85C0            test    eax, eax
75C6BD89    0F85 9BC30100   jnz     75C8812A
75C6BD8F    FF75 C8         push    dword ptr [ebp-38]
75C6BD92    FF15 0C12C675   call    dword ptr [<&KERNEL32.LoadLibrar>; kernel32.LoadLibraryA
75C6BD98    8BF8            mov     edi, eax
75C6BD9A    85FF            test    edi, edi
75C6BD9C    0F84 95C30100   je      75C88137
75C6BDA2    57              push    edi
75C6BDA3    FF75 E8         push    dword ptr [ebp-18]
75C6BDA6    FF15 7012C675   call    dword ptr [<&KERNEL32.Interlocke>; kernel32.InterlockedExchange
75C6BDAC    3BC7            cmp     eax, edi
75C6BDAE    74 21           je      short 75C6BDD1
75C6BDB0    837E 18 00      cmp     dword ptr [esi+18], 0
75C6BDB4 ^ 74 80           je      short 75C6BD36
75C6BDB6    E9 C1C30100     jmp     75C8817C
75C6BDBB    81E2 FFFF0000   and     edx, 0FFFF
75C6BDC1    8955 D0         mov     dword ptr [ebp-30], edx
75C6BDC4 ^ E9 5AFFFFFF     jmp     75C6BD23
75C6BDC9    8BF8            mov     edi, eax
75C6BDCB    85FF            test    edi, edi
75C6BDCD ^ 74 C0           je      short 75C6BD8F
75C6BDCF ^ EB D1           jmp     short 75C6BDA2
75C6BDD1    57              push    edi
75C6BDD2    FF15 3812C675   call    dword ptr [<&KERNEL32.FreeLibrar>; kernel32.FreeLibrary
75C6BDD8 ^ E9 59FFFFFF     jmp     75C6BD36
图六
 
当在同一个dll文件中有多个函数需要延迟加载时,当加载该dll中的第一个函数时,在执行(图五)的函数的过程中会调用(图六)中的这段代码,来获得该dll的基地址,即调用kernel32.LoadLibraryA的返回值。

参考来源: http://blog.csdn.net/anzijin/article/details/2008337

你可能感兴趣的:(延迟加载Dll的实现原理)