跳至主要內容

远程线程注入

chanchaw大约 8 分钟cpp

调用宿主函数

概述

通过调用函数 CreateRemoteThread 在宿主程序中创建线程,在该线程中调用宿主程序的函数,流程是:

  1. 通过 FindWindow 获取窗口句柄
  2. 通过 GetWindowThreadProcessID 获取目标进程PID
  3. 通过 OpenProcess 获取目标进程句柄(拥有创建线程权限的句柄)
  4. 通过 CreateRemoteThread 调用远程 call,调用目标进程函数,需要用到前面获取的目标进程句柄

实现

// A05Injector.cpp : 此文件包含 "main" 函数。程序执行将在此处开始并结束。
// 2024年01月22日创建本程序,用于向项目 A05forInject 客户端注入代码调用1个参数的函数 add1

#include <iostream>
#include <Windows.h>

// 目标进程函数起始地址
int funAddr = 0x00FA1070;

int main()
{
	char windowClassName[] = "ConsoleWindowClass";
	char windowTitle[] = R"(D:\source\cpp\windowsapi\Release\A05forInject.exe)";
	HWND h = FindWindowA(windowClassName, windowTitle);// 获取窗口句柄
	DWORD pid = 0;// 目标进程PID=任务管理器中的PID
	GetWindowThreadProcessId(h, &pid);// 通过窗口句柄获取窗口进程PID
	HANDLE processHandle = OpenProcess(PROCESS_ALL_ACCESS, 0, pid);// 通过PID获取目标进程句柄
	printf("窗口句柄:%p,进程PID:%d,进程句柄:%p\r\n",h,pid,processHandle);

	/*
	 * 在目标进程的虚拟内存空间中申请内存,后续将裸汇编代码写入。如果申请内存失败则会返回 NULL
	 * 参数介绍:
	 * processHandle:要操作的目标进程句柄,要求该句柄有权限 PROCESS_VM_OPERATION,上面的 PROCESS_ALL_ACCESS 包含了该权限
	 * NULL:申请内存空间的起始地址,留空则由函数自动判断
	 * 4*1024:申请的内存大小
	 * MEM_COMMIT:申请的内存空间的提交类型
	 * 0x40:内存空间的可访问类型,此处表示可以读写
	 * return: 如果申请成功则返回申请空间的起始地址,失败则返回NULL
	 */
	//void *mem = VirtualAllocEx(processHandle, NULL, 4 * 1024, MEM_COMMIT, 0x40 /*PAGE_EXECUTE_READWRITE,表示这块内存区域可读写,可执行*/);
	void *mem = VirtualAllocEx(processHandle, NULL, 4 * 1024, MEM_COMMIT, PAGE_EXECUTE_READWRITE /*PAGE_EXECUTE_READWRITE=0x40,表示这块内存区域可读写,可执行*/);
	if (mem == NULL) {
		printf("在目标进程申请内存失败,无法注入代码!!!\r\n");
		return 0;

	}
	printf("已在目标进程申请内存空间:%p\r\n", mem);

	// 向上面申请的内存空间中注入代码
	//bool retWriteCode = WriteProcessMemory(processHandle, mem, injectedCode, 0x300, 0);
	//printf("向目标进程注入代码成功:%d\r\n", retWriteCode);

	// 调用上面注入的自定义代码,注意多次执行本程序会多次向目标进程注入代码,如果裸汇编(上面的等待被注入的代码injectedCode)
	// 代码有变动则注意要在 xdbg 中更换断点地址(ctrl+g 调出查找工具,选中汇编代码行后通过 F2 下断)
	// 如果是硬编码调用函数的地址(通过 CreateRemoteThread 创建远程线程进而调用目标进程指定内存地址的函数)
	// 第一次运行本程序时还没有得到有效地址,所以要注释下面代码,第二次运行开始硬编码上次注入的代码块的首地址
	// 参数介绍:
	// 1. 目标进程句柄(有创建线程权限的句柄)
	// 2. 创建的远程线程的安全属性,一般默认0
	// 3. 线程栈初始大小,以字节为单位,如果设置为0则采用系统默认大小
	// 4. 目标进程函数的起始地址(call 调用函数的起始地址)
	// 5. call调用函数的传入参数(目标进程函数的参数)这里只支持一个参数
	// 6. 0表示创建线程后立即运行,非0则会挂起线程(仅创建线程不执行)
	::CreateRemoteThread(processHandle, 0, 0, (LPTHREAD_START_ROUTINE)funAddr, 0, 0, 0);
	printf("已创建远程线程调用内存地址:%X\r\n", funAddr);

	// 调用结束后要释放进程句柄
	CloseHandle(processHandle);
	system("pause");
}

