注意:这文章是
2013-12-9
写的,留了很久现在自己也不做TO C的安全了,就把文章整理发出来,留在硬盘也是烂了而已
今晚回来的时候,刚开电脑启动到用户选择界面处,我还没有输入密码,过了一会就莫名的蓝屏然后重启了,幸好我的电脑设置了核心转储的设置,于是电脑很顺利的把DUMP保存下来了,之后再重启电脑,打开WINDBG看下这个DUMP到底是什么原因倒致蓝屏的。
一般分析内核DUMP,WINDBG会提示你用!anaylze -v来看DUMP的相关信息.在执行上述指令后我们得到了相关的信息
1: kd> !analyze -v
*******************************************************************************
* *
* Bugcheck Analysis *
* *
*******************************************************************************
PAGE_FAULT_IN_NONPAGED_AREA (50)
Invalid system memory was referenced. This cannot be protected by try-except,
it must be protected by a Probe. Typically the address is just plain bad or it
is pointing at freed memory.
Arguments:
Arg1: fffff8a002408000, memory referenced.
Arg2: 0000000000000000, value 0 = read operation, 1 = write operation.
Arg3: fffff88001aed3f0, If non-zero, the instruction address which referenced the bad memory
address.
Arg4: 0000000000000000, (reserved)
Debugging Details:
------------------
READ_ADDRESS: fffff8a002408000 Paged pool
FAULTING_IP:
nvpciflt+13f0
fffff880`01aed3f0 6642391c49 cmp word ptr [rcx+r9*2],bx
MM_INTERNAL_CODE: 0
IMAGE_NAME: nvpciflt.sys
DEBUG_FLR_IMAGE_TIMESTAMP: 52314e29
MODULE_NAME: nvpciflt
FAULTING_MODULE: fffff88001aec000 nvpciflt
DEFAULT_BUCKET_ID: WIN7_DRIVER_FAULT
BUGCHECK_STR: 0x50
PROCESS_NAME: svchost.exe
CURRENT_IRQL: 0
TRAP_FRAME: fffff880043bd720 -- (.trap 0xfffff880043bd720)
NOTE: The trap frame does not contain all registers.
Some register values may be zeroed or incorrect.
rax=fffff8a002407f70 rbx=0000000000000000 rcx=fffff8a002407f80
rdx=fffffa800ab04b50 rsi=0000000000000000 rdi=0000000000000000
rip=fffff88001aed3f0 rsp=fffff880043bd8b0 rbp=fffff880043bdb60
r8=0000000000000000 r9=0000000000000040 r10=fffffa8003f0f148
r11=fffffa800ab04b50 r12=0000000000000000 r13=0000000000000000
r14=0000000000000000 r15=0000000000000000
iopl=0 nv up ei pl nz ac pe nc
nvpciflt+0x13f0:
fffff880`01aed3f0 6642391c49 cmp word ptr [rcx+r9*2],bx ds:fffff8a0`02408000=????
Resetting default scope
LAST_CONTROL_TRANSFER: from fffff80004301be0 to fffff80004283b80
STACK_TEXT:
fffff880`043bd5b8 fffff800`04301be0 : 00000000`00000050 fffff8a0`02408000 00000000`00000000 fffff880`043bd720 : nt!KeBugCheckEx
fffff880`043bd5c0 fffff800`04281cae : 00000000`00000000 fffff8a0`02408000 00000000`00000000 00000000`00000000 : nt! ?? ::FNODOBFM::`string'+0x4518f
fffff880`043bd720 fffff880`01aed3f0 : 00000000`00000000 fffff880`043bdb60 fffff880`043bda60 fffff880`043bd8f8 : nt!KiPageFault+0x16e
fffff880`043bd8b0 fffff800`0462f460 : 00000000`00000000 00000000`00000000 00000000`00000000 fffff8a0`02407f70 : nvpciflt+0x13f0
fffff880`043bd910 fffff800`04504b34 : 00000000`00000002 fffff880`043bda60 00000000`00000001 00000000`00000011 : nt!CmpCallCallBacks+0x1c0
fffff880`043bd9e0 fffff800`04282e13 : 00000000`00000624 fffffa80`0ab04b50 00000000`00000000 00000000`00000000 : nt!NtDeleteValueKey+0x2b3
fffff880`043bdae0 00000000`77c91e4a : 00000000`00000000 00000000`00000000 00000000`00000000 00000000`00000000 : nt!KiSystemServiceCopyEnd+0x13
00000000`01d3e538 00000000`00000000 : 00000000`00000000 00000000`00000000 00000000`00000000 00000000`00000000 : 0x77c91e4a
STACK_COMMAND: kb
FOLLOWUP_IP:
nvpciflt+13f0
fffff880`01aed3f0 6642391c49 cmp word ptr [rcx+r9*2],bx
SYMBOL_STACK_INDEX: 3
SYMBOL_NAME: nvpciflt+13f0
FOLLOWUP_NAME: MachineOwner
FAILURE_BUCKET_ID: X64_0x50_nvpciflt+13f0
BUCKET_ID: X64_0x50_nvpciflt+13f0
Followup: MachineOwner
---------
从这些信息来看是因为CPU在读取一个无效的地址倒致引起一个PAGE FAULT从而系统挂掉了.
这个PAGE_FAULT_IN_NONPAGED_AREA的代号相关描述MS是这样定义的:
The PAGE_FAULT_IN_NONPAGED_AREA bug check has a value of 0x00000050.This indicates that invalid system memory has been referenced.
上述的说明也很清楚的说到了是引用到了无效的系统内存.
好了有了这些基本的信息我们再进一步去获取更有用的信息.接下来我们想知道到底是什么进程引起这个蓝屏的.我们可以通过!pcr这个命令获取当前CPU的相关信息.由于我的电脑是双核的, 从上面的DUMP信息我们也看到了1: kd>.这里的1就是当前是我的CPU 1正在运行.于是我们输入!pcr 1看下当前这个CPU的信息
1: kd> !pcr 1
KPCR for Processor 1 at fffff88004900000:
Major 1 Minor 1
NtTib.ExceptionList: fffff8800490b540
NtTib.StackBase: fffff88004904f40
NtTib.StackLimit: 0000000001d3e538
NtTib.SubSystemTib: fffff88004900000
NtTib.Version: 0000000004900180
NtTib.UserPointer: fffff880049007f0
NtTib.SelfTib: 000007fffff86000
SelfPcr: 0000000000000000
Prcb: fffff88004900180
Irql: 0000000000000000
IRR: 0000000000000000
IDR: 0000000000000000
InterruptMode: 0000000000000000
IDT: 0000000000000000
GDT: 0000000000000000
TSS: 0000000000000000
CurrentThread: fffffa800ab04b50
NextThread: 0000000000000000
IdleThread: fffff8800490afc0
DpcQueue:
上面的信息记录了当前CPU的很多有用的状态,其中CurrentThread这个字段是我们要关心的,这个说明当前CPU正在某个线程里面跑.这个线程里面有什么信息呢?我们可以通过输入!thread fffffa800ab04b50,来得到当前CPU正在执行的栈的信息.
1: kd> !thread fffffa800ab04b50
THREAD fffffa800ab04b50 Cid 0364.050c Teb: 000007fffff86000 Win32Thread: 0000000000000000 RUNNING on processor 1
Not impersonating
DeviceMap fffff8a000008aa0
Owning Process fffffa80078b4b30 Image: svchost.exe
Attached Process N/A Image: N/A
Wait Start TickCount 4313 Ticks: 0
Context Switch Count 3239 IdealProcessor: 2
UserTime 00:00:00.046
KernelTime 00:00:00.109
Win32 Start Address 0x0000000077c5fbf0
Stack Init fffff880043bdc70 Current fffff880043bd680
Base fffff880043be000 Limit fffff880043b8000 Call 0
Priority 8 BasePriority 8 UnusualBoost 0 ForegroundBoost 0 IoPriority 2 PagePriority 5
Child-SP RetAddr : Args to Child : Call Site
fffff880`043bd5b8 fffff800`04301be0 : 00000000`00000050 fffff8a0`02408000 00000000`00000000 fffff880`043bd720 : nt!KeBugCheckEx
fffff880`043bd5c0 fffff800`04281cae : 00000000`00000000 fffff8a0`02408000 00000000`00000000 00000000`00000000 : nt! ?? ::FNODOBFM::`string'+0x4518f
fffff880`043bd720 fffff880`01aed3f0 : 00000000`00000000 fffff880`043bdb60 fffff880`043bda60 fffff880`043bd8f8 : nt!KiPageFault+0x16e (TrapFrame @ fffff880`043bd720)
fffff880`043bd8b0 fffff800`0462f460 : 00000000`00000000 00000000`00000000 00000000`00000000 fffff8a0`02407f70 : nvpciflt+0x13f0
fffff880`043bd910 fffff800`04504b34 : 00000000`00000002 fffff880`043bda60 00000000`00000001 00000000`00000011 : nt!CmpCallCallBacks+0x1c0
fffff880`043bd9e0 fffff800`04282e13 : 00000000`00000624 fffffa80`0ab04b50 00000000`00000000 00000000`00000000 : nt!NtDeleteValueKey+0x2b3
fffff880`043bdae0 00000000`77c91e4a : 00000000`00000000 00000000`00000000 00000000`00000000 00000000`00000000 : nt!KiSystemServiceCopyEnd+0x13 (TrapFrame @ fffff880`043bdae0)
00000000`01d3e538 00000000`00000000 : 00000000`00000000 00000000`00000000 00000000`00000000 00000000`00000000 : 0x77c91e4a
确实这个过程和用!analyze -v分析的栈信息一致.如果我们还在怀疑的话我们可以读取下当前CPU的CR3寄存器,看下当前CR3这个寄存器里的值是否和这个进程一致
1: kd> r cr3
cr3=000000007ac7e0001: kd> !process fffffa80078b4b30 2
PROCESS fffffa80078b4b30
SessionId: 0 Cid: 0364 Peb: 7fffffdf000 ParentCid: 02a8
DirBase:7ac7e000 ObjectTable: fffff8a001530870 HandleCount: 388.
Image: svchost.exe
可以看到当前CPU确实在响应这个svchost.exe这个进程的操作.这个栈是可信的.接下我们就要看这个进程为什么引起蓝屏?我们可以看下整个栈回溯,我们可以通过kv这个命令来看下
1: kd> kv
Child-SP RetAddr : Args to Child : Call Site
fffff880`043bd5b8 fffff800`04301be0 : 00000000`00000050 fffff8a0`02408000 00000000`00000000 fffff880`043bd720 : nt!KeBugCheckEx
fffff880`043bd5c0 fffff800`04281cae : 00000000`00000000 fffff8a0`02408000 00000000`00000000 00000000`00000000 : nt! ?? ::FNODOBFM::`string'+0x4518f
fffff880`043bd720 fffff880`01aed3f0 : 00000000`00000000 fffff880`043bdb60 fffff880`043bda60 fffff880`043bd8f8 : nt!KiPageFault+0x16e (TrapFrame @ fffff880`043bd720)
fffff880`043bd8b0 fffff800`0462f460 : 00000000`00000000 00000000`00000000 00000000`00000000 fffff8a0`02407f70 : nvpciflt+0x13f0
fffff880`043bd910 fffff800`04504b34 : 00000000`00000002 fffff880`043bda60 00000000`00000001 00000000`00000011 : nt!CmpCallCallBacks+0x1c0
fffff880`043bd9e0 fffff800`04282e13 : 00000000`00000624 fffffa80`0ab04b50 00000000`00000000 00000000`00000000 : nt!NtDeleteValueKey+0x2b3
fffff880`043bdae0 00000000`77c91e4a : 00000000`00000000 00000000`00000000 00000000`00000000 00000000`00000000 : nt!KiSystemServiceCopyEnd+0x13 (TrapFrame @ fffff880`043bdae0)
00000000`01d3e538 00000000`00000000 : 00000000`00000000 00000000`00000000 00000000`00000000 00000000`00000000 : 0x77c91e4a
从这个栈来看svchost.exe进程在执行一个删除注册表键值的操作.于时我们看下这个操作触发错误是指令是什么。
我们如果仔细的话我们可以注意到上面的回溯中有
fffff880`043bd720 fffff880`01aed3f0 : 00000000`00000000 fffff880`043bdb60 fffff880`043bda60 fffff880`043bd8f8 : nt!KiPageFault+0x16e (TrapFrame @ fffff880`043bd720)这么一个语句,这个语句后面有个TrapFrame的提示,这个结构里面才是保存当时CPU执行的指令引起崩溃的地方,我们看下里面是什么内容,我们可以用.trap fffff880`043bd720 查看下.
1: kd> .trap fffff880`043bd720
NOTE: The trap frame does not contain all registers.
Some register values may be zeroed or incorrect.
rax=fffff8a002407f70 rbx=0000000000000000 rcx=fffff8a002407f80
rdx=fffffa800ab04b50 rsi=0000000000000000 rdi=0000000000000000
rip=fffff88001aed3f0 rsp=fffff880043bd8b0 rbp=fffff880043bdb60
r8=0000000000000000 r9=0000000000000040 r10=fffffa8003f0f148
r11=fffffa800ab04b50 r12=0000000000000000 r13=0000000000000000
r14=0000000000000000 r15=0000000000000000
iopl=0 nv up ei pl nz ac pe nc
nvpciflt+0x13f0:
fffff880`01aed3f0 6642391c49 cmp word ptr [rcx+r9*2],bx ds:fffff8a0`02408000=????
我们发现这个崩溃是因为rcx+r9*2存放的地址是无效的.我们可以dd看下是否真的无效
1: kd> dd rcx+r9*2
fffff8a0`02408000 ???????? ???????? ???????? ????????
fffff8a0`02408010 ???????? ???????? ???????? ????????
fffff8a0`02408020 ???????? ???????? ???????? ????????
确实,这个内存地址不存在,而上面的语句还要执行到这里。当然就会蓝屏了,通过反汇编我们看到当前这个语句所在汇编上下文知道CPU此时在获取这个注册表键路径的长度,但是不幸的时它读取到了无效的系统内存地址,从而倒致CPU在读取的时候触发#PF
1: kd> ub eip
nvpciflt+0x13c9:
fffff880`01aed3c9 488d0d682f0000 lea rcx,[nvpciflt+0x4338 (fffff880`01af0338)]
fffff880`01aed3d0 ff15821c0000 call qword ptr [nvpciflt+0x3058 (fffff880`01aef058)]
fffff880`01aed3d6 3d0d0000c0 cmp eax,0C000000Dh
fffff880`01aed3db 0f84f2000000 je nvpciflt+0x14d3 (fffff880`01aed4d3)
fffff880`01aed3e1 488b442478 mov rax,qword ptr [rsp+78h]
fffff880`01aed3e6 488b4808 mov rcx,qword ptr [rax+8]
fffff880`01aed3ea 4d8bce mov r9,r14
fffff880`01aed3ed49ffc1 inc r9
1: kd> u eip
nvpciflt+0x13f0:
fffff880`01aed3f0 6642391c49 cmp word ptr [rcx+r9*2],bx //bx = 0
fffff880`01aed3f5 75f6 jne nvpciflt+0x13ed (fffff880`01aed3ed)
从反汇编代码看出在执行一个循环。我们可以du下rcx看下内里是什么内容
1: kd> du rcx
fffff8a0`02407f80 "\REGISTRY\MACHINE\SYSTEM\Control"
fffff8a0`02407fc0 "Set001\Enum\ROOT\*ISATAP\0003獥.."
fffff8a0`02408000 "????????????????????????????????"
fffff8a0`02408040 "????????????????????????????????"
我们看下IDA里面是怎么写的
可以知道rcx指向CmCallbackGetKeyObjectID函数返回的PUNICODE_STRING类型参数的buffer,由于这个值是存放在栈上面的且没有被修改过我们可以通过WINDBG去读取并格式化显示它
1: kd> dQ rsp+78
fffff880`043bd928 fffff8a0`02407f7000000000`00020000
fffff880`043bd938 fffff8a0`00337470 00000000`01b29901
fffff880`043bd948 fffffa80`0ab04b50 fffff8a0`0277c3b0
fffff880`043bd958 00000000`00000000 00000000`00000001
fffff880`043bd968 fffff880`043bdb60 00000000`00000000
fffff880`043bd978 fffff800`0453a5e1 fffff880`043bd900
fffff880`043bd988 00000000`00000002 fffffa80`03f82080
fffff880`043bd998 00000000`00240001 00000000`00000000
1: kd> dt _UNICODE_STRINGfffff8a0`02407f70
nt!_UNICODE_STRING
"\REGISTRY\MACHINE\SYSTEM\ControlSet001\Enum\ROOT\*ISATAP\0003"
+0x000 Length : 0x7a
+0x002 MaximumLength : 0x7a
+0x008 Buffer : 0xfffff8a0`02407f80 "\REGISTRY\MACHINE\SYSTEM\ControlSet001\Enum\ROOT\*ISATAP\0003"
我们可以看到这个注册表路径长度为0x7a,而崩溃时读取的长度为0x40*2 =0x80,崩溃时r9执行了0x40次
1: kd> r r9
Last set context:
r9=0000000000000040
显然读取的长度超过了0x7a。为了更明显的看出问题根源,我们用db显示下buffer里面的内容
1: kd> db 0xfffff8a0`02407f80
fffff8a0`02407f80 5c 00 52 00 45 00 47 00-49 00 53 00 54 00 52 00 \.R.E.G.I.S.T.R.
fffff8a0`02407f90 59 00 5c 00 4d 00 41 00-43 00 48 00 49 00 4e 00 Y.\.M.A.C.H.I.N.
fffff8a0`02407fa0 45 00 5c 00 53 00 59 00-53 00 54 00 45 00 4d 00 E.\.S.Y.S.T.E.M.
fffff8a0`02407fb0 5c 00 43 00 6f 00 6e 00-74 00 72 00 6f 00 6c 00 \.C.o.n.t.r.o.l.
fffff8a0`02407fc0 53 00 65 00 74 00 30 00-30 00 31 00 5c 00 45 00 S.e.t.0.0.1.\.E.
fffff8a0`02407fd0 6e 00 75 00 6d 00 5c 00-52 00 4f 00 4f 00 54 00 n.u.m.\.R.O.O.T.
fffff8a0`02407fe0 5c 00 2a 00 49 00 53 00-41 00 54 00 41 00 50 00 \.*.I.S.A.T.A.P.
fffff8a0`02407ff0 5c 00 30 00 30 00 30 00-33 00 65 73 04 f0 ff ff \.0.0.0.3.es....
注意下后面的es 很明显这个BUFFER并不是以NULL为结束的。在这里我们不得不提下UNICODE_STRING这个结构。
typedef struct _LSA_UNICODE_STRING {
USHORT Length;
USHORT MaximumLength;
PWSTR Buffer;
} LSA_UNICODE_STRING, *PLSA_UNICODE_STRING, UNICODE_STRING, *PUNICODE_STRING;
其中Buffer这个字段并不一定是以NULL为结束的,这和我们平时在RING3环写的字符串数组还不一样。MSDN上有相关描述:http://msdn.microsoft.com/zh-cn/aa380518(v=vs.85)Buffer
Pointer to a wide-character string.Note that the strings returned by the various LSA functions might not be null-terminated.
其中文档上写得清清楚楚,这个Buffer里面可以不包含空字符。在RIG3层我们的程序员比较喜欢用下面的方法去定义字符串:
char* str="test by robin!";
wchar_t* str = L"test by robin!";
实际上这种字符串相当的不安全,很容易倒致缓冲区溢出漏洞。这是因为没有任何地方确切地表明一个字符串的长度,仅仅用一个'\0'字符来表示这个字符串的结束。一旦碰到根本没有空结束的字符串,程序可能就会陷入崩溃。
在驱动开发中,一般不再使用空来表示一个字符串的结束,而是定义了UNICODE_STRING这么一个结构来存放字符串。如果要判断这个字符串的长度,可以通过结构里面的Length来确定。
而在这个崩溃的DUMP中,我们看到写该驱动的开发人员直接想通过Buffer的空结束符来确定该注册表路径的长度,而这个路径刚好并没有像作者想像的那样以空结束符存在,于时在遍历的时候越界了,读取到了一个不存在的内存地址,从而倒致蓝屏。
查看了这个驱动文件发现是NV显卡的一个驱动,我电脑上崩溃的驱动版本如下:
后记:
在获取UNICODE_STRING中BUFFER的长度时,不要自作聪明的想当然,按规章办事还是很有必要的,就像NV(英伟达)这种大公司写的驱动也是不可避免存在这些低级错误(该驱动的这个函数中的其地方很多这样的写法!!!!!!!)。
===================================================================================
今天早上起来更新了下显卡驱动发现这个问题已经修复了。
那段有问题的代码已经删除了。 09/12/2013
[课程]FART 脱壳王!加量不加价!FART作者讲授!