自由控制程序运行方式的编程技巧

admin 2022年6月19日01:00:58评论4 views字数 18304阅读61分0秒阅读模式



自由控制程序运行方式的编程技巧


前言

本篇继续阅读学习《有趣的二进制:软件安全与逆向分析》,本章是自由控制程序运行方式的编程技巧,主要介绍调试器的原理、代码注入和API钩子

一、调试器

本节给出了一个简单的调试器源码,通过实践来学习一些基本知识

1、调试器是怎样工作的

一段最简单的调试器代码如下:

  1. // wdbg01a.cpp : 定义命令行应用程序入口点

    #include "stdafx.h"

    #include <Windows.h>

    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 函数如下:

  1. 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 结构体如下:

  1. 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、实现反汇编功能

本小节添加反汇编功能,希望能实现一下功能:

  •     显示出发生异常的地址以及当前寄存器的值

  •     显示发生异常时所执行的指令

  1. // wdbg02a.cpp : 定义命令行应用程序入口点

    #include "stdafx.h"

    #include <Windows.h>#include "udis86.h" // udis86 是一个开源的反汇编器 https://github.com/vmt/udis86

    #pragma comment(lib, "libudis86.lib")

    //disas 函数负责对机器语言进行反汇编,使用了 udis86int 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、运行改良版调试器

进行一个测试


一个简单的会发生异常的程序如下:

  1. 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 函数:

  1. //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 如下:

  1. //loging.h

    #ifdef LOGING_EXPORTS#define LOGING_API extern "C" __declspec(dllexport)#else#define LOGING_API extern "C" __declspec(dllimport)#endif

    LOGING_API int CallSetWindowsHookEx(VOID);LOGING_API int CallUnhookWindowsHookEx(VOID);
  1. // loging.cpp

    #include "stdafx.h"#include "loging.h"



    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

  1. // dllmain.cpp#include "stdafx.h"//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:

  1. // setwindowshook.cpp

    #include "stdafx.h"#include <Windows.h>



    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;}

运行

  1. setwindowshook.exe loging.dll

在 C:Users 用户名 AppDataLocalTemploging.log 可以查看加载 DLL 的日志

2、将 DLL 路径配置到注册表的 AppInit_DLLs 项

如果我们将 DLL 的路径配置在注册表的 AppInit_DLLs 项(位置见下图)中,就可以在系统启动时将任意 DLL 加载到其他进程中

自由控制程序运行方式的编程技巧

writeappinit.cpp (如下)可以向注册表的 AppInit_DLLs 项写入任意值

因此我们可以指定 loging.dll 的路径并运行这个程序

  1. // writeappinit.cpp

    #include "stdafx.h"#include <Windows.h>



    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;}

运行

  1. writeappinit.exe "loging.dll"

用OD打开任意一个程序,能在模块列表中看到 loging.dll

3、通过 CreateRemoteThread 在其他进程中创建线程

CreateRemoteThread 这个 API 函数可以在其他进程中创建线程,然后在新线程中运行 LoadLibrary,从而使得其他进程强制加载某个 DLL,其结构见下:

  1. 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如下:

  1. // injectcode.h

    //按照可执行文件名找到相应的进程并注入 DLLint InjectDLLtoProcessFromName(TCHAR *szTarget, TCHAR *szDllPath);//按照进程 ID 找到相应的进程并注入 DLLint InjectDLLtoProcessFromPid(DWORD dwPid, TCHAR *szDllPath);//创建新进程并注入 DLLint InjectDLLtoNewProcess(TCHAR *szCommandLine, TCHAR *szDllPath);
  1. // injectcode.cpp

    #include "stdafx.h"#include <tlhelp32.h>#include "injectcode.h"



    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 函数的例子

  1. // codeinjection.cpp#include "stdafx.h"#include <windows.h>
    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 如下:

  1. //detourshook.h

    #ifdef DETOURSHOOK_EXPORTS#define DETOURSHOOK_API __declspec(dllexport)#else#define DETOURSHOOK_API __declspec(dllimport)#endif

    DETOURSHOOK_API int WINAPI HookedMessageBoxA(HWND hWnd, LPCTSTR lpText, LPCTSTR lpCaption, UINT uType);
  1. // dllmain.cpp

    #include "stdafx.h"#include "detours.h"#include "detourshook.h"

    //将 user32.dll 导出的函数 MessageBoxA 替换成 HookedMessageBoxAstatic 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”,一个简单测试如下:

  1. // helloworld.cpp

    #include "stdafx.h"#include <Windows.h>



    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钩子
不过仅仅是了解,还需深入学习





红客突击队于2019年由队长k龙牵头,联合国内多位顶尖高校研究生成立。其团队从成立至今多次参加国际网络安全竞赛并取得良好成绩,积累了丰富的竞赛经验。团队现有三十多位正式成员及若干预备人员,下属联合分队数支。红客突击队始终秉承先做人后技术的宗旨,旨在打造国际顶尖网络安全团队。




原文始发于微信公众号(红客突击队):自由控制程序运行方式的编程技巧

免责声明:文章中涉及的程序(方法)可能带有攻击性,仅供安全研究与教学之用,读者将其信息做其他用途,由读者承担全部法律及连带责任,本站不承担任何法律及连带责任;如有问题可邮件联系(建议使用企业邮箱或有效邮箱,避免邮件被拦截,联系方式见首页),望知悉。
  • 左青龙
  • 微信扫一扫
  • weinxin
  • 右白虎
  • 微信扫一扫
  • weinxin
admin
  • 本文由 发表于 2022年6月19日01:00:58
  • 转载请保留本文链接(CN-SEC中文网:感谢原作者辛苦付出):
                   自由控制程序运行方式的编程技巧https://cn-sec.com/archives/1127945.html
                  免责声明:文章中涉及的程序(方法)可能带有攻击性,仅供安全研究与教学之用,读者将其信息做其他用途,由读者承担全部法律及连带责任,本站不承担任何法律及连带责任;如有问题可邮件联系(建议使用企业邮箱或有效邮箱,避免邮件被拦截,联系方式见首页),望知悉.

发表评论

匿名网友 填写信息