0x01、原理
- 通过钩子代码,来调试被钩取程序。
- 此时钩子代码就是
调式器
,被钩取程序就是被调试者
。 - 钩子代码在要被钩取程序的API位置下
0xCC
断点. - 当被调试者运行到断点处时,就会触发断点异常,此时要调试器来处理。
- 此时就可以更改要钩取的API的函数功能,达到自己的目的。
- 执行完放开让程序继续执行。
0x02、源代码
#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()
// 将第一个byte更改为0xCC(INT3)
// (orginal byte是备份)
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;
// 如果是BreakPoint exception(INT3)
if( EXCEPTION_BREAKPOINT == per->ExceptionCode )
{
// 如果BP地址为WriteFile()
if( g_pfWriteFile == per->ExceptionAddress )
{
// #1. Unhook
// 将0xCC覆盖的部分返回到original byte
WriteProcessMemory(g_cpdi.hProcess, g_pfWriteFile,
&g_chOrgByte, sizeof(BYTE), NULL);
// #2. 获取Thread Context
ctx.ContextFlags = CONTEXT_CONTROL;
GetThreadContext(g_cpdi.hThread, &ctx);
// #3. 求WriteFile()的param2,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. 将Thread Context的EIP更改为WriteFile()开头
// (目前已通过WriteFile() +1)
ctx.Eip = (DWORD)g_pfWriteFile;
SetThreadContext(g_cpdi.hThread, &ctx);
// #10. 进行Debuggee过程
ContinueDebugEvent(pde->dwProcessId, pde->dwThreadId, DBG_CONTINUE);
// 这个Sleep是让程序放弃CPU的时间片,让其他程序执行,此时钩取的notepad才能调用WriteFile函数保存内容
// 否则,下面的WriteProcessMemory立刻会把WriteFile钩取,notepad去执行WriteFile还是被断下
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;
// 等待Debuggee事件发生
while( WaitForDebugEvent(&de, INFINITE) )
{
dwContinueStatus = DBG_CONTINUE;
// 创建Debuggee进程或attach事件
if( CREATE_PROCESS_DEBUG_EVENT == de.dwDebugEventCode )
{
OnCreateProcessDebugEvent(&de);
}
// 异常事件
else if( EXCEPTION_DEBUG_EVENT == de.dwDebugEventCode )
{
if( OnExceptionDebugEvent(&de) )
continue;
}
// Debuggee进程终止事件
else if( EXIT_PROCESS_DEBUG_EVENT == de.dwDebugEventCode )
{
// debuggee结束->debugger结束
break;
}
// 恢复Debuggee的运行
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;
}
0x03、程序执行流程
0x04、结果分析
运用在windows10 32bit Debug/Release
64bit参数结构、寄存器名字不同,需要修改才能使用
0x05、用到的API
// 使调试器可以附加到活动进程并调试它
BOOL WINAPI DebugActiveProcess(
__in DWORD dwProcessId// 要附加进程的PID
);
// 等待调试事件发生
BOOL WINAPI WaitForDebugEvent(
__out LPDEBUG_EVENT lpDebugEvent,// 接收DEBUG_EVENT结构,保存被调试程序的信息
__in DWORD dwMilliseconds// 等待调试事件的毫秒数。如果此参数为零,则函数测试调试事件并立即返回。如果参数为INFINITE,则函数在发生调试事件之前不会返回。
);
// 继续执行被调试的程序
BOOL WINAPI ContinueDebugEvent(
__in DWORD dwProcessId,// 继续执行程序的进程
__in DWORD dwThreadId,// 继续执行程序的线程
__in DWORD dwContinueStatus// DBG_CONTINUE继续执行;DBG_EXCEPTION_NOT_HANDLED交给SEH处理
);
// 获取线程上下文
BOOL WINAPI GetThreadContext(
__in HANDLE hThread,// 线程ID
__inout LPCONTEXT lpContext// 保存线程上下文的CONTEXT结构
);
// 设定线程上下文
BOOL WINAPI SetThreadContext(
__in HANDLE hThread,// 线程ID
__in const CONTEXT* lpContext// 要设置的CONTEXT结构
);
// 从目标进程赋复制nsize大小的数据到lpBuffer中
BOOL WINAPI ReadProcessMemory(
__in HANDLE hProcess,// 目标进程
__in LPCVOID lpBaseAddress,// 读取数据的起始地址,在读取数据前,系统将先检验该地址的数据是否可读,如果不可读,函数将调用失败
__out LPVOID lpBuffer,// 读取的数据保存在的缓冲区
__in SIZE_T nSize,// 读取数据大小
__out SIZE_T* lpNumberOfBytesRead// 实际被读取数据大小的存放地址。如果被指定为NULL,那么将忽略此参数。
);
// 向目标进程写入nsize大小的数据到lpBaseAddress中
BOOL WINAPI WriteProcessMemory(
__in HANDLE hProcess,// 目标进程
__in LPVOID lpBaseAddress,// 要写入的地址
__in LPCVOID lpBuffer,// 要写入的数据存放的缓冲区
__in SIZE_T nSize,// 写入数据大小
__out SIZE_T* lpNumberOfBytesWritten// 实际写入数据大小的存放地址。如果被指定为NULL,那么将忽略此参数。
);
0x06用到的结构
// 调试事件发生后,保存被调试程序的信息
typedef struct _DEBUG_EVENT {
DWORD dwDebugEventCode;
DWORD dwProcessId;
DWORD dwThreadId;
// 每种异常也是一个结构体,说一下CREATE_PROCESS_DEBUG_INFO
union {
EXCEPTION_DEBUG_INFO Exception;
CREATE_THREAD_DEBUG_INFO CreateThread;
CREATE_PROCESS_DEBUG_INFO CreateProcessInfo;
EXIT_THREAD_DEBUG_INFO ExitThread;
EXIT_PROCESS_DEBUG_INFO ExitProcess;
LOAD_DLL_DEBUG_INFO LoadDll;
UNLOAD_DLL_DEBUG_INFO UnloadDll;
OUTPUT_DEBUG_STRING_INFO DebugString;
RIP_INFO RipInfo;
} u;
} DEBUG_EVENT, *LPDEBUG_EVENT;
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;
// CONTEXT x86
struct _CONTEXT {
DWORD ContextFlags;
DWORD Dr0;
DWORD Dr1;
DWORD Dr2;
DWORD Dr3;
DWORD Dr6;
DWORD Dr7;
//
// This section is specified/returned if the
// ContextFlags word contians the flag CONTEXT_FLOATING_POINT.
//
FLOATING_SAVE_AREA FloatSave;
//
// This section is specified/returned if the
// ContextFlags word contians the flag CONTEXT_SEGMENTS.
//
DWORD SegGs;
DWORD SegFs;
DWORD SegEs;
DWORD SegDs;
//
// This section is specified/returned if the
// ContextFlags word contians the flag CONTEXT_INTEGER.
//
DWORD Edi;
DWORD Esi;
DWORD Ebx;
DWORD Edx;
DWORD Ecx;
DWORD Eax;
//
// This section is specified/returned if the
// ContextFlags word contians the flag CONTEXT_CONTROL.
//
DWORD Ebp;
DWORD Eip;
DWORD SegCs; // MUST BE SANITIZED
DWORD EFlags; // MUST BE SANITIZED
DWORD Esp;
DWORD SegSs;
//
// This section is specified/returned if the ContextFlags word
// contains the flag CONTEXT_EXTENDED_REGISTERS.
// The format and contexts are processor specific
//
BYTE ExtendedRegisters[MAXIMUM_SUPPORTED_EXTENSION];
} CONTEXT;
// CONTEXT x64
typedef struct _CONTEXT {
DWORD64 P1Home;
DWORD64 P2Home;
DWORD64 P3Home;
DWORD64 P4Home;
DWORD64 P5Home;
DWORD64 P6Home;
DWORD ContextFlags;
DWORD MxCsr;
WORD SegCs;
WORD SegDs;
WORD SegEs;
WORD SegFs;
WORD SegGs;
WORD SegSs;
DWORD EFlags;
DWORD64 Dr0;
DWORD64 Dr1;
DWORD64 Dr2;
DWORD64 Dr3;
DWORD64 Dr6;
DWORD64 Dr7;
DWORD64 Rax;
DWORD64 Rcx;
DWORD64 Rdx;
DWORD64 Rbx;
DWORD64 Rsp;
DWORD64 Rbp;
DWORD64 Rsi;
DWORD64 Rdi;
DWORD64 R8;
DWORD64 R9;
DWORD64 R10;
DWORD64 R11;
DWORD64 R12;
DWORD64 R13;
DWORD64 R14;
DWORD64 R15;
DWORD64 Rip;
union {
XMM_SAVE_AREA32 FltSave;
NEON128 Q[16];
ULONGLONG D[32];
struct {
M128A Header[2];
M128A Legacy[8];
M128A Xmm0;
M128A Xmm1;
M128A Xmm2;
M128A Xmm3;
M128A Xmm4;
M128A Xmm5;
M128A Xmm6;
M128A Xmm7;
M128A Xmm8;
M128A Xmm9;
M128A Xmm10;
M128A Xmm11;
M128A Xmm12;
M128A Xmm13;
M128A Xmm14;
M128A Xmm15;
} DUMMYSTRUCTNAME;
DWORD S[32];
} DUMMYUNIONNAME;
M128A VectorRegister[26];
DWORD64 VectorControl;
DWORD64 DebugControl;
DWORD64 LastBranchToRip;
DWORD64 LastBranchFromRip;
DWORD64 LastExceptionToRip;
DWORD64 LastExceptionFromRip;
} CONTEXT, *PCONTEXT;