本文的前置知识见:注入器注入代码

注入代码

概述

在目标进程中申请一段内存并向其中注入自定义代码,为之后注入 dll 做技术准备

实现

创建控制台应用程序,测试通过的源码如下

// A027InjectCode.cpp : 此文件包含 "main" 函数。程序执行将在此处开始并结束。
//

#include <iostream>
#include <Windows.h>

// 准备目标进程的窗口类和窗口标题,通过 spy++ 获取
char windowClassName[] = "ConsoleWindowClass";
char windowTitle[] = "D:\\source\\cpp\\windowsapi\\Release\\A05forInject.exe";
char windowTitle01[] = "D:/source/cpp/windowsapi/Release/A05forInject.exe";
char windowTitle02[] = R"(D:\source\cpp\windowsapi\Release\A05forInject.exe)";

// 裸函数
__declspec(naked)int nakedFunc() {
	_asm {
		push 11 // 压栈 - 写入函数参数
		mov eax, 0x141070 // 保存目标进程函数地址,等待 call
		call eax // 调用目标进程函数
		add esp,4 // 平栈
		ret // 裸函数必须手动返回
	}
}

int main()
{
	// 使用窗口类、窗口标题获取窗口句柄
	HWND hwnd = FindWindowA(windowClassName, windowTitle02);// 获取窗口句柄	
	printf("窗口句柄:%p\r\n", hwnd);

	// 根据窗口句柄获取进程PID
	DWORD pid = 0;// 目标进程PID
	GetWindowThreadProcessId(hwnd, &pid);// 通过窗口句柄获取窗口进程PID
	printf("目标进程PID:%d\r\n", pid);

	// 获取目标进程句柄
	HANDLE handle = OpenProcess(PROCESS_ALL_ACCESS, 0, pid);// 通过PID获取目标进程句柄
	printf("进程句柄:%p\r\n", handle);

	/**
	 * 向目标进程中申请内存空间,下面逐个介绍参数:
	 * 1. handle: 目标进程句柄
	 * 2. NULL:
	 * 3. 4 * 1024:申请的内存空间大小,32位系统会保证4K对齐,所以一次最少申请4K
	 * 4. MEM_COMMIT:申请的内存的性质,这里表示物理内存或者页面内存
	 * 5. PAGE_EXECUTE_READWRITE:申请的内存空间可读写
	 */
	void *mem = VirtualAllocEx(handle, NULL, 4 * 1024, MEM_COMMIT, PAGE_EXECUTE_READWRITE /*PAGE_EXECUTE_READWRITE=0x40,表示这块内存区域可读写,可执行*/);
	if (mem == NULL) {
		printf("在目标进程申请内存失败,无法注入代码!!!");
		return 0;
	}
	printf("已在目标进程申请内存空间:%p\r\n", mem);

	/**
	 * 向申请的内存区域写入准备好的自定义代码 - 将上面的裸函数写入目标进程中自己申请的内存区域
	 * 向IDE中编译的控制台应用程序中注入代码后通过 `x32dbg.exe` 查看申请的内存区域的首地址
	 * 有可能看到 jmp 代码,则需要 vs2017 中修改 C++ 优化为禁用状态
	 * 查看图片:https://www.xdfznh.club/kbp/cpp/编译cpp程序禁用优化.png
	 */
	bool retWriteProcessMemory = WriteProcessMemory(handle, mem, nakedFunc, 0x300, 0);
	printf("向申请的内存空间写入自定义代码收到的响应:%d\r\n", retWriteProcessMemory);

	system("pause");
}

