天天看點

Win32API起始處的mov edi, edi與使用者空間InlineHook

在x86平台上,無論是在調試器中跟到系統DLL中時,還是反彙編某個系統DLL時,經常會發現很多API的第一條彙編指令都是mov edi, edi。根據經驗來講,C函數的彙編形式,應該是首先push ebp儲存原始棧頂,然後mov ebp, esp構造新的棧幀,接下來sub esp, 0nnh配置設定局部變量空間,之後就是函數體了,結束時先mov esp, ebp恢複棧指針,然後pop ebp恢複棧頂指針,最後retn。

push    ebp             ; 儲存原始棧頂指針到棧
mov     ebp, esp        ; 擷取新棧頂指針,用以通路參數和局部變量
sub     esp, 0NNh       ; 配置設定局部變量空間,大小0xNN位元組
...
...

mov     esp, ebp        ; 恢複初始棧指針
pop     ebp             ; 恢複原始棧頂指針,調用者用來通路參數和局部變量
retn                    ; 傳回到調用者      

現在,前面多了一條mov edi, edi,是用來幹什麼的呢?看了網上好多論壇部落格,衆說紛纭。有的說是一條延時指令為了實作一個短的等待,有的說是為了對齊以便于Cache,等等。看了半天,都不是很有說服力,最終在MSDN Blog上找到一篇相關說明,應該是最有道理的。上面說,這條指令的添加主要是為了支援hotpatch,翻譯過來好像是叫熱修補,類似于Inline Hook。說到這裡就要先提一下MSDN幫助文檔上與此相關的說明,是關于hotpatch的兩個編譯連結選項:

首先是編譯器選項/hotpatch:

When /hotpatch is used in a compilation, the compiler ensures that first instruction of each function is at least two bytes, which is required for hot patching.

To complete the preparation for making an image hotpatchable, after you use /hotpatch to compile, you must use /FUNCTIONPADMIN to link. When you compile and link an image by using one invocation of cl.exe, /hotpatch implies /functionpadmin.

Because instructions are always two bytes or larger on the ARM architecture, and because x64 compilation is always treated as if /hotpatch has been specified, you don't have to specify /hotpatch when you compile for these targets; however, you must still link by using /functionpadmin to create hotpatchable images for them. The /hotpatch compiler option only affects x86 compilation.

大緻含義:

當/hotpatch用于編譯時,編譯器會確定每個函數的第一條指令至少占兩個位元組,這是熱修補的要求。

要使生成的映像檔案可以熱修補,除了使用/hotpatch來編譯,你還必須使用/FUNCTIONPADMIN進行連結。當你使用cl.exe一次性完成編譯和連結時,/hotpatch隐含了/FUNCTIONPADMIN。

因為ARM架構的指令至少占兩個位元組,而且x64編譯總是視為/hotpatch已指定,是以當編譯目标為這兩種架構時不必指定/hotpatch參數;然而,你仍然必須使用/FUNCTIONPADMIN來為它們生成可以熱修補的映像檔案。/hotpatch編譯器選項僅影響x86編譯。

連接配接器選項/FUNCTIONPADMIN:

The amount of padding to add to the beginning of each function, 5, 6, or 16. x86 images require five bytes of padding, x64 images require 6 bytes, and images built for the Itanium Processor Family require 16 bytes of padding at the beginning of each function.

By default, the compiler will add the correct amount of space to the image, based on the machine type of the image.

In order for the linker to produce a hotpatchable image, the .obj files must have been compiled with /hotpatch (Create Hotpatchable Image).

When you compile and link an image with a single invocation of cl.exe, /hotpatch implies /functionpadmin.

機器譯文:

要添加到每個函數開頭的填充量,為 5、6 或 16位元組。在每個函數的開頭,x86 映像需要填充 5 個位元組,x64 映像需要填充 6 個位元組,為 Itanium 處理器系列生成的映像需要填充 16 個位元組。

預設情況下,編譯器根據映像的計算機類型将正确的空格數添加到映像中。

為了使連結器生成可熱修補的映像,.obj 檔案必須已使用 /hotpatch 進行編譯。

使用 cl.exe 的單個調用來編譯和連結映像時,/hotpatch 隐含表示 /functionpadmin。

看完上面兩段文字,應該會有所思。回過頭來繼續剛才的mov edi, edi,先來看一下函數開始幾條指令的大小:

00000000    8BFF          mov     edi, edi        ; 2位元組
00000002    55            push    ebp             ; 1位元組
00000003    8BEC          mov     ebp, esp        ; 2位元組      

我們可以看到正如上面幫助文檔所講,mov edi, edi占用了兩個位元組。那麼為什麼第一條指令不能少于兩個位元組呢?其實以前的MSDN上本來說的是確定第一條指令為兩位元組,後來更改為不少于兩位元組,估計早時就是針對x86上的mov edi, edi來說的。。根據hotpatch的設計思路,要在第一條指令處寫入一個短跳轉,x86平台為0xEB後跟一位元組偏移立即數,通過這個短跳轉跳轉到函數間的填充區域,在填充區域寫入一個長跳轉,x86平台為0xE9後跟一個雙字立即數偏移,通過這個長跳轉就可以跳轉到我們的Patch函數或者Hook函數。

00000000    E902000000    jmp     07h             ; 函數間至少有5位元組填充,将雙字偏移2改為實際函數的相對位址
00000005    EBF9          jmp     0h              ; 原來的mov edi, edi,負向跳轉7位元組到長跳轉指令      

函數頭部的第一條指令如果是像mov edi, edi這樣的一條無作用指令,那麼跳回前也不需要再進行等價操作來實作這條指令的作用了,直接跳轉回mov edi, edi的下一條指令即可。看一看現在好多Inline Hook在實作的時候,并沒有利用這種機制,而是直接覆寫掉函數的前5位元組,然後自己實作被覆寫的指令。其實如果目标函數前三條指令是mov edi, edi、push ebp和mov ebp, esp的話,這樣也是一個普适的方法,就是跳轉回來之前需要自己實作push ebp和mov ebp, esp這兩條指令。對于其他情況,需要有針對的特殊化處理。

綜上所述,在Hook以mov edi, edi開頭的函數時,還是使用短跳轉加長跳轉的方式實作起來最友善,完全用C語言即可實作,不需要再寫彙編代碼,還可以做成一個通用Hook函數。隻是這種友善隻存在于32位的大多數API中,還是有很大局限性的。

OK. That's all. 學識有限,大家有好的思路歡迎交流。

轉載于:https://www.cnblogs.com/youlin/p/mov_edi_edi_inline_hook.html