自由控制程序运行方式的编程技巧
前言
本篇继续阅读学习《有趣的二进制:软件安全与逆向分析》,本章是自由控制程序运行方式的编程技巧,主要介绍调试器的原理、代码注入和API钩子
一、调试器
本节给出了一个简单的调试器源码,通过实践来学习一些基本知识
1、调试器是怎样工作的
一段最简单的调试器代码如下:
// wdbg01a.cpp : 定义命令行应用程序入口点
int _tmain(int argc, _TCHAR* argv[])
{
PROCESS_INFORMATION pi;
STARTUPINFO si;
if(argc < 2){
fprintf(stderr, "C:\>%s <sample.exe>n", argv[0]);
return 1;
}
memset(&pi, 0, sizeof(pi));
memset(&si, 0, sizeof(si));
si.cb = sizeof(STARTUPINFO);
//程序通过 CreateProcess 函数启动调试目标进程,调试目标进程也叫调试对象或者被调试程序(debuggee)
BOOL r = CreateProcess(
NULL, argv[1], NULL, NULL, FALSE,
NORMAL_PRIORITY_CLASS | CREATE_SUSPENDED | DEBUG_PROCESS,
NULL, NULL, &si, &pi);
if(!r)
return -1;
//直接调用 ResumeThread 函数,这时调试对象的所有线程就会恢复运行
ResumeThread(pi.hThread);
while(1) {
DEBUG_EVENT de; //保存调试事件信息的结构体指针
//调试事件会通过 WaitForDebugEvent 函数来进行接收
if(!WaitForDebugEvent(&de, INFINITE)) // INFINITE 表示一直等待
break;
DWORD dwContinueStatus = DBG_CONTINUE;
//这里是根据事件将事件的内容显示出来,具体可参见下面的 DEBUG_EVENT 结构体
switch(de.dwDebugEventCode)
{
case CREATE_PROCESS_DEBUG_EVENT:
printf("CREATE_PROCESS_DEBUG_EVENTn");
break;
case CREATE_THREAD_DEBUG_EVENT:
printf("CREATE_THREAD_DEBUG_EVENTn");
break;
case EXIT_THREAD_DEBUG_EVENT:
printf("EXIT_THREAD_DEBUG_EVENTn");
break;
case EXIT_PROCESS_DEBUG_EVENT:
printf("EXIT_PROCESS_DEBUG_EVENTn");
break;
case EXCEPTION_DEBUG_EVENT:
if(de.u.Exception.ExceptionRecord.ExceptionCode !=
EXCEPTION_BREAKPOINT)
{
dwContinueStatus = DBG_EXCEPTION_NOT_HANDLED;
}
printf("EXCEPTION_DEBUG_EVENTn");
break;
case OUTPUT_DEBUG_STRING_EVENT:
printf("OUTPUT_DEBUG_STRING_EVENTn");
break;
case RIP_EVENT:
printf("RIP_EVENTn");
break;
case LOAD_DLL_DEBUG_EVENT:
printf("LOAD_DLL_DEBUG_EVENTn");
break;
case UNLOAD_DLL_DEBUG_EVENT:
printf("UNLOAD_DLL_DEBUG_EVENTn");
break;
}
if(de.dwDebugEventCode == EXIT_PROCESS_DEBUG_EVENT)
break;
//当处理被交给调试器时,调试对象会暂停运行。因此,在我们的调试器显示消息的过程中,调试对象是处于暂停状态的
//调用 ContinueDebugEvent 函数可以让调试对象恢复运行,这时调试器又回到 WatiForDebugEvent 函数等待下一条调试事件
ContinueDebugEvent(
de.dwProcessId, de.dwThreadId, dwContinueStatus);
}
CloseHandle(pi.hThread);
CloseHandle(pi.hProcess);
return 0;
}
CreateProcess 函数如下:
BOOL CreateProcess(
LPCTSTR lpApplicationName, // 可执行模块名称
LPTSTR lpCommandLine, // 命令行字符串
LPSECURITY_ATTRIBUTES lpProcessAttributes,
LPSECURITY_ATTRIBUTES lpThreadAttributes,
BOOL bInheritHandles, // 句柄继承选项
DWORD dwCreationFlags, // 创建标志
LPVOID lpEnvironment, // 新进程的环境变量块
LPCTSTR lpCurrentDirectory, // 当前路径
LPSTARTUPINFO lpStartupInfo, // 启动信息
LPPROCESS_INFORMATION lpProcessInformation // 进程信息
);
如果设置了 DEBUG_PROCESS 或 DEBUG_ONLY_THIS_PROCESS 标志,则启动的进程(调试对象)中所产生的异常都会被调试器捕捉到
通过 CREATE_SUSPENDED 标志可以让进程在启动后进入挂起状态。当设置这一标志时,CreateProcess 函数调用完成之后,新进程中的所有线程都会暂停
DEBUG_EVENT 结构体如下:
typedef struct _DEBUG_EVENT {
DWORD dwDebugEventCode; //调试事件编号
DWORD dwProcessId; //进程 ID
DWORD dwThreadId; //线程 ID
union { //接下来的数据会随 dwDebugEventCode 的不同而发生变化
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; //加载 DLL
UNLOAD_DLL_DEBUG_INFO UnloadDll; //卸载 DLL
OUTPUT_DEBUG_STRING_INFO DebugString; //调用 OutputDebugString 函数
RIP_INFO RipInfo; //发生系统调试错误
} u;
} DEBUG_EVENT, *LPDEBUG_EVENT;
这个最简单的调试器可以捕捉到创建进程、线程以及加载、卸载 DLL 等事件
2、实现反汇编功能
本小节添加反汇编功能,希望能实现一下功能:
-
显示出发生异常的地址以及当前寄存器的值
-
显示发生异常时所执行的指令
// wdbg02a.cpp : 定义命令行应用程序入口点
//disas 函数负责对机器语言进行反汇编,使用了 udis86
int disas(unsigned char *buff, char *out, int size)
{
ud_t ud_obj;
ud_init(&ud_obj);
ud_set_input_buffer(&ud_obj, buff, 32);
ud_set_mode(&ud_obj, 32);
ud_set_syntax(&ud_obj, UD_SYN_INTEL);
if(ud_disassemble(&ud_obj)){
sprintf_s(out, size, "%14s %s",
ud_insn_hex(&ud_obj), ud_insn_asm(&ud_obj));
}else{
return -1;
}
return (int)ud_insn_len(&ud_obj);
}
//exception_debug_event 函数会在发生异常时运行
int exception_debug_event(DEBUG_EVENT *pde)
{
DWORD dwReadBytes;
//在 Windows 中,即便我们的程序不是作为调试器挂载在目标进程上, 只要能够获取目标进程的句柄,就可以随意读写该进程的内存空间
HANDLE ph = OpenProcess(
PROCESS_VM_WRITE | PROCESS_VM_READ | PROCESS_VM_OPERATION, // 访问标志
FALSE, // 句柄继承选项
pde->dwProcessId); // 进程ID
if(!ph)
return -1;
// 用 OpenThread 打开线程之后,可通过 GetThreadContext 和 SetThreadContext 来读写寄存器
HANDLE th = OpenThread(THREAD_GET_CONTEXT | THREAD_SET_CONTEXT, // 访问标志
FALSE, // 句柄继承选项
pde->dwThreadId); // 线程ID
if(!th)
return -1;
CONTEXT ctx;
ctx.ContextFlags = CONTEXT_ALL;
GetThreadContext(th, &ctx); // 参数如下:拥有上下文的线程句柄,接收上下文的结构体地址
char asm_string[256];
unsigned char asm_code[32];
ReadProcessMemory(ph, (VOID *)ctx.Eip, asm_code, 32, &dwReadBytes); //参数如下:进程句柄,读取起始地址,用于存放数据的缓冲区,要读取的字节数,实际读取的字节数
if(disas(asm_code, asm_string, sizeof(asm_string)) == -1)
asm_string[0] = '';
printf("Exception: %08x (PID:%d, TID:%d)n",
pde->u.Exception.ExceptionRecord.ExceptionAddress,
pde->dwProcessId, pde->dwThreadId);
printf(" %08x: %sn", ctx.Eip, asm_string);
printf(" Reg: EAX=%08x ECX=%08x EDX=%08x EBX=%08xn",
ctx.Eax, ctx.Ecx, ctx.Edx, ctx.Ebx);
printf(" ESI=%08x EDI=%08x ESP=%08x EBP=%08xn",
ctx.Esi, ctx.Edi, ctx.Esp, ctx.Ebp);
SetThreadContext(th, &ctx);
CloseHandle(th);
CloseHandle(ph);
return 0;
}
int _tmain(int argc, _TCHAR* argv[])
{
STARTUPINFO si;
PROCESS_INFORMATION pi;
if(argc < 2){
fprintf(stderr, "C:\>%s <sample.exe>n", argv[0]);
return 1;
}
memset(&pi, 0, sizeof(pi));
memset(&si, 0, sizeof(si));
si.cb = sizeof(STARTUPINFO);
BOOL r = CreateProcess(
NULL, argv[1], NULL, NULL, FALSE,
NORMAL_PRIORITY_CLASS | CREATE_SUSPENDED | DEBUG_PROCESS,
NULL, NULL, &si, &pi);
if(!r)
return -1;
ResumeThread(pi.hThread);
int process_counter = 0;
do{
DEBUG_EVENT de;
if(!WaitForDebugEvent(&de, INFINITE))
break;
DWORD dwContinueStatus = DBG_CONTINUE;
switch(de.dwDebugEventCode)
{
case CREATE_PROCESS_DEBUG_EVENT:
process_counter++;
break;
case EXIT_PROCESS_DEBUG_EVENT:
process_counter--;
break;
case EXCEPTION_DEBUG_EVENT:
if(de.u.Exception.ExceptionRecord.ExceptionCode !=
EXCEPTION_BREAKPOINT)
{
dwContinueStatus = DBG_EXCEPTION_NOT_HANDLED;
}
exception_debug_event(&de);
break;
}
ContinueDebugEvent(
de.dwProcessId, de.dwThreadId, dwContinueStatus);
}while(process_counter > 0);
CloseHandle(pi.hThread);
CloseHandle(pi.hProcess);
return 0;
}
3、运行改良版调试器
进行一个测试
一个简单的会发生异常的程序如下:
int main(int argc, char *argv[]) {
char *s = NULL;
*s = 0xFF;
return 0;
}
测试结果如下:
可以看到在 mov byte [eax], 0xff 的地方发生了第 2 个异常
对应源代码中的 *s = 0xFF
二、代码注入
本节介绍了3种代码注入的手法
1、用 SetWindowsHookEx 劫持系统消息
先简单看下三个 Windows 官方 API 函数:
//SetWindowsHookEx 的功能是将原本传递给窗口过程的消息劫持下来,交给第 2 参数中所指定的函数来进行处理
HHOOK SetWindowsHookEx(
int idHook, // 钩子类型
HOOKPROC lpfn, // 钩子过程
HINSTANCE hMod, // 应用程序实例的句柄
DWORD dwThreadId // 线程ID
);
LRESULT CallNextHookEx(
HHOOK hhk, // 当前钩子的句柄
int nCode, // 传递给钩子过程的代码
WPARAM wParam, // 传递给钩子过程的值
LPARAM lParam // 传递给钩子过程的值
);
BOOL UnhookWindowsHookEx(
HHOOK hhk // 要解除的对象的钩子过程句柄
);
利用这 SetWindowsHookEx 这个API,书里给出了 loging.h 和 loging.cpp 如下:
//loging.h
LOGING_API int CallSetWindowsHookEx(VOID);
LOGING_API int CallUnhookWindowsHookEx(VOID);
// loging.cpp
HHOOK g_hhook = NULL;
// GetMsgProc 中调用了 CallNextHookEx 函数,这时消息会继续传递给下一个钩子过程
static LRESULT WINAPI GetMsgProc(int code, WPARAM wParam, LPARAM lParam)
{
return(CallNextHookEx(NULL, code, wParam, lParam));
}
LOGING_API int CallSetWindowsHookEx(VOID)
{
if(g_hhook != NULL)
return -1;
MEMORY_BASIC_INFORMATION mbi;
if(VirtualQuery(CallSetWindowsHookEx, &mbi, sizeof(mbi)) == 0)
return -1;
HMODULE hModule = (HMODULE) mbi.AllocationBase;
g_hhook = SetWindowsHookEx(
WH_GETMESSAGE, GetMsgProc, hModule, 0); //将 GetMsgProc 设为钩子过程,因此系统消息在传递给目标线程原有的窗口过程之前,会先由 GetMsgProc 来进行处理
if(g_hhook == NULL)
return -1;
return 0;
}
LOGING_API int CallUnhookWindowsHookEx(VOID)
{
if(g_hhook == NULL)
return -1;
UnhookWindowsHookEx(g_hhook);
g_hhook = NULL;
return 0;
}
将 loging.cpp 编译成 DLL
// dllmain.cpp
//DLL 成功加载之后, 向 %TEMP% 目录输出一个名为 loging.log 的日志文件。日志的内容包括进程 ID 和模块路径
int WriteLog(TCHAR *szData)
{
TCHAR szTempPath[1024];
GetTempPath(sizeof(szTempPath), szTempPath);
lstrcat(szTempPath, "loging.log");
TCHAR szModuleName[1024];
GetModuleFileName(GetModuleHandle(NULL),
szModuleName, sizeof(szModuleName));
TCHAR szHead[1024];
wsprintf(szHead, "[PID:%d][Module:%s] ",
GetCurrentProcessId(), szModuleName);
HANDLE hFile = CreateFile(
szTempPath, GENERIC_WRITE, 0, NULL,
OPEN_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
if(hFile == INVALID_HANDLE_VALUE)
return -1;
SetFilePointer(hFile, 0, NULL, FILE_END);
DWORD dwWriteSize;
WriteFile(hFile, szHead, lstrlen(szHead), &dwWriteSize, NULL);
WriteFile(hFile, szData, lstrlen(szData), &dwWriteSize, NULL);
CloseHandle(hFile);
return 0;
}
BOOL APIENTRY DllMain( HMODULE hModule,
DWORD ul_reason_for_call,
LPVOID lpReserved
)
{
switch (ul_reason_for_call)
{
case DLL_PROCESS_ATTACH:
WriteLog("DLL_PROCESS_ATTACHn");
break;
case DLL_THREAD_ATTACH:
break;
case DLL_THREAD_DETACH:
break;
case DLL_PROCESS_DETACH:
WriteLog("DLL_PROCESS_DETACHn");
break;
}
return TRUE;
}
将 dllmain.cpp、loging.cpp 和 loging.h 进行编译得到 loging.dll
然后再利用 CallSetWindowsHookEx 加载这个DLL,见 setwindowshook.cpp:
// setwindowshook.cpp
int _tmain(int argc, _TCHAR* argv[])
{
if(argc < 2){
fprintf(stderr, "%s <DLL Name>n", argv[0]);
return 1;
}
HMODULE h = LoadLibrary(argv[1]);
if(h == NULL)
return -1;
int (__stdcall *fcall) (VOID);
fcall = (int (WINAPI *)(VOID))
GetProcAddress(h, "CallSetWindowsHookEx");
if(fcall == NULL){
fprintf(stderr, "ERROR: GetProcAddressn");
goto _Exit;
}
int (__stdcall *ffree) (VOID);
ffree = (int (WINAPI *)(VOID))
GetProcAddress(h, "CallUnhookWindowsHookEx");
if(ffree == NULL){
fprintf(stderr, "ERROR: GetProcAddressn");
goto _Exit;
}
if(fcall()){
fprintf(stderr, "ERROR: CallSetWindowsHookExn");
goto _Exit;
}
printf("Call SetWindowsHookExn");
getchar();
if(ffree()){
fprintf(stderr, "ERROR: CallUnhookWindowsHookExn");
goto _Exit;
}
printf("Call UnhookWindowsHookExn");
_Exit:
FreeLibrary(h);
return 0;
}
运行
setwindowshook.exe loging.dll
在 C:Users 用户名 AppDataLocalTemploging.log 可以查看加载 DLL 的日志
2、将 DLL 路径配置到注册表的 AppInit_DLLs 项
如果我们将 DLL 的路径配置在注册表的 AppInit_DLLs 项(位置见下图)中,就可以在系统启动时将任意 DLL 加载到其他进程中
writeappinit.cpp (如下)可以向注册表的 AppInit_DLLs 项写入任意值
因此我们可以指定 loging.dll 的路径并运行这个程序
// writeappinit.cpp
int _tmain(int argc, _TCHAR* argv[])
{
if(argc < 2){
fprintf(stderr, "%s <DLL Name>n", argv[0]);
return 1;
}
HKEY hKey;
LSTATUS lResult = RegOpenKeyEx(HKEY_LOCAL_MACHINE,
"SOFTWARE\Microsoft\Windows NT\CurrentVersion\Windows",
NULL, KEY_ALL_ACCESS, &hKey);
if(lResult != ERROR_SUCCESS){
printf("Error: RegOpenKeyEx failed.n");
return -1;
}
DWORD dwSize, dwType;
TCHAR szDllName[256];
RegQueryValueEx(hKey, "AppInit_DLLs", NULL, &dwType, NULL, &dwSize);
RegQueryValueEx(hKey, "AppInit_DLLs", NULL, &dwType, (LPBYTE)szDllName, &dwSize);
printf("AppInit_DLLs: %s -> ", szDllName);
lstrcpy(szDllName, argv[1]);
lResult = RegSetValueEx(hKey, "AppInit_DLLs",
0, REG_SZ, (PBYTE)szDllName, lstrlen(szDllName) + 1);
if(lResult != ERROR_SUCCESS){
printf("Error: RegSetValueEx failed.n");
}
RegQueryValueEx(hKey, "AppInit_DLLs", NULL, &dwType, NULL, &dwSize);
RegQueryValueEx(hKey, "AppInit_DLLs", NULL, &dwType, (LPBYTE)szDllName, &dwSize);
printf("%sn", szDllName);
RegCloseKey(hKey);
return 0;
}
运行
writeappinit.exe "loging.dll"
用OD打开任意一个程序,能在模块列表中看到 loging.dll
3、通过 CreateRemoteThread 在其他进程中创建线程
CreateRemoteThread 这个 API 函数可以在其他进程中创建线程,然后在新线程中运行 LoadLibrary,从而使得其他进程强制加载某个 DLL,其结构见下:
HANDLE CreateRemoteThread(
HANDLE hProcess, // 进程句柄
LPSECURITY_ATTRIBUTES lpThreadAttributes,
DWORD dwStackSize, // 栈初始长度(字节数)
LPTHREAD_START_ROUTINE lpStartAddress,
LPVOID lpParameter, // 新线程的参数指针
DWORD dwCreationFlags, // 创建标志
LPDWORD lpThreadId // 分配的线程ID指针
);
要注意的是,LoadLibrary 的参数必须位于目标进程内部,因此,LoadLibrary 所需要的参数字符串必须事先写入目标进程的内存空间中
书中给了 injectcode.h 和 injection.cpp如下:
// injectcode.h
//按照可执行文件名找到相应的进程并注入 DLL
int InjectDLLtoProcessFromName(TCHAR *szTarget, TCHAR *szDllPath);
//按照进程 ID 找到相应的进程并注入 DLL
int InjectDLLtoProcessFromPid(DWORD dwPid, TCHAR *szDllPath);
//创建新进程并注入 DLL
int InjectDLLtoNewProcess(TCHAR *szCommandLine, TCHAR *szDllPath);
// injectcode.cpp
DWORD GetProcessIdFromName(TCHAR *szTargetProcessName)
{
HANDLE hSnap = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
if(hSnap == INVALID_HANDLE_VALUE)
return 0;
PROCESSENTRY32 pe;
pe.dwSize = sizeof(pe);
DWORD dwProcessId = 0;
BOOL bResult = Process32First(hSnap, &pe);
while(bResult){
if(!lstrcmp(pe.szExeFile, szTargetProcessName)){
dwProcessId = pe.th32ProcessID;
break;
}
bResult = Process32Next(hSnap, &pe);
}
CloseHandle(hSnap);
return dwProcessId;
}
int InjectDLL(HANDLE hProcess, TCHAR *szDllPath)
{
int szDllPathLen = lstrlen(szDllPath) + 1;
PWSTR RemoteProcessMemory = (PWSTR)VirtualAllocEx(hProcess,
NULL, szDllPathLen, MEM_RESERVE|MEM_COMMIT, PAGE_READWRITE);
if(RemoteProcessMemory == NULL)
return -1;
BOOL bRet = WriteProcessMemory(hProcess,
RemoteProcessMemory, (PVOID)szDllPath, szDllPathLen, NULL);
if(bRet == FALSE)
return -1;
PTHREAD_START_ROUTINE pfnThreadRtn;
pfnThreadRtn = (PTHREAD_START_ROUTINE)GetProcAddress(
GetModuleHandle("kernel32"), "LoadLibraryA");
if(pfnThreadRtn == NULL)
return -1;
HANDLE hThread = CreateRemoteThread(hProcess, NULL, 0,
pfnThreadRtn, RemoteProcessMemory, 0, NULL);
if(hThread == NULL)
return -1;
WaitForSingleObject(hThread, INFINITE);
VirtualFreeEx(hProcess,
RemoteProcessMemory, szDllPathLen, MEM_RELEASE);
CloseHandle(hThread);
return 0;
}
int InjectDLLtoExistedProcess(DWORD dwPid, TCHAR *szDllPath)
{
HANDLE hProcess = OpenProcess(
PROCESS_CREATE_THREAD | PROCESS_VM_READ | PROCESS_VM_WRITE |
PROCESS_VM_OPERATION | PROCESS_QUERY_INFORMATION , FALSE, dwPid);
if(hProcess == NULL)
return -1;
/*
BOOL bJudgeWow64;
IsWow64Process(hProcess, &bJudgeWow64);
if(bJudgeWow64 == FALSE){
CloseHandle(hProcess);
return -1;
}
*/
if(InjectDLL(hProcess, szDllPath))
return -1;
CloseHandle(hProcess);
return 0;
}
int InjectDLLtoProcessFromName(TCHAR *szTarget, TCHAR *szDllPath)
{
DWORD dwPid = GetProcessIdFromName(szTarget);
if(dwPid == 0)
return -1;
if(InjectDLLtoExistedProcess(dwPid, szDllPath))
return -1;
return 0;
}
int InjectDLLtoProcessFromPid(DWORD dwPid, TCHAR *szDllPath)
{
if(InjectDLLtoExistedProcess(dwPid, szDllPath))
return -1;
return 0;
}
int InjectDLLtoNewProcess(TCHAR *szCommandLine, TCHAR *szDllPath)
{
STARTUPINFO si;
PROCESS_INFORMATION pi;
ZeroMemory(&si, sizeof(STARTUPINFO));
si.cb = sizeof(STARTUPINFO);
BOOL bResult = CreateProcess(NULL, szCommandLine, NULL, NULL,
FALSE, CREATE_SUSPENDED, NULL, NULL, &si, &pi);
if(bResult == FALSE)
return -1;
int nRet = -1;
/*
BOOL bJudgeWow64;
IsWow64Process(pi.hProcess, &bJudgeWow64);
if(bJudgeWow64 == FALSE)
goto _Exit;
*/
if(InjectDLL(pi.hProcess, szDllPath))
goto _Exit;
nRet = 0;
_Exit:
ResumeThread(pi.hThread);
CloseHandle(pi.hThread);
CloseHandle(pi.hProcess);
return nRet;
}
4、CreateRemoteThread 的拓展
对上一小节的拓展:只要我们能够将任意函数(代码)事先复制到目标进程内部,就可以用 CreateRemoteThread 来运行它
一个对 IE(32 位版本)注入 func 函数的例子
// codeinjection.cpp
typedef HWND (WINAPI *GETFORGROUNDWINDOW)(void);
typedef int (WINAPI *MSGBOX)(HWND, PCTSTR, PCTSTR, UINT);
typedef struct _injectdata {
TCHAR szTitle[32];
TCHAR szMessage[32];
HANDLE hProcess;
PDWORD pdwCodeRemote;
PDWORD pdwDataRemote;
MSGBOX fnMessageBox;
GETFORGROUNDWINDOW fnGetForegroundWindow;
} INJECTDATA, *PINJECTDATA;
static DWORD WINAPI func(PINJECTDATA myAPI)
{
myAPI->fnMessageBox((HWND)myAPI->fnGetForegroundWindow(),
myAPI->szMessage, myAPI->szTitle, MB_OK);
/*
if(myAPI->pCodeRemote != NULL)
VirtualFreeEx(myAPI->hProcess,
myAPI->pCodeRemote, 0, MEM_RELEASE);
if(myAPI->pDataRemote != NULL)
VirtualFreeEx(myAPI->hProcess,
myAPI->pDataRemote, 0, MEM_RELEASE);
*/
return 0;
}
int _tmain(int argc, _TCHAR* argv[])
{
HMODULE h = LoadLibrary("user32.dll");
if(h == NULL){
printf("ERR: LoadLibraryn");
return -1;
}
INJECTDATA id;
id.fnGetForegroundWindow = (GETFORGROUNDWINDOW)
GetProcAddress(
GetModuleHandle("user32"), "GetForegroundWindow");
id.fnMessageBox = (MSGBOX)
GetProcAddress(
GetModuleHandle("user32"), "MessageBoxA");
lstrcpy(id.szTitle, "Message");
lstrcpy(id.szMessage, "Hello World!");
HWND hTarget = FindWindow("IEFrame", NULL);
if(hTarget == NULL){
printf("ERR: FindWindown");
goto _END1;
}
DWORD dwPID; // PID of iexplore.exe
GetWindowThreadProcessId(hTarget, (DWORD *)&dwPID);
id.hProcess = OpenProcess(PROCESS_CREATE_THREAD |
PROCESS_QUERY_INFORMATION | PROCESS_VM_OPERATION |
PROCESS_VM_WRITE | PROCESS_VM_READ, FALSE, dwPID);
if(id.hProcess == NULL){
printf("ERR: OpenProcessn");
goto _END1;
}
DWORD dwLen;
if((id.pdwCodeRemote = (PDWORD)VirtualAllocEx(id.hProcess,
0, 4096, MEM_COMMIT, PAGE_EXECUTE_READWRITE)) == NULL)
{
printf("ERR: VirtualAllocEx(pdwCodeRemote)n");
goto _END2;
}
if((id.pdwDataRemote = (PDWORD)VirtualAllocEx(id.hProcess,
0, 4096, MEM_COMMIT, PAGE_EXECUTE_READWRITE)) == NULL)
{
printf("ERR: VirtualAllocEx(pdwDataRemote)n");
goto _END3;
}
WriteProcessMemory(id.hProcess,
id.pdwCodeRemote, &func, 4096, &dwLen);
WriteProcessMemory(id.hProcess,
id.pdwDataRemote, &id, sizeof(INJECTDATA), &dwLen);
HANDLE hThread = CreateRemoteThread(id.hProcess, NULL, 0,
(LPTHREAD_START_ROUTINE)id.pdwCodeRemote, id.pdwDataRemote,
0, &dwLen);
if(hThread == NULL){
printf("ERR: CreateRemoteThreadn");
goto _END4;
}
WaitForSingleObject(hThread, INFINITE);
GetExitCodeThread(hThread, (PDWORD)&dwPID);
CloseHandle(hThread);
_END4:
VirtualFreeEx(id.hProcess, id.pdwDataRemote, 0, MEM_RELEASE);
_END3:
VirtualFreeEx(id.hProcess, id.pdwCodeRemote, 0, MEM_RELEASE);
_END2:
CloseHandle(id.hProcess);
_END1:
FreeLibrary(h);
return 0;
}
三、API 钩子
1、概念
定义:
-
钩子:在程序中插入额外的逻辑
-
API 钩子:对 API 插入额外逻辑
API 钩子大体上可分为两种类型:
-
改写目标函数开头几个字节
-
改写导入地址表(Import Address Table,IAT)
2、用 Detours 实现一个简单的 API 钩子
本小节利用微软研究院发布的 API 钩子库 Detours :http://research.microsoft.com/en-us/projects/detours/
只要我们知道 DLL 所导出的函数,就可以在运行时对该函数的调用进行劫持
书中给了 detourshook.h 和 dllmain.cpp 如下:
//detourshook.h
DETOURSHOOK_API int WINAPI HookedMessageBoxA(HWND hWnd,
LPCTSTR lpText, LPCTSTR lpCaption, UINT uType);
// dllmain.cpp
//将 user32.dll 导出的函数 MessageBoxA 替换成 HookedMessageBoxA
static int (WINAPI * TrueMessageBoxA)(HWND hWnd, LPCTSTR lpText,
LPCTSTR lpCaption, UINT uType) = MessageBoxA;
DETOURSHOOK_API int WINAPI HookedMessageBoxA(HWND hWnd,
LPCTSTR lpText, LPCTSTR lpCaption, UINT uType)
{
int nRet = TrueMessageBoxA(hWnd, lpText, "Hooked Message", uType);
return nRet;
}
int DllProcessAttach(VOID)
{
DetourRestoreAfterWith();
DetourTransactionBegin();
DetourUpdateThread(GetCurrentThread());
DetourAttach(&(PVOID&)TrueMessageBoxA, HookedMessageBoxA);
if(DetourTransactionCommit() == NO_ERROR)
return -1;
return 0;
}
int DllProcessDetach(VOID)
{
DetourTransactionBegin();
DetourUpdateThread(GetCurrentThread());
DetourDetach(&(PVOID&)TrueMessageBoxA, HookedMessageBoxA);
DetourTransactionCommit();
return 0;
}
BOOL APIENTRY DllMain( HMODULE hModule,
DWORD ul_reason_for_call,
LPVOID lpReserved
)
{
switch (ul_reason_for_call)
{
case DLL_PROCESS_ATTACH:
DllProcessAttach(); //DllProcessAttach 用于挂载钩子,当 DLL 被加载到进程中时,API 钩子就开始生效了
break;
case DLL_THREAD_ATTACH:
break;
case DLL_THREAD_DETACH:
break;
case DLL_PROCESS_DETACH:
DllProcessDetach();
break;
}
return TRUE;
}
为了确认 HookedMessageBoxA 确实被调用 过,我们可以将消息框的标题栏改为“Hooked Message”,一个简单测试如下:
// helloworld.cpp
int _tmain(int argc, _TCHAR* argv[])
{
HMODULE h = LoadLibrary("detourshook.dll");
MessageBoxA(GetForegroundWindow(),
"Hello World! using MessageBoxA", "Message", MB_OK);
FreeLibrary(h);
return 0;
}
结语
简单通过一些例子了解学习了调试器、DLL注入和API钩子
不过仅仅是了解,还需深入学习
原文始发于微信公众号(红客突击队):自由控制程序运行方式的编程技巧
- 左青龙
- 微信扫一扫
-
- 右白虎
- 微信扫一扫
-
评论