验证注入是否成功,使用 x32dbg.exe 打开注入用程序 注入用宿主进程,然后运行本 “远程线程注入代码” 根据申请到的内存的起始地址到 x32dbg.exe 中通过快捷键 ctrl+g 找到该内存地址,要能看到 nakedFunc 的代码

本文的前置知识见:远程线程调用宿主函数

注入dll

概述

本文制作控制台应用程序向 目标进程 注入 自定义的dll

实现

源码

// A028InjectDll.cpp : 此文件包含 "main" 函数。程序执行将在此处开始并结束。
#include <iostream>
#include <Windows.h>

// 准备目标进程的窗口类和窗口标题,通过 spy++ 获取
char windowClassName[] = "ConsoleWindowClass";
char windowTitle[] = "D:\\source\\cpp\\windowsapi\\Release\\A05forInject.exe";
char windowTitle01[] = "D:/source/cpp/windowsapi/Release/A05forInject.exe";
char windowTitle02[] = R"(D:\source\cpp\windowsapi\Release\A05forInject.exe)";

char dllPath[] = R"(D:\source\cpp\windowsapi\Release\A026MFCdll.dll)";// dll路径

int main()
{
	// 使用窗口类、窗口标题获取窗口句柄
	HWND hwnd = FindWindowA(windowClassName, windowTitle02);// 获取窗口句柄	
	printf("窗口句柄:%p\r\n", hwnd);

	// 根据窗口句柄获取进程PID
	DWORD pid = 0;// 目标进程PID
	GetWindowThreadProcessId(hwnd, &pid);// 通过窗口句柄获取窗口进程PID
	printf("目标进程PID:%d\r\n", pid);

	// 获取目标进程句柄
	HANDLE handle = OpenProcess(PROCESS_ALL_ACCESS, 0, pid);// 通过PID获取目标进程句柄
	printf("进程句柄:%p\r\n", handle);

	/**
	 * 向目标进程中申请内存空间,下面逐个介绍参数:
	 * 1. handle: 目标进程句柄
	 * 2. NULL:
	 * 3. 4 * 1024:申请的内存空间大小,32位系统会保证4K对齐,所以一次最少申请4K
	 * 4. MEM_COMMIT:申请的内存的性质,这里表示物理内存或者页面内存
	 * 5. PAGE_EXECUTE_READWRITE:申请的内存空间可读写
	 */
	void *mem = VirtualAllocEx(handle, NULL, 4 * 1024, MEM_COMMIT, PAGE_EXECUTE_READWRITE /*PAGE_EXECUTE_READWRITE=0x40,表示这块内存区域可读写,可执行*/);
	if (mem == NULL) {
		printf("在目标进程申请内存失败,无法注入代码!!!");
		return 0;
	}
	printf("已在目标进程申请内存空间:%p\r\n", mem);

	/**
	 * 向申请的内存区域写入准备好的自定义代码 - 将上面的裸函数写入目标进程中自己申请的内存区域
	 * 向IDE中编译的控制台应用程序中注入代码后通过 `x32dbg.exe` 查看申请的内存区域的首地址
	 * 有可能看到 jmp 代码,则需要 vs2017 中修改 C++ 优化为禁用状态
	 * 查看图片:https://www.xdfznh.club/kbp/cpp/编译cpp程序禁用优化.png
	 * 第三个参数 dllPath 在案例 “远程线程注入代码” 中是裸函数,这里填写自定义的dll绝对路径
	 * 后面的 CreateRemoteThread 通过 LoadLibraryA 调用申请内存,即调用了自定义 dll
	 */
	bool retWriteProcessMemory = WriteProcessMemory(handle, mem, dllPath, 0x300, 0);
	printf("向申请的内存空间写入自定义代码收到的响应:%d\r\n", retWriteProcessMemory);

	// 调用申请内存中注入的自定义代码
	::CreateRemoteThread(handle, 0, 0, (LPTHREAD_START_ROUTINE)LoadLibraryA, mem, 0, 0);

	system("pause");
}

总结

本文的前置案例是 远程线程注入代码