乐于分享
好东西不私藏

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

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

官网:http://securitytech.cc

六、系统调用汇编实现深度分析

本文档从汇编层面深入剖析 Windows x64 系统调用的完整实现机制,包括 syscall stub 结构、寄存器传递、特权级转换、内核分发等核心流程。通过本文档,你将完全理解从用户态到内核态的每一个机器指令级别细节。

1. syscall stub 汇编结构

1.1 标准 syscall stub 格式

SysWhispers4 生成的典型 stub:

  1. ; SW4Syscalls.asm- MASM 语法
  2. .code
  3. SW4_NtAllocateVirtualMemory PROC
  4.     mov r10, rcx           ;1参数 RCX  R10
  5.     mov eax,18h; SSN =24(Windows1021H2)
  6.     syscall                ;进入内核模式
  7.     ret                    ;返回到调用者
  8. SW4_NtAllocateVirtualMemory ENDP
  9. SW4_NtCreateThreadEx PROC
  10.     mov r10, rcx
  11.     mov eax,0C9h; SSN =201
  12.     syscall
  13.     ret
  14. SW4_NtCreateThreadEx ENDP
  15. 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
  2. SW4_NtAllocateVirtualMemory PROC
  3.     mov r10, rcx
  4.     mov eax,18h
  5.     jmp qword ptr [syscall_gadget];间接跳转
  6. 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个及以后:栈上
  7. 浮点参数传递:
  8. -1-4个:XMM0-XMM3
  9. 返回值:
  10. -整数:RAX
  11. -浮点: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
  7. // 正确示例
  8. NtAllocateVirtualMemory PROC
  9.     mov r10, rcx         ;先复制 RCX  R10
  10.     mov eax,18h
  11.     syscall              ; RCX 被覆盖也没关系了
  12.     ret
  13. 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参数
  8. ;栈上参数(第5个及以后)
  9. ;[RSP+28h]=5参数(AllocationType)
  10. ;[RSP+30h]=6参数(Protect)
  11. ;设置 SSN
  12.     mov eax,18h
  13. ;执行 syscall
  14.     syscall
  15. ;返回值在 RAX (NTSTATUS)
  16.     ret
  17. 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)
  9. syscall 后栈布局(内核视角):
  10. [RSP]=内核栈帧
  11. ...
  12. [原 RSP+28h]=5参数(仍可通过原偏移访问)
  13. [原 RSP+30h]=6参数

2.4 影子空间(Shadow Space)

