個人簡歷及水平:。 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地址上。