有趣的二进制-自由控制程序运行方式的编程技巧

通过自制调试器来理解其原理

  1. 先来查看一段简单的调试器代码
#include "stdafx.h"
#include 
int _tmain(int argc, _TCHAR* argv[])
{
    PROCESS_INFORMATION pi;
    STARTUPINFO si;
    
    if(argc < 2){
        fprintf(stderr, "C:\\>%s \n", argv[0]);
        return 1;
    }

    memset(&pi, 0, sizeof(pi));
    memset(&si, 0, sizeof(si));
    si.cb = sizeof(STARTUPINFO);
	//1
    BOOL r = CreateProcess(
        NULL, argv[1], NULL, NULL, FALSE, 
        NORMAL_PRIORITY_CLASS | CREATE_SUSPENDED | DEBUG_PROCESS,
        NULL, NULL, &si, &pi);
    if(!r)
        return -1;
	//2
    ResumeThread(pi.hThread);

    while(1) {
        DEBUG_EVENT de;
        if(!WaitForDebugEvent(&de, INFINITE))
            break;
        
        DWORD dwContinueStatus = DBG_CONTINUE;
        
        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(
            de.dwProcessId, de.dwThreadId, dwContinueStatus);
    }

    CloseHandle(pi.hThread);
    CloseHandle(pi.hProcess);
    return 0;
}
  • 程序通过CreateProcess函数启动调试目标进程,调试目标进程也叫调试对象或者调试程序。
  • 调用CreateProcess时,如果设置了DEBUG_PROCESS或DEBUG_ONLY_THIS_PROCESS标志,则启动的进程中所产生的异常都会被调试器捕捉到。
  • 上述两个标志的区别:

DEBUG_PROCESS标识:调试对象所产生的子进程,以及子进程的子进程都作为调试对象
DEBUG_ONLY_THIS_PROCESS:启动的那一个进程作为调试对象

  • CreatProcess函数的第1个参数或者第2个参数可用于传递目标程序的路径,然后便可启动进程。
  • CreateProcess函数:
    有趣的二进制-自由控制程序运行方式的编程技巧_第1张图片
  • 通过CREATE_SUSPENDED标志可以让进程在启动后进入挂起状态。
  • 当设置这一标志时,CreateProcess函数调用完成之后,新进程中的所有线程都会暂停,尽管程序没有运行,但程序的可执行文件已经被载入内存,这是我们可以在运行之前对调试对象的数据进行改写。
  • 示例程序中,没有进行任何操作,而是直接调用了ResumeThread这个函数,调试对象的所有线程就会恢复运行。
  • ResumeThread函数
    有趣的二进制-自由控制程序运行方式的编程技巧_第2张图片
  • 当调试对象程序开始运行后,调试器就开始等待捕捉异常,调试事件会通过WaitForDebugEvent函数来进行接收。
  • WaitForDebugEvent函数
    有趣的二进制-自由控制程序运行方式的编程技巧_第3张图片
  • WaitForDebugEvent函数的第1个参数传递了一个DEBUG_EVENT结构体,捕捉到的调试事件会被存放在这个结构体中。第2个参数dwMilliseconds如果设置为INFINITE则表示一直等待。
  • 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 UnloadDll;
    OUTPUT_DEBUG_STRING_INFO DebugString;
    RIP_INFO RipInfo; } u; 
 } DEBUG_EVENT, *LPDEBUG_EVENT;
  • 其中第一个成员dwDebugEventCode代表调试事件编号。
  • dwProcessId为进程ID,dwThreadId为线程ID
  • 接下来的数据会随着dwDebugEventCode的不同而发生变化,dwDebugEventCode可以取下列值:
    有趣的二进制-自由控制程序运行方式的编程技巧_第4张图片
    有趣的二进制-自由控制程序运行方式的编程技巧_第5张图片
  • 示例程序中,当接收到调试事件时,会使用printf函数将事件的内容显示出来,通过访问union定义的结构体就可以获取调试对象的信息。
  • 当处理被交给调试器时,调试对象会暂停运行,因此,在我们的调试器显示消息的过程中,调试对象是处于暂停状态的。
  • 调用ContinueDebugEvent函数可以让调试对象恢复运行,这是调试器又回到WaitForDebugEvent函数等待下一条调试事件。
  • 运行示例
