// HOOK_IAT.cpp : 定义控制台应用程序的入口点。
//
#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" )
);
// 调用原版函数
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的地址都保存到了导入表中.
// 程序调用一个API时, 先会从导入表中得到API
// 的地址, 再调用这个地址.
// 如果将导入表中的API地址替换掉, 那么调用
// API时, 就会调用被替换的地址.
// HOOK IAT的步骤:
// 1. 解析PE文件,找到导入表
// 2. 找到导入表中对应的模块
// 3. 找到对应模块的对应函数.
// 4. 修改函数地址.
// 导入表中有两张表, 一张是导入名称表, 一张是导入
// 地址表, 这两张表的元素一一对应的.
// 导入名称表中存放的是函数名
// 导入地址表中存放的是函数地址.
HANDLE hProc = GetCurrentProcess( );
PIMAGE_DOS_HEADER pDosHeader; // Dos头
PIMAGE_NT_HEADERS pNtHeader; // Nt头
PIMAGE_IMPORT_DESCRIPTOR pImpTable; // 导入表
PIMAGE_THUNK_DATA pInt; // 导入表中的导入名称表
PIMAGE_THUNK_DATA pIat; // 导入表中的导入地址表
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 );
// 获取导入表
pImpTable = ( PIMAGE_IMPORT_DESCRIPTOR )
(hModule + pNtHeader->OptionalHeader.DataDirectory[1].VirtualAddress);
// 遍历导入表
while( pImpTable->FirstThunk != 0 && pImpTable->OriginalFirstThunk != 0 ) {
// 判断是否找到了对应的模块名
if( _stricmp( (char*)(pImpTable->Name+hModule) , pszDllName ) != 0 ) {
++pImpTable;
continue;
}
// 遍历名称表,找到函数名
pInt = (PIMAGE_THUNK_DATA)( pImpTable->OriginalFirstThunk + hModule );
pIat = (PIMAGE_THUNK_DATA)( pImpTable->FirstThunk + hModule );
while( pInt->u1.AddressOfData != 0 ) {
// 判断是以名称导入还是以需要导入
if( pInt->u1.Ordinal & 0x80000000 == 1 ) {
// 以序号导入
// 判断是否找到了对应的函数序号
if( pInt->u1.Ordinal == ( (DWORD)pszFunction ) & 0xFFFF ) {
// 找到之后,将钩子函数的地址写入到iat
VirtualProtect( &pIat->u1.Function ,
4 ,
PAGE_READWRITE ,
&dwOldProtect
);
pIat->u1.Function = (DWORD)pNewFunction;
VirtualProtect( &pIat->u1.Function ,
4 ,
dwOldProtect ,
&dwOldProtect
);
return true;
}
}
else {
// 以名称导入
pFunctionName = (char*)( pInt->u1.Function + hModule + 2);
// 判断是否找到了对应的函数名
if( strcmp( pszFunction , pFunctionName ) == 0 ) {
VirtualProtect( &pIat->u1.Function ,
4 ,
PAGE_READWRITE ,
&dwOldProtect
);
// 找到之后,将钩子函数的地址写入到iat
pIat->u1.Function = (DWORD)pNewFunction;
VirtualProtect( &pIat->u1.Function ,
4 ,
dwOldProtect ,
&dwOldProtect
);
return true;
}
}
++pIat;
++pInt;
}
++pImpTable;
}
return false;
}
========================
// HOOK_内联HOOK.cpp : 定义控制台应用程序的入口点。
//
#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
);
// 恢复函数原来的内容
WriteProcessMemory( GetCurrentProcess( ) ,
&MessageBox ,
g_oldOpcode ,
sizeof( g_jmpShellCode ) ,
&dwWrite
);
// 调用原版函数
MessageBox( 0 , L"在盗版的MessageBox中弹出此框" , L"提示" , 0 );
// 调用完原版函数之后,再次HOOK这个函数
WriteProcessMemory( GetCurrentProcess( ) ,
&MessageBox ,
g_jmpShellCode ,
sizeof( g_jmpShellCode ) ,
&dwWrite
);
// 6. 恢复内存分页的属性
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[])
{
// 内联HOOK的示意:
// 未被拦截的函数
// MessageBox:
// +--------------+
// ==> | mov edi,edi |
// | push ebp |
// | mov ebp,esp |
// | |
// | XXXXXXXXX |
// +--------------+
//
// 被拦截的函数
// 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的真正代码被执行前拦截它
// 跳转到另一个地方执行代码, 执行后再跳转回去,或不跳转.
// HOOK的概念比简单, 但一些细节需要注意
// 例如:
// 1. 原函数是没有跳转到XXX地址的代码的,jmp XXX是我们通过修改内存
// 加入进去的, 在修改内存时,需要注意的是,一般能够被执行的内存分页
// 都没有可写的属性,因此,在修改内存前,需要修改内存分页属性.
// 2. jmp 指令需要用到一个跳转偏移, 而非一个绝对的地址. 将jmp XXX的
// opcode写入到内存时, 必须知道怎么计算出这个跳转偏移. 一般跳转
// 偏移需要用到以下公式来计算:
// 跳转偏移 = 目标地址 - 当前跳转指令所在地址 - 跳转指令的总体长度
// 如:
// 指令要跳转到 011AB61F
// jmp 011AB61F 这条跳转指令所在地址为 011AB618
// jmp 011AB61F 这条指令的总体长度为2, 因为它的opcode是EB 05,只有2个字节
// 所以它的偏移为: 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
// 内联HOOK步骤:
// 1. 设置要修改的内存的分页属性为可写
// 2. 准备跳转的shellcode
// 3. 计算跳转偏移,并将计算好的跳转偏移写入到shellcode中
// 4. 将函数开始地址处的opcode备份,字节数和shellcode等长.
// 5. 将opcode写入到要HOOK的函数
// 6. 恢复内存分页的属性
// 1. 设置要修改的内存的分页属性为可写
DWORD dwOldProtect = 0;
VirtualProtectEx( GetCurrentProcess( ) ,
&MessageBox ,
4 ,
PAGE_EXECUTE_READWRITE ,
&dwOldProtect
);
// 2. 准备跳转的shellcode
// BYTE g_jmpShellCode[5] = { "\xe9" };
// 3. 计算跳转偏移,并将计算好的跳转偏移写入到shellcode中
DWORD dwJmpOffset = (DWORD)&MyMessageBox - (DWORD)&MessageBoxW - 5;
*(DWORD*)( g_jmpShellCode + 1 ) = dwJmpOffset;
// 4. 将函数开始地址处的opcode备份,字节数和shellcode等长.
// BYTE g_oldOpcode[ 5 ] = { 0 };
DWORD dwRead = 0;
ReadProcessMemory( GetCurrentProcess( ) ,
&MessageBox ,
g_oldOpcode ,
sizeof( g_jmpShellCode ) ,
&dwRead
);
// 5. 将opcode写入到要HOOK的函数
WriteProcessMemory( GetCurrentProcess( ) ,
&MessageBox ,
g_jmpShellCode ,
sizeof( g_jmpShellCode ) ,
&dwRead
);
// 6. 恢复内存分页的属性
VirtualProtectEx( GetCurrentProcess( ) ,
&MessageBox ,
4 ,
dwOldProtect ,
&dwOldProtect
);
// 函数被调用后,将跑到钩子函数中执行代码
MessageBox( NULL , L"我是正版MessageBox" , L"提示" , 0 );
return 0;
}