帶你認識 DLL DelayLoad

2020-12-08 悅文天下

本文轉載自【微信公眾號: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」。

相關焦點

  • 我的dll封裝學習筆記(一)
    學習封裝dll源於對代碼保密的需要,本文從最簡單的開始,封裝自定義函數為dll並註冊使用。第三步:在excel中註冊dll(如筆記中寫的有兩種方法)。①開發工具-加載項-自動化-找到封裝的dll,點確定完成註冊。這種方法,只要關閉excel再次打開,就失效了。需要重新註冊。
  • 缺少.dll文件很煩躁?別急試試這幾招
    1什麼是.dll文件?    我們在日常使用電腦的過程中,經常會涉及到軟體程序的拷貝。或是出於人為,亦或是軟體自身,我們經常會遇到.dll文件缺失,而導致辛苦拷貝的軟體無法使用的尷尬。那麼.dll文件究竟是什麼?
  • 一種通用DLL劫持技術研究
    Dll劫持的目的一般都是為了自己的dll模塊能夠在別人進程中運行,然後做些不可描述的事情。為了讓別人的程序能夠正常運行,通常都需要在自己的dll中導出和劫持的目標dll相同的函數接口,然後在自己的接口函數中調用原始dll的函數,如此使得原始dll的功能能夠正常被使用。導出接口可以自己手工寫,也可以通過工具自動生成,比如著名的Aheadlib。
  • 如何將C++對象導出到DLL,直接看DLL內部解析
    )就可以了,當然導出也很簡單,換成就可以了class_declspec(dllimport) DLLClass。當然通常我們是用宏定義來控制的 #ifdef DLL_FILE #define _declspec(dllexport) __DLL__//導出類 #else #define _declspec(dllimport) __DLL__//導入類 #endif class __DLL__ DLLClass{};這樣在工程加入頭文件
  • 惡意代碼分析之調試.NET平臺dll
    接著往下分析,發現會內存加載dll並反射調用執行dll中的LOLY()方法,如下。將上面提取的dll進行本地反編譯,查看下dll文件內容,如下。場景二被調試的母體文件非.NET平臺程序,而由於缺少標準入口點,dnSpy不允許本地調試dll,此時就需要藉助loader進行調試。首先我們可以想到,ollydbg是如何調試dll文件的?其實會發現工具自身會加載一個loader進行輔助調試,調用dll裡的導出方法或者斷點停在dll入口區域。
  • [MiniDumpWriteDump via COM+ Services DLL]的利用測試
    編寫powershell腳本,實現自動化掃描系統目錄下所有dll的導出函數,查看是否存在其他可用的dll,介紹腳本實現的細節。代碼先開啟SeDebugPrivilege權限,再調用comsvcs.dll的導出函數MiniDumpW,測試成功。 0x04 編寫腳本實現自動化掃描dll的導出函數學習完odzhan的文章以後,我產生了一個疑問:Windows系統目錄下是否存在其他可用的dll?
  • 應急響應中分析64位惡意dll的小故事
    文件監控軟體利用文件監控軟體往往能對惡意程序的讀寫的文件的路徑進行快速定位。靜態分析用IDA對正常路徑C:\windows\system32\ 下的midimap.dll與在C:\windows\ 下的惡意midimap.dll進行對比。可見惡意dll導出函數與正常dll的完全相同。
  • 賽博朋克2077缺少msvcp140_1.dll怎麼辦 缺少msvcp140_1.dll解決教程
    賽博朋克2077缺少msvcp140_1.dll怎麼辦?不少的玩家在進入遊戲被提示「缺少msvcp140_1.dll」,該提示導致無法進行遊戲。那麼大家知道該如何解決嗎,下面小編就來給大家介紹一下吧。
  • SolidWorks無法裝入GdtAnalysisSupport.dll文件解決方法
    solidWorks無法裝入solidworks dll文件怎麼辦?SolidWorks打開提示無法裝入GdtAnalysisSupport.dll文件,那麼為什麼會出現這個問題呢?  查找相關文件是一個很好用的命令,可以將文件打包到硬碟的指定地點;【路徑:SolidWorks-文件-查找相關文件】  但是有時候引用此命令是出現「solidWorks無法裝入solidworks dll文件:sldshellutils.dll」通知,這是由於sldshellutils這個文件沒有為2008或2009註冊而造成的,解決方法很簡單請跟隨我的思路~~~go~
  • 打開C4D時報錯libmmd.dll的正確解決方法
    打開C4D出現「libmmd.dll錯誤」是打開C4D軟體在WIN系統下經常會遇到的情況,很多小夥伴經常會遇到包括我自己也遇到好多次
  • Python調用VC++的動態連結庫DLL
    首先VC++的DLL的導出函數定義成標準C的導出函數:#ifdef LRDLLTEST_EXPORTS #define LRDLLTEST_API __declspec(dllexport) #else #define LRDLLTEST_API __declspec(dllimport) #endif extern
  • 調試實戰 —— dll 加載失敗之全局變量初始化篇
    exe 會加載兩個 dll 並調用它們的導出函數(GetCallCount),結果只有一個 dll 的導出函數被成功調用。會是什麼原因呢?現象 運行效果如下圖:run_result通過 dumpbin 已經確認兩個 dll 都有名為 GetCallCount 的函數。
  • 解決PS軟體安裝成功,打開提示:xxx.dll文件丟失
    並不代表你也可以完全解決,但是同樣的問題嘗試下解決會比摸索其他方式可能會高效多。本方法僅供參考,如您有更好方法評論區可留言分享。你正好遇到沒其他好方法可參考本次方法嘗試處理。公眾號psxitongban今天在看到一個ps的相關問題,可能很多同學也遇到過,安裝好ps軟體之後,打開會提示無法啟動此程序。
  • 為Linux應用程式編寫DLL程序函數 (2)
    選項 RTLD_LAZY 推遲解析 dll 的外部引用,直到 dll 被執行。 選項 RTLD_NOW 在 dlopen 返回之前解析所有的外部引用。 dlsym 返回入口點 printUPPERCASE 的地址。
  • C語言遊戲外掛(四):簡單 DLL 注入遊戲
    string.h>#include <string>#include <iostream>using namespace std;HWND hwnd = NULL;DWORD processid = NULL;HANDLE hprocess = NULL;PVOID procdlladdr
  • Excel提示Excel詞典xllex.dll文件丟失或損壞
    最近有Win10用戶反映,運行Excel表格的時候出現提示「Excel詞典xllex.dll文件丟失或損壞」,導致Excel表格運行失敗,這讓用戶非常煩惱。那麼,Win10運行Excel表格提示「Excel詞典xllex.dll文件丟失或損壞」怎麼辦呢?
  • 地求生cannot find dxgi.dll,please,re-install this application...
    絕地求生cannot find dxgi.dll,please,re-install this application是什麼?絕地求生cannot find dxgi.dll,please,re-install this application怎麼解決、 是不是還有好多小夥伴不知道、那些還不知道的小夥伴快來一起看看絕地求生遊戲時,會出現cannot find dxgi.dll,please,re-install this application的白框,然後遊戲就無法啟動。
  • 電腦提示.dll文件缺失?DirectX修復工具一鍵修復
    想必大家可能都遇到過,當運行某一個軟體或者遊戲的時候,彈出錯誤彈窗,意思大概是缺失什麼什麼.dll文件等等。這種問題幾乎都是缺少庫文件。那後綴為.dll的文件是什麼呢?先看一下吧。許多應用程式被分割成一些相對獨立的動態連結庫,放置於系統中,就產生了DLL文件。
  • Windows編程技術:4種Dll注入技術(上)
    如鍵盤鉤子,許多木馬都有這東西,監視你的鍵盤操作。全局鉤子是系統鉤子的一種,當指定的一些消息被系統中任何應用程式所處理時,這個鉤子就被調用。實例中有兩個程序:test.exe和GlobalHook_Test.dll;運行test.exe,會加載起來GlobalHook_Test.dll,鉤住消息隊列。
  • VB中創建可以輸出函數的DLL
    exe "e:\vbdll\Class1.obj" "e:\vbdll\Module1.obj" "e:\vbdll\Project1.obj"   "E:\Program Files\Microsoft Visual Studio\VB98\VBAEXE6.LIB" /ENTRY:__vbaS /EXPORT:mathadd /OUT:  "e:\vbdll\ProjectOK.dll