本文轉載自【微信公眾號:MicroPest,ID:gh_696c36c5382b】
題外話:dll的的載入有兩種最基本的方法:隱式加載和顯式加載。DLL DelayLoad,即延遲加載,是一項提高程序啟動速度,減少進程地址空間的重要技術,用通俗一點的話來講,就是對模塊「呼之即來,揮之即去」的能力。DelayLoad是微軟提供的一項技術,讓模塊在其symbol第一次被引用到時才被加載。我認為DelayLoad是顯式加載的一種花式應用。
本篇文章帶你進入DelayLoad,了解windows的巧妙運作;文章的最後,給出了它的具體影像。
一、簡介:
通常的,當調用一個dll中的函數時,連接器會將dll和函數加入你的可執行文件。最後,所有引用的函數會放在imports段中;當加載該程序的時候,win32程序加載器會掃描所有imports段的每個dll加載和重新定位imports段的所有函數,將信息寫入導入表IAT。簡單說來,IAT就是一個函數指針的表,調用該引入函數的時候,就到IAT中去找。
那麼,DelayLoad的機理是什麼呢?當你為一個Dll進行"DelayLoad"的時候,連接器不將原來的值放入imports段;相反,它為每個DelayLoad的引入函數的名稱和地址生成一個小的根區,備份下來。第一次引用的時候,它調用LoadLibrary加載Dll,然後它調用GetProcAddress取得該函數的地址。最後,改寫自己在IAT的值,以便以後的程序可以直接調用。
例如:你的程序中包含列印的代碼,毫無疑問,即使用戶沒有使用列印功能,你的程序也一定要加載winspool.drv。在這種情況下,使用DelayLoad,你就不必加載和初始化Winspool.drv。
二、如何使用DelayLoad的呢?
1、兩件事:
A、cpp的開頭加上#pragma comment(lib, "DelayImp.lib"),如果你使用vs也可以在input裡面加上這麼一項。
B、在編譯的時候加上:/link /DELAYLOAD:xxx.dll,如果使用vs只要在項目屬性中找到DelayLoad這一項,加上dll的名字。
2、分析用DelayLoad之後跟之前產生了什麼變化:
1)該dll相關的輸入表肯定沒了。毫無疑問,否則程序啟動的時候還是會被無辜的加載。
2)需要有什麼欄位記錄dll的加載地址和函數的地址吧?否則每次調用都要LoadLibrary+GetProcAddress豈不是太不智能了?
3)誰來調用LoadLibrary+GetProcAddress以及填充這些欄位麼?看來在連結的時候肯定要嵌入一下代碼。那嵌入的代碼哪裡來?還記得之前的#pragma comment(lib, "DelayImp.lib")麼?對了,就是這裡來。
3、接下來我們就用一個最簡單的例子來分析:
PE結構中Data Directory中有一項IMAGE_DIRECTORY_ENTRY_DELAY_IMPORT就是維護了跟DelayLoad相關的信息。
我們先看一下對應的ImgDelayDescr結構體:
看著是不是有點眼熟?跟IMAGE_IMPORT_DESCRIPTOR有點像:都有dll name,INT,IAT。
編譯+連結:cl sample.cpp /link /DELAYLOAD:delayload.dll
我們看sample.exe中ImgDelayDescr現在什麼情況:
接下來用ollydbg打開sample.exe,
我們以第一個export1的調用為例:CALL DWORD PTR DS:[39B20]
[39B20]往上看看rvaIAT的值正是9B20,也就是rvaIAT中的第一項。rvaIAT中第一項對應的數據是401034(1034),也就是說這個調用其實就是CALL 31034:
結合31023中的代碼我們可以推出如下結論:
1)每一個rvaIAT項中保存了一個地址,該地址位於代碼段中,由CALL DWORD PTR DS:[XXX]跳轉進入到該代碼段(XXX是rvaIAT中某一項的地址);
2)由CALL DWORD PTR DS:[XXX]跳轉進入的代碼段具有統一的格式:
A、將該rvaIAT項的地址保存在EAX中;
B、跳轉到某一個地址(本程序中31023);
C、在該地址中調用一個函數(本程序中3103E,見後面),該函數將完成DelayLoad的所有工作,並修改rvaIAT中的對應項使之擁有正確的函數地址;
D、調用完該函數後JMP EAX,這個時候rvaIAT中對應的項已經有正確的函數地址了。
3)3103E處的代碼:完成DelayLoad的所有工作
(可以跳過,這下面太複雜,僅給有能力看的人;)
0003103E 8BFF MOV EDI,EDI
00031040 55 PUSH EBP
00031041 8BEC MOV EBP,ESP
00031043 83EC 44 SUB ESP,44
00031046 53 PUSH EBX
00031047 B8 00000300 MOV EAX,test.00030000
// EXE的加載地址
0003104C 56 PUSH ESI
0003104D 8B75 08 MOV ESI,DWORD PTR SS:[EBP+8]
// EBP+8指向第二個參數(ESI:00037A1C ImgDelayDescr地址) 註:EBP+4指向返回地址
00031050 8B56 08 MOV EDX,DWORD PTR DS:[ESI+8]
// ImgDelayDescr::rvaHmod(EDX:9B40) rvaHmod將來保存DLL加載地址
00031053 8B4E 04 MOV ECX,DWORD PTR DS:[ESI+4]
// ImgDelayDescr::rvaDllName(ECX:6130)
00031056 8B5E 0C MOV EBX,DWORD PTR DS:[ESI+C]
// ImgDelayDescr::rvaIAT(EBX:9B20)
00031059 03D0 ADD EDX,EAX
// EDX: 39B40(rvaHmod)
0003105B 57 PUSH EDI
0003105C 8B7E 14 MOV EDI,DWORD PTR DS:[ESI+14]
// ImgDelayDescr::rvaBoundIAT(EDI:7A7C)
0003105F 03F8 ADD EDI,EAX
// EDI:37A7C(rvaBoundIAT)
00031061 03C8 ADD ECX,EAX
// ECX:36130(rvaDllName)
00031063 8955 E8 MOV DWORD PTR SS:[EBP-18],EDX
// EDX:
00031066 8B56 10 MOV EDX,DWORD PTR DS:[ESI+10]
// ImgDelayDescr::rvaINT(EDX:7A5C)
00031069 03D8 ADD EBX,EAX
// EBX: 39B20(rvaIAT)
0003106B 03D0 ADD EDX,EAX
// EDX: 37A5C(rvaINT)
0003106D 8B46 1C MOV EAX,DWORD PTR DS:[ESI+1C]
// ImgDelayDescr::dwTimeStamp(EAX:0)
00031070 8945 FC MOV DWORD PTR SS:[EBP-4],EAX
// EAX: 0(dwTimeStamp)
00031073 8B45 0C MOV EAX,DWORD PTR SS:[EBP+C]
// EBP+C指向第一個參數(EAX: 39B20) 該地址將來保存函數地址
00031076 894D C8 MOV DWORD PTR SS:[EBP-38],ECX
// ECX: 36130(rvaDllName)
00031079 33C9 XOR ECX,ECX
0003107B 897D F4 MOV DWORD PTR SS:[EBP-C],EDI
// EDI: 37A7C(rvaBoundIAT)
0003107E 8945 C4 MOV DWORD PTR SS:[EBP-3C],EAX
// EAX: 39B20(函數地址保存位置)
00031081 33C0 XOR EAX,EAX
00031083 F706 01000000 TEST DWORD PTR DS:[ESI],1
// ZF=0
00031089 8D7D D0 LEA EDI,DWORD PTR SS:[EBP-30]
// EDI=EBP-30=003EF9D8
0003108C C745 BC 24000>MOV DWORD PTR SS:[EBP-44],24
00031093 8975 C0 MOV DWORD PTR SS:[EBP-40],ESI
// ESI: 37A1C(ImgDelayDescr)
00031096 894D CC MOV DWORD PTR SS:[EBP-34],ECX
// ECX: 0
00031099 AB STOS DWORD PTR ES:[EDI]
0003109A 894D D4 MOV DWORD PTR SS:[EBP-2C],ECX
0003109D 894D D8 MOV DWORD PTR SS:[EBP-28],ECX
000310A0 894D DC MOV DWORD PTR SS:[EBP-24],ECX
000310A3 75 1F JNZ SHORT test.000310C4
// JMP 000310C4 因為grAttrs=1
/////////////////////////////////////////////////////////////////////////////////////
// 如果grAttrs==0,引發一個異常並JMP 00031272結束這段子程,也就是目前為止微軟只支持RVA之中方式
// RaiseExcep的最後一個參數在做什麼?看這個代碼似乎不是很明朗...回頭再看看能不能解決...
000310A5 8D45 BC LEA EAX,DWORD PTR SS:[EBP-44]
000310A8 8945 0C MOV DWORD PTR SS:[EBP+C],EAX
000310AB 8D45 0C LEA EAX,DWORD PTR SS:[EBP+C]
000310AE 50 PUSH EAX
// parguments
000310AF 6A 01 PUSH 1
// nNumberOfarguments = 1
000310B1 51 PUSH ECX
// dwExceptionFlags = 0(允許過濾器返回EXCEPTION_CONTINUE_EXECUTION)
000310B2 68 57006DC0 PUSH C06D0057
// dwExceptionCode = C06D0057
000310B7 FF15 18600300 CALL DWORD PTR DS:[<&KERNEL32.RaiseExcep>;
000310BD 33C0 XOR EAX,EAX
000310BF E9 AE010000 JMP test.00031272
/////////////////////////////////////////////////////////////////////////////////////
000310C4 8B45 E8 MOV EAX,DWORD PTR SS:[EBP-18]
// EAX: 39B40(rvaHmod)
000310C7 8B38 MOV EDI,DWORD PTR DS:[EAX]
// EDI: 0(現在rvaHmod的值是0,表示此DLL還未加載過)
000310C9 8B45 0C MOV EAX,DWORD PTR SS:[EBP+C]
// EAX: 39B20(參數一:最終函數的保存地址)
000310CC 2BC3 SUB EAX,EBX
// 該函數的保存地址 - rvaIAT首地址 = 該函數在rvaIAT中的偏移,因為export1是第一項,所以為0
000310CE 83E0 FC AND EAX,FFFFFFFC
// 4的倍數
000310D1 8B1402 MOV EDX,DWORD PTR DS:[EDX+EAX]
// EDX: 7A72(通過偏移找到對應rvaINT中位置,並讀取該位置的值,通過該值可以找到需要的函數名)
000310D4 8945 08 MOV DWORD PTR SS:[EBP+8],EAX
// EAX: 0(偏移)
000310D7 8BC2 MOV EAX,EDX
// EAX: 7A72(export1對應的IMAGE_IMPORT_BY_NAME的地址)
000310D9 C1E8 1F SHR EAX,1F
000310DC F7D0 NOT EAX
000310DE 83E0 01 AND EAX,1
000310E1 8945 CC MOV DWORD PTR SS:[EBP-34],EAX
// 此時EAX=1
000310E4 8D82 02000300 LEA EAX,DWORD PTR DS:[EDX+30002]
// EAX=EDX+30002=00037A74(此處正是除去Hint之後export1函數名的地址!)
000310EA 75 03 JNZ SHORT test.000310EF
// JMP 000310EF
000310EC 0FB7C2 MOVZX EAX,DX
000310EF 8945 D0 MOV DWORD PTR SS:[EBP-30],EAX
// EAX: 00037A74(export1函數名地址)
000310F2 A1 4C9B0300 MOV EAX,DWORD PTR DS:[39B4C]
// 39B4C: 這個是什麼地址?EAX: 0
000310F7 33DB XOR EBX,EBX
000310F9 3BC1 CMP EAX,ECX
// CMP EAX, 0
000310FB 74 11 JE SHORT test.0003110E
// JMP 0003110E
/////////////////////////////////////////////////////////////////////////////////////
// 如果39B4C這個地址不是0,那就表示一個函數地址,用0,EDX(EBP-44=3EF9C4)分別作為兩個參數傳入
// 如果該函數的返回值不是0,JMP 00031255
000310FD 8D55 BC LEA EDX,DWORD PTR SS:[EBP-44]
00031100 52 PUSH EDX
00031101 51 PUSH ECX
00031102 FFD0 CALL EAX
00031104 8BD8 MOV EBX,EAX
00031106 85DB TEST EBX,EBX
00031108 0F85 47010000 JNZ test.00031255
/////////////////////////////////////////////////////////////////////////////////////
0003110E 85FF TEST EDI,EDI
// EDI: 0(DLL的加載地址) ###如果不是第一次調用該DLL中的函數,那就!=0,JMP 000311B8###
00031110 0F85 A2000000 JNZ test.000311B8
// No JMP. 因為DLL未加載過,先要LoadLibrary. ###如果DLL已經加載,JMP有效###
00031116 A1 4C9B0300 MOV EAX,DWORD PTR DS:[39B4C]
// 39B4C: 這個是什麼地址?EAX: 0
0003111B 85C0 TEST EAX,EAX
0003111D 74 0E JE SHORT test.0003112D
// JMP 0003112D 因為EAX=0
/////////////////////////////////////////////////////////////////////////////////////
// 如果39B4C這個地址不是0,那就表示一個函數地址,用1,ECX(EBP-44=3EF9C4)分別作為兩個參數傳入
// 如果該函數的返回值不是0,JMP 0003117D(也就是調用InterlockedExchange的地方)
0003111F 8D4D BC LEA ECX,DWORD PTR SS:[EBP-44]
00031122 51 PUSH ECX
00031123 6A 01 PUSH 1
00031125 FFD0 CALL EAX
00031127 8BF8 MOV EDI,EAX
00031129 85FF TEST EDI,EDI
0003112B 75 50 JNZ SHORT test.0003117D
/////////////////////////////////////////////////////////////////////////////////////
0003112D FF75 C8 PUSH DWORD PTR SS:[EBP-38]
// rvaDLLName: delayLoad.dll
00031130 FF15 14600300 CALL DWORD PTR DS:[<&KERNEL32.LoadLibrar>; // LoadLibraryA
00031136 8BF8 MOV EDI,EAX
// EDI:6EE40000(DLL加載地址)
00031138 85FF TEST EDI,EDI
0003113A 75 41 JNZ SHORT test.0003117D
// JMP 0003117D
/////////////////////////////////////////////////////////////////////////////////////
// 如果LoadLibrary失敗, 調用一系列的處理函數(GetLastError/RaiseException)並返回.
0003113C FF15 10600300 CALL DWORD PTR DS:[<&KERNEL32.GetLastErr>; [GetLastError
00031142 8945 DC MOV DWORD PTR SS:[EBP-24],EAX
00031145 A1 489B0300 MOV EAX,DWORD PTR DS:[39B48]
0003114A 85C0 TEST EAX,EAX
0003114C 74 0E JE SHORT test.0003115C
0003114E 8D4D BC LEA ECX,DWORD PTR SS:[EBP-44]
00031151 51 PUSH ECX
00031152 6A 03 PUSH 3
00031154 FFD0 CALL EAX
00031156 8BF8 MOV EDI,EAX
00031158 85FF TEST EDI,EDI
0003115A 75 21 JNZ SHORT test.0003117D
0003115C 8D45 BC LEA EAX,DWORD PTR SS:[EBP-44]
0003115F 8945 0C MOV DWORD PTR SS:[EBP+C],EAX
00031162 8D45 0C LEA EAX,DWORD PTR SS:[EBP+C]
00031165 50 PUSH EAX
; /pArguments
00031166 6A 01 PUSH 1
;|nArguments = 1
00031168 6A 00 PUSH 0
; |ExceptionFlags = EXCEPTION_CONTINUABLE
0003116A 68 7E006DC0 PUSH C06D007E
; |ExceptionCode = C06D007E
0003116F FF15 18600300 CALL DWORD PTR DS:[<&KERNEL32.RaiseExcep>; /RaiseException
00031175 8B45 D8 MOV EAX,DWORD PTR SS:[EBP-28]
00031178 E9 F5000000 JMP test.00031272
/////////////////////////////////////////////////////////////////////////////////////
0003117D 57 PUSH EDI
// InterlockedExchange參數2:6EE40000(DLL加載地址)
0003117E FF75 E8 PUSH DWORD PTR SS:[EBP-18]
// InterlockedExchange參數1:0
00031181 FF15 0C600300 CALL DWORD PTR DS:[<&KERNEL32.Interlocke>;
00031187 3BC7 CMP EAX,EDI
// 比較原來DLL加載地址中存放的值和通過LoadLibrary獲得的值
00031189 74 26 JE SHORT test.000311B1
// No JMP 因為兩個值不一樣
0003118B 837E 18 00 CMP DWORD PTR DS:[ESI+18],0
// ImgDelayDescr::rvaUnloadIAT(0)
0003118F 74 27 JE SHORT test.000311B8
// JMP 000311B8
/////////////////////////////////////////////////////////////////////////////////////
// 這裡暫時不知道在做什麼...
00031191 6A 08 PUSH 8
00031193 6A 40 PUSH 40
00031195 FF15 00600300 CALL DWORD PTR DS:[<&KERNEL32.LocalAlloc>
0003119B 85C0 TEST EAX,EAX
0003119D 74 19 JE SHORT test.000311B8
0003119F 8970 04 MOV DWORD PTR DS:[EAX+4],ESI
000311A2 8B0D 449B0300 MOV ECX,DWORD PTR DS:[39B44]
000311A8 8908 MOV DWORD PTR DS:[EAX],ECX
000311AA A3 449B0300 MOV DWORD PTR DS:[39B44],EAX
000311AF EB 07 JMP SHORT test.000311B8
000311B1 57 PUSH EDI
000311B2 FF15 08600300 CALL DWORD PTR DS:[<&KERNEL32.FreeLibrar>
/////////////////////////////////////////////////////////////////////////////////////
000311B8 A1 4C9B0300 MOV EAX,DWORD PTR DS:[39B4C]
// 39B4C: 這個是什麼地址?EAX: 0 ###如果DLL加載過,直接JMP到這裡###
000311BD 897D D4 MOV DWORD PTR SS:[EBP-2C],EDI
// EDI: 6EE40000(DLL的加載地址)
000311C0 85C0 TEST EAX,EAX
000311C2 74 0A JE SHORT test.000311CE
// JMP 000311CE
/////////////////////////////////////////////////////////////////////////////////////
// 如果39B4C這個地址不是0,那就表示一個函數地址,用2,ECX(EBP-44=3EF9C4)分別作為兩個參數傳入
000311C4 8D4D BC LEA ECX,DWORD PTR SS:[EBP-44]
000311C7 51 PUSH ECX
000311C8 6A 02 PUSH 2
000311CA FFD0 CALL EAX
000311CC 8BD8 MOV EBX,EAX
/////////////////////////////////////////////////////////////////////////////////////
000311CE 85DB TEST EBX,EBX
// EBX: 0
000311D0 75 7E JNZ SHORT test.00031250
// No JMP
000311D2 395E 14 CMP DWORD PTR DS:[ESI+14],EBX
// CMP rvaBoundIAT, EBX
000311D5 74 2D JE SHORT test.00031204
// No JMP
000311D7 395E 1C CMP DWORD PTR DS:[ESI+1C],EBX
// CMP dwTimeStamp, EBX
000311DA 74 28 JE SHORT test.00031204
// JMP 00031204
000311DC 8B47 3C MOV EAX,DWORD PTR DS:[EDI+3C]
// 接下來一串的條件跳轉,暫時跳過...
000311DF 813C38 504500>CMP DWORD PTR DS:[EAX+EDI],4550
000311E6 75 1C JNZ SHORT test.00031204
000311E8 8B4D FC MOV ECX,DWORD PTR SS:[EBP-4]
000311EB 394C38 08 CMP DWORD PTR DS:[EAX+EDI+8],ECX
000311EF 75 13 JNZ SHORT test.00031204
000311F1 3B7C38 34 CMP EDI,DWORD PTR DS:[EAX+EDI+34]
000311F5 75 0D JNZ SHORT test.00031204
000311F7 8B45 F4 MOV EAX,DWORD PTR SS:[EBP-C]
000311FA 8B4D 08 MOV ECX,DWORD PTR SS:[EBP+8]
000311FD 8B1C01 MOV EBX,DWORD PTR DS:[ECX+EAX]
00031200 85DB TEST EBX,EBX
00031202 75 4C JNZ SHORT test.00031250
00031204 FF75 D0 PUSH DWORD PTR SS:[EBP-30]
// GetProcAdd參數2:37A74: export1的函數名
00031207 57 PUSH EDI
// GetProcAdd參數1:DLL加載地址
00031208 FF15 04600300 CALL DWORD PTR DS:[<&KERNEL32.GetProcAdd>
0003120E 8BD8 MOV EBX,EAX
// EBX: 6EE41000(export1在進程中的實際地址)
00031210 85DB TEST EBX,EBX
00031212 75 3C JNZ SHORT test.00031250
// JMP 00031250 因為GetProcAdd成功
/////////////////////////////////////////////////////////////////////////////////////
// 如果GetProcAdd失敗, 調用一系列的處理函數(GetLastError/RaiseException)並返回.
00031214 FF15 10600300 CALL DWORD PTR DS:[<&KERNEL32.GetLastErr>
0003121A 8945 DC MOV DWORD PTR SS:[EBP-24],EAX
0003121D A1 489B0300 MOV EAX,DWORD PTR DS:[39B48]
00031222 85C0 TEST EAX,EAX
00031224 74 0E JE SHORT test.00031234
00031226 8D4D BC LEA ECX,DWORD PTR SS:[EBP-44]
00031229 51 PUSH ECX
0003122A 6A 04 PUSH 4
0003122C FFD0 CALL EAX
0003122E 8BD8 MOV EBX,EAX
00031230 85DB TEST EBX,EBX
00031232 75 1C JNZ SHORT test.00031250
00031234 8D45 BC LEA EAX,DWORD PTR SS:[EBP-44]
00031237 8945 08 MOV DWORD PTR SS:[EBP+8],EAX
0003123A 8D45 08 LEA EAX,DWORD PTR SS:[EBP+8]
0003123D 50 PUSH EAX
0003123E 6A 01 PUSH 1
00031240 6A 00 PUSH 0
00031242 68 7F006DC0 PUSH C06D007F
00031247 FF15 18600300 CALL DWORD PTR DS:[<&KERNEL32.RaiseExcep>
0003124D 8B5D D8 MOV EBX,DWORD PTR SS:[EBP-28]
/////////////////////////////////////////////////////////////////////////////////////
00031250 8B45 0C MOV EAX,DWORD PTR SS:[EBP+C]
// EAX: 39B20(第一個參數也就是函數地址即將儲存的位置)
00031253 8918 MOV DWORD PTR DS:[EAX],EBX
// EBX: 6EE41000(export1在進程中的實際地址) 終於完成了我們期待的一步!
00031255 A1 4C9B0300 MOV EAX,DWORD PTR DS:[39B4C]
// 39B4C: 這個是什麼地址?EAX: 0
0003125A 85C0 TEST EAX,EAX
0003125C 74 12 JE SHORT test.00031270
// JMP 00031270
/////////////////////////////////////////////////////////////////////////////////////
// 如果39B4C這個地址不是0,那就表示一個函數地址,用5,ECX(EBP-44=3EF9C4)分別作為兩個參數傳入
0003125E 8365 DC 00 AND DWORD PTR SS:[EBP-24],0
00031262 8D4D BC LEA ECX,DWORD PTR SS:[EBP-44]
00031265 51 PUSH ECX
00031266 6A 05 PUSH 5
00031268 897D D4 MOV DWORD PTR SS:[EBP-2C],EDI
0003126B 895D D8 MOV DWORD PTR SS:[EBP-28],EBX
0003126E FFD0 CALL EAX
/////////////////////////////////////////////////////////////////////////////////////
00031270 8BC3 MOV EAX,EBX
// EBX: 6EE41000(export1在進程中的實際地址)
00031272 5F POP EDI
00031273 5E POP ESI
00031274 5B POP EBX
00031275 C9 LEAVE
00031276 C2 0800 RETN 8
4)針對第一次調用DLL中函數,對照下微軟的delayhlp.cpp:
extern "C" FARPROC WINAPI __delayLoadHelper2(PCImgDelayDescr pidd, FARPROC* ppfnIATEntry)
{
// Set up some data we use for the hook procs but also useful for
// our own use
//
InternalImgDelayDescr idd = {
pidd->grAttrs,
PFromRva<LPCSTR>(pidd->rvaDLLName),
PFromRva<HMODULE*>(pidd->rvaHmod),
PFromRva<PImgThunkData>(pidd->rvaIAT),
PFromRva<PCImgThunkData>(pidd->rvaINT),
PFromRva<PCImgThunkData>(pidd->rvaBoundIAT),
PFromRva<PCImgThunkData>(pidd->rvaUnloadIAT),
pidd->dwTimeStamp
};
DelayLoadInfo dli = {
sizeof DelayLoadInfo,
pidd,
ppfnIATEntry,
idd.szName,
{ 0 },
0,
0,
};
if (0 == (idd.grAttrs & dlattrRva)) {
PDelayLoadInfo rgpdli[1] = { &dli };
RaiseException(
VcppException(ERROR_SEVERITY_ERROR, ERROR_INVALID_PARAMETER),
0,
1,
PULONG_PTR(rgpdli)
);
return 0;
}
HMODULE hmod = *idd.phmod;
// Calculate the index for the IAT entry in the import address table
// N.B. The INT entries are ordered the same as the IAT entries so
// the calculation can be done on the IAT side.
//
const unsigned iIAT = IndexFromPImgThunkData(PCImgThunkData(ppfnIATEntry), idd.pIAT);
const unsigned iINT = iIAT;
PCImgThunkData pitd = &(idd.pINT[iINT]);
dli.dlp.fImportByName = !IMAGE_SNAP_BY_ORDINAL(pitd->u1.Ordinal);
if (dli.dlp.fImportByName)
dli.dlp.szProcName = LPCSTR(PFromRva<PIMAGE_IMPORT_BY_NAME>(RVA(UINT_PTR(pitd->u1.AddressOfData)))->Name);
else
dli.dlp.dwOrdinal = DWORD(IMAGE_ORDINAL(pitd->u1.Ordinal));
// Call the initial hook. If it exists and returns a function pointer,
// abort the rest of the processing and just return it for the call.
//
FARPROC pfnRet = NULL;
if (__pfnDliNotifyHook2) {
pfnRet = ((*__pfnDliNotifyHook2)(dliStartProcessing, &dli));
if (pfnRet != NULL) {
goto HookBypass;
}
}
// Check to see if we need to try to load the library.
//
if (hmod == 0) {
if (__pfnDliNotifyHook2) {
hmod = HMODULE(((*__pfnDliNotifyHook2)(dliNotePreLoadLibrary, &dli)));
}
if (hmod == 0) {
hmod = ::LoadLibrary(dli.szDll);
}
if (hmod == 0) {
dli.dwLastError = ::GetLastError();
if (__pfnDliFailureHook2) {
// when the hook is called on LoadLibrary failure, it will
// return 0 for failure and an hmod for the lib if it fixed
// the problem.
//
hmod = HMODULE((*__pfnDliFailureHook2)(dliFailLoadLib, &dli));
}
if (hmod == 0) {
PDelayLoadInfo rgpdli[1] = { &dli };
RaiseException(
VcppException(ERROR_SEVERITY_ERROR, ERROR_MOD_NOT_FOUND),
0,
1,
PULONG_PTR(rgpdli)
);
// If we get to here, we blindly assume that the handler of the exception
// has magically fixed everything up and left the function pointer in
// dli.pfnCur.
//
return dli.pfnCur;
}
}
// Store the library handle. If it is already there, we infer
// that another thread got there first, and we need to do a
// FreeLibrary() to reduce the refcount
//
HMODULE hmodT = HMODULE(InterlockedExchangePointer((PVOID *) idd.phmod, PVOID(hmod)));
if (hmodT != hmod) {
// add lib to unload list if we have unload data
if (pidd->rvaUnloadIAT) {
// suppress prefast warning 6014, the object is saved in a link list in the constructor of ULI
#pragma warning(suppress:6014)
new ULI(pidd);
}
}
else {
::FreeLibrary(hmod);
}
}
// Go for the procedure now.
//
dli.hmodCur = hmod;
if (__pfnDliNotifyHook2) {
pfnRet = (*__pfnDliNotifyHook2)(dliNotePreGetProcAddress, &dli);
}
if (pfnRet == 0) {
if (pidd->rvaBoundIAT && pidd->dwTimeStamp) {
// bound imports exist...check the timestamp from the target image
//
PIMAGE_NT_HEADERS pinh(PinhFromImageBase(hmod));
if (pinh->Signature == IMAGE_NT_SIGNATURE &&
TimeStampOfImage(pinh) == idd.dwTimeStamp &&
FLoadedAtPreferredAddress(pinh, hmod)) {
// Everything is good to go, if we have a decent address
// in the bound IAT!
//
pfnRet = FARPROC(UINT_PTR(idd.pBoundIAT[iIAT].u1.Function));
if (pfnRet != 0) {
goto SetEntryHookBypass;
}
}
}
pfnRet = ::GetProcAddress(hmod, dli.dlp.szProcName);
}
if (pfnRet == 0) {
dli.dwLastError = ::GetLastError();
if (__pfnDliFailureHook2) {
// when the hook is called on GetProcAddress failure, it will
// return 0 on failure and a valid proc address on success
//
pfnRet = (*__pfnDliFailureHook2)(dliFailGetProc, &dli);
}
if (pfnRet == 0) {
PDelayLoadInfo rgpdli[1] = { &dli };
RaiseException(
VcppException(ERROR_SEVERITY_ERROR, ERROR_PROC_NOT_FOUND),
0,
1,
PULONG_PTR(rgpdli)
);
// If we get to here, we blindly assume that the handler of the exception
// has magically fixed everything up and left the function pointer in
// dli.pfnCur.
//
pfnRet = dli.pfnCur;
}
}
SetEntryHookBypass:
*ppfnIATEntry = pfnRet;
HookBypass:
if (__pfnDliNotifyHook2) {
dli.dwLastError = 0;
dli.hmodCur = hmod;
dli.pfnCur = pfnRet;
(*__pfnDliNotifyHook2)(dliNoteEndProcessing, &dli);
}
return pfnRet;
}
5)我們已經清楚DelayLoad的工作原理,那麼再讓我們思考一下當第二次調用export1時發生了什麼事情呢?還是調用CALL DWORD PTR DS:[39B20],但是此時39B20已經存放了正確的export1地址,以後再使用到這個函數的話就可以直接使用了!
三、DelayLoad在我們身邊?
看了這麼多,是不是頭早已昏了,不知道所云,其實我們不必知道那麼細;我們來看看它的具體形象,
以這個為例,在進程中查看調用的DLL,
看到了吧,在這裡有個「引用數」,shell32.dll被引用1810次。因為Windows不會重複加載相同名字的DLL,當Windows系統需要加載DLL時,發現該DLL已經被你加載過了,只是增加一個引用計數,然後直接使用已加載的DLL。
這就是「延遲加載-DelayLoad」。