延遲匯入(delay import)。看名字就知道,這種匯入機制匯入其他dll的時機比較「遲」,為什麼要遲呢?因為有些匯入函式可能使用的頻率比較低,或者在某些特定的場合才會用到,而有些函式可能要在程式執行一段時間後才會用到,這些函式可以等到他實際使用的時候再去載入對應的dll,而沒必要再程式一裝載就初始化好。
這個機制聽起來很誘人,因為他可以加快啟動速度,我們應該如何利用這項機制呢?vc有乙個選項,可以讓我們很方便的使用到這項特性,如下圖所示:
在這一項後面填寫需要延遲匯入的dll名稱,聯結器就會自動幫我們將這些dll的匯入變為延遲匯入。
現在我們知道如何使用延遲匯入了,那這個看上去很厲害的機制是如何實現的呢?接下來我們來探索一番。在image_data_directory中,有一項為image_directory_entry_delay_import,這一項便延遲匯入表,image_data_directory.virtualaddress就指向延遲匯入表的起始位址。既然是表,肯定又是乙個陣列,每一項都是乙個imgdelaydescr結構體,和匯入表一樣,每一項都代表乙個匯入的dll,來看看定義:
[cpp]view plain
copy
typedef
struct
imgdelaydescr imgdelaydescr, * pimgdelaydescr;
typedef
const
imgdelaydescr * pcimgdelaydescr;
grattrs:用來區分版本,1是新版本,0是舊版本,舊版本中後續的rva******域使用的都是指標,而新版本中都用rva,我們只討論新版本。
rvadllname:乙個rva,指向匯入dll的名字。
rvahmod:乙個rva,指向匯入dll的模組基位址,這個基位址在dll真正被匯入前是null,匯入後才是實際的基位址。
rvaiat:乙個rva,表示匯入函式表,實際上指向iat,在dll載入前,iat裡存放的是一小段**的位址,載入後才是真正的匯入函式位址。
rvaint:乙個rva,指向匯入函式的名字表。
rvaunloadiat:延遲匯入函式解除安裝表。
dwtimestamp:延遲匯入dll的時間戳。
定義知道了,那他是怎麼被處理的呢?前面提到了,在延遲匯入函式指向的iat裡,預設儲存的是一段**的位址,當程式第一次呼叫到這個延遲匯入函式時,流程會走到那段**,這段**用來幹什麼呢?請看乙個真實的延遲匯入函式的例子:
[cpp]view plain
copy
.text:75c7a363 __imp_load__internetconnecta@32: ; internetconnecta(x,x,x,x,x,x,x,x)
.text:75c7a363 mov eax, offset __imp__internetconnecta@32
.text:75c7a368 jmp __tailmerge_wininet
這段**其實只有兩行彙編,第一行把匯入函式iat項的位址放到eax中,然後用乙個jmp跳轉走,那麼他跳轉到**了呢?我們繼續跟蹤:
[cpp]view plain
copy
__tailmerge_wininet proc near
.text:75c6bef0 push ecx
.text:75c6bef1 push edx
.text:75c6bef2 push eax
.text:75c6bef3 push offset __delay_import_descriptor_wininet
.text:75c6bef8 call __delayloadhelper
.text:75c6befd pop edx
.text:75c6befe pop ecx
.text:75c6beff jmp eax
.text:75c6beff __tailmerge_wininet endp
其中最重要的是push了乙個__delay_import_descriptor_wininet,這個就是上文中看到的imgdelaydescr結構,他的dll名字是wininet.dll。之後,call了乙個__delayloadhelper,在這個函式裡,執行了載入dll,查詢匯出函式,填充匯入表等一系列操作,函式結束時iat中已經是真正的匯入函式的位址,這個函式同時返回了匯入函式的位址,因此之後的eax裡儲存的就是函式位址,最後的jmp eax就跳轉到了真實的匯入函式中。
這個過程很完美,也很靈巧,但是如果仔細觀察就會發現什麼地方有點不對勁,你發現了嗎?__delayloadhelper的引數中只有iat項的偏移和整個模組的延遲匯入描述__delay_import_descriptor_wininet,但是引數中並沒有要匯入函式的名字。也許你說,名字在__delay_import_descriptor_wininet的名字表中,是的,那裡確實有名字,但是別忘了,那是個表,裡面存的是所有要從該模組匯入的函式名字,而不是「當前」這個被呼叫函式的函式名。或許你覺得引數中應該有個索引號,用來表示名字列表中的第幾項是即將被匯入的那個函式的名字,不幸的是我們也沒有看到引數中有這樣的資訊存在,那windows執行到這裡是如何得到名字的呢?ms在這裡使用了乙個巧妙的辦法:__delay_import_descriptor_wininet中有一項是rvaiat,前面提到了,這裡實際上就是指向了iat,而且是該模組第乙個匯入函式的iat的偏移,現在我們有兩個偏移,即將匯入的函式iat項的偏移(記作rva1)和要匯入模組第乙個函式iat項的偏移(記作rva0),(rva1-rva0)/4 = 匯入函式iat項在rvaiat中的下標,rvaint中的名字順序與rvaiat中的順序是相同的,所以下標也相同,這樣就能獲取到匯入函式的名字了。有了模組名和函式名,用getprocaddress就可以獲取到匯入函式的位址了。
上述流程用一張圖來總結一下:
最後還有兩點要提醒大家:
延遲匯入的載入只發生在函式第一次被呼叫的時候,之後iat就填充為正確函式位址,不會再走__delayloadhelper了。
延遲匯入一次只會匯入乙個函式,而不是一次匯入整個模組的所有函式。
PE檔案結構詳解(五)延遲匯入表
pe檔案結構詳解 四 pe匯入表講了一般的pe匯入表,這次我們來看一下另外一種匯入表 延遲匯入 delay import 看名字就知道,這種匯入機制匯入其他dll的時機比較 遲 為什麼要遲呢?因為有些匯入函式可能使用的頻率比較低,或者在某些特定的場合才會用到,而有些函式可能要在程式執行一段時間後才會...
PE檔案結構詳解(五)延遲匯入表
pe檔案結構詳解 四 pe匯入表講了一般的pe匯入表,這次我們來看一下另外一種匯入表 延遲匯入 delay import 看名字就知道,這種匯入機制匯入其他dll的時機比較 遲 為什麼要遲呢?因為有些匯入函式可能使用的頻率比較低,或者在某些特定的場合才會用到,而有些函式可能要在程式執行一段時間後才會...
PE檔案結構詳解(五)延遲匯入表
pe檔案結構詳解 四 pe匯入表講了一般的pe匯入表,這次我們來看一下另外一種匯入表 延遲匯入 delay import 看名字就知道,這種匯入機制匯入其他dll的時機比較 遲 為什麼要遲呢?因為有些匯入函式可能使用的頻率比較低,或者在某些特定的場合才會用到,而有些函式可能要在程式執行一段時間後才會...