0
点赞
收藏
分享

微信扫一扫

《有趣的二进制:软件安全与逆向分析》读书笔记(四):自由控制程序运行方式的编程技巧

目录

前言

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

一、调试器

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

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_EVENT\n");
            break;
        case CREATE_THREAD_DEBUG_EVENT:
            printf("CREATE_THREAD_DEBUG_EVENT\n");
            break;
        case EXIT_THREAD_DEBUG_EVENT:
            printf("EXIT_THREAD_DEBUG_EVENT\n");
            break;
        case EXIT_PROCESS_DEBUG_EVENT:
            printf("EXIT_PROCESS_DEBUG_EVENT\n");
            break;
        case EXCEPTION_DEBUG_EVENT:
            if(de.u.Exception.ExceptionRecord.ExceptionCode != 
				EXCEPTION_BREAKPOINT)
			{
                dwContinueStatus = DBG_EXCEPTION_NOT_HANDLED;
			}
            printf("EXCEPTION_DEBUG_EVENT\n");
            break;
        case OUTPUT_DEBUG_STRING_EVENT:
            printf("OUTPUT_DEBUG_STRING_EVENT\n");
            break;
        case RIP_EVENT:
            printf("RIP_EVENT\n");
            break;
        case LOAD_DLL_DEBUG_EVENT:
            printf("LOAD_DLL_DEBUG_EVENT\n");
            break;
        case UNLOAD_DLL_DEBUG_EVENT:
            printf("UNLOAD_DLL_DEBUG_EVENT\n");
            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_PROCESSDEBUG_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 : 定义命令行应用程序入口点

#include "stdafx.h"

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

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

//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] = '\0';

	printf("Exception: %08x (PID:%d, TID:%d)\n", 
		pde->u.Exception.ExceptionRecord.ExceptionAddress,
		pde->dwProcessId, pde->dwThreadId);
	printf("  %08x: %s\n", ctx.Eip, asm_string);
	printf("    Reg: EAX=%08x ECX=%08x EDX=%08x EBX=%08x\n", 
		ctx.Eax, ctx.Ecx, ctx.Edx, ctx.Ebx);
	printf("         ESI=%08x EDI=%08x ESP=%08x EBP=%08x\n", 
		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

#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);

// 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

// 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_ATTACH\n");
		break;
	case DLL_THREAD_ATTACH:
		break;
	case DLL_THREAD_DETACH:
		break;
	case DLL_PROCESS_DETACH:
		WriteLog("DLL_PROCESS_DETACH\n");
		break;
	}
	return TRUE;
}


将 dllmain.cpp、loging.cpp 和 loging.h 进行编译得到 loging.dll
然后再利用 CallSetWindowsHookEx 加载这个DLL,见 setwindowshook.cpp:

// 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: GetProcAddress\n");
		goto _Exit;
	}

	int (__stdcall *ffree) (VOID);
	ffree = (int (WINAPI *)(VOID))
		GetProcAddress(h, "CallUnhookWindowsHookEx");
	if(ffree == NULL){
		fprintf(stderr, "ERROR: GetProcAddress\n");
		goto _Exit;
	}

	if(fcall()){
		fprintf(stderr, "ERROR: CallSetWindowsHookEx\n");
		goto _Exit;
	}
	printf("Call SetWindowsHookEx\n");

	getchar();

	if(ffree()){
		fprintf(stderr, "ERROR: CallUnhookWindowsHookEx\n");
		goto _Exit;
	}
	printf("Call UnhookWindowsHookEx\n");

_Exit:
	FreeLibrary(h);
	return 0;
}


运行

setwindowshook.exe loging.dll

C:\Users\ 用户名 \AppData\Local\Temp\loging.log 可以查看加载 DLL 的日志

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

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

在这里插入图片描述
writeappinit.cpp (如下)可以向注册表的 AppInit_DLLs 项写入任意值
因此我们可以指定 loging.dll 的路径并运行这个程序

// 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("%s\n", 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

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

// 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: LoadLibrary\n");
		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: FindWindow\n");
		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: OpenProcess\n");
		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: CreateRemoteThread\n");
		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

#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);

// dllmain.cpp

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

//将 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

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

举报

相关推荐

0 条评论