系统调用之SysWhispers

一、介绍

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 PROC
	mov rax, gs:[60h]                             ; 将 PEB 加载到 RAX 中。
NtMapViewOfSection_Check_X_X_XXXX:                ; 检查主版本号。
	cmp dword ptr [rax+118h], 5
	je  NtMapViewOfSection_SystemCall_5_X_XXXX
	cmp dword ptr [rax+118h], 6
	je  NtMapViewOfSection_Check_6_X_XXXX
	cmp dword ptr [rax+118h], 10
	je  NtMapViewOfSection_Check_10_0_XXXX
	jmp NtMapViewOfSection_SystemCall_Unknown
NtMapViewOfSection_Check_6_X_XXXX:               ; 检查 Windows Vista/7/8 的次版本号。
	cmp dword ptr [rax+11ch], 0
	je  NtMapViewOfSection_Check_6_0_XXXX
	cmp dword ptr [rax+11ch], 1
	je  NtMapViewOfSection_Check_6_1_XXXX
	cmp dword ptr [rax+11ch], 2
	je  NtMapViewOfSection_SystemCall_6_2_XXXX
	cmp dword ptr [rax+11ch], 2
	je  NtMapViewOfSection_SystemCall_6_3_XXXX
	jmp NtMapViewOfSection_SystemCall_Unknown
NtMapViewOfSection_Check_6_0_XXXX:               ; 检查 Windows Vista 的内部版本号。
	cmp dword ptr [rax+120h], 6000
	je  NtMapViewOfSection_SystemCall_6_0_6000
	cmp dword ptr [rax+120h], 6001
	je  NtMapViewOfSection_SystemCall_6_0_6001
	cmp dword ptr [rax+120h], 6002
	je  NtMapViewOfSection_SystemCall_6_0_6002
	jmp NtMapViewOfSection_SystemCall_Unknown
NtMapViewOfSection_Check_6_1_XXXX:               ; 检查 Windows 7 的内部版本号。
	cmp dword ptr [rax+120h], 7600
	je  NtMapViewOfSection_SystemCall_6_1_7600
	cmp dword ptr [rax+120h], 7601
	je  NtMapViewOfSection_SystemCall_6_1_7601
	jmp NtMapViewOfSection_SystemCall_Unknown
NtMapViewOfSection_Check_10_0_XXXX:              ; 检查 Windows 10 的内部版本号。
	cmp dword ptr [rax+120h], 10240
	je  NtMapViewOfSection_SystemCall_10_0_10240
	cmp dword ptr [rax+120h], 10586
	je  NtMapViewOfSection_SystemCall_10_0_10586
	cmp dword ptr [rax+120h], 14393
	je  NtMapViewOfSection_SystemCall_10_0_14393
	cmp dword ptr [rax+120h], 15063
	je  NtMapViewOfSection_SystemCall_10_0_15063
	cmp dword ptr [rax+120h], 16299
	je  NtMapViewOfSection_SystemCall_10_0_16299
	cmp dword ptr [rax+120h], 17134
	je  NtMapViewOfSection_SystemCall_10_0_17134
	cmp dword ptr [rax+120h], 17763
	je  NtMapViewOfSection_SystemCall_10_0_17763
	cmp dword ptr [rax+120h], 18362
	je  NtMapViewOfSection_SystemCall_10_0_18362
	cmp dword ptr [rax+120h], 18363
	je  NtMapViewOfSection_SystemCall_10_0_18363
	jmp NtMapViewOfSection_SystemCall_Unknown
NtMapViewOfSection_SystemCall_5_X_XXXX:          ; Windows XP 和 Server 2003
	mov eax, 0025h
	jmp NtMapViewOfSection_Epilogue
NtMapViewOfSection_SystemCall_6_0_6000:          ; Windows Vista SP0
	mov eax, 0025h
	jmp NtMapViewOfSection_Epilogue
NtMapViewOfSection_SystemCall_6_0_6001:          ; Windows Vista SP1 和 Server 2008 SP0
	mov eax, 0025h
	jmp NtMapViewOfSection_Epilogue
NtMapViewOfSection_SystemCall_6_0_6002:          ; Windows Vista SP2 和 Server 2008 SP2
	mov eax, 0025h
	jmp NtMapViewOfSection_Epilogue
NtMapViewOfSection_SystemCall_6_1_7600:          ; Windows 7 SP0
	mov eax, 0025h
	jmp NtMapViewOfSection_Epilogue
NtMapViewOfSection_SystemCall_6_1_7601:          ; Windows 7 SP1 和 Server 2008 R2 SP0
	mov eax, 0025h
	jmp NtMapViewOfSection_Epilogue
NtMapViewOfSection_SystemCall_6_2_XXXX:          ; Windows 8 和 Server 2012
	mov eax, 0026h
	jmp NtMapViewOfSection_Epilogue
NtMapViewOfSection_SystemCall_6_3_XXXX:          ; Windows 8.1 和 Server 2012 R2
	mov eax, 0027h
	jmp NtMapViewOfSection_Epilogue
