《逆向工程核心原理》学习笔记:API钩取
Posted 思源湖的鱼
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了《逆向工程核心原理》学习笔记:API钩取相关的知识,希望对你有一定的参考价值。
目录
前言
继续学习《逆向工程核心原理》,本篇笔记是第四部分:API钩取,主要介绍了调试钩取、DLL注入实现IAT钩取、API代码修改钩取和全局API钩取等内容
一、API钩取简介
1、基础概念
钩取(Hook):截取信息、更改程序执行流向、添加新功能的技术
- 使用反汇编/调试器把握程序结构与原理
- 开发Hook代码,以修改bug、改善程序功能
- 灵活操作可执行文件和进程内存,设置Hook代码
API(Application Programming Interface):可以认为是调用资源的路径,notepad.exe为例如下图所示
其API调用如下:
API钩取:对Win32 API的钩取,一个例子如下图所示
2、技术图表
(下划线的是常用且好用的方法)
二、调试钩取技术
通过调试钩取技术,来钩取kernel32!WriteFile() API
1、调试器
调试器(Debbuger):能逐一执行被调试者的指令,拥有对寄存器和内存的所有访问权限,工作原理如下图所示
调试事件共9种,见微软官方调试事件
其中,与调试相关的是EXCEPTION_DEBUG_EVENT,与其相关的异常列表如下:
其中,调试器必须处理的是EXCEPTION_BREAKPOINT(断点),对应汇编指令是INT3
,IA-32指令是0xCC
要设置断点时,只需将代码在内存中的起始地址的1个字节设置为0xCC
即可,想继续调试就把它恢复
2、调试流程
基本思路:被调试者的API起始部份修改为0xCC
,控制权转移到调试器后执行指定操作,最后使被调试者重新进入运行状态
- 对目标进程进行附加操作,使之成为被调试者
- Hook:API起始地址的第1个字节修改为
0xCC
- 调用相应API,控制权转移到调试器
- 执行操作:操作参数、返回值等
- 脱钩:
0xCC
恢复原值 - 运行相应API(正常状态)
- Hook:再次修改为
0xCC
(继续钩取) - 控制权返还被调试者
3、示例:记事本 WriteFile() API钩取
目标是将notepad.exe中所有小写字母都变成大写字母
WriteFile() 定义如下:
BOOL WriteFile(
HANDLE hFile, //文件句柄
LPCVOID lpBuffer, //数据缓存区指针
DWORD nNumberOfBytesToWrite, //要写的字节数
LPDWORD lpNumberOfBytesWritten, //用于保存实际写入字节数的存储区域的指针
LPOVERLAPPED lpOverlapped //OVERLAPPED结构体指针
);
(1)hookdbg.exe
源码如下:
// hookdbg.exe
#include "windows.h"
#include "stdio.h"
LPVOID g_pfWriteFile = NULL;
CREATE_PROCESS_DEBUG_INFO g_cpdi;
BYTE g_chINT3 = 0xCC, g_chOrgByte = 0;
BOOL OnCreateProcessDebugEvent(LPDEBUG_EVENT pde)
// 获取 WriteFile() API 地址(注意是调试进程的内存地址,不是被调试进程)
g_pfWriteFile = GetProcAddress(GetModuleHandleA("kernel32.dll"), "WriteFile");
// API Hook - WriteFile()
// 更改第一个字节为 0xCC (INT 3)
// (orginal byte 是g_chOrgByte备份)
memcpy(&g_cpdi, &pde->u.CreateProcessInfo, sizeof(CREATE_PROCESS_DEBUG_INFO));
ReadProcessMemory(g_cpdi.hProcess, g_pfWriteFile,
&g_chOrgByte, sizeof(BYTE), NULL);
WriteProcessMemory(g_cpdi.hProcess, g_pfWriteFile,
&g_chINT3, sizeof(BYTE), NULL);
return TRUE;
BOOL OnExceptionDebugEvent(LPDEBUG_EVENT pde)
CONTEXT ctx;
PBYTE lpBuffer = NULL;
DWORD dwNumOfBytesToWrite, dwAddrOfBuffer, i;
PEXCEPTION_RECORD per = &pde->u.Exception.ExceptionRecord;
// 断点异常 (INT 3)
if( EXCEPTION_BREAKPOINT == per->ExceptionCode )
// 断点地址为 WriteFile() API 地址
if( g_pfWriteFile == per->ExceptionAddress )
// #1. Unhook
// 0xCC 恢复为 original byte
WriteProcessMemory(g_cpdi.hProcess, g_pfWriteFile,
&g_chOrgByte, sizeof(BYTE), NULL);
// #2. 获取线程上下文
ctx.ContextFlags = CONTEXT_CONTROL;
GetThreadContext(g_cpdi.hThread, &ctx);
// #3. 获取 WriteFile() 的 param 2, 3 值
// 函数参数存在于相应进程的栈
// param 2 : ESP + 0x8 缓冲区地址
// param 3 : ESP + 0xC 缓冲区大小
ReadProcessMemory(g_cpdi.hProcess, (LPVOID)(ctx.Esp + 0x8),
&dwAddrOfBuffer, sizeof(DWORD), NULL);
ReadProcessMemory(g_cpdi.hProcess, (LPVOID)(ctx.Esp + 0xC),
&dwNumOfBytesToWrite, sizeof(DWORD), NULL);
// #4. 分配临时缓冲区
lpBuffer = (PBYTE)malloc(dwNumOfBytesToWrite+1);
memset(lpBuffer, 0, dwNumOfBytesToWrite+1);
// #5. 复制 WriteFile() 缓冲区到临时缓冲区
ReadProcessMemory(g_cpdi.hProcess, (LPVOID)dwAddrOfBuffer,
lpBuffer, dwNumOfBytesToWrite, NULL);
printf("\\n### original string ###\\n%s\\n", lpBuffer);
// #6. 小写字母 -> 大写字母
for( i = 0; i < dwNumOfBytesToWrite; i++ )
if( 0x61 <= lpBuffer[i] && lpBuffer[i] <= 0x7A )
lpBuffer[i] -= 0x20;
printf("\\n### converted string ###\\n%s\\n", lpBuffer);
// #7. 变换后的缓冲区复制到 WriteFile() 缓冲区
WriteProcessMemory(g_cpdi.hProcess, (LPVOID)dwAddrOfBuffer,
lpBuffer, dwNumOfBytesToWrite, NULL);
// #8. 释放临时缓冲区
free(lpBuffer);
// #9. 线程上下文的 EIP 改为 WriteFile() 首地址
// (当前为 WriteFile() + 1 位置,INT3命令后)
ctx.Eip = (DWORD)g_pfWriteFile;
SetThreadContext(g_cpdi.hThread, &ctx);
// #10. 运行被调试者
ContinueDebugEvent(pde->dwProcessId, pde->dwThreadId, DBG_CONTINUE);
Sleep(0);
// #11. API Hook
WriteProcessMemory(g_cpdi.hProcess, g_pfWriteFile,
&g_chINT3, sizeof(BYTE), NULL);
return TRUE;
return FALSE;
void DebugLoop()
DEBUG_EVENT de;
DWORD dwContinueStatus;
// 等待被调试者发生事件
while( WaitForDebugEvent(&de, INFINITE) )
dwContinueStatus = DBG_CONTINUE; //dwContinueStatus值为DBG_CONTINUE(处理正常)或DBG_EXCEPTION_NOT_HANDLED(无法处理或在SEH中处理)
// 被调试者生成或附加事件
if( CREATE_PROCESS_DEBUG_EVENT == de.dwDebugEventCode )
OnCreateProcessDebugEvent(&de); //OnCreateProcessDebugEvent是CREATE_PROCESS_DEBUG_EVENT事件句柄
// 异常事件
else if( EXCEPTION_DEBUG_EVENT == de.dwDebugEventCode )
if( OnExceptionDebugEvent(&de) ) //OnExceptionDebugEvent是EXCEPTION_DEBUG_EVENT事件句柄,处理被调试者的INT3指令
continue;
// 被调试者终止事件
else if( EXIT_PROCESS_DEBUG_EVENT == de.dwDebugEventCode )
// 被调试者终止 -> debugger 终止
break;
// 再次运行被调试者
ContinueDebugEvent(de.dwProcessId, de.dwThreadId, dwContinueStatus);
int main(int argc, char* argv[])
DWORD dwPID;
if( argc != 2 )
printf("\\nUSAGE : hookdbg.exe <pid>\\n");
return 1;
// Attach Process
dwPID = atoi(argv[1]); //以程序运行参数的形式接收进程PID
if( !DebugActiveProcess(dwPID) ) //将调试器附加到进程上
printf("DebugActiveProcess(%d) failed!!!\\n"
"Error Code = %d\\n", dwPID, GetLastError());
return 1;
// 调试器循环
DebugLoop();
return 0;
(2)试验
先运行notepad.exe,并获取notepad的PID是32220
,然后运行hookdbg.exe,如下
输入一串字符串,保存
再次打开的时候会发现都变成大写字母了
三、DLL注入实现IAT钩取技术
本节向计算器calc.exe插入用户的DLL文件,钩取IAT的user32.SetWindowTextW() API地址,使得计算器显示中文数字
1、选定目标API
PEView打开calc.exe,在IAT中寻找API,如下两个负责显示文本
其中,SetDigitemTextW()
又调用了SetWindowTextW()
SetWindowTextW()
API定义如下:
BOOL SetWindowText(
HWND hwnd, //窗口句柄
LPCTSTR lpString //字符串指针(钩取的目标)
);
在OD里验证下
右键-search for-all intermodular calls
,在SetWindowTextW()
设置断点
运行,然后可以看到第一个断点处lpString
的值是0
,就是计算器显示的初始值
在计算器中输入7
,继续运行,发现lpString
的值变为7
(注意此时地址不同)
尝试修改为中文“七”,Unicode码4e03
,记住是小端序故要逆序
然后就会在计算器上显示“七”
验证完毕
2、IAT钩取工作原理
关于IAT,可以见《逆向工程核心原理》学习笔记(二):PE文件
原理如下图所示:
- 首先注入hookiat.dll文件,文件中提供了
MySetWindowTextW()
函数 - 然后修改IAT中的CALL的值为
MySetWindowTextW()
函数起始地址 - 一系列处理后,再CALL到
user32.SetWindowTextW()
函数起始地址 user32.SetWindowTextW()
执行完后返回到hookiat.dll执行下一条指令- 最后返回到
01002628
3、示例:计算器显示中文数字
(1)hookiat.dll
// hookiat.dll
#include "stdio.h"
#include "wchar.h"
#include "windows.h"
// typedef
typedef BOOL (WINAPI *PFSETWINDOWTEXTW)(HWND hWnd, LPWSTR lpString);
// globals
FARPROC g_pOrgFunc = NULL;
BOOL WINAPI MySetWindowTextW(HWND hWnd, LPWSTR lpString)
wchar_t* pNum = L"零一二三四五六七八九";
wchar_t temp[2] = 0,;
int i = 0, nLen = 0, nIndex = 0;
nLen = wcslen(lpString);
for(i = 0; i < nLen; i++)
// 阿拉伯数字转换为中文数字
// lpString 是 wide-character (2 byte) 字符串
if( L'0' <= lpString[i] && lpString[i] <= L'9' )
temp[0] = lpString[i];
nIndex = _wtoi(temp);
lpString[i] = pNum[nIndex];
// 调用 user32!SetWindowTextW() API
// (修改 lpString 缓冲区中内容)
return ((PFSETWINDOWTEXTW)g_pOrgFunc)(hWnd, lpString);
// hook_iat
// 负责钩取 IAT
BOOL hook_iat(LPCSTR szDllName, PROC pfnOrg, PROC pfnNew)
HMODULE hMod;
LPCSTR szLibName;
PIMAGE_IMPORT_DESCRIPTOR pImportDesc;
PIMAGE_THUNK_DATA pThunk;
DWORD dwOldProtect, dwRVA;
PBYTE pAddr;
//查找 IAT 位置
// hMod, pAddr = ImageBase of calc.exe
// = VA to MZ signature (IMAGE_DOS_HEADER)
hMod = GetModuleHandle(NULL);
pAddr = (PBYTE)hMod;
// pAddr = VA to PE signature (IMAGE_NT_HEADERS)
pAddr += *((DWORD*)&pAddr[0x3C]);
// dwRVA = RVA to IMAGE_IMPORT_DESCRIPTOR Table
dwRVA = *((DWORD*)&pAddr[0x80]);
// pImportDesc = VA to IMAGE_IMPORT_DESCRIPTOR Table
pImportDesc = (PIMAGE_IMPORT_DESCRIPTOR)((DWORD)hMod+dwRVA);
// for循环找到user32.dll
for( ; pImportDesc->Name; pImportDesc++ )
// szLibName = VA to IMAGE_IMPORT_DESCRIPTOR.Name
szLibName = (LPCSTR)((DWORD)hMod + pImportDesc->Name);
if( !_stricmp(szLibName, szDllName) )
// pThunk = IMAGE_IMPORT_DESCRIPTOR.FirstThunk
// = VA to IAT(Import Address Table)
pThunk = (PIMAGE_THUNK_DATA)((DWORD)hMod +
pImportDesc->FirstThunk);
// for循环找到SetWindowTextW的IAT地址
// pThunk->u1.Function = VA to API
for( ; pThunk->u1.Function; pThunk++ )
if( pThunk->u1.Function == (DWORD)pfnOrg )
// 更改内存属性为 E/R/W
VirtualProtect((LPVOID)&pThunk->u1.Function,
4,
PAGE_EXECUTE_READWRITE,
&dwOldProtect);
// 修改 IAT 值(钩取)
pThunk->u1.Function = (DWORD)pfnNew;
// 恢复内存属性
VirtualProtect((LPVOID)&pThunk->u1.Function,
4,
dwOldProtect,
&dwOldProtect);
return TRUE;
return FALSE;
BOOL WINAPI DllMain(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpvReserved)
switch( fdwReason )
case DLL_PROCESS_ATTACH :
// 保存原始 API 地址
g_pOrgFunc = GetProcAddress(GetModuleHandle(L"user32.dll"),
"SetWindowTextW");
// # hook
// 用 hookiat.MySetWindowText() 钩取 user32.SetWindowTextW()
hook_iat("user32.dll", g_pOrgFunc, (PROC)MySetWindowTextW);
break;
case DLL_PROCESS_DETACH :
// # unhook
// 将 calc.exe 的 IAT 恢复原值
hook_iat("user32.dll", (PROC)MySetWindowTextW, g_pOrgFunc);
break;
return TRUE;
(2)InjectDll.exe
// InjectDll.exe
#include "stdio.h"
#include "windows.h"
#include "tlhelp32.h"
#include "winbase.h"
#include "tchar.h"
void usage()
printf("\\nInjectDll.exe by ReverseCore\\n"
"- blog : http://www.reversecore.com\\n"
"- email : reversecore@gmail.com\\n\\n"
"- USAGE : InjectDll.exe <i|e> <PID> <dll_path>\\n\\n");
BOOL InjectDll(DWORD dwPID, LPCTSTR szDllName)
HANDLE hProcess, hThread;
LPVOID pRemoteBuf;
DWORD dwBufSize = (DWORD)(_tcslen(szDllName) + 1) * sizeof(TCHAR);
LPTHREAD_START_ROUTINE pThreadProc;
if ( !(hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, dwPID)) )
DWORD dwErr = GetLastError();
return FALSE;
pRemoteBuf = VirtualAllocEx(hProcess, NULL, dwBufSize, MEM_COMMIT, PAGE_READWRITE);
WriteProcessMemory(hProcess, pRemoteBuf, (LPVOID)szDllName, dwBufSize, NULL);
pThreadProc = (LPTHREAD_START_ROUTINE)GetProcAddress(GetModuleHandle(L"kernel32.dll"), "LoadLibraryW");
hThread = CreateRemoteThread(hProcess, NULL, 0, pThreadProc, pRemoteBuf, 0, NULL);
WaitForSingleObject(hThread, INFINITE);
CloseHandle(hThread);
CloseHandle(hProcess);
return TRUE;
BOOL EjectDll(DWORD dwPID, LPCTSTR szDllName)
BOOL bMore = FALSE, bFound = FALSE;
HANDLE hSnapshot, hProcess, hThread;
MODULEENTRY32 me = sizeof(me) ;
LPTHREAD_START_ROUTINE pThreadProc;
if( INVALID_HANDLE_VALUE == (hSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPMODULE, dwPID)) )
return FALSE;
bMore = Module32First(hSnapshot, &me);
for( ;bMore ;bMore = Module32Next(hSnapshot, &me) )
if( !_tcsicmp(me.szModule, szDllName) || !_tcsicmp(me.szExePath, szDllName) )
bFound = TRUE;
break;
if( !bFound )
CloseHandle(hSnapshot);
return FALSE;
if( !(hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, dwPID)) )
CloseHandle(hSnapshot);
return FALSE;
pThreadProc = (LPTHREAD_START_ROUTINE)GetProcAddress(GetModuleHandle(L"kernel32.dll"), "FreeLibrary");
hThread = CreateRemoteThread(hProcess, NULL, 0, pThreadProc, me.modBaseAddr, 0, NULL);
WaitForSingleObject(hThread, INFINITE);
CloseHandle(hThread);
CloseHandle(hProcess);
CloseHandle(hSnapshot);
return TRUE;
DWORD _EnableNTPrivilege(LPCTSTR szPrivilege, DWORD dwState)
DWORD dwRtn = 《逆向工程核心原理》学习笔记:API钩取