什么是影子空间?

  1. 影子空间定义:
  2. 调用者必须在栈上预留32字节(4×8字节)
  3. 供被调用函数临时存储 RCXRDXR8R9
  4. 位置:
  5. [RSP+8]=Shadowfor RCX
  6. [RSP+10h]=Shadowfor RDX
  7. [RSP+18h]=Shadowfor R8
  8. [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 掩码
  5. 如果检查失败:
  6. #GP(General Protection Fault) 异常

3.2 syscall 执行的微观流程

阶段 1:保存用户态上下文

  1. 执行动作(硬件自动):
  2. ┌──────────────────────────────────────┐
  3. 1. RCX  RIP +2
  4. 保存下一条指令地址(返回地址)
  5. 2. R11  RFLAGS                      
  6. 保存标志寄存器状态
  7. 3. IA32_KERNEL_GSBASE  GS           
  8. 切换到内核 GS 基址
  9. 4. RSP  IA32_PGTBL_ADDR             
  10. 切换到内核页表
  11. └──────────────────────────────────────┘

阶段 2:加载内核态上下文

  1. 执行动作(硬件自动):
  2. ┌──────────────────────────────────────┐
  3. 5. IA32_LSTAR  RIP                  
  4. 跳转到内核 syscall 入口
  5. (通常是KiSystemCall64)
  6. 6. IA32_STAR  CS/SS                 
  7. 切换到内核代码段/数据段
  8. (Ring0)
  9. 7. RFLAGS &= IA32_FMASK              
  10. 应用 RFLAGS 掩码
  11. 8. IF 0
  12. 禁用中断
  13. └──────────────────────────────────────┘

阶段 3:执行内核入口代码

  1. ; nt!KiSystemCall64(简化版)
  2. KiSystemCall64:
  3. ;1.交换 GS 基址(用户内核)
  4.     swapgs
  5. ;2.保存用户栈指针
  6.     mov qword ptr gs:0x10, rsp
  7. ;3.加载内核栈
  8.     mov rsp, gs:0x18
  9. ;4.保存非易失性寄存器
  10.     push rbp
  11.     push rbx
  12.     push rdi
  13.     push rsi
  14.     push r12
  15.     push r13
  16.     push r14
  17.     push r15
  18. ;5.根据 EAX 查找 SSDT
  19.     lea r10,[KeServiceDescriptorTable]
  20.     movzx rax, al              ; SSN
  21.     shl rax,4;每个表项16字节
  22.     add r10, rax
  23. ;6.调用对应的内核函数
  24.     mov rax,[r10];获取函数地址
  25.     call rax                   ;调用Nt*函数
  26. ;7.恢复寄存器
  27.     pop r15
  28.     pop r14
  29.     pop r13
  30.     pop r12
  31.     pop r11
  32.     pop r10
  33.     pop r9
  34.     pop r8
  35.     pop rdx
  36.     pop rcx
  37.     pop rax
  38. ;8.返回用户态
  39.     swapgs
  40.     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. |Count0x00000123
  5. |Limit0x00000200
  6. |Number[参数长度表]
  7. +---------------------------+
  8. 函数地址表(Base):
  9. [0x000]NtAcceptConnectPort
  10. [0x010]NtAccessCheck
  11. [0x020]NtAccessCheckByType
  12. ...
  13. [0x180]NtAllocateVirtualMemory SSN 24
  14. ...
  15. [0x200]NtWriteVirtualMemory

4. 用户态到内核态切换

4.1 特权级概念

Intel 特权级模型:

  1. Ring0(最高特权级)
  2. ├─内核代码(ntoskrnl.exe)
  3. ├─内核驱动(*.sys)
  4. └─访问所有内存和指令
  5. Ring1(未使用)
  6. Ring2(未使用)
  7. Ring3(最低特权级)
  8. ├─用户应用程序
  9. ├─Win32子系统(csrss.exe)
  10. └─受限访问

Windows 的特权级使用:

  1. Windows只使用两个特权级:
  2. -Ring0:内核模式(KernelMode)
  3. -Ring3:用户模式(UserMode)
  4. 特权级转换只能通过特定指令:
  5. - syscall / sysenter (Ring3Ring0)
  6. - sysret / sysexit (Ring0Ring3)
  7. - iret (通用返回)

4.2 完整的特权级转换流程

用户态 → 内核态转换步骤:

  1. 步骤1:用户程序执行 syscall 指令
  2. 步骤2: CPU 硬件自动保存上下文
  3. ├─ RCX  RIP +2(返回地址)
  4. ├─ R11  RFLAGS           (标志寄存器)
  5. ├─ GS  IA32_KERNEL_GSBASE (GS 基址)
  6. └─禁用中断(IF=0)
  7. 步骤3: CPU 加载内核上下文
  8. ├─ RIP  IA32_LSTAR       (内核入口地址)
  9. ├─ CS KernelCodeSegment
  10. ├─ SS KernelDataSegment
  11. └─ CPL 0(Ring0)
  12. 步骤4:开始执行内核代码(KiSystemCall64)
  13. ├─ swapgs                 (切换 GS 基址)
  14. ├─保存用户栈指针
  15. ├─加载内核栈
  16. └─执行内核函数

寄存器变化对比:

  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 基址
  8. syscall (内核态Ring0):
  9. RIP  = FFFFF800`12345678  (内核代码 KiSystemCall64)
  10. CS   = 0010 (内核代码段)
  11. SS   = 0018 (内核数据段)
  12. CPL  = 0 (Ring 0)
  13. RSP  = FFFF8A00`12345000(内核栈)
  14. 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 入口)
  3. 0: kd> rdmsr 0xC0000084
  4. msr[c0000084] = 00000000`00050202; IA32_FMASK
  5. 0: kd> rdmsr 0xC0000101
  6. msr[c0000101]= fffff800`56789ABC  ; IA32_KERNEL_GSBASE

