做左邊,有Self標識的是對象的實例數據塊。某一個幾口指針IMyInterface指針,指向了一個VTable。而VTable中的每一個Method,都指向了一段代碼,這段代碼的前一部分,是為了計算EAX(保證將IMyInterface的地址,偏移到Self所在地址)。
分析上面的結構,再實際在CPU窗體中,調試以下接口方法的調用過程,發現,必然和Method地址有關。因此,Hook的目標,就非常自然地變成修改VTable中的Method1的地址值。
第四、如何修改代碼?這里建議大家學習以下FastCode代碼。簡單一點,就是通過調用VirutalProtect方法,修改代碼段中內存的訪問屬性,然后修改地址,最后再恢復回去。
顯然在Hook之前,必須聲明新的函數。
第五、新的函數并不是那么好聲明的。關注一下,接口函數的調用代碼,你會發現很多問題。下面舉一個簡單的例子。
IMyInterface = interface(IInvokable)
procedure AAA;
end;
假設TMyIntfImpl類實現了上面的接口。那么oIntfObj: IMyInterface聲明的對象,oIntfObj.AAA;的匯編代碼是如下的樣子:
上面是兩段代碼的圖片,其中[dex+$0c]指的就是$004661FD,也就是第二段代碼圖片的首地址。大家可以再聯系一下上面的示意圖理解一下地址的關鍵。
好,言歸正傳。這里注意一下,我們要修改的是[dex+$0c]里面的值。但是由于這個是call過去的。所以在call之前,會在堆棧中壓入函數返回地址。另外,在調用函數之前,還有函數參數的準備。比如說Self指針的傳入到EAX中,如果本身方法還有參數的話,可能占用其他寄存器或者堆棧。
由于我們要求是Hook住所有的方法,并且所有方法的參數類型并不一定一樣。所以在call之前的代碼,是無法預計的。所以在新的函數中,必須考慮如何做到保存寄存器和做到ret時候的棧平衡。
通過我的實踐,我的做法是通過先彈出當前的ret地址,保存到一個數據區中。等待調用完原先的代碼后,再壓棧。而調用Writelog的時候,先保存寄存器,調用完了之后,再恢復寄存器。這是因為寄存器也可能是返回值的地方。而且后續代碼有可能優化使用。
第六、完成了匯編的編寫,還有一個問題,那就是由于每一個函數的原地址不一樣,所以必須為每一個函數,定義一個代理函數。由于這些函數的地址和個數都是未定的,所以,這里就必須要用到動態創建代碼。
動態創建代碼的方法看上去簡單,申請一段空間,將那一段模板代碼地址復制過來。但是,實際情況并非如此。
首先,申請控件的時候,使用VirutalAlloc,并指定EXECUTE_READWRITE屬性。另外,要關注到原來的代碼是在代碼段執行的,所以有些函數的地址可能只是一個偏移地址。而后申請的代碼,是在HEAP中運行的,所以,如果只是單純地復制,函數調用就會報錯了。
好了,上面講了六點關鍵因素。如果你足夠理解上面的過程,你也可以做到AOP了。這篇文章是一個純技術的,可能關心測試的會非常失望,只能說sorry了。
文章來源于領測軟件測試網 http://www.kjueaiud.com/