起因
前几天有朋友遇到一个问题来问我。他有一个Windows 7 x64下的minidump文件(http://pan.baidu.com/s/1dD524hz),经过初步分析,知道系统崩溃最初是因用户态调用了NtDeviceIoControlFile造成的,KeBugCheckEx调用已经位于多重函数调用的深处,问如何找到最初NtDeviceIoControlFile的参数,尤其是头4个参数。经过一番努力,我总算是把问题解决了,其中得到一些领悟,想趁热记录,也想趁这个机会将内核服务调用的基本技术点梳理一下。
在开始针对问题开始讨论之前,我们先要补习一下Windows x64下函数调用的基本知识。
x64函数调用堆栈
x64下,不再使用x86下以“pushebp/mov ebp, esp”为特征的堆栈帧格式。取而代之的是如下图所示格局:
函数调用的头4个参数虽然在堆栈中预留了位置,但参数值并不真的存储在那里。实际上从第1个参数到第4个参数是依次是使用rcx、rdx、r8、r9(浮点数情况下依次对应:xmm0、xmm1、xmm2、xmm3)。几乎(注意是:几乎)所有函数在头几条指令范围内就通过sub rsp, xxh的形式准备好了本函数用到的堆栈空间,之后直到函数返回,此函数不再进行改变rsp的任何操作。也就是说函数的堆栈是“一步到位”的。这样一步到位的好处是避免堆栈指针的反复调整,从而有效提高代码执行效率。其副作用是:头4个参数不在堆栈上,不利于追踪;没有特殊的ebp堆栈基址(rbp不再使用),堆栈回溯较难。在函数调用时,rdi、rsi、rbx、rbx、rbp、r12、r13、r14、r15是所谓的非容失性(nonvolatile)寄存器。也就是说,在函数调用过程中,被调用函数必须保证这些寄存器在函数返回时和进入函数时是一样的。
某处代码在调用某个函数时,会将头4个参数分别存入相应寄存器,如果超过4个参数,会在位于rsp+20h的地方存入第5个参数。随后call指令会将返回地址压入堆栈,造成rsp减8。此时第1个参数对应位置是rsp+8,第4个参数对应位置是rsp+20h。随后,一般的函数会一次性sub rsp, xxh,其中xxh不同函数有所不同。通过IDA Pro进行观察会发现,函数内部代码对局部变量和参数的访问,都翻译成[rsp+xxh+yyh]和形式,其中yyh是数据相对于函数第1条指令将要执行而尚未执行时rsp的指针位置(当然返回地址位置是rsp+0)。
补习完基础知识,我们就可以开始研究问题了。当然在开始之前,要启动我们的Windbg,装入dump文件,下载并装入相应符号。
第5个及以后的参数
首先NtDeviceIoControlFile的原型是:
NTSTATUS WINAPI NtDeviceIoControlFile(
_In_ HANDLE FileHandle,
_In_ HANDLE Event,
_In_ PIO_APC_ROUTINE ApcRoutine,
_In_ PVOID ApcContext,
_Out_ PIO_STATUS_BLOCK IoStatusBlock,
_In_ ULONG IoControlCode,
_In_ PVOID InputBuffer,
_In_ ULONG InputBufferLength,
_Out_ PVOID OutputBuffer,
_In_ ULONG OutputBufferLength
);
毫无疑问,第5个及以后的参数存储在堆栈中。我们使用kv来观察堆栈:
1:kd> kv
Child-SP RetAddr : Args to Child : Call Site
fffff880`0253ff58 fffff800`03f577ab : 00000000`0000001e ffffffff`c0000005 fffff880`0254001000000000`00000000 : nt!KeBugCheckEx
fffff880`0253ff60 fffff800`03f16118 : 00000000`00000001 00000000`00000000 fffffa80`03306b30fffff880`02540920 : nt!KipFatalFilter+0x1b
fffff880`0253ffa0 fffff800`03eee89c : 00000000`00000000 fffffa80`03ee0da0 fffff8a0`00262450fffff8a0`00262140 : nt! ?? ::FNODOBFM::`string'+0x83d
fffff880`0253ffe0 fffff800`03eee31d : fffff800`0400f30c fffff880`02541610 00000000`00000000fffff800`03e50000 : nt!_C_specific_handler+0x8c
fffff880`02540050 fffff800`03eed0f5 : fffff800`0400f30c fffff880`025400c8 fffff880`02540f38fffff800`03e50000 : nt!RtlpExecuteHandlerForException+0xd
fffff880`02540080 fffff800`03efe081 : fffff880`02540f38 fffff880`02540790 fffff880`0000000000000000`00000000 : nt!RtlDispatchException+0x415
fffff880`02540760 fffff800`03ec20c2 : fffff880`02540f38 fffffa80`02884010 fffff880`02540fe000000000`00000010 : nt!KiDispatchException+0x135
fffff880`02540e00 fffff800`03ec0c3a : 00000000`00000001 00000000`00000018 00000000`00000000ffff