NtMapViewOfSection_SystemCall_10_0_10240:        ; Windows 10.0.10240 (1507)
	mov eax, 0028h
	jmp NtMapViewOfSection_Epilogue
NtMapViewOfSection_SystemCall_10_0_10586:        ; Windows 10.0.10586 (1511)
	mov eax, 0028h
	jmp NtMapViewOfSection_Epilogue
NtMapViewOfSection_SystemCall_10_0_14393:        ; Windows 10.0.14393 (1607)
	mov eax, 0028h
	jmp NtMapViewOfSection_Epilogue
NtMapViewOfSection_SystemCall_10_0_15063:        ; Windows 10.0.15063 (1703)
	mov eax, 0028h
	jmp NtMapViewOfSection_Epilogue
NtMapViewOfSection_SystemCall_10_0_16299:        ; Windows 10.0.16299 (1709)
	mov eax, 0028h
	jmp NtMapViewOfSection_Epilogue
NtMapViewOfSection_SystemCall_10_0_17134:        ; Windows 10.0.17134 (1803)
	mov eax, 0028h
	jmp NtMapViewOfSection_Epilogue
NtMapViewOfSection_SystemCall_10_0_17763:        ; Windows 10.0.17763 (1809)
	mov eax, 0028h
	jmp NtMapViewOfSection_Epilogue
NtMapViewOfSection_SystemCall_10_0_18362:        ; Windows 10.0.18362 (1903)
	mov eax, 0028h
	jmp NtMapViewOfSection_Epilogue
NtMapViewOfSection_SystemCall_10_0_18363:        ; Windows 10.0.18363 (1909)
	mov eax, 0028h
	jmp NtMapViewOfSection_Epilogue
NtMapViewOfSection_SystemCall_Unknown:           ; 未知/不支持的版本。
	ret
NtMapViewOfSection_Epilogue:
	mov r10, rcx
	syscall
	ret
NtMapViewOfSection ENDP

PEB 结构包含三个可用于确定 Windows 操作系统版本信息成员:

1. OSMajorVersion

  • 类型: DWORD
  • 作用: 该字段存储 Windows 操作系统的主版本号。例如:
    • Windows 7 对应的主版本号是 6
    • Windows 10 对应的主版本号是 10

2. OSMinorVersion

  • 类型: DWORD
  • 作用: 该字段存储 Windows 操作系统的次版本号。例如:
    • Windows 7 SP1 对应的次版本号是 1
    • Windows 8 对应的次版本号是 2
    • Windows 10 对应的次版本号可能为 0 或更高的版本号(取决于 Windows 10 的不同版本)。

3. OSBuildNumber

  • 类型: DWORD
  • 作用: 该字段存储操作系统的构建号,也就是操作系统的具体版本。例如:
    • Windows 7 SP1 的构建号是 7601
    • Windows 10 版本 15063(1703)对应的构建号是 15063
    • Windows 10 版本 1909 对应的构建号是 18363

SysWhispers 生成的 64 位汇编函数使用这些成员跳转到硬编码值 SSN 所在的位置。使用的逻辑本质上是若干 if 和 else if 语句。例如,如果目标计算机是 Windows 10 1809,则出现以下逻辑:

  1. 由于 PEB 的 主版本 成员等于 10,执行 NtMapViewOfSection_Check_10_0_XXXX 标签。

  2. 此标签接着检查系统的 生成版本号。在这个示例中,这个数字是 1809,这会使其跳转到 NtMapViewOfSection_SystemCall_10_0_17763 标签。

  3. 然后,SSN 设置为 0028h

  4. 最终跳转到 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: PROC
    
WhisperMain PROC
    pop rax
    mov [rsp+ 8], rcx             ; 保存寄存器。
    mov [rsp+16], rdx
    mov [rsp+24], r8
    mov [rsp+32], r9
    sub rsp, 28h
    mov ecx, currentHash
    call SW2_GetSyscallNumber
    add rsp, 28h
    mov rcx, [rsp+ 8]             ; 还原寄存器。
    mov rdx, [rsp+16]
    mov r8, [rsp+24]
    mov r9, [rsp+32]
    mov r10, rcx
    syscall                     ; 发出系统调用
    ret
WhisperMain ENDP

NtMapViewOfSection PROC
    mov currentHash, 060C9AE95h ; 将函数哈希加载到全局变量中。
    call WhisperMain            ; 将函数哈希解析为系统调用号并进行调用
NtMapViewOfSection ENDP

end

060C9AE95h 是 ZwMapViewOfSection 字符串的十六进制哈希值。调用 NtMapViewOfSection 时,会先将该哈希值加载到全局变量 currentHash 中,然后调用 WhisperMainWhisperMain 函数负责调用前面介绍的 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 寄存器中。

posted @ 2025-02-08 16:41  aoaoaoao  阅读(220)  评论(0)    收藏  举报