原文
窗口上的系统调用通过,每个由系统调用(x64)或sysenter(x86)CPU指令调用的NTDLL.dll,如NTDLL的NtCreateFile的以下输出所示:
这里
0:000> u
ntdll!NtCreateFile:
00007ffc`c07fcb50 4c8bd1 mov r10,rcx
00007ffc`c07fcb53 b855000000 mov eax,55h
00007ffc`c07fcb58 f604250803fe7f01 test byte ptr [SharedUserData+0x308 (00000000`7ffe0308)],1
00007ffc`c07fcb60 7503 jne ntdll!NtCreateFile+0x15 (00007ffc`c07fcb65)
00007ffc`c07fcb62 0f05 syscall
00007ffc`c07fcb64 c3 ret
00007ffc`c07fcb65 cd2e int 2Eh
00007ffc`c07fcb67 c3 ret
EAX的值是系统服务编号(在本例中为0x55).接着是系统调用指令(测试的条件一般没有分支).
系统调用(syscall)导致内核传输到负责分发到执行器中的实际系统调用实现的系统服务分发器例程.最终,必须将EAX寄存器作为,每个系统服务编号(索引)都指向实际例程的系统服务分发表(SSDT)的查找索引.
在x64版本的窗口上,SSDT在内核调试器中的nt!KiServiceTable符号中:
lkd> dd nt!KiServiceTable
fffff804`13c3ec20 fced7204 fcf77b00 02b94a02 04747400
fffff804`13c3ec30 01cef300 fda01f00 01c06005 01c3b506
fffff804`13c3ec40 02218b05 0289df01 028bd600 01a98d00
fffff804`13c3ec50 01e31b00 01c2a200 028b7200 01cca500
fffff804`13c3ec60 02229b01 01bf9901 0296d100 01fea002
你可能期望SSDT中的值是,直接指向系统服务(这是x86系统上使用的方案)的64位指针.在x64上,这些值为32位,它们是SSDT自身开头的偏移.
但是,偏移不包括最后十六进制数字(4位):最后值是系统调用的参数个数.
来看看这是否适合NtCreateFile.正如用户模式,它的服务编号是0x55的,因此要取得实际的偏移,需要简单计算:
kd> dd nt!KiServiceTable+55*4 L1
fffff804`13c3ed74 020b9207
现在,需要取(不带最后十六进制数字的)此偏移,在SSDT中添加它,它应该指向NtCreateFile:
lkd> u nt!KiServiceTable+020b920
nt!NtCreateFile:
fffff804`13e4a540 4881ec88000000 sub rsp,88h
fffff804`13e4a547 33c0 xor eax,eax
fffff804`13e4a549 4889442478 mov qword ptr [rsp+78h],rax
fffff804`13e4a54e c744247020000000 mov dword ptr [rsp+70h],20h
事实上,这是NtCreateFile.参数个数呢?存储的值是7.下面是NtCreateFile的原型(在WDK中记录为ZwCreateFile):
NTSTATUS NtCreateFile(PHANDLE FileHandle,ACCESS_MASK DesiredAccess,POBJECT_ATTRIBUTES ObjectAttributes,PIO_STATUS_BLOCK IoStatusBlock,PLARGE_INTEGER AllocationSize,ULONG FileAttributes,ULONG ShareAccess,ULONG CreateDisposition,ULONG CreateOptions,PVOID EaBuffer,ULONG EaLength);
显然,有11个参数,而不仅是7个.为什么会有差异?存储的值是使用栈传递的参数个数.在x64调用约定中,前4个参数使用寄存器传递:RCX,RDX,R8,R9(按此顺序).
现在回到文章标题.下面是SSDT中的前几个项:
lkd> dd nt!KiServiceTable
fffff804`13c3ec20 fced7204 fcf77b00 02b94a02 04747400
fffff804`13c3ec30 01cef300 fda01f00 01c06005 01c3b506
前两个项的数字要大得多.试对第一个值(索引0)应用相同逻辑:
kd> u nt!KiServiceTable+fced720
fffff804`2392c340 ??
???^ Memory access error in 'u nt!KiServiceTable+fced720'
(按二进制补码)该值实际上是一个负值,因此需要符号扩展到64位,然后加起来(如前省略最后十六进制数字):
kd> u nt!KiServiceTable+ffffffff`ffced720
nt!NtAccessCheck:
fffff804`1392c340 4c8bdc mov r11,rsp
fffff804`1392c343 4883ec68 sub rsp,68h
fffff804`1392c347 488b8424a8000000 mov rax,qword ptr [rsp+0A8h]
这是NtAccessCheck.该函数的实现在比SSDT自身更低的地址中.来试用1索引执行相同练习:
kd> u nt!KiServiceTable+ffffffff`ffcf77b0
nt!NtWorkerFactoryWorkerReady:
fffff804`139363d0 4c8bdc mov r11,rsp
fffff804`139363d3 49895b08 mov qword ptr [r11+8],rbx
现在得到1系统调用号:NtWorkerFactoryWorkerReady.
对那些喜欢WinDbg脚本的人,编写一个脚本来很好地显示所有系统调用函数及其索引.