《逆向工程核心原理》<04-30> 通过Debug修改代码实现API钩取的技术
2017-05-03 21:41
946 查看
原理: 通过Debug修改代码实现API钩取的技术
(技术流程: 代码-> 调试-> DebugActiveProcesss.etc)在”Debugger–Debuggee”的状态下, 将Debuggee的API其实部分修改为0xCC (INT3), 控制权转移到Debugger后执行指定操作, 最后使Debuggee重新进入运行状态
Detail: 1. 对想钩取的进程进行附加操作, 使之成为Debugee; 2. "钩子": 将API的初始地址的第一个字节修改为0xcc; 3. 调用相应API时, 控制权转移到Debugger 4. 执行需要的操作(操作参数, 返回值.etc) 5. "脱钩": 将0xcc恢复原值(为了正常运行API) 6. 运行相关API(无0xcc的正常状态) 7. "钩子": 再次修改为0xcc(为了继续钩取) 8. 控制权返回Debuggee
代码:
01- main()
#include "windows.h" #include "stdio.h" LPVOID g_pfWriteFile = NULL; CREATE_PROCESS_DEBUG_INFO g_cpdi; // CreateProcessInfo BYTE g_chINT3 = 0xCC, g_chOrgByte = 0; 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; }
02- DebugLoop()
void DebugLoop() { DEBUG_EVENT de; // DEBUG_EVENT结构体 DWORD dwContinueStatus; // 等待被调试者发生事件 while( WaitForDebugEvent(&de, INFINITE) ) { dwContinueStatus = DBG_CONTINUE; // 被调试进程生成或者附加事件 if( CREATE_PROCESS_DEBUG_EVENT == de.dwDebugEventCode ) // CREATE_PROCESS_DEBUG_EVENT 在DEBUG_EVENT结构体 { OnCreateProcessDebugEvent(&de); } // 异常事件 else if( EXCEPTION_DEBUG_EVENT == de.dwDebugEventCode ) // EXCEPTION_DEBUG_EVENT 在DEBUG_EVENT结构体 { if( OnExceptionDebugEvent(&de) ) continue; } // 被调试进程终止事件 else if( EXIT_PROCESS_DEBUG_EVENT == de.dwDebugEventCode ) { // 被调试者终止-调试器终止 break; } // 再次运行被调试者 ContinueDebugEvent(de.dwProcessId, de.dwThreadId, dwContinueStatus); // dwContinueStatus 若处理正常, 值为DBG_CONTINUE; 若无法处理, 或者希望在应用程序的SEH中处理, 值为DBG_EXCEPTION_NOT_HANDLED } }
03- OnCreateProcessDebugEvent()
BOOL OnCreateProcessDebugEvent(LPDEBUG_EVENT pde) { // 获取WriteFile() API地址 g_pfWriteFile = GetProcAddress(GetModuleHandleA("kernel32.dll"), "WriteFile"); // API"钩子" - WriteFile() // 更改第一个字节为0xCC(INT3) // orginal byte是g_ch0rgByte备份 memcpy(&g_cpdi, &pde->u.CreateProcessInfo, sizeof(CREATE_PROCESS_DEBUG_INFO)); // CREATE_PROCESS_DEBUG_INFO g_cpdi; ReadProcessMemory(g_cpdi.hProcess, g_pfWriteFile, &g_chOrgByte, sizeof(BYTE), NULL); // g_cpdi.hProcess 为Debuggee进程 // g_pfWriteFile 为WriteFile()地址 // Read BYTE大小的数据放到 g_chOrgByte WriteProcessMemory(g_cpdi.hProcess, g_pfWriteFile, &g_chINT3, sizeof(BYTE), NULL); // Write BYTE大小的数据放到地址首位置 return TRUE; }
04- 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恢复为orignal 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); // Read DWORD大小的数据放到 dwAddrOfBuffer ReadProcessMemory(g_cpdi.hProcess, (LPVOID)(ctx.Esp + 0xC), &dwNumOfBytesToWrite, sizeof(DWORD), NULL); // // Read DWORD大小的数据放到dwNumOfBytesToWrite // #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命令之后) // 简而言之: 执行了INT3后, EIP+1, 需要把EIP+1再还原为EIP原来的值 ctx.Eip = (DWORD)g_pfWriteFile; // 还原EIP 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; }
05- DEBUG_EVENT
typedef struct _DEBUG_EVENT { DWORD dwDebugEventCode; DWORD dwProcessId; DWORD dwThreadId; 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 UnlaodDll; OUTPUT_DEBUG_STRING_INFO RipInfo; }u; }DEBUG_EVENT, *LPDEBUG_EVENT;
06- CREATE_THREAD_DEBUG_INFO
typedef struct _CREATE_THREAD_DEBUG_INFO { HANDLE hFile; HANDLE hProcess; HANDLE hThread; LPVOID lpBaseOfImage; DWORD dwDebugINfoFileOffset; DWORD nDebugInfoSize; LPVOID lpThreadLocalBase; LPTHREAD_START_ROUNTINE lpStartAddress; LPVOID lpImageName; WORD fUnicode; }CREATE_THREAD_DEBUG_INFO, *LPCREATE_THREAD_DEBUG_INFO;
07- CONTEXT
typedef struct _CONTEXT { DWORD ContextFlag; DWORD Dr0; DWORD Dr1; DWORD Dr2; DWORD Dr3; DWORD Dr6; DWORD Dr7; FLOATING_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[MAXTINUM_SUPPORTED_EXTENSION]; }CONTEXT;
Debug:
Debug_notepad:这里目的是观察栈的数据, 找到需要覆盖的数据缓冲区的位置
1. OD-> Attach notepad.exe-> 查找-> 所有模块中的名称-> WriteFile() f2后f9
2. 在notepad输入文本后, save
3. OD断在 WriteFile()
//注: 如果直接attach后 f9运行notepad卡死, 可以先输入文本后再attach
WriteFile()定义:4 ``` BOOL WriteFile(){ HANDLE hFile; LPCVOID lpBuffer; DWORD nNumberOfBytesWritten; LPOVERLAPPED lpoverlapped; } ``` 断下后, [esp]为返回地址, [esp+4]为hFile, [esp+8]为lpBuffer
相关文章推荐
- 《逆向工程核心原理》<04-33> 通过DLL注入修改API代码实现API钩取的技术
- 《逆向工程核心原理》<04-32> 通过DLL注入实现IAT钩取的技术
- novaclient代码解析之---通过nova show <server-id>命令解析Nova client与Nova API之间的调用关系
- 《逆向工程核心原理》<03-25> 通过修改PE加载DLL
- Nop-通过IStartupTask实现代码执行<六>
- Nop-通过IStartupTask实现代码执行<六>
- # include <errno.h >查看错误代码errno是调试程序的一个重要方法。当Linux C API函数发生异常时,一般会将errno变量赋值一个整数,不同的值表示不同的含义,可以通过查看
- 一起谈.NET技术,如何通过ildasm/ilasm修改assembly的IL代码
- 使用PHP实现密保卡功能实现代码<打包下载直接运行>
- 通过调试寄存器不修改代码实现bt功能
- Box2d源码学习<四>数学库API的实现
- android中通过代码实现文件权限修改(chmod)
- 通过<iframe>..........</iframe >来实现获取其它网页指定的内容。
- android中通过代码实现文件权限修改(chmod)
- 《Effective C#》读书笔记——条目22:通过定义并实现接口替代继承<使用C#表达设计>
- 最简单的struts2中使用<s:iterator>实现隔行变色代码[珍藏版]
- <转载>C# 通过API改变窗体形状
- 一起谈.NET技术,从数据到代码—通过代码生成机制实现强类型编程[下篇]
- TestLink测试用例:Excel转换XML工具<二>实现代码
- List<T>泛型数组API自带的Sort() 排序方法根据自定义排序实现