// HOOK_IAT.cpp : 定義控制臺(tái)應(yīng)用程序的入口點(diǎn)。
//
#include "stdafx.h"
#include <windows.h>
bool hookIat( const char* pszDllName , const char* pszFunction , LPVOID pNewFunction );
// 盜版的MessageBox
DWORD WINAPI MyMessageBox( HWND hWnd , TCHAR* pText , TCHAR* pTitle , DWORD type ) {
// 還原IAT
hookIat( "User32.dll" ,
"MessageBoxW" ,
GetProcAddress(GetModuleHandleA("User32.dll"),"MessageBoxW" )
);
// 調(diào)用原版函數(shù)
MessageBox( 0 , L"在盜版的MessageBox中彈出此框" , L"提示" , 0 );
// HOOK IAT
hookIat( "User32.dll" , "MessageBoxW" , &MyMessageBox );
return 0;
}
int _tmain(int argc, _TCHAR* argv[])
{
MessageBox( 0 , L"正版API" , L"提示" , 0 );
// HOOK IAT
hookIat( "User32.dll" , "MessageBoxW" , &MyMessageBox );
MessageBox( 0 , L"正版API" , L"提示" , 0 );
MessageBox( 0 , L"正版API" , L"提示" , 0 );
return 0;
}
bool hookIat( const char* pszDllName ,
const char* pszFunction ,
LPVOID pNewFunction ) {
// PE文件中,所有的API的地址都保存到了導(dǎo)入表中.
// 程序調(diào)用一個(gè)API時(shí), 先會(huì)從導(dǎo)入表中得到API
// 的地址, 再調(diào)用這個(gè)地址.
// 如果將導(dǎo)入表中的API地址替換掉, 那么調(diào)用
// API時(shí), 就會(huì)調(diào)用被替換的地址.
// HOOK IAT的步驟:
// 1. 解析PE文件,找到導(dǎo)入表
// 2. 找到導(dǎo)入表中對(duì)應(yīng)的模塊
// 3. 找到對(duì)應(yīng)模塊的對(duì)應(yīng)函數(shù).
// 4. 修改函數(shù)地址.
// 導(dǎo)入表中有兩張表, 一張是導(dǎo)入名稱表, 一張是導(dǎo)入
// 地址表, 這兩張表的元素一一對(duì)應(yīng)的.
// 導(dǎo)入名稱表中存放的是函數(shù)名
// 導(dǎo)入地址表中存放的是函數(shù)地址.
HANDLE hProc = GetCurrentProcess( );
PIMAGE_DOS_HEADER pDosHeader; // Dos頭
PIMAGE_NT_HEADERS pNtHeader; // Nt頭
PIMAGE_IMPORT_DESCRIPTOR pImpTable; // 導(dǎo)入表
PIMAGE_THUNK_DATA pInt; // 導(dǎo)入表中的導(dǎo)入名稱表
PIMAGE_THUNK_DATA pIat; // 導(dǎo)入表中的導(dǎo)入地址表
DWORD dwSize;
DWORD hModule;
char* pFunctionName;
DWORD dwOldProtect;
hModule = ( DWORD)GetModuleHandle( NULL );
// 讀取dos頭
pDosHeader = (PIMAGE_DOS_HEADER)hModule;
// 讀取Nt頭
pNtHeader = (PIMAGE_NT_HEADERS)( hModule + pDosHeader->e_lfanew );
// 獲取導(dǎo)入表
pImpTable = ( PIMAGE_IMPORT_DESCRIPTOR )
(hModule + pNtHeader->OptionalHeader.DataDirectory[1].VirtualAddress);
// 遍歷導(dǎo)入表
while( pImpTable->FirstThunk != 0 && pImpTable->OriginalFirstThunk != 0 ) {
// 判斷是否找到了對(duì)應(yīng)的模塊名
if( _stricmp( (char*)(pImpTable->Name+hModule) , pszDllName ) != 0 ) {
++pImpTable;
continue;
}
// 遍歷名稱表,找到函數(shù)名
pInt = (PIMAGE_THUNK_DATA)( pImpTable->OriginalFirstThunk + hModule );
pIat = (PIMAGE_THUNK_DATA)( pImpTable->FirstThunk + hModule );
while( pInt->u1.AddressOfData != 0 ) {
// 判斷是以名稱導(dǎo)入還是以需要導(dǎo)入
if( pInt->u1.Ordinal & 0x80000000 == 1 ) {
// 以序號(hào)導(dǎo)入
// 判斷是否找到了對(duì)應(yīng)的函數(shù)序號(hào)
if( pInt->u1.Ordinal == ( (DWORD)pszFunction ) & 0xFFFF ) {
// 找到之后,將鉤子函數(shù)的地址寫入到iat
VirtualProtect( &pIat->u1.Function ,
4 ,
PAGE_READWRITE ,
&dwOldProtect
);
pIat->u1.Function = (DWORD)pNewFunction;
VirtualProtect( &pIat->u1.Function ,
4 ,
dwOldProtect ,
&dwOldProtect
);
return true;
}
}
else {
// 以名稱導(dǎo)入
pFunctionName = (char*)( pInt->u1.Function + hModule + 2);
// 判斷是否找到了對(duì)應(yīng)的函數(shù)名
if( strcmp( pszFunction , pFunctionName ) == 0 ) {
VirtualProtect( &pIat->u1.Function ,
4 ,
PAGE_READWRITE ,
&dwOldProtect
);
// 找到之后,將鉤子函數(shù)的地址寫入到iat
pIat->u1.Function = (DWORD)pNewFunction;
VirtualProtect( &pIat->u1.Function ,
4 ,
dwOldProtect ,
&dwOldProtect
);
return true;
}
}
++pIat;
++pInt;
}
++pImpTable;
}
return false;
}
========================
// HOOK_內(nèi)聯(lián)HOOK.cpp : 定義控制臺(tái)應(yīng)用程序的入口點(diǎn)。
//
#include "stdafx.h"
#include <windows.h>
BYTE g_jmpShellCode[5] = { "\xe9" };
BYTE g_oldOpcode[ 5 ] = { 0 };
#include "hookFunction.h"
// 盜版的MessageBox
DWORD WINAPI MyMessageBox( HWND hWnd , TCHAR* pText , TCHAR* pTitle , DWORD type )
{
DWORD dwOldProtect = 0;
DWORD dwWrite = 0;
VirtualProtectEx( GetCurrentProcess( ) ,
&MessageBox ,
4 ,
PAGE_EXECUTE_READWRITE ,
&dwOldProtect
);
// 恢復(fù)函數(shù)原來的內(nèi)容
WriteProcessMemory( GetCurrentProcess( ) ,
&MessageBox ,
g_oldOpcode ,
sizeof( g_jmpShellCode ) ,
&dwWrite
);
// 調(diào)用原版函數(shù)
MessageBox( 0 , L"在盜版的MessageBox中彈出此框" , L"提示" , 0 );
// 調(diào)用完原版函數(shù)之后,再次HOOK這個(gè)函數(shù)
WriteProcessMemory( GetCurrentProcess( ) ,
&MessageBox ,
g_jmpShellCode ,
sizeof( g_jmpShellCode ) ,
&dwWrite
);
// 6. 恢復(fù)內(nèi)存分頁的屬性
VirtualProtectEx( GetCurrentProcess( ) ,
&MessageBox ,
4 ,
dwOldProtect ,
&dwOldProtect
);
return 0;
}
// 盜版的MessageBox
DWORD WINAPI MyMessageBox2( HWND hWnd , TCHAR* pText , TCHAR* pTitle , DWORD type ) {
//MessageBox( hWnd , pText , pTitle , type );
MessageBox( hWnd , L"--------------", pTitle , type );
return 0;
}
int _tmain(int argc, _TCHAR* argv[])
{
// 內(nèi)聯(lián)HOOK的示意:
// 未被攔截的函數(shù)
// MessageBox:
// +--------------+
// ==> | mov edi,edi |
// | push ebp |
// | mov ebp,esp |
// | |
// | XXXXXXXXX |
// +--------------+
//
// 被攔截的函數(shù)
// function
// MessageBox: +--->>-------------------+
// +--------------+ | | push ebp |
// ==> | jmp function>>------+ | mov ebp,esp |
// | push ebp | | |
// | mov ebp,esp | | XXXXXX |
// | | | XXXXXX |
// | XXXXXXXXX | | XXXXXX |
// +--------------+ | XXXXXX |
// | jmp MessageBox+5 |
// +-------------------+
//
// 所謂HOOK, 就是攔截, HOOK API就是在API的真正代碼被執(zhí)行前攔截它
// 跳轉(zhuǎn)到另一個(gè)地方執(zhí)行代碼, 執(zhí)行后再跳轉(zhuǎn)回去,或不跳轉(zhuǎn).
// HOOK的概念比簡(jiǎn)單, 但一些細(xì)節(jié)需要注意
// 例如:
// 1. 原函數(shù)是沒有跳轉(zhuǎn)到XXX地址的代碼的,jmp XXX是我們通過修改內(nèi)存
// 加入進(jìn)去的, 在修改內(nèi)存時(shí),需要注意的是,一般能夠被執(zhí)行的內(nèi)存分頁
// 都沒有可寫的屬性,因此,在修改內(nèi)存前,需要修改內(nèi)存分頁屬性.
// 2. jmp 指令需要用到一個(gè)跳轉(zhuǎn)偏移, 而非一個(gè)絕對(duì)的地址. 將jmp XXX的
// opcode寫入到內(nèi)存時(shí), 必須知道怎么計(jì)算出這個(gè)跳轉(zhuǎn)偏移. 一般跳轉(zhuǎn)
// 偏移需要用到以下公式來計(jì)算:
// 跳轉(zhuǎn)偏移 = 目標(biāo)地址 - 當(dāng)前跳轉(zhuǎn)指令所在地址 - 跳轉(zhuǎn)指令的總體長(zhǎng)度
// 如:
// 指令要跳轉(zhuǎn)到 011AB61F
// jmp 011AB61F 這條跳轉(zhuǎn)指令所在地址為 011AB618
// jmp 011AB61F 這條指令的總體長(zhǎng)度為2, 因?yàn)樗膐pcode是EB 05,只有2個(gè)字節(jié)
// 所以它的偏移為: 011AB61F - 011AB61F - 2 => 5
//
// +-<011AB618 | EB 05 | JMP 011AB61F
// | 011AB61A | 31 C0 | XOR EAX , EAX
// | 011AB61C | 31 DB | XOR EBX , EBX
// | 011AB61E | 40 | INC EAX
// +> 011AB61F | 43 | INC EBX
// 內(nèi)聯(lián)HOOK步驟:
// 1. 設(shè)置要修改的內(nèi)存的分頁屬性為可寫
// 2. 準(zhǔn)備跳轉(zhuǎn)的shellcode
// 3. 計(jì)算跳轉(zhuǎn)偏移,并將計(jì)算好的跳轉(zhuǎn)偏移寫入到shellcode中
// 4. 將函數(shù)開始地址處的opcode備份,字節(jié)數(shù)和shellcode等長(zhǎng).
// 5. 將opcode寫入到要HOOK的函數(shù)
// 6. 恢復(fù)內(nèi)存分頁的屬性
// 1. 設(shè)置要修改的內(nèi)存的分頁屬性為可寫
DWORD dwOldProtect = 0;
VirtualProtectEx( GetCurrentProcess( ) ,
&MessageBox ,
4 ,
PAGE_EXECUTE_READWRITE ,
&dwOldProtect
);
// 2. 準(zhǔn)備跳轉(zhuǎn)的shellcode
// BYTE g_jmpShellCode[5] = { "\xe9" };
// 3. 計(jì)算跳轉(zhuǎn)偏移,并將計(jì)算好的跳轉(zhuǎn)偏移寫入到shellcode中
DWORD dwJmpOffset = (DWORD)&MyMessageBox - (DWORD)&MessageBoxW - 5;
*(DWORD*)( g_jmpShellCode + 1 ) = dwJmpOffset;
// 4. 將函數(shù)開始地址處的opcode備份,字節(jié)數(shù)和shellcode等長(zhǎng).
// BYTE g_oldOpcode[ 5 ] = { 0 };
DWORD dwRead = 0;
ReadProcessMemory( GetCurrentProcess( ) ,
&MessageBox ,
g_oldOpcode ,
sizeof( g_jmpShellCode ) ,
&dwRead
);
// 5. 將opcode寫入到要HOOK的函數(shù)
WriteProcessMemory( GetCurrentProcess( ) ,
&MessageBox ,
g_jmpShellCode ,
sizeof( g_jmpShellCode ) ,
&dwRead
);
// 6. 恢復(fù)內(nèi)存分頁的屬性
VirtualProtectEx( GetCurrentProcess( ) ,
&MessageBox ,
4 ,
dwOldProtect ,
&dwOldProtect
);
// 函數(shù)被調(diào)用后,將跑到鉤子函數(shù)中執(zhí)行代碼
MessageBox( NULL , L"我是正版MessageBox" , L"提示" , 0 );
return 0;
}