返回信息流昨天在Exploit-DB上看到外国友人爆出德国的一款名为G-Data的安全软件的驱动程序中存在漏洞。攻击者通过向驱动对象发送一个精心构造的IRP数据包就可以劫持内核态下的EIP执行用户地址空间的Shellcode,从而进行OOXX。外国友人仅给出了POC代码,并没有详细分析漏洞形成的原因以及触发的方式,毕竟有些事不能说的太细。本着学习的目的,我对这个漏洞进行了简单的调试分析,以下的文字算是一个调试笔记吧。
根据外国友人提供的POC代码,可以看到漏洞的触发方式是这样的:
1、在当前进程的地址空间为Shellcode分配内存,并将Shellcode写入到分配的内存上。
2、CreaetFile打开驱动对象\\\\.\\MiniIcptControlDevice0
3、DeviceIoControl向驱动对象发送一个DeviceIoControlCode为0x83170180的IRP数据包,在这个IRP中精心构造了一个结构体,并将Shellcode的地址保存在这个结构体中。
4、驱动对象\\\\.\\MiniIcptControlDevice0在处理攻击者发送的畸形IRP数据包时,就会跳转到Shellcode处执行。
显然,应该着重对驱动对象处理IRP_MJ_DEVICE_CONTROL类型IRP的分发函数(Dispatch Routine)进行分析。在System32\Drivers目录下找到这个驱动对象对应的驱动文件MiniIcpt.sys,然后拖进IDA Pro里F5之。
编写驱动程序有一个基本的套路,比如在DriverEntry里先IoCreateDevice,然后再注册驱动对象的IRP处理例程,如果有需要还会创建符号链接,以及设置设备对象扩展属性等。
我一开始并没有在DriverEntry里找到给有驱动对象注册IRP处理例程的操作(call来call去的一会儿就晕了),最后想注册IRP处理例程也是对DriverObject进行操作,那就跟踪那些传入DriverOjbect为参数的函数调用吧。最后终于在一个短小精悍的函数里,发现了注册IRP例程的操作。
在DriverObject数据结构中,偏移0x38处是一个函数指针数组。数组成员的下标用于标识该函数处理的IRP的类型,IRP_MJ_DEVICE_CONTROL处理例程的数组下标是0xE。每个成员大小为4字节,那么偏移0x38+0xE*4处就是IRP_MJ_DEVICE_CONTROL Irp处理例程的的地址了
INIT:0001AFF4 mov edx, [ecx+8] ;edx 存放驱动对象指针
INIT:0001AFF7 mov eax, offset MajorIrpDispatchRoutine
NIT:0001AFFC mov [edx+38h], eax
INIT:0001AFFF mov edx, [ecx+8]
INIT:0001B002 mov [edx+40h], eax
INIT:0001B005 mov edx, [ecx+8]
INIT:0001B008 and dword ptr [ecx+18h], 0FFFFFFF9h
INIT:0001B00C mov [edx+70h], eax ;IRP_MJ_DEVICE_CONTROL 例程
跳转到MajorIrpDiapatchRoutine处分析,
.text:00012C5E mov edi, edi
.text:00012C60 push ebp
.text:00012C61 mov ebp, esp
.text:00012C63 cmp byte_19CFC, 0
.text:00012C6A jnz short loc_12C7C
.text:00012C6C mov eax, [ebp+theDevObj] ; EAX指向设备对象
.text:00012C6F mov ecx, [eax+28h] ; ECX指向设备扩展属性
.text:00012C72 push [ebp+theIrp] ; 将IRP压栈
.text:00012C75 mov eax, [ecx]
.text:00012C77 call dword ptr [eax+8] ; call MajorIrpDiapatchRoutine_2
通过动态调试,可以确定程序在00012C77会调用函数MajorIrpDiapatchRoutine_2,于是跳转过来继续分析,
text:00017E9D mov edi, edi
.text:00017E9F push ebp
.text:00017EA0 mov ebp, esp
.text:00017EA2 mov eax, [ebp+arg_0] ;EAX指向IRP
.text:00017EA5 mov edx, [eax+60h]l ;EDX指向IO_STACK_LOCATION
.text:00017EA8 movzx edx, byte ptr [edx] ;EDX中存放着IRP的类型
.text:00017EAB push esi
.text:00017EAC push ecx
.text:00017EAD mov esi, esp
.text:00017EAF mov [esi], eax ;将IRP压栈
.text:00017EB1 call off_19BD8[edx*4] ; edx为MajorFunction数组的下标
.text:00017EB1
.text:00017EB8 pop esi
.text:00017EB9 pop ebp
.text:00017EBA retn 4
通过动态调试,可以确定程序在00017EB1会跳转到JumpToDeviceIoIoControlRoutine处执行,而JumpToDeviceIoIoControlRoutine顾名思义,也只是一个简单的跳转。程序的控制流最终会走到函数DeviceIoControlDispatchRoutine_2处执行
.text:00011198 mov edi, edi
.text:0001119A push ebp
.text:0001119B mov ebp, esp
.text:0001119D push edi
.text:0001119E mov edi, ecx ;
.text:000111A0 call ds:KeGetCurrentIrql ; 获得当前的中断级别
.text:000111A6 test al, al ;判断当前CPU的中断级别是否PASSIVE_LEVEL
.text:000111A8 jnz short loc_111E2 ;如果不是,跳转
.text:000111AA mov ecx, [ebp+arg_0] ;ECX指向IRP
.text:000111AD mov eax, [ecx+60h] ;EAX指向IO_STACK_LOCATION
.text:000111B0 mov edx, [edi] ;EDI指向设备扩展属性
.text:000111B2 push esi
.text:000111B3 push 1
.text:000111B5 lea esi, [ecx+18h]
.text:000111B8 mov ecx, [ecx+0Ch] ;ECX指向内核模式下用户传入的缓冲区地址
.text:000111BB push esi
.text:000111BC push dword ptr [eax+0Ch];将I/O控制代码压栈
.text:000111BF push dword ptr [eax+4] ; 将用户层输入缓冲区长度压栈
.text:000111C2 push ecx
.text:000111C3 push dword ptr [eax+8] 将用户层输出缓冲区长度压栈
.text:000111C6 push ecx
.text:000111C7 push 0
.text:000111C9 push 0
.text:000111CB mov ecx, edi ;
.text:000111CD call dword ptr [edx+88h] ; call RealDeviceCtrlDispatchRoutine
通过到动态调试,可以确定程序在000111CD 处会跳转到RealDeviceCtrlDispatchRoutine来执行。 攻击者在调用DeviceIoControl向驱动对象发送IRP数据包时,会传入一个缓冲区地址,当参数传入到内核时,会在线程的内核堆栈上另开辟一块缓冲区并且将用户态缓冲区的中值复制到这个新开辟的内核态的缓冲区中。这个新开辟的缓冲区地址就保存在IRP数据结构中的AsocaiteIrp.SystemBuffer中。地址0x000111B8的指令就是将AssociatedIrp.SystemBuffer中的值写入到ECX中,并在0x000111C2处将这个地址压栈,作为函数RealDeviceCtrlDispatchRoutine的参数。
在这个函数中将根据在000111BC压入的I/O控制代码来决定执行什么操作。POC中传入的是一个0x83170180,根据这个值,可以直接定位到以下代码片段
.text:00010EE1 cmp [ebp+10], 0Ch
.text:00010EE5 jnz short loc_10F45
.text:00010EE7 mov eax,[ebp+10] ;EAX指向AssociatedIrp.SystemBuffer
.text:00010EEA cmp eax, esi ;判断指针是否为空
.text:00010EEC jz short loc_10F45
.text:00010EEE mov eax, [eax] ;AssociatedIrp.SystemBuffer中的值写入EAX
.text:00010EF0 cmp eax, esi ;判断指针是否为空
.text:00010EF2 jz short loc_10F43
.text:00010EF4 push eax
.text:00010EF5 call FltReleaseContext ; 在这里发生了悲剧!!!!
.text:00010EFA jmp short loc_10F43
动态调试时发现,一旦单步跟过(Step Over)00010EF5处的call指令,Shellcode代码就会执行!那现在可以将漏洞触发的地址精确到系统函数FltReleaseContext中了!
根据WDK提供的文档,这个函数用于减少对一个上下文(Context)的引用,它的原型是
VOID FltReleaseContext(
IN PFLT_CONTEXT Context );
WDK的头文件中并没有对FLT_CONTEXT这个结构体给出定义,对PFLT_CONTEXT的定义也仅仅是一个简单的typedef PVOID PFLT_CONTEXT;但是可以确定此时传入的一定是一个畸形的FLT_CONTEXT结构体。在调用FltRelleaseContext时,传入的参数EAX的值为0x00362bc0,这个值保存在AssociatedIrp.SystemBuffer所指向的缓冲区当中。
kd> dd eax
00362bc0 90909090 90909090 90909090 90909090
00362bd0 90909090 90909090 90909090 90909090
00362be0 90909090 90909090 90909090 90909090
00362bf0 90909090 90909090 90909090 90909090
这个地址正是攻击者构造的畸形IRP数据包的地址,在0x00362bc0之前0x28字节的地方,存放着一个地址0x00360768
kd> dd 00362bc0-28
00362b98 00360768 90909090 90909090 90909090
00362ba8 90909090 90909090 90909090 90909090
00362bb8 90909090 00000001 90909090 90909090
00362bc8 90909090 90909090 90909090 90909090
0x00360768中的内容为
kd> dd 00360768
00360768 00360768 00380000 baadf00d baadf00d
00360778 abababab abababab 00000000 00000000
00360788 00050076 00ee04ee 00360528 00360528
0x00360768指向的内存单元保存着自己的地址0x00360768,而在地址0x00360768偏移4自己的地方,保存着地址0x00380000。这显然是一个精心构造的结构体
0x00360768,0x00380000这几个值有什么用呢?单步跟入FltReleaseContext,继续分析!
.text:000120E8 mov edi, edi
.text:000120EA push ebp
.text:000120EB mov ebp, esp
.text:000120ED mov eax, [ebp+arg_0] ; EAX为0x00362bc0
.text:000120F0 add eax, 0FFFFFFD8h ;其实就是sub eax,0x28
.text:000120F3 push eax ;此时EAX就是00362b98
.text:000120F4 call _DoReleaseContext@4 ; DoReleaseContext(x)
这个函数非常简单,只是将传入的参数减去0x28,然后就调用了DoReleaseContext。在后者中,先判断了当前CPU的中段等级。如果中断等级不大于APC_LEVEL,就会以同样的参数再调用_DoFreeContext,
.text:00011F0B mov edi, [ebp+arg_0] ; EDI的值为00362b98
.text:00011F0E mov esi, [edi] ; ESI 的值为0x00360768
.text:00011F10 mov eax, [esi+4] ; EAX 的值为00380000
.text:00011F13 test eax, eax
.text:00011F15 jz short loc_11F24
.text:00011F17 xor ecx, ecx
.text:00011F19 mov cx, [esi+0Ch]
.text:00011F1D push ecx
.text:00011F1E lea ecx, [edi+28h]
.text:00011F21 push ecx
.text:00011F22 call eax ;跳转到0x00380000
可以看到在这个函数里从传入的参数0x00362b98指向的内存单元读取了一个地址0x00360768,然后又从这个地址偏移4的地方读取了一个函数地址0x00380000,随后在00011F22就跳转到这个地址开始执行!
0x0038000正是当前进程用户空间的地址,而在这个地址上已经布置好了Shellcode。
kd> uf 00380000
00380000 b824f1dfff mov eax,0FFDFF124h ;EAX指向内核数据结构_KPCR
00380005 8b00 mov eax,dword ptr [eax];EAX指向当前线程的_NT_TIB
00380007 8b7044 mov esi,dword ptr [eax+44h];ESI指向当前进程的EPROCESS
0038000a 89f0 mov eax,esi
//…此处省略Shellcode代码
虽然Shellcode位于用户地址空间,但此时CPU却运行于特权级,而且线程的堆栈也是内核态堆栈,所以在Shellcode中可以做很多内核态才能做的事。从而实现了本地提权。
总的来说漏洞形成的原因有以下三点:
1、G-Data没有对自己的驱动对象进行保护,允许任意进程可以通过调用CreateFile来打开驱动对象,并进一步通过调用DeviceIoControl与之通信。应该在IRP分发例程中判断当前向驱动对象发送IRP处理请求的进程是否为G-Data自己创建的进程。这一点360就做得很好!
2、G-Data的驱动对象在处理I/O控制代码为0x83170180的IRP请求时没有对来自用户空间的参数进行严格的验证。
3、在系统驱动fltMgr的函数FltReleaseContext中在执行call eax指令时,没有判断跳转地址是否位于内核地址空间。
参考:
1. IDA Pro 强大的F5
2. http://www.exploit-db.com/exploits/15461/
这是一条镜像帖。来源:北邮人论坛 / security / #30983同步于 2010/11/17
该镜像源已超过 30 天没有更新,可能在源站已被删除。
Security机器人发帖
对一个内核漏洞的简单分析
MJ1112
2010/11/17镜像同步7 回复
订阅后,新回复会通过你的通知中心匿名送达。
7 条回复
下一个MJ。。。
【 在 MJ1112 (MJ1112) 的大作中提到: 】
: [size=3]昨天在Exploit-DB上看到外国友人爆出德国的一款名为G-Data的安全软件的驱动程序中存在漏洞。攻击者通过向驱动对象发送一个精心构造的IRP数据包就可以劫持内核态下的EIP执行用户地址空间的Shellcode,从而进行OOXX。外国友人仅给出了POC代码,并没有详细分析漏洞
: 根据外国友人提供的POC代码,可以看到漏洞的触发方式是这样的:
: 1、在当前进程的地址空间为Shellcode分配内存,并将Shellcode写入到分配的内存上。
: ...................
【 在 MJ1112 的大作中提到: 】
: : returnME?
: : --
: 我跟舍瓦大婶还有很大的差距~
: ...................
呵呵 你们认识啊
【 在 MJ1112 的大作中提到: 】
: : returnME?
: : --
: 我跟舍瓦大婶还有很大的差距~
: ...................
大婶来了~ LZ表谦虚....