文章总结: 本文深入剖析了SysWhispers4的系统调用汇编实现机制,解读了syscallstub的标准格式与机器码编码,对比了不同架构差异及四种stub变体。文章重点解析了movr10,rcx指令背景与影子空间原理,阐述了syscall指令从用户态切换至内核态的微观流程及SSDT查找机制,揭示了直接系统调用绕过EDR监控的底层原理。 综合评分: 95 文章分类: 红队,免杀,逆向分析,二进制安全,安全工具
edr绕过工具 SysWhispers4 源码分析系列(六)
原创
haidragon haidragon
安全狗的自我修养
2026年3月13日 12:09 湖南
官网:http://securitytech.cc
六、系统调用汇编实现深度分析
本文档从汇编层面深入剖析 Windows x64 系统调用的完整实现机制,包括 syscall stub 结构、寄存器传递、特权级转换、内核分发等核心流程。通过本文档,你将完全理解从用户态到内核态的每一个机器指令级别细节。
1. syscall stub 汇编结构
1.1 标准 syscall stub 格式
SysWhispers4 生成的典型 stub:
1. ; SW4Syscalls.asm- MASM 语法
2. .code
4. SW4_NtAllocateVirtualMemory PROC
5. mov r10, rcx ;第1参数 RCX → R10
6. mov eax,18h; SSN =24(Windows1021H2)
7. syscall ;进入内核模式
8. ret ;返回到调用者
9. SW4_NtAllocateVirtualMemory ENDP
11. SW4_NtCreateThreadEx PROC
12. mov r10, rcx
13. mov eax,0C9h; SSN =201
14. syscall
15. ret
16. SW4_NtCreateThreadEx ENDP
18. END
1.2 指令编码分析
机器码反汇编:
1. 00007FF7`12345678 4C8BD9 mov r10,rcx ; 3 bytes
2. 00007FF7`1234567B B818000000 mov eax,18h;5 bytes
3. 00007FF7`12345680 0F05 syscall ; 2 bytes
4. 00007FF7`12345682 C3 ret ;1byte
5. ;总计:11 bytes
字节码详解:
| 偏移 | 机器码 | 汇编指令 | 作用 |
| — | — | — | — |
| +0 | 4C8BD9 | mov r10,rcx | 参数传递准备 |
| +3 | B818000000 | mov eax,18h | 设置 SSN |
| +8 | 0F05 | syscall | 特权级转换 |
| +A | C3 | ret | 返回用户态 |
1.3 不同架构的 stub 对比
x64 架构(syscall)
1. ; x64 -使用 syscall 指令
2. NtAllocateVirtualMemory PROC
3. mov r10, rcx
4. mov eax,18h
5. syscall
6. ret
7. NtAllocateVirtualMemory ENDP
特点:
- 使用
syscall指令(最快) - RCX → R10 传递约定
- EAX 存放 SSN
x86 架构(sysenter)
1. ; x86 -使用 sysenter 指令
2. _NtAllocateVirtualMemory@24 PROC
3. mov eax,46h; SSN =70(Windows7)
4. mov edx,7FFE0300h; SYSENTER_RETURN_ADDRESS
5. sysenter ;进入内核
6. ;注意:sysenter 不自动返回
7. _NtAllocateVirtualMemory@24 ENDP
特点:
- 使用
sysenter指令 - 需要特殊处理返回
- 参数通过栈传递
ARM64 架构(SVC)
1. ; ARM64 -使用 SVC 指令
2. NtAllocateVirtualMemory PROC
3. mov x16,#0x18 ; SSN 放入 X16
4. svc #0 ; Supervisor Call
5. ret
6. NtAllocateVirtualMemory ENDP
特点:
- 使用
svc#0指令 - X16 存放 SSN
- 异常机制进入内核
1.4 不同调用方式的 stub 变体
Embedded(直接式)
1. SW4_NtAllocateVirtualMemory PROC
2. mov r10, rcx
3. mov eax,18h
4. syscall ;直接 syscall
5. ret
6. SW4_NtAllocateVirtualMemory ENDP
机器码: 4C8BD9 B8180000000F05C3
特征:
- 包含明显的
0F05(syscall) - 静态可检测
- 最简单快速
Indirect(间接式)
1. EXTERN syscall_gadget:QWORD
3. SW4_NtAllocateVirtualMemory PROC
4. mov r10, rcx
5. mov eax,18h
6. jmp qword ptr [syscall_gadget];间接跳转
7. SW4_NtAllocateVirtualMemory ENDP
机器码: 4C8BD9 B818000000FF25XX XX XX XX
特征:
- 使用
FF25(远跳转) - RIP 显示在 ntdll.dll
- 绕过基于 RIP 的检测
Randomized(随机化)
1. SW4_NtAllocateVirtualMemory PROC
2. mov r10, rcx
3. push rdx ;保存寄存器
4. rdtsc ;获取时间戳
5. and eax,3Fh;取模64
6. shl eax,3;*8(每个 gadget 8字节)
7. lea rax,[gadget_pool]
8. mov rax,[rax + rax];随机选择 gadget
9. call rax ;调用 gadget
10. pop rdx ;恢复寄存器
11. ret
12. SW4_NtAllocateVirtualMemory ENDP
特征:
- 包含
RDTSC指令 - 随机选择 gadget
- 每次调用路径不同
Egg(标记式)
1. SW4_NtAllocateVirtualMemory PROC
2. mov r10, rcx
3. ; egg marker (8字节占位符)
4. DB 41h,42h,43h,44h,45h,46h,47h,48h
5. ret
6. SW4_NtAllocateVirtualMemory ENDP
特征:
- 无 syscall 指令
- 运行时替换为 syscall
- 需要调用
HatchEggs()
2. mov r10,rcx 原理深度解析
2.1 x64 调用约定要求
Microsoft x64 调用约定规则:
1. 整数参数传递:
2. -第1个参数:RCX
3. -第2个参数:RDX
4. -第3个参数:R8
5. -第4个参数:R9
6. -第5个及以后:栈上
8. 浮点参数传递:
9. -第1-4个:XMM0-XMM3
11. 返回值:
12. -整数:RAX
13. -浮点:XMM0
syscall 的特殊要求:
1. syscall 指令使用前必须:
2. 1. RCX → R10(第1参数)
3. 2. SSN → EAX(系统服务号)
4. 3.其他参数保持不变
2.2 为什么需要 RCX → R10?
历史原因和技术需求:
1. x64 syscall 设计规范:
2. ┌─────────────────────────────────────┐
3. │ syscall 指令会自动覆盖 RCX 寄存器│
4. │用于保存返回地址(类似 CALL 指令)│
5. └─────────────────────────────────────┘
syscall 执行时的硬件行为:
1. 执行 syscall 瞬间,CPU 自动完成:
2. 1. RCX ← RIP +2;保存返回地址
3. 2. R11 ← RFLAGS ;保存标志寄存器
4. 3. RIP ← IA32_LSTAR ;跳转到内核入口
5. 4. CS/SS ←内核选择子;切换到内核段
6. 5. RSP ←内核栈;切换到内核栈
如果不复制 RCX 会怎样?
1. // 错误示例(没有 mov r10, rcx)
2. NtAllocateVirtualMemory PROC
3. mov eax,18h; SSN
4. syscall ;❌ RCX 被覆盖!第1参数丢失
5. ret
6. NtAllocateVirtualMemory ENDP
8. // 正确示例
9. NtAllocateVirtualMemory PROC
10. mov r10, rcx ;✓先复制 RCX 到 R10
11. mov eax,18h
12. syscall ; RCX 被覆盖也没关系了
13. ret
14. NtAllocateVirtualMemory ENDP
2.3 参数传递完整流程
6 参数函数的参数传递示例:
1. NTSTATUS NtAllocateVirtualMemory(
2. HANDLE ProcessHandle,// [1] RCX → R10
3. PVOID*BaseAddress,// [2] RDX
4. ULONG_PTR ZeroBits,// [3] R8
5. PSIZE_T RegionSize,// [4] R9
6. ULONG AllocationType,// [5] [RSP+28h]
7. ULONG Protect// [6] [RSP+30h]
8. );
汇编实现:
1. SW4_NtAllocateVirtualMemory PROC
2. ;寄存器参数(前4个)
3. ; RCX 已经在 RCX →复制到 R10
4. mov r10, rcx ;第1参数
5. ; RDX 已经在 RDX ;第2参数
6. ; R8 已经在 R8 ;第3参数
7. ; R9 已经在 R9 ;第4参数
9. ;栈上参数(第5个及以后)
10. ;[RSP+28h]=第5参数(AllocationType)
11. ;[RSP+30h]=第6参数(Protect)
13. ;设置 SSN
14. mov eax,18h
16. ;执行 syscall
17. syscall
19. ;返回值在 RAX (NTSTATUS)
20. ret
21. SW4_NtAllocateVirtualMemory ENDP
调用者的栈布局:
1. 调用前栈布局:
2. [RSP]=返回地址(到调用者)
3. [RSP+8]=影子空间(ShadowSpacefor RCX)
4. [RSP+10h]=影子空间(ShadowSpacefor RDX)
5. [RSP+18h]=影子空间(ShadowSpacefor R8)
6. [RSP+20h]=影子空间(ShadowSpacefor R9)
7. [RSP+28h]=第5参数(AllocationType)
8. [RSP+30h]=第6参数(Protect)
10. syscall 后栈布局(内核视角):
11. [RSP]=内核栈帧
12. ...
13. [原 RSP+28h]=第5参数(仍可通过原偏移访问)
14. [原 RSP+30h]=第6参数
2.4 影子空间(Shadow Space)
什么是影子空间?
1. 影子空间定义:
2. 调用者必须在栈上预留32字节(4×8字节)
3. 供被调用函数临时存储 RCX、RDX、R8、R9
5. 位置:
6. [RSP+8]=Shadowfor RCX
7. [RSP+10h]=Shadowfor RDX
8. [RSP+18h]=Shadowfor R8
9. [RSP+20h]=Shadowfor R9
为什么需要影子空间?
1. ;被调用函数可以随意使用影子空间
2. CalleeFunction PROC
3. ;可以把寄存器值暂存到影子空间
4. mov [rcx], rdx ;保存参数
5. mov [rsp+8], r8 ;使用影子空间
6. ;...
7. ret
8. CalleeFunction ENDP
syscall stub 中的影子空间:
1. ;SysWhispers4生成的 stub 隐式依赖影子空间
2. SW4_NtAllocateVirtualMemory PROC
3. mov r10, rcx ; RCX → R10
4. ;调用者负责维护影子空间
5. mov eax,18h
6. syscall
7. ret ;不调用其他函数,无需额外栈
8. SW4_NtAllocateVirtualMemory ENDP
3. syscall 指令执行流程
3.1 syscall 指令编码
指令格式:
1. syscall 指令
2. ├─操作码:0F05(2字节)
3. ├─特权级:只能在Ring3执行
4. └─作用:快速系统调用
执行特权检查:
1. CPU 执行 syscall 前检查:
2. 1. CPL (CurrentPrivilegeLevel)=3?✓用户态
3. 2. IA32_LSTAR MSR 已设置?✓内核入口
4. 3. IA32_FMASK MSR 已设置?✓ RFLAGS 掩码
6. 如果检查失败:
7. →#GP(General Protection Fault) 异常
3.2 syscall 执行的微观流程
阶段 1:保存用户态上下文
1. 执行动作(硬件自动):
2. ┌──────────────────────────────────────┐
3. │1. RCX ← RIP +2│
4. │保存下一条指令地址(返回地址)│
5. ││
6. │2. R11 ← RFLAGS │
7. │保存标志寄存器状态│
8. ││
9. │3. IA32_KERNEL_GSBASE → GS │
10. │切换到内核 GS 基址│
11. ││
12. │4. RSP ← IA32_PGTBL_ADDR │
13. │切换到内核页表│
14. └──────────────────────────────────────┘
阶段 2:加载内核态上下文
1. 执行动作(硬件自动):
2. ┌──────────────────────────────────────┐
3. │5. IA32_LSTAR → RIP │
4. │跳转到内核 syscall 入口│
5. │(通常是KiSystemCall64)│
6. ││
7. │6. IA32_STAR → CS/SS │
8. │切换到内核代码段/数据段│
9. │(Ring0)│
10. ││
11. │7. RFLAGS &= IA32_FMASK │
12. │应用 RFLAGS 掩码│
13. ││
14. │8. IF ←0│
15. │禁用中断│
16. └──────────────────────────────────────┘
阶段 3:执行内核入口代码
1. ; nt!KiSystemCall64(简化版)
2. KiSystemCall64:
3. ;1.交换 GS 基址(用户↔内核)
4. swapgs
6. ;2.保存用户栈指针
7. mov qword ptr gs:0x10, rsp
9. ;3.加载内核栈
10. mov rsp, gs:0x18
12. ;4.保存非易失性寄存器
13. push rbp
14. push rbx
15. push rdi
16. push rsi
17. push r12
18. push r13
19. push r14
20. push r15
22. ;5.根据 EAX 查找 SSDT
23. lea r10,[KeServiceDescriptorTable]
24. movzx rax, al ; SSN
25. shl rax,4;每个表项16字节
26. add r10, rax
28. ;6.调用对应的内核函数
29. mov rax,[r10];获取函数地址
30. call rax ;调用Nt*函数
32. ;7.恢复寄存器
33. pop r15
34. pop r14
35. pop r13
36. pop r12
37. pop r11
38. pop r10
39. pop r9
40. pop r8
41. pop rdx
42. pop rcx
43. pop rax
45. ;8.返回用户态
46. swapgs
47. o64 sysretl
3.3 SSDT 查找机制
SSDT 表结构:
1. typedefstruct _KSERVICE_TABLE_DESCRIPTOR {
2. PULONG_PTR Base;// 系统服务函数地址表
3. PULONG Count;// 服务数量
4. ULONG Limit;// 表大小
5. PUCHAR Number;// 参数长度表
6. } KSERVICE_TABLE_DESCRIPTOR,*PKSERVICE_TABLE_DESCRIPTOR;
查找算法:
1. ;假设 EAX =0x18(SSN =24)
2. lea r10,[KeServiceDescriptorTable]; r10 = SSDT 基址
3. movzx rax, al ; rax =0x18
4. shl rax,4; rax =0x180(每个表项16字节)
5. add r10, rax ; r10 = SSDT[24]
6. mov rax,[r10]; rax =NtAllocateVirtualMemory地址
7. call rax ;调用内核函数
内存布局图:
1. KeServiceDescriptorTable(SSDT)
2. +---------------------------+
3. |Base→[函数地址表]│
4. |Count→0x00000123│
5. |Limit→0x00000200│
6. |Number→[参数长度表]│
7. +---------------------------+
9. 函数地址表(Base):
10. [0x000]→NtAcceptConnectPort
11. [0x010]→NtAccessCheck
12. [0x020]→NtAccessCheckByType
13. ...
14. [0x180]→NtAllocateVirtualMemory← SSN 24
15. ...
16. [0x200]→NtWriteVirtualMemory
4. 用户态到内核态切换
4.1 特权级概念
Intel 特权级模型:
1. Ring0(最高特权级)
2. ├─内核代码(ntoskrnl.exe)
3. ├─内核驱动(*.sys)
4. └─访问所有内存和指令
6. Ring1(未使用)
7. Ring2(未使用)
9. Ring3(最低特权级)
10. ├─用户应用程序
11. ├─Win32子系统(csrss.exe)
12. └─受限访问
Windows 的特权级使用:
1. Windows只使用两个特权级:
2. -Ring0:内核模式(KernelMode)
3. -Ring3:用户模式(UserMode)
5. 特权级转换只能通过特定指令:
6. - syscall / sysenter (Ring3→Ring0)
7. - sysret / sysexit (Ring0→Ring3)
8. - iret (通用返回)
4.2 完整的特权级转换流程
用户态 → 内核态转换步骤:
1. 步骤1:用户程序执行 syscall 指令
2. ↓
3. 步骤2: CPU 硬件自动保存上下文
4. ├─ RCX ← RIP +2(返回地址)
5. ├─ R11 ← RFLAGS (标志寄存器)
6. ├─ GS ← IA32_KERNEL_GSBASE (GS 基址)
7. └─禁用中断(IF=0)
8. ↓
9. 步骤3: CPU 加载内核上下文
10. ├─ RIP ← IA32_LSTAR (内核入口地址)
11. ├─ CS ←KernelCodeSegment
12. ├─ SS ←KernelDataSegment
13. └─ CPL ←0(Ring0)
14. ↓
15. 步骤4:开始执行内核代码(KiSystemCall64)
16. ├─ swapgs (切换 GS 基址)
17. ├─保存用户栈指针
18. ├─加载内核栈
19. └─执行内核函数
寄存器变化对比:
1. syscall 前(用户态Ring3):
2. RIP =00007FF7`12345680 (用户代码)
3. CS = 0033 (用户代码段)
4. SS = 002B (用户数据段)
5. CPL = 3 (Ring 3)
6. RSP = 000000E1`23456780(用户栈)
7. GS =用户 GS 基址
9. syscall 后(内核态Ring0):
10. RIP = FFFFF800`12345678 (内核代码 KiSystemCall64)
11. CS = 0010 (内核代码段)
12. SS = 0018 (内核数据段)
13. CPL = 0 (Ring 0)
14. RSP = FFFF8A00`12345000(内核栈)
15. GS =内核 GS 基址
4.3 MSR 寄存器配置
关键 MSR 寄存器:
| MSR 地址 | 名称 | 作用 | | — | — | — | | 0xC0000080 | IA32_EFER | 扩展功能启用 | | 0xC0000081 | IA32_STAR | syscall 目标 CS/SS | | 0xC0000082 | IA32_LSTAR | syscall 入口 RIP | | 0xC0000084 | IA32_FMASK | RFLAGS 掩码 | | 0xC0000101 | IA32KERNELGSBASE | 内核 GS 基址 |
读取 MSR 值(WinDbg):
1. 0: kd> rdmsr 0xC0000082
2. msr[c0000082]= fffff800`12345678 ; IA32_LSTAR (syscall 入口)
4. 0: kd> rdmsr 0xC0000084
5. msr[c0000084] = 00000000`00050202; IA32_FMASK
7. 0: kd> rdmsr 0xC0000101
8. msr[c0000101]= fffff800`56789ABC ; IA32_KERNEL_GSBASE
IA32_LSTAR 设置过程(内核启动时):
1. // 内核初始化代码(伪代码)
2. VOID InitializeSyscallEntry(){
3. // 设置 syscall 入口点为 KiSystemCall64
4. __writemsr(0xC0000082,(ULONG64)KiSystemCall64);
6. // 设置内核段选择子
7. __writemsr(0xC0000081,
8. (KERNEL_CS <<16)|(USER_CS <<32));
10. // 设置 RFLAGS 掩码(清除中断和陷阱标志)
11. __writemsr(0xC0000084,0x50202);
12. }
4.4 栈切换机制
为什么要切换栈?
1. 用户栈 vs 内核栈:
2. ┌─────────────────────────────────────┐
3. │用户栈(Ring3)│
4. │-位于用户空间(0x0000000000000000│
5. │到0x00007FFFFFFFFFFF)│
6. │-可能被攻击者控制│
7. │-不可信│
8. └─────────────────────────────────────┘
10. ┌─────────────────────────────────────┐
11. │内核栈(Ring0)│
12. │-位于内核空间(0xFFFF000000000000│
13. │到0xFFFFFFFFFFFFFFFF)│
14. │-受内核保护│
15. │-可信│
16. └─────────────────────────────────────┘
栈切换过程:
1. ; syscall 硬件自动切换
2. 执行 syscall:
3. 1.保存当前 RSP 到某处
4. 2. RSP ← IA32_PGTBL_ADDR (内核页表)
5. 3.或者 RSP ← TSS.RSP0 (任务状态段)
7. ;KiSystemCall64手动切换
8. KiSystemCall64:
9. swapgs ;切换到内核 GS
10. mov gs:0x10, rsp ;保存用户 RSP
11. mov rsp, gs:0x18;加载内核 RSP
12. ;现在在内核栈上了
内核栈结构:
1. 内核栈(从高地址向低地址增长)
3. 高地址
4. +---------------------------+
5. |内核栈底部│
6. | THREAD_STACK_SIZE =12KB│
7. +---------------------------+
8. |局部变量│
9. |保存的寄存器│
10. |函数参数│
11. +---------------------------+
12. | TRAP_FRAME 结构│← gs:0x10指向这里
13. |-保存的用户态寄存器│
14. |- RAX, RCX, RDX,...│
15. +---------------------------+
16. | KTRAP_FRAME 结构│
17. +---------------------------+
18. 低地址(RSP 当前位置)
5. syscall 返回流程
5.1 sysret 指令
sysret 指令格式:
1. sysret 指令
2. ├─操作码:0F07(2字节)
3. ├─特权级:只能在Ring0执行
4. └─作用:快速返回用户态
sysret 执行流程:
1. 执行动作(硬件自动):
2. ┌──────────────────────────────────────┐
3. │1. RIP ← RCX │
4. │恢复到用户态返回地址│
5. ││
6. │2. RFLAGS ← R11 │
7. │恢复标志寄存器│
8. ││
9. │3. CS/SS ←用户段选择子│
10. │从 IA32_STAR 加载│
11. ││
12. │4. CPL ←3│
13. │切换到用户特权级│
14. ││
15. │5. RSP ←用户栈指针│
16. │从 TSS 或保存的位置恢复│
17. ││
18. │6.启用中断(IF=1)│
19. └──────────────────────────────────────┘
5.2 完整的返回路径
内核函数返回流程:
1. ;内核函数执行完毕
2. NtAllocateVirtualMemory:
3. ;...执行内存分配逻辑
4. mov eax,0;返回 STATUS_SUCCESS
5. ret ;返回到KiSystemCall64
7. ;返回到 syscall 分发器
8. KiSystemCall64:
9. ;此时 RAX =返回值(NTSTATUS)
11. ;恢复保存的寄存器
12. pop r15
13. pop r14
14. pop r13
15. pop r12
16. pop r11
17. pop r10
18. pop r9
19. pop r8
20. pop rdx
21. pop rcx
22. pop rax
24. ;准备返回用户态
25. swapgs ;切换回用户 GS
27. ; sysret 返回
28. sysret ;等价于:
29. ; RIP ← RCX
30. ; RFLAGS ← R11
31. ; CS/SS ←用户段
32. ; CPL ←3
5.3 返回值传递
NTSTATUS 返回值:
1. typedef LONG NTSTATUS;
3. // 常见返回值
4. #define STATUS_SUCCESS ((NTSTATUS)0x00000000L)
5. #define STATUS_ACCESS_DENIED ((NTSTATUS)0xC0000022L)
6. #define STATUS_INVALID_HANDLE ((NTSTATUS)0xC0000008L)
7. #define STATUS_BUFFER_OVERFLOW ((NTSTATUS)0x80000005L)
返回值传递链:
1. 内核函数返回值(RAX)
2. ↓
3. KiSystemCall64(保持 RAX 不变)
4. ↓
5. sysret (RAX 仍然是返回值)
6. ↓
7. 用户态 stub (RAX = NTSTATUS)
8. ↓
9. C 语言调用者(接收返回值)
汇编示例:
1. ;用户态 stub
2. SW4_NtAllocateVirtualMemory PROC
3. mov r10, rcx
4. mov eax,18h
5. syscall ;执行后 RAX = NTSTATUS
6. ret ;返回值保持在 RAX
7. SW4_NtAllocateVirtualMemory ENDP
9. ; C 语言调用
10. NTSTATUS status = SW4_NtAllocateVirtualMemory(...);
11. // status 的值就是 RAX
5.4 错误处理
NT_SUCCESS 宏:
1. #define NT_SUCCESS(Status)(((NTSTATUS)(Status))>=0)
3. // 判断逻辑
4. if(status >=0){
5. // 成功 (0x00000000 - 0x7FFFFFFF)
6. }else{
7. // 失败 (0x80000000 - 0xFFFFFFFF)
8. }
错误码分类:
1. 严重程度位(bits 30-31):
2. 00=Success(成功)
3. 01=Informational(信息)
4. 10=Warning(警告)
5. 11=Error(错误)
7. 示例:
8. 0x00000000= STATUS_SUCCESS (成功)
9. 0x40000000= STATUS_PENDING (等待中)
10. 0x80000005= STATUS_BUFFER_OVERFLOW (缓冲区溢出-警告)
11. 0xC0000022= STATUS_ACCESS_DENIED (访问拒绝-错误)
错误处理示例:
1. NTSTATUS status = SW4_NtAllocateVirtualMemory(...);
3. if(NT_SUCCESS(status)){
4. printf("Success!\n");
5. }else{
6. switch(status){
7. case STATUS_ACCESS_DENIED:
8. printf("Access denied\n");
9. break;
10. case STATUS_INVALID_HANDLE:
11. printf("Invalid handle\n");
12. break;
13. case STATUS_NO_MEMORY:
14. printf("Out of memory\n");
15. break;
16. default:
17. printf("Error: 0x%08X\n", status);
18. }
19. }
5.5 异常处理
syscall 可能触发的异常:
1. 1.#GP (General Protection Fault)
2. 原因:特权级违规、段选择子错误
4. 2.#PF (Page Fault)
5. 原因:访问无效内存地址
7. 3.#UD (Undefined Opcode)
8. 原因:CPU 不支持 syscall 指令
10. 4.#NM (Device Not Available)
11. 原因:FPU/SSE 不可用
异常处理流程:
1. 发生异常
2. ↓
3. CPU 自动保存现场
4. ↓
5. 查找 IDT (中断描述符表)
6. ↓
7. 调用对应的异常处理程序
8. ↓
9. KiTrapXX处理例程
10. ↓
11. 分析异常原因
12. ↓
13. 可能终止进程或恢复执行
WinDbg 调试异常:
1. 0:000> g
2. (1234.5678):Access violation - code c0000005 (!!! second chance)
3. ntdll!NtAllocateVirtualMemory+0x18:
4. 00007ff8`12345690 488b01 mov rax,qword ptr [rcx] ds:00000000`00000000=????????????????
6. 0:000> r
7. rax=0000000000000000 rbx=0000000000000000
8. rcx=0000000000000000; NULL 指针!
9. rdx=0000000000000000
11. 0:000> k
12. # Child-SP RetAddr
13. 00000000e1`23456780 00007ff7`12345678 ntdll!NtAllocateVirtualMemory+0x18
14. 01000000e1`23456788 00007ff7`12345680 myapp!main
15. 02000000e1`23456790 00007ff8`56789abc kernel32!BaseThreadInitThunk
16. 03000000e1`23456798 00007ff8`6789abcd ntdll!RtlUserThreadStart
总结
通过对 syscall 汇编实现的深度分析,我们理解了:
syscall stub 结构
- 标准格式:
mov r10,rcx;mov eax,SSN;syscall;ret - 机器码编码:
4C8BD9 B8 xx xx xx xx0F05C3 - 4 种调用方式的变体
mov r10, rcx 原理
- x64 调用约定要求
- syscall 会覆盖 RCX
- 参数传递完整流程
- 影子空间的作用
syscall 执行流程
- 保存用户态上下文(RCX、R11)
- 加载内核态上下文(RIP、CS、SS)
- SSDT 查找机制
- 内核入口代码
特权级切换
- Ring 3 → Ring 0 转换
- MSR 寄存器配置
- 栈切换机制
- GS 基址切换
syscall 返回流程
- sysret 指令执行
- 返回值传递(RAX = NTSTATUS)
- 错误处理机制
- 异常处理流程
这些知识使我们能够从汇编层面完全理解和控制 Windows 系统调用,为开发高级安全工具和研究底层机制奠定了坚实基础。
- 公众号:安全狗的自我修养
- vx:2207344074
- http://gitee.com/haidragon
- http://github.com/haidragon
- bilibili:haidragonx
#
免责声明:
本文所载程序、技术方法仅面向合法合规的安全研究与教学场景,旨在提升网络安全防护能力,具有明确的技术研究属性。
任何单位或个人未经授权,将本文内容用于攻击、破坏等非法用途的,由此引发的全部法律责任、民事赔偿及连带责任,均由行为人独立承担,本站不承担任何连带责任。
本站内容均为技术交流与知识分享目的发布,若存在版权侵权或其他异议,请通过邮件联系处理,具体联系方式可点击页面上方的联系我。
本文转载自:安全狗的自我修养 haidragon haidragon《edr绕过工具 SysWhispers4 源码分析系列(六)》
版权声明
本站仅做备份收录,仅供研究与教学参考之用。
读者将信息用于其他用途的,全部法律及连带责任由读者自行承担,本站不承担任何责任。









评论