IA32_LSTAR 设置过程(内核启动时):

  1. // 内核初始化代码(伪代码)
  2. VOID InitializeSyscallEntry(){
  3. // 设置 syscall 入口点为 KiSystemCall64
  4.     __writemsr(0xC0000082,(ULONG64)KiSystemCall64);
  5. // 设置内核段选择子
  6.     __writemsr(0xC0000081,
  7. (KERNEL_CS <<16)|(USER_CS <<32));
  8. // 设置 RFLAGS 掩码(清除中断和陷阱标志)
  9.     __writemsr(0xC0000084,0x50202);
  10. }

4.4 栈切换机制

为什么要切换栈?

  1. 用户栈 vs 内核栈:
  2. ┌─────────────────────────────────────┐
  3. 用户栈(Ring3)
  4. -位于用户空间(0x0000000000000000
  5. 0x00007FFFFFFFFFFF)
  6. -可能被攻击者控制
  7. -不可信
  8. └─────────────────────────────────────┘
  9. ┌─────────────────────────────────────┐
  10. 内核栈(Ring0)
  11. -位于内核空间(0xFFFF000000000000
  12. 0xFFFFFFFFFFFFFFFF)
  13. -受内核保护
  14. -可信
  15. └─────────────────────────────────────┘

栈切换过程:

  1. ; syscall 硬件自动切换
  2. 执行 syscall:
  3. 1.保存当前 RSP 到某处
  4. 2. RSP  IA32_PGTBL_ADDR (内核页表)
  5. 3.或者 RSP  TSS.RSP0 (任务状态段)
  6. ;KiSystemCall64手动切换
  7. KiSystemCall64:
  8.     swapgs                  ;切换到内核 GS
  9.     mov gs:0x10, rsp        ;保存用户 RSP
  10.     mov rsp, gs:0x18;加载内核 RSP
  11. ;现在在内核栈上了

内核栈结构:

  1. 内核栈(从高地址向低地址增长)
  2. 高地址
  3. +---------------------------+
  4. |内核栈底部
  5. | THREAD_STACK_SIZE =12KB
  6. +---------------------------+
  7. |局部变量
  8. |保存的寄存器
  9. |函数参数
  10. +---------------------------+
  11. | TRAP_FRAME 结构 gs:0x10指向这里
  12. |-保存的用户态寄存器
  13. |- RAX, RCX, RDX,...
  14. +---------------------------+
  15. | KTRAP_FRAME 结构
  16. +---------------------------+
  17. 低地址(RSP 当前位置)

5. syscall 返回流程

5.1 sysret 指令

sysret 指令格式:

  1. sysret 指令
  2. ├─操作码:0F07(2字节)
  3. ├─特权级:只能在Ring0执行
  4. └─作用:快速返回用户态

sysret 执行流程:

  1. 执行动作(硬件自动):
  2. ┌──────────────────────────────────────┐
  3. 1. RIP  RCX                         
  4. 恢复到用户态返回地址
  5. 2. RFLAGS  R11                      
  6. 恢复标志寄存器
  7. 3. CS/SS 用户段选择子
  8.  IA32_STAR 加载
  9. 4. CPL 3
  10. 切换到用户特权级
  11. 5. RSP 用户栈指针
  12.  TSS 或保存的位置恢复
  13. 6.启用中断(IF=1)
  14. └──────────────────────────────────────┘

5.2 完整的返回路径

内核函数返回流程:

  1. ;内核函数执行完毕
  2. NtAllocateVirtualMemory:
  3. ;...执行内存分配逻辑
  4.     mov eax,0;返回 STATUS_SUCCESS
  5.     ret                     ;返回到KiSystemCall64
  6. ;返回到 syscall 分发器
  7. KiSystemCall64:
  8. ;此时 RAX =返回值(NTSTATUS)
  9. ;恢复保存的寄存器
  10.     pop r15
  11.     pop r14
  12.     pop r13
  13.     pop r12
  14.     pop r11
  15.     pop r10
  16.     pop r9
  17.     pop r8
  18.     pop rdx
  19.     pop rcx
  20.     pop rax
  21. ;准备返回用户态
  22.     swapgs                  ;切换回用户 GS
  23. ; sysret 返回
  24.     sysret                  ;等价于:
  25. ; RIP  RCX
  26. ; RFLAGS  R11
  27. ; CS/SS 用户段
  28. ; CPL 3