C:\>wdbg01a.exe "C:\Program Files\Internet Explorer\iexplore.exe"

有趣的二进制-自由控制程序运行方式的编程技巧_第6张图片

  • 可以看到,创建进程、线程以及加载、卸载DLL等事件都被调试器捕捉到了。
  1. 实现反汇编功能
  • 希望在发生异常时,能够显示异常的地址以及当前寄存器的值,同时,我们还希望显示发生异常所执行的指令,因此我们来实现反汇编功能。
  • wdbg02a.cpp
#include "stdafx.h"
#include 
#include "udis86.h"
#pragma comment(lib, "libudis86.lib")
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);
}
int exception_debug_event(DEBUG_EVENT *pde)
{
	DWORD dwReadBytes;

	HANDLE ph = OpenProcess(
		PROCESS_VM_WRITE | PROCESS_VM_READ | PROCESS_VM_OPERATION, 
		FALSE, pde->dwProcessId);
	if(!ph)
		return -1;

	HANDLE th = OpenThread(THREAD_GET_CONTEXT | THREAD_SET_CONTEXT, 
		FALSE, pde->dwThreadId);
	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 \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;
}
  • disas函数负责对机器语言进行反汇编,这里使用了udis86的功能。
  • exception_debug_event函数会在发生异常时运行,其中调用了下列函数:

OpenProcess
ReadProceeMemory
OpenThread
GetThreadContext
SetThreadContext

  • 上面这些函数,再加上WriteProcessMemory函数,就是用于访问其他进程的必备工具包。
  • 在Windows中,即使我们的程序不是作为调试器挂载在目标进程上,只要能够获取目标进程的句柄,就可以随意读写该进程的内存空间,当然如果用户没有相应的权限,调用OpenProcess会失败,但是只要能够通过其他方法获取进程句柄,也可以自由读写该进程的内存空间。
  • OpenProcess函数
HANDLE OpenProcess(DWORD dwDesiredAcess,BOOL bInheritHandle,DWORD dwProcessId)
//params:访问标志,句柄继承选项,进程ID
  • 在exception_debug_event函数中,为了获取发生异常时所执行的指令,我们需要使用ReadProcessMemory函数:
BOOL ReadProcessMemory(HANDLE hProcess,LPCVOID lpBaseAddress,LPVOID lpBUffer,DWORD nSize,LPDWORD lpNumberOFBytesRead)
//pamars:进程句柄,读取起始地址,用于存放数据的缓冲区,要读取的字节数,实际读取的字节数
  • WriteProcessMemory函数
BOOL WriteProcessMemory(HANDLE hProcess,LPCVOID lpBaseAddress,LPVOID lpBUffer,DWORD nSize,LPDWORD lpNumberOFBytesRead)
//params:进程句柄,写入起始地址,数据缓冲区,要写入的字节数,实际写入的字节数
  • 用OpenThread打开线程后,可以通过GetThreadContext和SeatThreadContext来读写寄存器。
  • OpenThread函数
HANDLE OpenThread(DWORD dwDesiredAccess,BOOL bInheritHandle,DWORD dwThreadId);
//params:访问标志,句柄继承选项,线程ID
  • GetThreadContext函数:
BOOL GetThreadContext(HANDLE hThread,LPCONTEXT lpContext);
//params:拥有上下文的线程句柄,接受上下文的结构体地址
  • SetThreadContext
BOOL SetThreadContext(HANDLE hThread,CONST CONTEXT *lpContext);
//params:拥有上下文的线程句柄,存放上下文的结构体地址
  • 使用这些API函数就可以随意干预其他进程
  1. 运行改良版的调试器
  • 运行一下wdbg02a.exe,准备一个会发生异常的程序test.exe,然后将这个程序作为参数来运行wdbg02a.exe。
  • test.cpp
int main(int argc, char *argv[])
 { 
 char *s = NULL; 
 *s = 0xFF; return 0;
  }

有趣的二进制-自由控制程序运行方式的编程技巧_第7张图片

  • 可以看到mov byte[eax],0xff的地方发生了2个异常,这里对应源代码中的*s=0xFF。

