個人簡歷及水平:。 http://www.cnblogs.com/hackdragon/p/3662599.html
接到一個項目實現對屏幕輸出內容的獲取,於是OD載入,發現是XX加殼保護,正常情況寫代碼采用jmp跳轉到自己的代碼處 采用前人使用的CHookApi_Jmp類 源碼在http://www.cnblogs.com/showna/articles/850279.html。(我自己用的時候把CHookApi_Jmp修改成了CHookApi)當然我是截取目標程序的繪制函數TextOutA,所以代碼如下:
// 原函數聲明 //__gdi_entry WINGDIAPI BOOL WINAPI TextOutA( __in HDC hdc, __in int x, __in int y, __in_ecount(c) LPCSTR lpString, __in int c); //全局初始化類 CHookApi m_HookTextOutA; //自己的函數 BOOL WINAPI FuncJMP_TextOutA(HDC hdc, int x, int y,LPCSTR lpString, int c) { m_HookTextOutA.SetHookOff();//關閉HOOK 實現自己調用原來的WINAPI { //做你想做的事情 } int Func_Ret = TextOutA(hdc,x,y,lpString,c); m_HookTextOutA.SetHookOn();//開啟HOOK 實現攔截下一次函數 return Func_Ret; } //適當的位置初始化 void Func_Init()// { m_HookTextOutA.Initialize(_T("Gdi32.dll"),"TextOutA",(FARPROC)Hook_FuncTextOutA); m_HookTextOutA.SetHookOn(); }
正常情況下已經完成了Inline hook,但是由於軟件加殼保護了TextOutA函數的前5個字節,我們的jmp轉移大法不行了,你修改完 他就給你修改回去,造成程序異常。
於是OD往下跟蹤,發現他繼續調用的其他DLL也是軟件保護下的,只有動態鏈接才不保護,本人比較懶不愛繼續跟蹤了,於是想了一下,他是保護前幾個字節,他不能全部保護,於是往下看了看匯編代碼(win7的GDI32.dll)。
看到這裡調用了下一個函數,那麼他這裡E8(CALL)後面也是4個字節,於是乎馬上我想到自己也可以篡改他的這個地址到咱自己的函數中,鬼使神差的我看了下X版本的GDI32的代碼
發現一個問題 他們在TextOutA函數頭處到E8的位置並不一樣。具體來說
WIN7的 089D-0878 = 0x25(偏移)
XP的 BA7C-BA4F = 0x2D (偏移)
2個的偏移量明顯不一樣了,這怎麼辦呢,如果在有其他版本的GDI32是不是也不一樣呢。於是想到如下解決辦法。
我們要實現inline hook 首先要知道我們要修改的內存地址,其次要知道原來的函數地址。下面這個函數來獲取我們的地址。
DWORD GetGDI32_TextOutAEx(DWORD &dwHook,DWORD &dwCall) { //取得TextOutA函數地址 也可以直接TextOutA DWORD dwGDI32TextOutA = (DWORD)GetProcAddress(GetModuleHandle(_T("Gdi32.dll")),"TextOutA"); //轉換下 BYTE *lpBuf = (BYTE *)dwGDI32TextOutA; //每字節對比 for(int i=0;i<0x30;i++) { DWORD dwAddress = (DWORD)(&lpBuf[i]); //字節地址 DWORD dwFind = *(DWORD*)(dwAddress); //轉換獲取內容 //為什麼是0xE80875FF 這個可以在OD上看下內存內容 //由於這個函數的前一部分都是 push dword ptr ss:[ebp+0x8] 緊跟這就是CALL //那麼對應的內存二進制就是 0xE80875FF 了 if(dwFind==0xE80875FF) { //當前地址+3那就是E8了 dwHook = dwAddress+3; //由於“CALL到哪裡”的計算方式是計算2個地址的差 //所以我們要取CALL 後面的 4個字節的內容 //當前地址+4那就是E8後面的了 DWORD dwNow = dwAddress+4; //讀出來這個值 DWORD dwOff = *(DWORD*)(dwNow); //和當前的地址相加+(這個自己想一下吧為什麼是+) //在+4是因為本身占用4個字節 dwCall = dwNow+dwOff+4; //得出這個CALL實際的內存地址了。 return 1; } } return 0; }
給我們的CHookApi增加一個函數。
BOOL CHookApi::InitializeE8(FARPROC lpOldFunc, FARPROC lpNewFunc) { m_lpHookFunc = lpOldFunc; hProc = GetCurrentProcess(); DWORD dwOldFlag; if(VirtualProtectEx(hProc,m_lpHookFunc,5,PAGE_READWRITE,&dwOldFlag)) { memcpy_s(m_OldFunc,5,m_lpHookFunc,5); //if(ReadProcessMemory(hProc,m_lpHookFunc,m_OldFunc,5,0)) { if(VirtualProtectEx(hProc,m_lpHookFunc,5,dwOldFlag,&dwOldFlag)) { m_NewFunc[0]=0xE8; DWORD *pNewFuncAddress; pNewFuncAddress=(DWORD*)&m_NewFunc[1]; *pNewFuncAddress=(DWORD)lpNewFunc-(DWORD)m_lpHookFunc-5; return TRUE; } } } return FALSE; }
他原來是E9(JMP)那麼我們要實現自己的CALL 那麼我們用E8 其實我只是偷懶而已。因為原來就是E8 我們只要寫入自己的函數的偏移就行了。
//全局初始化類 CHookApi m_HookTextOutAEx; //要掛鉤的地址 DWORD dwHook; //原來的CALL的地址 DWORD dwCall; //自己的函數 int WINAPI FuncJMP_TextOutEx(HDC hdc, int x, int y,int A,int B,LPCSTR lpString, int c,int C,int D) { //處理函數形式 typedef int (__stdcall *MyTextOut )(HDC hdc, int x, int y,int A,int B,LPCSTR lpString, int c,int C,int D); //函數指針 MyTextOut TextOutStr = (MyTextOut)dwCall; //調用 int Ret = TextOutStr(hdc,x,y,A,B,lpString,c,C,D); //取得返回的地址 DWORD *Ptr = (DWORD *)&D; if(Ptr[2]==0x12345678)//這裡你可以加入以提升性能,根據返回的地址來判斷是不是你要攔截的輸出內容 { //做你想做的事情 } return Ret; } //適當的位置初始化 void Func_Init2()// { GetGDI32_TextOutAEx(dwHook,dwCall); m_HookTextOutAEx.InitializeE8((FARPROC)dwHook,(FARPROC)FuncJMP_TextOutEx); m_HookTextOutAEx.SetHookOn(); }
函數參數的判斷可以根據push情況自己來處理下,我隨便處理下了。不知道多的幾個參數干什麼用的。
對於有保護的軟件,如果不脫殼處理,那麼可以曲線實現自己的目的。對於其他有內存校驗的軟件,則可根據它對應的調用其他未保護的API進行代碼插入。算是寫完了,也算是我cnblog的第二篇技術類文章吧。寫的不對的地方歡迎指正啊。本人QQ:78486367
hook 計算機裡面一般是指 掛鉤某函數, 就是替換掉原來的函數。
inline hook , 是直接在以前的函數替裡面修改指令,用一個跳轉或者其他指令來達到掛鉤的目的。
這是相對普通的hook來說,因為普通的hook只是修改函數的調用地址,而不是在原來的函數體裡面做修改。
一般來說 普通的hook比較穩定使用。 inline hook 更加高級一點,一般也跟難以被發現。所以很多人比如病毒制作者都比較推崇inline hook。
關鍵點嗎……
1、恢復現場,要存多少字節根據你填入的jmp占用多少字節而定,jmp rel32是5個字節。
2、od找0那一段,程序操作的話你可以用malloc申請的內存來,而不一定是exe裡的0
3、VirtualProtect調用可以使代碼段可寫(否則修改的時候會出現寫入異常,程序崩潰)
4、jmp rel32指令的機器代碼是 E9 rel32 一共5個字節。如果嫌計算相對地址麻煩你也可以用 jmp mem32,機器指令是FF 25 mem32(沒記錯的話……也有可能是FF 15)。但是我覺得這樣更麻煩
5、jmp rel32指令中,rel32相對的是執行jmp之後的eip。例如
40000: E9 11 22 33 44
40005: ......
那麼翻譯過來就是
40000: jmp 44372216
6、不確定要hook的代碼位置的指令多長的話,你可能需要一個反匯編庫來確定,當然更容易(但是不通用)的方式是人工查看……跳轉來跳轉去記得一定要跳轉到機器代碼邊界上,比如剛才那個
40000: E9 11 22 33 44
你不可以跳轉到(40000, 40005)區間的任何一個地址上,但是可以跳轉到40000或40005地址上。