其他
API 钩取:逆向分析之“花”
看雪论坛作者ID:Tray_PG
1
钩取
使用反汇编器/调试器把握程序的结构与工作原理。 开发需要的“钩子”代码,用于修改 bug 、改善程序功能。 灵活操作可执行文件与进程内存,设置“钩子”代码。
2
API
- msvcrt ! fopen()
kernel32 ! CreateFileW()
ntdll ! ZwCreateFile()
ntdll ! KiFastSystemCall()
SYSENTRY // IA-32 Instruction
——> 进入内核模式
3
API 钩取
在 API 调用前/后运行用户的“钩子”代码。 查看或操作传递给 API 的参数或 API 函数的返回时。 取消对 API 的调用,或者更改执行流程,运行用户代码。
正常调用 API
钩取API调用
4
技术图标
对象
位置
使用 JMP 指令修改起始代码; 覆写函数内部; 仅修改必需部分的局部
技术
代码注入
代码注入技术比 DLL 注入技术更发达(更复杂),广泛应用于恶意代码(病毒、Shellcode等)
5
记事本WriteFile() API 钩取
技术图表 - 调试技术
关于调试器
术语
调试器(Debugger):进行调试的程序
被调试器(Debuggee):被调试的程序
调试器功能
调试器的工作原理
一般的异常(Exception)也属于调试事件。 若相应进程处于非调试,调试事件会在其自身的异常处理或 OS 的异常处理机制中被处理掉。 调试器无法处理或不关心的调试事件最终由 OS 处理
下图用来说明调试器工作原理:
调试事件
EXCEPTION_DEBUG_EVENT
CREATE_THREAD_DEBUG_EVENT
CREATE_PROCESSDEBUG_EVENT
EXIT_THREAD_DEBUG_EVENT
EXIT_PROCESS_DEBUG_EVENT
LOAD_DLL_DEBUG_EVENT
UNLOAD_DLL_DEBUG_EVENT
OUTPUT_DEBUG_STRING_EVENT
RIP_EVENT
EXCEPTION_ACCESS_VIOLATION
EXCEPTION_ARRAY_BOUNDS_EXCEEDED
EXCEPTION_BREAKPOINT
EXCEPTION_DATATYPE_MISALIGNMENT
EXCEPTION_FLT_DENORMAL_OPERAND
EXCEPTION_ELT_DIVIDE_BY_ZERO
EXCEPTION_ELT_INEXACT_RESULT
EXCEPTION_ELT_INVALID_OPERATION
EXCEPTION_ELT_OVERFLOW
EXCEPTION_ELT_STACK_CHECK
EXCEPTION_ELT_UNDERFLOW
EXCEPTION_ILLEGAL_INSTRUCTION
EXCEPTION_IN_PAGE_ERROR
EXCEPTION_INT_DIVIDE_BY_ZERO
EXCEPTION_INT_OVERFLOW
EXCEPTION_INVALID_DISPOSITION
EXCEPTION_NONCONTINUABLE_EXCEPTION
EXCEPTION_PRIV_INSTRUCTION
EXCEPTION_SINGLE_STEP
EXCEPTION_STACK_OOVERFLOW
调试技术的流程
对想钩取的进程进行附加操作,使之成为被调试者; “钩子”:将 API 起始地址的第一个字节修改为 0xCC; 调用相应的 API 时,控制权转移到调试器; 执行需要的操作(操作参数、返回值等); 脱钩:将 0xCC 恢复原值(为了正常运行 API); 运行相应的API(无0xCC的正常状态); “钩子”:再次修改为 0xCC (为了继续钩取); 控制权返还被调试者。
6
练习
实验目标
工作原理
栈
BOOL WriteFile(
HANDLE hFile,
LPCVOID lpBuffer,
DWORD nNumberofBytesToWrite,
LPDWORD lpNumberofBytesWritten,
LPOVERLAPPED lpOverlapped
);
第一个参数(hFile)
第二个参数(lpBuffer)
第三个参数(nNumberOfBytesToWrite)
第四个参数(lpNumberofBytesWritten)
第五个参数(lpOverlapped)
执行流
“脱钩”&“钩子”
源代码分析
#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(INT3)
// originalbyte 是 g_ch0rgByte 备份
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;
// 是断点异常(INT3)时
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. 获取WriteFil() 的 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;
// 被调试进程生成或者附加事件
if (CREATE_PROCESS_DEBUG_EVENT == de.dwDebugEventCode)
{
OnCreateProcessDebugEvent(&de);
}
// 异常事件
else if (EXCEPTION_DEBUG_EVENT == de.dwDebugEventCode)
{
if (OnExceptionDebugEvent(&de))
continue;
}
// 被调试进程终止事件
else if (EXIT_PROCESS_DEBUG_EVENT == de.dwDebugEventCode)
{
// 被调试者终止---调试器终止
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]);
if (!DebugActiveProcess(dwPID))
{
printf("DebugActiveProcess(%d) failed!!!\n"
"Error Code = %d\n", dwPID, GetLastError());
return 1;
}
// 调试器循环
DebugLoop();
return 0;
}
main()
#include "windows.h"
#include "stdio.h"
LPVOID g_pfWriteFile = NULL;
CREATE_PROCESS_DEBUG_INFO g_cpdi;
BYTE g_chINT3 = 0xCC, g_ch0rgByte = 0;
int main(int agc, char* argv[])
{
DWORD dwPID;
if( argc !=2 )
{
printf("\n USEAGE : %s <PID>\n",argv[0],argv[1]);
return 1;
}
//Attach Process
dwPID = atoi(argv[1]);
if( !DebugActiveProcess(dwPID))
{
printf("DebugActiveProcess(%d) failed !!!\n""Error Code = %d\n",dwPID,GetLastError());
return 1;
}
//调试器
DebugLoop();
return 0;
}
BOOL DebugActiveProcess(
DWORD dwProcessId
);//使调试器能够附加到活动进程并对其进行调试。
Parameters
dwProcessId
//要调试的进程的标识符。
//调试器被授予对进程的调试访问权限,
//就像它使用DEBUG_ONLY_THIS_PROCESS标志创建进程一样。
void DebugLoop()
{
DEBUG_EVENT de; //描述调试事件
DWORD dwContinueStatus;
// 等待被调试者发生事件
while (WaitForDebugEvent(&de, INFINITE))//等待正在调试的进程中发生调试事件。
{
dwContinueStatus = DBG_CONTINUE;
// 被调试进程生成或者附加事件
if (CREATE_PROCESS_DEBUG_EVENT == de.dwDebugEventCode)
{
OnCreateProcessDebugEvent(&de);
}
// 异常事件
else if (EXCEPTION_DEBUG_EVENT == de.dwDebugEventCode)
{
if (OnExceptionDebugEvent(&de))
continue;
}
// 被调试进程终止事件
else if (EXIT_PROCESS_DEBUG_EVENT == de.dwDebugEventCode)
{
// 被调试者终止---调试器终止
break;
}
// 再次运行被调试者
ContinueDebugEvent(de.dwProcessId, de.dwThreadId, dwContinueStatus);
}
}
BOOL WaitForDebugEvent(
LPDEBUG_EVENT lpDebugEvent,
DWORD dwMilliseconds
);//等待正在调试的进程中发生调试事件。
Parameters
lpDebugEvent
指向接收调试事件信息的DEBUG_EVENT结构的指针 。
dwMilliseconds
等待调试事件的毫秒数。如果此参数为零,则该函数测试调试事件并立即返回。如果参数为 INFINITE,则函数在调试事件发生之前不会返回。
typedef struct _DEBUG_EVENT{
DWORD dwDebugEventCode;
DWORD dwProcessId;
DWORD dwThreadId;
union{
EXCEPTION_DEBUG_INFO Exception;
CREATE_THREAD_DEBUG_INFO CreateThread;
CREATE_PROCESS_DEBUG_INFO CreateProcess;
EXIT_THREAD_DEBUG_INGO ExitThread;
EXIT_PROCESS_DEBUG_INFO ExitProcess;
LOAD_DLL_DDEBUG_INFO LoadDll;
UNLOAD_DLL_DEBUG_INFO UnloadDll;
OUTPUT_DEBUG_STRING_INFO DebugString;
RIP_INFO Ripinfo
} u;
} DEBUG_EVENT,*LPDEBUG_EVENT;
BOOL WINAPI ContinueDebugEvent(
DWORD dwProcessId,
DWORD dwThreadId,
DWORD dwContinueStatus
);
EXIT_PROCESS_DEBUG_EVENT CREATE_PROCESS_DEBUG_EVENT EXCEPTION_DEBUG_EVENT
EXIT_PPPROCESS_DEBUG_EVENT
CREATE_PROCESS_DEBUG_EVENT-OnCreateProcessDebugEvent()
BOOL OnCreateProcessDebugEvent(LPDEBUG_EVENT pde)
{
// 获取WriteFile() API 地址
g_pfWriteFile = GetProcAddress(GetModuleHandleA("kernel32.dll"), "WriteFile");
// API Hook - WriteFile()
// 更改第一个字节为 0xCC(INT3)
// originalbyte 是 g_ch0rgByte 备份
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;
}
typedef struct _CREATE_PROCESS_DEBUG_INFO{
HANDLE hFile;
HANDLE hProcess;
HANDLE hThread;
LPVOID lpBaseOfImage;
DWORD dwDebugInfoFileOffset;
DWORD nDebugInfoSize;
LPVOID lpThreadLocalBase;
LPTHREAD_START_ROUTINE lpStartAddress;
LPVOID lpImageName;
WORD fUnicode;
} CREATE_PROCESS_DEBUG_INFO, *LPCREATE_PROCESS_DEBUG_INFO;
EXCEPTION_DEBUG_EVENT-OnExceptionDebugEvent()
BOOL OnExceptionDebugEvent(LPDEBUG_EVENT pde)
{
CONTEXT ctx;
PBYTE lpBuffer = NULL;
DWORD dwNumOfBytesToWrite, dwAddrOfBuffer, i;
PEXCEPTION_RECORD per = &pde->u.Exception.ExceptionRecord;
// 是断点异常(INT3)时
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. 获取WriteFil() 的 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;
}
“脱钩”(删除 API 钩子)
//将 0xCC 恢复为original byte
WriteProcessMemory( g_cpdi, hProcess, g_pfWriteFile,&g_chOrgByte, sizeof(BYTE), NULL)
获取上下文(Thread Context)
typedef struct _CONTEXT{
DWORD ContextFlags;
DWORD Dr0;
DWORD Dr1;
DWORD Dr2;
DWORD Dr3;
DWORD Dr6;
DWORD Dr7;
FLOADTING_SAVE_AREA FloatSave;
DWORD SegGs;
DWORD SegFs;
DWORD SegEs;
DWORD SegDs;
DWORD Edi;
DWORD Esi;
DWORD Ebx;
DWORD Edx;
DWORD Ecx;
DWORD Eax;
DWORD Ebp;
DWORD Eip;
DWORD SegCs;
DWORD EFlags;
DWORD Esp;
DWORD SegSs;
byte ExtendedRegisters[MAXIMUM_SUPPORTED_EXTENSION];
} CONTEXT;
//获取线程上下文
ctx.ContextFlags = CONTEXT_CONTROL;
GetThreadContext( g_cpdi,hThread, &ctx);
BOOL WINAPI GetThreadContext(
HANDLE hTread,
LPCONTEXT lpContext
);
获取 WriteFile() 的 param 2、3 值
//函数参数存在与相应进程栈
//param 2 : ESP + 0x8
//param 3 : ESP + 0xC
ReadProcessMemory(g_cpdi.Process,(LPVOID)(ctx.Esp + 0x8),
&dwAddrOfBuffer, sizeof(DWORD), NULL);
RradProcessMemory(g_cpdi.hProcess,(LPVOID)(ctx.Esp + 0xC),
&dwNumOfBytesToWrite, sizeof(DWORD), NULL);
存储在 dwAddrOfBuffer 中的数据缓冲区地址是被调试者(notepad.exe)虚拟内存空间中的地址。 param 2 与param 3 分别为 ESP + 0x8、ESP + 0xC。
把小写字母转换为大写字母后覆写 WriteFile() 缓冲区
//分配临时缓冲区
lpBuffer = (PBYTE)malloc(dwNumOfBytestoWrite + 1);
memset(lpBuffer, 0, dwNumOfBytesToWrite +1 );
//复制 WriteFile() 缓冲区到临时缓冲区
ReadProcessMemory( g_cpdi.hProcess, (LPVOID)dwAddrOfBuffer, lpBuffer.
dwNumberOfBytesToWrite, NULL);
printf("\n### oriiginal string : %s\n", lpBuffer);
//将小写字母转换为大写字母
for(i = 0; i < dwNumberOfBytesToWrite; i++)
{
if( 0x61 <= lpBuffer[i] && lpBuffer[i] <= 0x7A)
lpBuffer[i] -= 0x20;
}
printf("\n### converted string : %s\n", lpBuffer);
//将变换后的缓冲区复制到 WriteFile()缓冲区
WriteProcessMemory(g_cpdi.hProcess, (LPVOID)dwAddrOfBuffer,
lpBuffer, dwNumberOfBytesToWrite, NULL);
//释放缓冲区
free(lpBuffer);
把线程上下文的 EIP 修改为 WriteFile() 起始地址
//当前 WriteFie() + 1 位置,INT3命令之后。
ctx.Eip = (DWORD)g_pfWriteFile;
SetThreadContext(g_cpdi.hThread, &ctx);
SetThreadContext(
HANDLE hThread,
const CONTEXT *lpContext
);
运行调试进程
ContinueDebugEvent(pde -> dwProcessId, pde->dwThreadId, DBG_CONTINUE);
sleep(0);
设置 API “钩子”
WriteProcessMemory(g_cpdi.hProcess, g_pfWriteFile, &g_chINT3,
sizeof(BYTE), NULL);
效果图
看雪ID:Tray_PG
https://bbs.pediy.com/user-home-879928.htm
# 往期推荐
2. 新人PWN入坑总结
4. Galgame汉化中的逆向:ArmArm64_ELF中汉化字符串超长修改方法
6. V8利用初探 2019 StarCTF oob 复现分析
球分享
球点赞
球在看
点击“阅读原文”,了解更多!