在其他进程中运行任意代码:代码注入

  1. 在其他进程中运行任意代码的手法,统称为代码注入(code injection)。在使用DLL的情况下,一般叫做“DLL注入”,但“在其他进程中运行自己的代码”这一点是共同的。
  2. 用SetWindowsHookEx劫持系统消息
  • 使用下面三个API函数,我们就可以劫持系统消息:(可以用于单个线程,也可以用于进程)

SetWindowsHookEx

HHOOK SetWindowsHookEx(int idHook,HOOKPORC lpfn,HINSTANCE hMod,DWORD dwThreadId);
//params:钩子类型,钩子过程,应用程序示例的句柄,线程ID

CallNextHookEx

LRESULT CallNextHookEx(HHOOK hhk,int nCode,WPARAM wParam,LPARAM lParam);
//当前钩子的句柄,传递给钩子过程的代码,传递给钩子过程的值,传递给钩子过程的值

UnhookWindowsHookEx

BOOL UnhookWindowsHookEx(HHOOK hhk);
//要解除的对象的钩子过程句柄
  • 试一下SetWindowsHookEx

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来进行处理
static LRESULT WINAPI GetMsgProc(int code, WPARAM wParam, LPARAM lParam)
{
	return(CallNextHookEx(NULL, code, wParam, lParam));
	//调用了CallNextHookEx函数,这时消息会继续传递给下一个钩子过程
}
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);
	//SetWindowsHookEx的功能是将原来传递给窗口过程的消息劫持下来,交给的第2个参数指定的函数来进行处理
	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;
}
  • 这些API是用来劫持消息的,但如果要劫持其他进程的窗口过程消息,那么就需要再其他进程中加载我们的DLL。
  • 可以将loging.cpp编译成DLL,然后调用SetWindowsHookEx,将其第4参数(dwThreadId)设为0,这样,我们就可以对持有窗口过程的进程和线程应用钩子,也就是让这些进程加载我们的DLL。
  • dllmain.cpp
#include "stdafx.h"
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中添加一些代码,使得在DLL成功加载之后,向%TEMP%目录输出一个名为loging.log的日志文件,日志的内容包括进程ID和模块路径。
  • 将dllmaincpp、loging.cpp和loging.h进程编译,然后我们编写另一个程序,加载这个DLL并调用CallSetWindowsHookEx。
  • setwindowshook.cpp
#include "stdafx.h"
#include 


int _tmain(int argc, _TCHAR* argv[])
{
	if(argc < 2){
		fprintf(stderr, "%s \n", argv[0]);
		return 1;
	}
	//从命令行传入要载入的DLL
	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;
}
  • 运行示例
    在这里插入图片描述
  • 打开文件C:\Users\ 用户名 \AppData\Local\Temp\loging.log
    有趣的二进制-自由控制程序运行方式的编程技巧_第8张图片
  • 可以看到其他进程以及加载了loging.dll
  1. 将DLL路径配置到注册表的AppInit_DLLS项
  • SetWindowsHookEx可以在调用时将DLL映射到其他进程中,不过如果我们将DLL的路径配置在注册表的APPInit_DLLS项中,就可以在系统启动时将任意DLL加载到其他进程中。
  • 运行regedit,找到下面的路径
    有趣的二进制-自由控制程序运行方式的编程技巧_第9张图片
  • Windows7中多了一个叫做RequireSignedAppInit_DLLs的项,这一项代表只允许加载经过签名的DLL。
  • 在64位系统中,关于32位程序的相关设定已被重定向到Wow6432Node中。
    有趣的二进制-自由控制程序运行方式的编程技巧_第10张图片
  • AppInit_DLLs中所配置的DLL是通过user32.dll来加载的,因此,对于原本就不依赖user32.dll的进程来说,这一配置是无效的。
  • writeappinit.cpp
#include "stdafx.h"
#include 


