一、介绍
SysWhispers 是一个通过直接系统调用绕过系统调用钩子的工具。SysWhispers 有多个版本,具有不同的特性。本文将分析各个版本之间的改进。
SysWhispers是64 位系统生成了支持直接系统调用的头文件/ASM 文件植入。它支持从 Windows XP 到 Windows 10 19042(20H2)的系统调用。受支持的 Windows 版本受到限制,因为系统调用号 (SSN) 会随着每次 Windows 更新而更改。因此,在 Windows 10 1903 上针对特定系统调用的直接系统调用实现可能与 Windows 10 1909 上的相同系统调用不兼容,由于在不同版本的 Windows 中相同的系统调用可能具有不同的 SSN,因此 SysWhispers 在运行时检查目标系统的 Windows 版本,并将 SSN 手动设置到正确版本。
二、SysWhispers分析
SysWhispers项目的当中提供一个示例:https://github.com/jthuraisamy/SysWhispers/blob/master/example-output/syscalls.asm
SysWhispers - NtMapViewOfSection 示例
NtMapViewOfSection PROCmov rax, gs:[60h] ; 将 PEB 加载到 RAX 中。 NtMapViewOfSection_Check_X_X_XXXX: ; 检查主版本号。cmp dword ptr [rax+118h], 5je NtMapViewOfSection_SystemCall_5_X_XXXXcmp dword ptr [rax+118h], 6je NtMapViewOfSection_Check_6_X_XXXXcmp dword ptr [rax+118h], 10je NtMapViewOfSection_Check_10_0_XXXXjmp NtMapViewOfSection_SystemCall_Unknown NtMapViewOfSection_Check_6_X_XXXX: ; 检查 Windows Vista/7/8 的次版本号。cmp dword ptr [rax+11ch], 0je NtMapViewOfSection_Check_6_0_XXXXcmp dword ptr [rax+11ch], 1je NtMapViewOfSection_Check_6_1_XXXXcmp dword ptr [rax+11ch], 2je NtMapViewOfSection_SystemCall_6_2_XXXXcmp dword ptr [rax+11ch], 2je NtMapViewOfSection_SystemCall_6_3_XXXXjmp NtMapViewOfSection_SystemCall_Unknown NtMapViewOfSection_Check_6_0_XXXX: ; 检查 Windows Vista 的内部版本号。cmp dword ptr [rax+120h], 6000je NtMapViewOfSection_SystemCall_6_0_6000cmp dword ptr [rax+120h], 6001je NtMapViewOfSection_SystemCall_6_0_6001cmp dword ptr [rax+120h], 6002je NtMapViewOfSection_SystemCall_6_0_6002jmp NtMapViewOfSection_SystemCall_Unknown NtMapViewOfSection_Check_6_1_XXXX: ; 检查 Windows 7 的内部版本号。cmp dword ptr [rax+120h], 7600je NtMapViewOfSection_SystemCall_6_1_7600cmp dword ptr [rax+120h], 7601je NtMapViewOfSection_SystemCall_6_1_7601jmp NtMapViewOfSection_SystemCall_Unknown NtMapViewOfSection_Check_10_0_XXXX: ; 检查 Windows 10 的内部版本号。cmp dword ptr [rax+120h], 10240je NtMapViewOfSection_SystemCall_10_0_10240cmp dword ptr [rax+120h], 10586je NtMapViewOfSection_SystemCall_10_0_10586cmp dword ptr [rax+120h], 14393je NtMapViewOfSection_SystemCall_10_0_14393cmp dword ptr [rax+120h], 15063je NtMapViewOfSection_SystemCall_10_0_15063cmp dword ptr [rax+120h], 16299je NtMapViewOfSection_SystemCall_10_0_16299cmp dword ptr [rax+120h], 17134je NtMapViewOfSection_SystemCall_10_0_17134cmp dword ptr [rax+120h], 17763je NtMapViewOfSection_SystemCall_10_0_17763cmp dword ptr [rax+120h], 18362je NtMapViewOfSection_SystemCall_10_0_18362cmp dword ptr [rax+120h], 18363je NtMapViewOfSection_SystemCall_10_0_18363jmp NtMapViewOfSection_SystemCall_Unknown NtMapViewOfSection_SystemCall_5_X_XXXX: ; Windows XP 和 Server 2003mov eax, 0025hjmp NtMapViewOfSection_Epilogue NtMapViewOfSection_SystemCall_6_0_6000: ; Windows Vista SP0mov eax, 0025hjmp NtMapViewOfSection_Epilogue NtMapViewOfSection_SystemCall_6_0_6001: ; Windows Vista SP1 和 Server 2008 SP0mov eax, 0025hjmp NtMapViewOfSection_Epilogue NtMapViewOfSection_SystemCall_6_0_6002: ; Windows Vista SP2 和 Server 2008 SP2mov eax, 0025hjmp NtMapViewOfSection_Epilogue NtMapViewOfSection_SystemCall_6_1_7600: ; Windows 7 SP0mov eax, 0025hjmp NtMapViewOfSection_Epilogue NtMapViewOfSection_SystemCall_6_1_7601: ; Windows 7 SP1 和 Server 2008 R2 SP0mov eax, 0025hjmp NtMapViewOfSection_Epilogue NtMapViewOfSection_SystemCall_6_2_XXXX: ; Windows 8 和 Server 2012mov eax, 0026hjmp NtMapViewOfSection_Epilogue NtMapViewOfSection_SystemCall_6_3_XXXX: ; Windows 8.1 和 Server 2012 R2mov eax, 0027hjmp NtMapViewOfSection_Epilogue NtMapViewOfSection_SystemCall_10_0_10240: ; Windows 10.0.10240 (1507)mov eax, 0028hjmp NtMapViewOfSection_Epilogue NtMapViewOfSection_SystemCall_10_0_10586: ; Windows 10.0.10586 (1511)mov eax, 0028hjmp NtMapViewOfSection_Epilogue NtMapViewOfSection_SystemCall_10_0_14393: ; Windows 10.0.14393 (1607)mov eax, 0028hjmp NtMapViewOfSection_Epilogue NtMapViewOfSection_SystemCall_10_0_15063: ; Windows 10.0.15063 (1703)mov eax, 0028hjmp NtMapViewOfSection_Epilogue NtMapViewOfSection_SystemCall_10_0_16299: ; Windows 10.0.16299 (1709)mov eax, 0028hjmp NtMapViewOfSection_Epilogue NtMapViewOfSection_SystemCall_10_0_17134: ; Windows 10.0.17134 (1803)mov eax, 0028hjmp NtMapViewOfSection_Epilogue NtMapViewOfSection_SystemCall_10_0_17763: ; Windows 10.0.17763 (1809)mov eax, 0028hjmp NtMapViewOfSection_Epilogue NtMapViewOfSection_SystemCall_10_0_18362: ; Windows 10.0.18362 (1903)mov eax, 0028hjmp NtMapViewOfSection_Epilogue NtMapViewOfSection_SystemCall_10_0_18363: ; Windows 10.0.18363 (1909)mov eax, 0028hjmp NtMapViewOfSection_Epilogue NtMapViewOfSection_SystemCall_Unknown: ; 未知/不支持的版本。ret NtMapViewOfSection_Epilogue:mov r10, rcxsyscallret NtMapViewOfSection ENDP
PEB 结构包含三个可用于确定 Windows 操作系统版本信息成员:
1. OSMajorVersion
- 类型:
DWORD
- 作用: 该字段存储 Windows 操作系统的主版本号。例如:
- Windows 7 对应的主版本号是
6
。 - Windows 10 对应的主版本号是
10
。
- Windows 7 对应的主版本号是
2. OSMinorVersion
- 类型:
DWORD
- 作用: 该字段存储 Windows 操作系统的次版本号。例如:
- Windows 7 SP1 对应的次版本号是
1
。 - Windows 8 对应的次版本号是
2
。 - Windows 10 对应的次版本号可能为
0
或更高的版本号(取决于 Windows 10 的不同版本)。
- Windows 7 SP1 对应的次版本号是
3. OSBuildNumber
- 类型:
DWORD
- 作用: 该字段存储操作系统的构建号,也就是操作系统的具体版本。例如:
- Windows 7 SP1 的构建号是
7601
。 - Windows 10 版本 15063(1703)对应的构建号是
15063
。 - Windows 10 版本 1909 对应的构建号是
18363
。
- Windows 7 SP1 的构建号是
SysWhispers 生成的 64 位汇编函数使用这些成员跳转到硬编码值 SSN 所在的位置。使用的逻辑本质上是若干 if 和 else if 语句。例如,如果目标计算机是 Windows 10 1809,则出现以下逻辑:
-
由于 PEB 的 主版本 成员等于 10,执行
NtMapViewOfSection_Check_10_0_XXXX
标签。 -
此标签接着检查系统的 生成版本号。在这个示例中,这个数字是 1809,这会使其跳转到
NtMapViewOfSection_SystemCall_10_0_17763
标签。 -
然后,SSN 设置为
0028h
-
最终跳转到
NtMapViewOfSection_Epilogue
标签,在那里执行剩余的系统调用指令。回忆一下,系统调用函数的格式如下:
mov r10, rcx mov eax, SSN syscall ret
三、SysWhispers2分析
SysWhispers2采用了名为“按系统调用地址排序”的方法。此方法消除了汇编指令在运行时手动选择 SSN 的需要,从而缩小了系统调用存根。 按系统调用地址排序
按系统调用地址排序是一种在运行时获取系统调用 SSN 的方法。此方法通过查找所有以 “Zw
” 开头的系统调用并将其地址存储在一个数组中,然后按升序(最小地址到最大地址)对这些地址进行排序来实现。SSN 将成为存储在数组中系统调用的索引。
举例说明:
将系统调用号(SSN)作为数组的索引:由于这些系统调用的地址已经按升序排列,现在可以将它们的“索引”作为它们的 SSN。例如,假设我们为这些地址分配一个索引:索引 0 对应 ZwCreateFile 地址 0x7FFD12345678 索引 1 对应 ZwQuerySystemInformation 地址 0x7FFD12345690 索引 2 对应 ZwReadFile 地址 0x7FFD12345700 索引 3 对应 ZwWriteFile 地址 0x7FFD12345750 在这种情况下,SSN 就是这个数组的索引。因此:SSN 为 0 的系统调用是 ZwCreateFile SSN 为 1 的系统调用是 ZwQuerySystemInformation SSN 为 2 的系统调用是 ZwReadFile SSN 为 3 的系统调用是 ZwWriteFile
https://github.com/jthuraisamy/SysWhispers2/blob/main/example-output/Syscalls.c
SW2_PopulateSyscallList
函数对系统调用地址进行排序,该函数 获取 NTDLL 的基地址和其导出目录。利用该信息,它 计算导出函数的 VA(地址、名称、序号) ,接下来,SysWhispers2 检查导出的函数名称,寻找以 Zw
为前缀的函数名称。这些函数名称会被哈希,并与它们的地址一起保存在 数组中。之后,SW2_PopulateSyscallList
会按 升序对收集到的地址进行排序
为了找到系统调用的 SSN,SW2_GetSyscallNumber函数会获取目标系统调用名称的哈希,并返回索引,表示在数组中找到此系统调用哈希的位置。此索引值是系统调用的 SSN
SysWhispers2 用于为 NtMapViewOfSection 生成直接系统调用。
.data currentHash DWORD 0.code EXTERN SW2_GetSyscallNumber: PROCWhisperMain PROCpop raxmov [rsp+ 8], rcx ; 保存寄存器。mov [rsp+16], rdxmov [rsp+24], r8mov [rsp+32], r9sub rsp, 28hmov ecx, currentHashcall SW2_GetSyscallNumberadd rsp, 28hmov rcx, [rsp+ 8] ; 还原寄存器。mov rdx, [rsp+16]mov r8, [rsp+24]mov r9, [rsp+32]mov r10, rcxsyscall ; 发出系统调用ret WhisperMain ENDPNtMapViewOfSection PROCmov currentHash, 060C9AE95h ; 将函数哈希加载到全局变量中。call WhisperMain ; 将函数哈希解析为系统调用号并进行调用 NtMapViewOfSection ENDPend
060C9AE95h
是 ZwMapViewOfSection
字符串的十六进制哈希值。调用 NtMapViewOfSection
时,会先将该哈希值加载到全局变量 currentHash
中,然后调用 WhisperMain
。WhisperMain
函数负责调用前面介绍的 C 函数 SW2_GetSyscallNumber
,该函数将使用哈希值为 currentHash
的系统调用返回 SSN
mov [rsp+XX], XXX
指令用于在调用 SW2_GetSyscallNumber
之前将寄存器保存到堆栈中,而 mov XXX, [rsp+ XX]
指令用于将寄存器恢复到调用 SW2_GetSyscallNumber
之前的状态。之所以需要这样做,是因为调用 SW2_GetSyscallNumber
会更改这些寄存器的值。最后,在 WhisperMain
函数的末尾,出现了常见的系统调用指令:
mov r10, rcx syscall ret
请注意,这里缺少 mov eax, SSN
指令。这是因为当一个函数被调用时,其返回输出会存储在 eax
寄存器中。由于在这些指令之前已经调用了 SW2_GetSyscallNumber
,这意味着 SSN 已经存储在 eax
寄存器中。