调试器的工作原理:
调试进程经过注册后,每当被调试者发生调试事件(DebugEvent)时,OS就会暂停其运行,并向调试器报告相应事件,然后调试器对相应事件进行处理,使被调试者继续运行。
具体调试流程如下:
- 对想Hook的进程进行附加操作,使进程成为被调试者。
- Hook:将API起始的第一个字节修改成0xcc 也就是int3 断点。
- 调用相应API 时,控制权转移到调试器上。
- 执行需要的操作(参数,返回值等等)。
- 脱钩: 将0xcc 恢复成原本的字节(为了正常运行API)。
- Hook:再次修改为0xcc 也就是int3 断点(为了继续Hook)。
- 控制权返还给被调试者(进程本身)。
源代码例子分析:
1.首先判断是否附加成功,如果没有附加成功则提示错误信息,并退出;
int main(int argc, char* argv[])
{
DWORD dwPID;
if( argc != 2 ) // argc=2,表示除了程序名外还有一个参数。
{
printf("\nUSAGE : hookdbg.exe <pid>\n");
return 1;
}
// Attach Process
dwPID = atoi(argv[1]); //atoi把字符串转换成整型数
if( !DebugActiveProcess(dwPID) ) //如果调试器没有附加到的进程并且调试它
{
printf("Error:DebugActiveProcess(%d) failed!!!\n"
"Error Code = %d\n", dwPID, GetLastError());
return 1;
}
// 调试器循环
DebugLoop();
return 0;
}
2.当被调试者发生调试事件(DebugEvent)时,向调试器报告相应事件
void DebugLoop()
{
DEBUG_EVENT de; //描述调试事件。
DWORD dwContinueStatus;
// 等待被调试者发生事件
while( WaitForDebugEvent(&de, INFINITE) )
{
dwContinueStatus = DBG_CONTINUE; // 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);
}
}
3.被调试进程生成或者附加事件(报告创建进程调试事件)将API起始的第一个字节修改成0xcc 也就是int3 断点方便调试。
/************************************************************************/
/* OnCreateProcessDebugEvent是CREATE_PROCESS_DEBUG_EVENT事件句柄被调试进程启动(或附加)时即调用执行该函数 */
/************************************************************************/
BOOL OnCreateProcessDebugEvent(LPDEBUG_EVENT pde)
{
// 获取WriterFile()API地址
g_pfWriteFile = GetProcAddress(GetModuleHandleA("kernel32.dll"), "WriteFile");
// API Hook - WriteFile()
// 更改第一个字节为0xCC(int3)
// (orginalbyte是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;
}
4.异常事件(报告异常调试事件)上面修改成了0xcc 则会出发int3异常,那么我们在接收到异常后则可以开始我们自己带代码处理,处理完成后恢复原有运行环境。
//此段代码主要是我们将小写转换为大写字母的操作
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 )
{
//断点地址为WriterFile()API地址时
if( g_pfWriteFile == per->ExceptionAddress )
{
// #1. Unhook
// 将0xCC恢复成Originalbyte
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 : 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;
}
以下为完整代码:
#include "stdafx.h"
LPVOID g_pfWriteFile = NULL;
CREATE_PROCESS_DEBUG_INFO g_cpdi; //被调试进程的EXE文件被映射到内存中的内存文件映射句柄
BYTE g_chINT3 = 0xCC, g_chOrgByte = 0;
/************************************************************************/
/* OnCreateProcessDebugEvent是CREATE_PROCESS_DEBUG_EVENT事件句柄被调试进程启动(或附加)时即调用执行该函数 */
/************************************************************************/
BOOL OnCreateProcessDebugEvent(LPDEBUG_EVENT pde)
{
// 获取WriterFile()API地址
g_pfWriteFile = GetProcAddress(GetModuleHandleA("kernel32.dll"), "WriteFile");
// API Hook - WriteFile()
// 更改第一个字节为0xCC(int3)
// (orginalbyte是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;
// 是INT3 断点时
if( EXCEPTION_BREAKPOINT == per->ExceptionCode )
{
//断点地址为WriterFile()API地址时
if( g_pfWriteFile == per->ExceptionAddress )
{
// #1. Unhook
// 将0xCC恢复成Originalbyte
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 : 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; // 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 ) // argc=2,表示除了程序名外还有一个参数。
{
printf("\nUSAGE : hookdbg.exe <pid>\n");
return 1;
}
// Attach Process
dwPID = atoi(argv[1]); //atoi把字符串转换成整型数
if( !DebugActiveProcess(dwPID) ) //如果调试器没有附加到的进程并且调试它
{
printf("Error:DebugActiveProcess(%d) failed!!!\n"
"Error Code = %d\n", dwPID, GetLastError());
return 1;
}
// 调试器循环
DebugLoop();
return 0;
}