逆向工程核心原理读书笔记-API钩取之记事本小写转大写
来源:互联网 发布:睡眠计时器软件 编辑:程序博客网 时间:2024/06/05 01:12
我们通过一个示例来练习钩取Notepad.exe的WriteFile,保存文件时将小写字母全部转换为大写字母。下面我们先测试一下代码。
运行notepad.exe并查看PID。
在命令行窗口中输入命令与参数。
输入一串小写字母之后选择保存。
再次打开文件,之前的小写字母全部变成了大写字母。
我们来分析一下源代码,看看是怎么实现的。
首先看一下main函数。main函数通过DebugActiveProcess将调试器附加到该运行的进程上,然后进入DebugLoop处理来自被调试者的调试事件。
- int main(int argc, char* argv[])
- {
- DWORD dwPID;
-
- if( argc != 2 )
- {
- printf("\nUSAGE : hookdbg.exe <pid>\n");
- return 1;
- }
-
-
- dwPID = atoi(argv[1]);
- if( !DebugActiveProcess(dwPID) )
- {
- printf("DebugActiveProcess(%d) failed!!!\n"
- "Error Code = %d\n", dwPID, GetLastError());
- return 1;
- }
-
-
- DebugLoop();
-
- return 0;
- }
接下来是DebugLoop。它从被调试者处接收事件并处理,然后使被调试者继续运行。ContinueDebugEvent是一个使被调试者继续运行的函数。- 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);
- }
- }
DebugLoop处理3种调试事件,分别是EXIT_PROCESS_DEBUG_EVENT、CREATE_PROCESS_DEBUG_EVENT、EXCEPTION_DEBUG_EVENT。
1.被调试进程终止时会触发EXIT_PROCESS_DEBUG_EVENT。这里在发生该事件时,调试器与被调试者将一起终止。
2.OnCreateProcessDebugEvent是CREATE_PROCESS_DEBUG_EVENT事件句柄,被调试进程启动(或者附加)时即调用执行该函数。下面看一下它的核心部分。
- BOOL OnCreateProcessDebugEvent(LPDEBUG_EVENT pde)
- {
-
- g_pfWriteFile = GetProcAddress(GetModuleHandleA("kernel32.dll"), "WriteFile");
-
-
-
- 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;
- }
首先获取WriteFile的起始地址,它获取的不是被调试进程的内存地址,而是调试进行的内存地址。对于Windows XP的DLL而言,它们在所有进程中都会加载到相同的地址(虚拟内存)。由于调试器拥有被调试进程的句柄(带有调试权限),所以可以使用ReadProcessMemory和WriteProcessMemory对被调试进程的内存空间自由进行读写操作。
3.OnExceptionDebugEvent是EXCEPTION_DEBUG_EVENT事件句柄,它处理被调试者的INT3指令。下面看一下它的核心部分。
- BOOL OnExceptionDebugEvent(LPDEBUG_EVENT pde)
- {
- CONTEXT ctx;
- PBYTE lpBuffer = NULL;
- DWORD dwNumOfBytesToWrite, dwAddrOfBuffer, i;
- PEXCEPTION_RECORD per = &pde->u.Exception.ExceptionRecord;
-
-
- if( EXCEPTION_BREAKPOINT == per->ExceptionCode )
- {
-
- if( g_pfWriteFile == per->ExceptionAddress )
- {
-
-
- WriteProcessMemory(g_cpdi.hProcess, g_pfWriteFile,
- &g_chOrgByte, sizeof(BYTE), NULL);
-
- ctx.ContextFlags = CONTEXT_CONTROL;
- GetThreadContext(g_cpdi.hThread, &ctx);
-
-
-
- ReadProcessMemory(g_cpdi.hProcess, (LPVOID)(ctx.Esp + 0x8),
- &dwAddrOfBuffer, sizeof(DWORD), NULL);
- ReadProcessMemory(g_cpdi.hProcess, (LPVOID)(ctx.Esp + 0xC),
- &dwNumOfBytesToWrite, sizeof(DWORD), NULL);
-
- lpBuffer = (PBYTE)malloc(dwNumOfBytesToWrite+1);
- memset(lpBuffer, 0, dwNumOfBytesToWrite+1);
-
- ReadProcessMemory(g_cpdi.hProcess, (LPVOID)dwAddrOfBuffer,
- lpBuffer, dwNumOfBytesToWrite, NULL);
- printf("\n### original string ###\n%s\n", lpBuffer);
-
- for( i = 0; i < dwNumOfBytesToWrite; i++ )
- {
- if( 0x61 <= lpBuffer[i] && lpBuffer[i] <= 0x7A )
- lpBuffer[i] -= 0x20;
- }
-
- printf("\n### converted string ###\n%s\n", lpBuffer);
-
-
- WriteProcessMemory(g_cpdi.hProcess, (LPVOID)dwAddrOfBuffer,
- lpBuffer, dwNumOfBytesToWrite, NULL);
-
-
- free(lpBuffer);
-
-
- ctx.Eip = (DWORD)g_pfWriteFile;
- SetThreadContext(g_cpdi.hThread, &ctx);
-
-
- ContinueDebugEvent(pde->dwProcessId, pde->dwThreadId, DBG_CONTINUE);
- Sleep(0);
-
-
- WriteProcessMemory(g_cpdi.hProcess, g_pfWriteFile,
- &g_chINT3, sizeof(BYTE), NULL);
-
- return TRUE;
- }
- }
-
- return FALSE;
- }
首先脱钩,因为在将小写字母转换为大写字母之后需要正常调用WriteFile。接着获取线程的上下文,获取WriteFile的第二个参数和第三个参数的值。调用WriteFile时,我们要在传递过来的参数中知道param2(缓冲区地址)和param3(缓冲区大小)这两个参数。函数参数存储在栈中,通过CONTEXT.Esp成员可以获得它们的值。然后把小写字母转换为大写字母之后覆写WriteFile缓冲区,把线程上下文的EIP修改为WriteFile起始地址。因为在WriteFile的起始地址处设置了断点,被调试者内部调用WriteFile时,会在起始地址处遇到INT3指令。执行该指令时,EIP的值会增加一个字节,所以EIP的当前地址为WriteFile+1。修改好CONTEXT.Eip成员后,调用SetThreadContext。现在运行调试进程。如果没有Sleep(0)语句,Notepad.exe在调用WriteFile的过程中我们的程序会尝试将WriteFile的首字节修改为0xCC,这可能导致内存访问异常。最后设置钩子,方便下次钩取操作。完整的代码如下。- #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)
- {
-
- g_pfWriteFile = GetProcAddress(GetModuleHandleA("kernel32.dll"), "WriteFile");
-
-
-
- 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;
-
-
- if( EXCEPTION_BREAKPOINT == per->ExceptionCode )
- {
-
- if( g_pfWriteFile == per->ExceptionAddress )
- {
-
-
- WriteProcessMemory(g_cpdi.hProcess, g_pfWriteFile,
- &g_chOrgByte, sizeof(BYTE), NULL);
-
- ctx.ContextFlags = CONTEXT_CONTROL;
- GetThreadContext(g_cpdi.hThread, &ctx);
-
-
-
- ReadProcessMemory(g_cpdi.hProcess, (LPVOID)(ctx.Esp + 0x8),
- &dwAddrOfBuffer, sizeof(DWORD), NULL);
- ReadProcessMemory(g_cpdi.hProcess, (LPVOID)(ctx.Esp + 0xC),
- &dwNumOfBytesToWrite, sizeof(DWORD), NULL);
-
- lpBuffer = (PBYTE)malloc(dwNumOfBytesToWrite+1);
- memset(lpBuffer, 0, dwNumOfBytesToWrite+1);
-
- ReadProcessMemory(g_cpdi.hProcess, (LPVOID)dwAddrOfBuffer,
- lpBuffer, dwNumOfBytesToWrite, NULL);
- printf("\n### original string ###\n%s\n", lpBuffer);
-
- for( i = 0; i < dwNumOfBytesToWrite; i++ )
- {
- if( 0x61 <= lpBuffer[i] && lpBuffer[i] <= 0x7A )
- lpBuffer[i] -= 0x20;
- }
-
- printf("\n### converted string ###\n%s\n", lpBuffer);
-
-
- WriteProcessMemory(g_cpdi.hProcess, (LPVOID)dwAddrOfBuffer,
- lpBuffer, dwNumOfBytesToWrite, NULL);
-
-
- free(lpBuffer);
-
-
- ctx.Eip = (DWORD)g_pfWriteFile;
- SetThreadContext(g_cpdi.hThread, &ctx);
-
-
- ContinueDebugEvent(pde->dwProcessId, pde->dwThreadId, DBG_CONTINUE);
- Sleep(0);
-
-
- 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;
- }
-
-
- dwPID = atoi(argv[1]);
- if( !DebugActiveProcess(dwPID) )
- {
- printf("DebugActiveProcess(%d) failed!!!\n"
- "Error Code = %d\n", dwPID, GetLastError());
- return 1;
- }
-
-
- DebugLoop();
-
- return 0;
- }