int _tmain(int argc, _TCHAR* argv[])
{
	if(argc < 2){
		fprintf(stderr, "%s \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.cpp可以向注册表的LoadAppInit_DLLs项写入任意值,可以指定loging.dll的路径并运行这个程序。
  • 凡是加载了user32.dll的进程,同时也会加载loging.dll。
  1. 通过CreateRomoteThread在其他进程中创建线程
  • 可以用CreateRomoteThread这个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
int InjectDLLtoProcessFromName(TCHAR *szTarget, TCHAR *szDllPath);
//按照可执行文件名找到相应的进程并注入DLL
int InjectDLLtoProcessFromPid(DWORD dwPid, TCHAR *szDllPath);
//按照进程ID找到相应的进程并注入DLL
int InjectDLLtoNewProcess(TCHAR *szCommandLine, TCHAR *szDllPath);
//创建新的进程并注入DLL
  • dllinjection.cpp
#include "stdafx.h"
#include 
#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;
}

  • 运行示例
C:\>dllinjection.exe Name iexplore.exe "C:\\sampledll.dll"
  • 在iexplore.exe中加载C:\sampledll.dll。(sampledll.dll只是显示一条对话框消息),sampledll.dll是一个能够显示DLL加载/卸载状态消息的程序,dllinjection.exe 运行时以及 IE 关闭时都会弹出相应的消息框。
  1. 注入函数
  • CreateRomoteThread调用了LoadLibrary,当然,不仅仅是DLL,只要我们能够将任意函数(代码)事先复制到目标进程内部,就可以用CreateRemoteThread来运行它。
#include "stdafx.h"

#include 


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;
}
  • 运行codeinjection.exe时,就会将func函数注入到IE中并运行它,func函数的功能时显示一个Hello World!消息框。
  • 在Windows中,只要拥有足够的权限,就可以随意访问其他进程的内存空间,因此我们基本上可以自由地向其他进程注入代码,而且即便我们的程序不是调试器,也可以比较容易地骗过其他进程。

API钩子

  1. 在程序中插入额外的逻辑称为钩子,而其中对API插入额外逻辑称为“API钩子”。
  2. API钩子大体上可分为两种类型:
  • 改写目标函数开头几个字节
  • 改写IAT(Import Address Table,导入地址表)
  1. 用Detours实现一个简单的API钩子
  • 使用Detours库我们用几十行就可以实现一个API钩子,只要我们知道DLL所导出的函数,就可以在运行时对该函数的调用进行劫持。
  • 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"
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();
		break;
	case DLL_THREAD_ATTACH:
		break;
	case DLL_THREAD_DETACH:
		break;
	case DLL_PROCESS_DETACH:
		DllProcessDetach();
		break;
	}
	return TRUE;
}
  • 上面代码可以将user32.dll导出的函数MessageBoxA替换成HookedMessageBoxA,将下列文件添加到工程并编译:

detours.cpp
detours.h
disasm.cpp
modules.cpp
detver.h

  • 当DllMain收到DLL_PROCESS_ATTACH消息时,会调用DllProcessAttach()函数,也就是说,当DLL被加载到进程中时,API钩子就开始生效了。
  • DllProcessAttach用于挂载钩子,DllProcessDetach用于解除钩子,在函数内部,会先调用DetourTransactionBegin和DetourUpdateThread,然后再用DetourAttach或DetourDetach来挂载或解除钩子。
  • 最后程序调用DetourTransctionCommint函数并推出。
  1. 修改消息框的标题栏
  • HookedMessageBoxA函数的内部会调用TureMessageBoxA,也就是原始的MessageBoxA函数。
  • 按下面的代码编写一段简单的程序并运行:
#include "stdafx.h"
#include 
int _tmain(int argc, _TCHAR* argv[])
{
	HMODULE h = LoadLibrary("detourshook.dll");
	MessageBoxA(GetForegroundWindow(), 
		"Hello World! using MessageBoxA", "Message", MB_OK);
	FreeLibrary(h);
	return 0;
}
  • 运行
    有趣的二进制-自由控制程序运行方式的编程技巧_第11张图片
  • 标题栏从Message变成了Hooked Message
  • 钩子的原理时将函数开头的几个字节替换成jmp指令,强制跳转到另一个函数。
  • 上面讲到的API钩子基本上只适用于运行在用户领域的DLL所导致的函数,但我们也可以通过劫持非公开的API等方式,对运行在内核领域(Ring0)的驱动程序挂载钩子。

你可能感兴趣的:(有趣的二进制)