5.3 返回值传递

NTSTATUS 返回值:

  1. typedef LONG NTSTATUS;
  2. // 常见返回值
  3. #define STATUS_SUCCESS              ((NTSTATUS)0x00000000L)
  4. #define STATUS_ACCESS_DENIED        ((NTSTATUS)0xC0000022L)
  5. #define STATUS_INVALID_HANDLE       ((NTSTATUS)0xC0000008L)
  6. #define STATUS_BUFFER_OVERFLOW      ((NTSTATUS)0x80000005L)

返回值传递链:

  1. 内核函数返回值(RAX)
  2. KiSystemCall64(保持 RAX 不变)
  3. sysret (RAX 仍然是返回值)
  4. 用户态 stub (RAX = NTSTATUS)
  5. 语言调用者(接收返回值)

汇编示例:

  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
  8. ; C 语言调用
  9. NTSTATUS status = SW4_NtAllocateVirtualMemory(...);
  10. // status 的值就是 RAX

5.4 错误处理

NT_SUCCESS 宏:

  1. #define NT_SUCCESS(Status)(((NTSTATUS)(Status))>=0)
  2. // 判断逻辑
  3. if(status >=0){
  4. // 成功 (0x00000000 - 0x7FFFFFFF)
  5. }else{
  6. // 失败 (0x80000000 - 0xFFFFFFFF)
  7. }

错误码分类:

  1. 严重程度位(bits 30-31):
  2. 00=Success(成功)
  3. 01=Informational(信息)
  4. 10=Warning(警告)
  5. 11=Error(错误)
  6. 示例:
  7. 0x00000000= STATUS_SUCCESS (成功)
  8. 0x40000000= STATUS_PENDING (等待中)
  9. 0x80000005= STATUS_BUFFER_OVERFLOW (缓冲区溢出-警告)
  10. 0xC0000022= STATUS_ACCESS_DENIED (访问拒绝-错误)

错误处理示例:

  1. NTSTATUS status = SW4_NtAllocateVirtualMemory(...);
  2. if(NT_SUCCESS(status)){
  3.     printf("Success!\n");
  4. }else{
  5. switch(status){
  6. case STATUS_ACCESS_DENIED:
  7.             printf("Access denied\n");
  8. break;
  9. case STATUS_INVALID_HANDLE:
  10.             printf("Invalid handle\n");
  11. break;
  12. case STATUS_NO_MEMORY:
  13.             printf("Out of memory\n");
  14. break;
  15. default:
  16.             printf("Error: 0x%08X\n", status);
  17. }
  18. }

5.5 异常处理

syscall 可能触发的异常:

  1. 1.#GP (General Protection Fault)
  2. 原因:特权级违规、段选择子错误
  3. 2.#PF (Page Fault)
  4. 原因:访问无效内存地址
  5. 3.#UD (Undefined Opcode)
  6. 原因:CPU 不支持 syscall 指令
  7. 4.#NM (Device Not Available)
  8. 原因:FPU/SSE 不可用

异常处理流程:

  1. 发生异常
  2. CPU 自动保存现场
  3. 查找 IDT (中断描述符表)
  4. 调用对应的异常处理程序
  5. KiTrapXX处理例程
  6. 分析异常原因
  7. 可能终止进程或恢复执行

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=????????????????
  5. 0:000> r
  6. rax=0000000000000000 rbx=0000000000000000
  7. rcx=0000000000000000; NULL 指针!
  8. rdx=0000000000000000
  9. 0:000> k
  10. # Child-SP          RetAddr
  11. 00000000e1`23456780 00007ff7`12345678 ntdll!NtAllocateVirtualMemory+0x18
  12. 01000000e1`23456788 00007ff7`12345680 myapp!main
  13. 02000000e1`23456790 00007ff8`56789abc kernel32!BaseThreadInitThunk
  14. 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

本站文章均为手工撰写未经允许谢绝转载:夜雨聆风 » edr绕过工具 SysWhispers4 源码分析系列(六)

猜你喜欢

  • 暂无文章