全面解析Windows 10上的Shadow SSDT

lkd> uf win32k!SysEntryGetW32pServiceTable
win32k!SysEntryGetW32pServiceTable:
fffff961`24b21060 488d05997f0100  lea     rax,[win32k!W32pServiceTable (fffff961`24b39000)]
fffff961`24b21067 c3              ret
lkd> uf win32k!SysEntryGetW32pArgumentTable
win32k!SysEntryGetW32pArgumentTable:
fffff961`24b21040 488d057da70100  lea     rax,[win32k!W32pArgumentTable (fffff961`24b3b7c4)]
fffff961`24b21047 c3              ret
lkd> uf win32k!SysEntryGetW32pServiceLimit
win32k!SysEntryGetW32pServiceLimit:
fffff961`24b21050 8b056aa70100    mov     eax,dword ptr [win32k!W32pServiceLimit (fffff961`24b3b7c0)]
fffff961`24b21056 c3              ret

众所周知,Windows 10的Win32子系统驱动由单一的win32k.sys一分为三变成了win32k.sys,win32kfull.sys和win32kbase.sys,其中win32k.sys已经变成了空壳,大部分的内容都是仅仅关于Shadow SSDT的,但具体函数的实现全部丢到了win32kfull.sys里,而win32kfull里则是所有Shadow SSDT函数的实现,另外的win32kbase.sys则是大部分基本函数的的实现,比如ValidateHwnd。接下来全面解析下Windows 10上Shadow SSDT。
在win32k.sys的导出表里,有三个函数分别叫SysEntryGetW32pServiceTable,SysEntryGetW32pServiceLimit和SysEntryGetW32pArgumentTable。这三个函数分别是获取Shadow SSDT的函数表,函数数量和参数表的。其中参数表在修改Win64上的SSSDT时显得尤为重要。
这三个函数虽然是导出的,但编写程序的时候无法直接导入,因为WDK10自带的win32k.lib是不包含这三个函数的,直接声明它会导致链接时错误。因此需要自行解析导出表并分析,或者自己DIY一个导入这三个函数的LIB出来。
这三个函数在Win32上特征如下:

lkd> uf win32k!SysEntryGetW32pServiceTable
win32k!SysEntryGetW32pServiceTable:
9d6f10d0 b80030709d      mov     eax,offset win32k!W32pServiceTable (9d703000)
9d6f10d5 c3              ret
lkd> uf win32k!SysEntryGetW32pArgumentTable
win32k!SysEntryGetW32pArgumentTable:
9d6f10b0 b83846709d      mov     eax,offset win32k!W32pArgumentTable (9d704638)
9d6f10b5 c3              ret
lkd> uf win32k!SysEntryGetW32pServiceLimit
win32k!SysEntryGetW32pServiceLimit:
9d6f10c0 a13446709d      mov     eax,dword ptr [win32k!W32pServiceLimit (9d704634)]
9d6f10c5 c3              ret
lkd> dd win32k!W32pServiceLimit l1
9d704634  00000470

解析指令时注意,mov指令跟的地址是绝对地址,因此根据内存解析的话直接照搬地址即可,若是根据文件解析的话还需要进行地址重定位操作。不过这个W32pServiceLimit也是个指针,因此获取函数数量的时候需要再读一下指针。
在Win64上特征如下:

lkd> uf win32k!SysEntryGetW32pServiceTable
win32k!SysEntryGetW32pServiceTable:
fffff961`24b21060 488d05997f0100  lea     rax,[win32k!W32pServiceTable (fffff961`24b39000)]
fffff961`24b21067 c3              ret
lkd> uf win32k!SysEntryGetW32pArgumentTable
win32k!SysEntryGetW32pArgumentTable:
fffff961`24b21040 488d057da70100  lea     rax,[win32k!W32pArgumentTable (fffff961`24b3b7c4)]
fffff961`24b21047 c3              ret
lkd> uf win32k!SysEntryGetW32pServiceLimit
win32k!SysEntryGetW32pServiceLimit:
fffff961`24b21050 8b056aa70100    mov     eax,dword ptr [win32k!W32pServiceLimit (fffff961`24b3b7c0)]
fffff961`24b21056 c3              ret

解析指令时注意,mov指令跟的地址是相对地址,因此根据无论根据内存解析还是根据文件解析都需要计算它的绝对地址。计算公式如下:
绝对地址=相对地址+指令地址+指令长度。此外,相对地址必须要声明为有符号整数型!
Shadow SSDT的函数表上的所有函数,都存于win32k.sys中,但所有的函数的风格都很简单,只有一行jmp指令
Win32:

win32k!NtUserQueryWindow:
9d6fec1a ff25a01a709d    jmp     dword ptr [win32k!_imp__NtUserQueryWindow (9d701aa0)]

Win64:

win32k!NtUserWindowFromPoint:
fffff961`24b21b30 ff25d2a90000    jmp     qword ptr [win32k!_imp_NtUserWindowFromPoint (fffff961`24b2c508)]

jmp跟的地址,实则就是导入表上的地址。不过仍然需要注意Win32上jmp指令跟的是绝对地址,而Win64上跟的是相对地址。
因此,在Windows 10上,Hook Shadow SSDT有一种很简便的方式,就是直接改掉IAT表的函数地址。这种挂钩方式使得在Windows 10上实现HOOK时无需使用函数索引,可以直接根据函数名去挂钩。这种钩子还有个特殊效果,就是钩子不会在PCHunter的Shadow SSDT的选项卡里显示出来,只会在内核钩子选项卡里显示出来。
此外,在Windows 10上挂钩Shadow SSDT时不可以使用MDL方式绕过只读保护,使用MDL会引起写入只读内存蓝屏,主要原因如下:
Windows 10上的Session段内存有更为特殊的保护属性,使得用MDL进行映射时仍然会映射出带只读属性的虚拟内存。因此如果不想修改CR0的WP标志位的话就需要自己解析页表,再对齐重算映射那段内存才行。单纯的MmGetPhysicalAddress+MmMapIoSpace是不行的,很有可能会出现只有前几个字节被正确写入的情况,具体原因不做解释,详情请自行学习物理内存和虚拟内存的关系。
从Windows 10 Rs1开始,user32.dll和gdi32.dll中需要进入内核进行操作的代码被全部分离到了win32u.dll里。因此从某种程度上说,win32u.dll实际上有和ntdll.dll的大致相同的性质,就是都有用户空间到内核空间的中转,前者是关于SSDT的,后者是关于Shadow SSDT的。换句话说,在Windows 10 Rs1,Rs2上枚举Shadow SSDT不需要再对Index和函数名进行硬编码了。

网传代码中的NtUserWindowFromPoint声明如下:

typedef HWND(*NTUSERWINDOWFROMPOINT)
(
 IN LONG X,
 IN LONG Y
);

在32位操作系统上,这么声明并没有什么太大的问题,但在64位操作系统上会引起调用约定错误。NtUserWindowFromPoint这个函数本质上是只有一个参数的,这个参数是以POINT类型,传值方式传入函数的。因此,正确的声明方式是

typedef HWND(*NTUSERWINDOWFROMPOINT)
(
 IN POINT Point
);

这也充分说明了为啥在Win32上NtUserWindowFromPoint有“两个”参数而Win64上只有一个了。

原创文章,转载请注明: 转载自Windows内核安全驱动编程

本文链接地址: 全面解析Windows 10上的Shadow SSDT

发表评论

您的电子邮箱地址不会被公开。 必填项已用*标注