-
-
[翻译]系统服务0和1去哪里了?
-
发表于: 2020-9-20 20:28 9494
-
Windows上的系统调用是通过Ntdll.dll来调用的。发生系统的调用的地方都会有一条syscall(x64)或者 sysenter(x86) cpu指令,比如说下面输出的NtCreateFile函数的反汇编
1 2 3 4 5 6 7 8 9 10 | 0 : kd> u ntdll!NtCreateFile ntdll!NtCreateFile: 00007ffa `f67fcb50 4c8bd1 mov r10,rcx 00007ffa `f67fcb53 b855000000 mov eax, 55h 00007ffa `f67fcb58 f604250803fe7f01 test byte ptr [SharedUserData + 0x308 ( 00000000 ` 7ffe0308 )], 1 00007ffa `f67fcb60 7503 jne ntdll!NtCreateFile + 0x15 ( 00007ffa `f67fcb65) 00007ffa `f67fcb62 0f05 syscall 00007ffa `f67fcb64 c3 ret 00007ffa `f67fcb65 cd2e int 2Eh 00007ffa `f67fcb67 c3 ret |
其中最重要的指令是mov eax,55h和syscall。EAX的值就是系统服务号,在这个例子中是0x55。syscall下面的另一条分支一般不会执行。syscall指令会转到系统服务派遣函数(负责调度到执行体里真正的系统调用实现)。我不会在这里进一步讨论细节,但最终EAX寄存器会被用来索引系统服务派遣表,每一个系统服务号都对应着一个实际的服务例程。
在64位系统上,SSDT可以通过内核调试器使用nt!KiServiceTable符号查看:
1 2 3 4 5 6 7 8 9 | 0 : kd> dd nt!KiServiceTable fffff803` 4e224c50 fced7204 fcf77b00 02b9be02 0474a000 fffff803` 4e224c60 01cf0a00 fda01f00 01c07705 01c3cc06 fffff803` 4e224c70 02216f05 028b9401 028d8b00 01a9a400 fffff803` 4e224c80 01e33200 01c2b900 028d2700 01ccbc00 fffff803` 4e224c90 02227f01 01bfb001 02988600 01feb702 fffff803` 4e224ca0 01a8a600 01e16f00 01d17501 01cf6402 fffff803` 4e224cb0 022c2d02 01f57101 01fc9601 0289ff05 fffff803` 4e224cc0 02298b00 028eb003 0236bd00 0461ab00 |
你可能认为SSDT中的值是一个指针,直接指向系统服务函数。(没错,x86系统上使用这个方案)。在64系统上,他是一个32位的值,用于表示与SSDT起始地址的偏移值。然而,这个偏移不包含最低4位(16进制里的最低位),他表示系统调用的参数个数。
让我们来看看这对于NtCreateFile是否成立。我们从用户模式看到他的服务号是0x55,为了得到实际的偏移,我们需要做一个简单的计算。
1 2 | 0 : kd> dd nt!KiServiceTable + 55 * 4 L1 fffff803` 4e224da4 020ba907 |
现在我们要用这个偏移(去除低4位),加上SSDT地址应该就指向NtCreateFile了。
1 2 3 4 5 6 7 8 9 10 | 0 : kd> u nt!KiServiceTable + 020ba90 nt!NtCreateFile: fffff803` 4e4306e0 4881ec88000000 sub rsp, 88h fffff803` 4e4306e7 33c0 xor eax,eax fffff803` 4e4306e9 4889442478 mov qword ptr [rsp + 78h ],rax fffff803` 4e4306ee c744247020000000 mov dword ptr [rsp + 70h ], 20h fffff803` 4e4306f6 89442468 mov dword ptr [rsp + 68h ],eax fffff803` 4e4306fa 4889442460 mov qword ptr [rsp + 60h ],rax fffff803` 4e4306ff 89442458 mov dword ptr [rsp + 58h ],eax fffff803` 4e430703 8b8424e0000000 mov eax,dword ptr [rsp + 0E0h ] |
确实如此!那么参数个数呢?低四位存储的值是7。这是NtCreateFile的原型(WDK里文档化的版本是ZwCreateFile):
1 2 3 4 5 6 7 8 9 10 11 12 13 | NTSTATUS NtCreateFile( OUT PHANDLE FileHandle, IN ACCESS_MASK DesiredAccess, IN POBJECT_ATTRIBUTES ObjectAttributes, OUT PIO_STATUS_BLOCK IoStatusBlock, IN PLARGE_INTEGER AllocationSize, IN ULONG FileAttributes, IN ULONG ShareAccess, IN ULONG CreateDisposition, IN ULONG CreateOptions, IN PVOID EaBuffer, IN ULONG EaLength ); |
可以明显看到,这里有11个参数,不是7个。为什么会出现这种差异呢?实际上这里存储的值的大小表示通过栈传递进来的参数个数。在x64调用约定中,前四个参数依次使用这几个寄存器:RCX,RDX,R8,R9来传递。
回到我们的博客的标题。这里让我们再次看看SSDT的前几项。
1 2 3 | 0 : kd> dd nt!KiServiceTable fffff803` 4e224c50 fced7204 fcf77b00 02b9be02 0474a000 fffff803` 4e224c60 01cf0a00 fda01f00 01c07705 01c3cc06 |
前两项看起来很特殊,有着较大的数值。让我们用刚才的逻辑验证一下第一个值(索引0)
1 2 3 | 0 : kd> u nt!KiServiceTable + fced720 fffff803` 5df12370 ?? ??? ^ Memory access error in 'u nt!KiServiceTable+fced720' |
很明显不对。因为这个值事实上是个负数(二进制补码),所以我们需要符号扩展到64bit,然后再进行加法运算(和之前一样移除低4位):
1 2 3 4 5 6 7 8 9 10 | 0 : kd> u nt!KiServiceTable + ffffffff`ffced720 nt!NtAccessCheck: fffff803` 4df12370 4c8bdc mov r11,rsp fffff803` 4df12373 4883ec68 sub rsp, 68h fffff803` 4df12377 488b8424a8000000 mov rax,qword ptr [rsp + 0A8h ] fffff803` 4df1237f 4533d2 xor r10d,r10d fffff803` 4df12382 458853f0 mov byte ptr [r11 - 10h ],r10b fffff803` 4df12386 498943e8 mov qword ptr [r11 - 18h ],rax fffff803` 4df1238a 488b8424a0000000 mov rax,qword ptr [rsp + 0A0h ] fffff803` 4df12392 498943e0 mov qword ptr [r11 - 20h ],rax |
可以看到这个函数是NtAccessCheck。这个函数的实现低于SSDT的起始地址。让我们尝试用索引1再练习一次。
1 2 3 4 5 6 7 8 9 10 | 0 : kd> u nt!KiServiceTable + ffffffff`ffcf77b0 nt!NtWorkerFactoryWorkerReady: fffff803` 4df1c400 4c8bdc mov r11,rsp fffff803` 4df1c403 49895b08 mov qword ptr [r11 + 8 ],rbx fffff803` 4df1c407 49897318 mov qword ptr [r11 + 18h ],rsi fffff803` 4df1c40b 57 push rdi fffff803` 4df1c40c 4883ec50 sub rsp, 50h fffff803` 4df1c410 498363d000 and qword ptr [r11 - 30h ], 0 fffff803` 4df1c415 33c0 xor eax,eax fffff803` 4df1c417 498943d8 mov qword ptr [r11 - 28h ],rax |
我们可以得到系统服务1:NtWorkerFactoryWorkerReady
对于那些喜欢Windbg脚本的人来说,可以写一个脚本来很好的显示所有的系统调用函数和他们的索引了。
赞赏
- 定位Windows分页结构内存区域 6942
- [原创]2022年,工业级EDR绕过蓝图 28438
- [分享]《探索现代化C++》泛读笔记摘要20 完! 7317
- [分享]《探索现代化C++》泛读笔记摘要19 7566
- [原创]摘微过滤驱动回调的研究-续 10171