-
-
[原创]初学Windows内核漏洞之CVE-2011-2005
-
2021-11-2 21:08 10355
-
一.前言
初学Windows内核漏洞,选择了比较早的CVE-2011-2005 Windows Afd.sys本地提权漏洞来试着分析学习,作为学习笔记分享给大家。
系统环境 | Windows XP sp3 32位 |
调试工具 | IDA Pro, WinDbg |
二.漏洞描述
在2011年的11月,微软补丁公告提到,Windows系统中的辅助功能驱动程序Afd.sys存在本地提权漏洞,影响Windows XP与Windows Server 2003系统。该漏洞主要是Microsoft Windows Ancillary Function Driver(afd.sys)驱动程序未对用户提交的数据进行完成地检查,导致存在本地提权漏洞,攻击者利用该漏洞可执行任意代码。
三.漏洞分析
1.POC代码分析
以下是POC中的部分代码
## Create our deviceiocontrol socket handle client = WSASocket(socket.AF_INET, socket.SOCK_STREAM, socket.IPPROTO_TCP, None, 0, 0) if client == ~0: raise OSError, "WSASocket: %s" % (WSAGetLastError(),) try: addr = sockaddr_in() addr.sin_family = socket.AF_INET addr.sin_port = socket.htons(4455) addr.sin_addr = socket.htonl(0x7f000001) # 127.0.0.1 ## We need to connect to a closed port, socket state must be CONNECTING connect(client, byref(addr), sizeof(addr)) except: closesocket(client) raise ## Trigger Pointer Overwrite print "[*] Triggering AFDJoinLeaf pointer overwrite..." IOCTL = 0x000120bb # AFDJoinLeaf inputbuffer = 0x1004 inputbuffer_size = 0x108 outputbuffer_size = 0x0 # Bypass Probe for Write outputbuffer = HalDispatchTable0x4 + 0x1 # HalDispatchTable+0x4+1 IoStatusBlock = c_ulong() NTSTATUS = ntdll.ZwDeviceIoControlFile(client, None, None, None, byref(IoStatusBlock), IOCTL, inputbuffer, inputbuffer_size, outputbuffer, outputbuffer_size )
根据上面的部分代码可以得出下面的结论:
程序通过socket与驱动程序进行通信,IP地址为127.0.0.1,端口为4455
触发这个漏洞的IOCTL为0x00120BB
而IO控制码的计算是由CTL_CODE完成的,它是一个宏定义,在文档中的定义如下
#define CTL_CODE( DeviceType, Function, Method, Access ) ( \ ((DeviceType) << 16) | ((Access) << 14) | ((Function) << 2) | (Method) \ )
可以知道控制码最低的两位保存的是用户层和内核层的通信方式,而触发漏洞的控制码的低两位是11(0x3),根据文档中的如下定义可以知道,此次通信方式为METHOD_NEITHER
#define METHOD_BUFFERED 0 #define METHOD_IN_DIRECT 1 #define METHOD_OUT_DIRECT 2 #define METHOD_NEITHER 3
而第2位到第14位保存的是发送的操作码,1000 0010 1110(0x82E)
2.驱动程序分析
首先看看afd驱动在系统中装载情况:
kd> lm start end module name 804d8000 806e6000 nt (pdb symbols) d:\xpsymbols\ntkrpamp.pdb\7075F995A48A414F8F7BE9A1E0240F821\ntkrpamp.pdb 806e6000 80706d00 hal (deferred) b2345000 b2347a00 vmmemctl (deferred) b2479000 b2490900 dump_atapi (deferred) b24b9000 b24e9000 BAPIDRV (deferred) b24e9000 b2558c80 mrxsmb (deferred) b2581000 b25abe80 rdbss (deferred) b25ac000 b25d2180 vmhgfs (deferred) b25d3000 b25f4d00 afd (deferred) b25f5000 b261cc00 netbt (deferred) b261d000 b2675480 tcpip (deferred)
可以看到,该驱动在内核地址0xB25D3000到0xB25F4D00。
再看看该驱动在分发函数的情况:
kd> !drvobj afd 2 Driver object (81a32558) is for: \Driver\AFD DriverEntry: b25f0f40 afd!GsDriverEntry DriverStartIo: 00000000 DriverUnload: b25da4a9 afd!AfdUnload AddDevice: 00000000 Dispatch routines: [00] IRP_MJ_CREATE b25ded50 afd!AfdDispatch [01] IRP_MJ_CREATE_NAMED_PIPE b25ded50 afd!AfdDispatch [02] IRP_MJ_CLOSE b25ded50 afd!AfdDispatch [03] IRP_MJ_READ b25ded50 afd!AfdDispatch [04] IRP_MJ_WRITE b25ded50 afd!AfdDispatch [05] IRP_MJ_QUERY_INFORMATION b25ded50 afd!AfdDispatch [06] IRP_MJ_SET_INFORMATION b25ded50 afd!AfdDispatch [07] IRP_MJ_QUERY_EA b25ded50 afd!AfdDispatch [08] IRP_MJ_SET_EA b25ded50 afd!AfdDispatch [09] IRP_MJ_FLUSH_BUFFERS b25ded50 afd!AfdDispatch [0a] IRP_MJ_QUERY_VOLUME_INFORMATION b25ded50 afd!AfdDispatch [0b] IRP_MJ_SET_VOLUME_INFORMATION b25ded50 afd!AfdDispatch [0c] IRP_MJ_DIRECTORY_CONTROL b25ded50 afd!AfdDispatch [0d] IRP_MJ_FILE_SYSTEM_CONTROL b25ded50 afd!AfdDispatch [0e] IRP_MJ_DEVICE_CONTROL b25de290 afd!AfdDispatchDeviceControl [0f] IRP_MJ_INTERNAL_DEVICE_CONTROL b25ded50 afd!AfdDispatch [10] IRP_MJ_SHUTDOWN b25ded50 afd!AfdDispatch [11] IRP_MJ_LOCK_CONTROL b25ded50 afd!AfdDispatch [12] IRP_MJ_CLEANUP b25ded50 afd!AfdDispatch [13] IRP_MJ_CREATE_MAILSLOT b25ded50 afd!AfdDispatch [14] IRP_MJ_QUERY_SECURITY b25ded50 afd!AfdDispatch [15] IRP_MJ_SET_SECURITY b25ded50 afd!AfdDispatch [16] IRP_MJ_POWER b25ded50 afd!AfdDispatch [17] IRP_MJ_SYSTEM_CONTROL b25ded50 afd!AfdDispatch [18] IRP_MJ_DEVICE_CHANGE b25ded50 afd!AfdDispatch [19] IRP_MJ_QUERY_QUOTA b25ded50 afd!AfdDispatch [1a] IRP_MJ_SET_QUOTA b25ded50 afd!AfdDispatch [1b] IRP_MJ_PNP b25ded50 afd!AfdDispatch Fast I/O routines: FastIoRead b25da1d9 afd!AfdFastIoRead FastIoWrite b25da2b8 afd!AfdFastIoWrite FastIoUnlockAll b25dd408 afd!AfdSanFastUnlockAll FastIoDeviceControl b25d5810 afd!AfdFastIoDeviceControl
可以看到IRP_MJ_DEVICE_CONTROL对应的分发函数是AfdDispatchDeviceControl,函数地址是0xB25DE290。那么接下来就可以在IDA中定位到这个分分发函数。
而要真正理解这个分发函数,需要了解以下的数据结构
首先是_IRP结构,该结构在WinDbg中查看可以得到如下结果
kd> dt -v -r 3 _IRP nt!_IRP struct _IRP, 21 elements, 0x70 bytes +0x000 Type : ?? +0x002 Size : ?? +0x004 MdlAddress : ???? +0x008 Flags : ?? +0x00c AssociatedIrp : union __unnamed, 3 elements, 0x4 bytes +0x000 MasterIrp : ???? +0x000 IrpCount : ?? +0x000 SystemBuffer : ???? +0x010 ThreadListEntry : struct _LIST_ENTRY, 2 elements, 0x8 bytes +0x000 Flink : ???? +0x004 Blink : ???? +0x018 IoStatus : struct _IO_STATUS_BLOCK, 3 elements, 0x8 bytes +0x000 Status : ?? +0x000 Pointer : ???? +0x004 Information : ?? +0x020 RequestorMode : ?? +0x021 PendingReturned : ?? +0x022 StackCount : ?? +0x023 CurrentLocation : ?? +0x024 Cancel : ?? +0x025 CancelIrql : ?? +0x026 ApcEnvironment : ?? +0x027 AllocationFlags : ?? +0x028 UserIosb : ???? +0x02c UserEvent : ???? +0x030 Overlay : union __unnamed, 2 elements, 0x8 bytes +0x000 AsynchronousParameters : struct __unnamed, 2 elements, 0x8 bytes +0x000 UserApcRoutine : ???? +0x004 UserApcContext : ???? +0x000 AllocationSize : union _LARGE_INTEGER, 4 elements, 0x8 bytes +0x000 LowPart : ?? +0x004 HighPart : ?? +0x000 u : struct __unnamed, 2 elements, 0x8 bytes +0x000 QuadPart : ?? +0x038 CancelRoutine : ???? +0x03c UserBuffer : ???? +0x040 Tail : union __unnamed, 3 elements, 0x30 bytes +0x000 Overlay : struct __unnamed, 8 elements, 0x28 bytes +0x000 DeviceQueueEntry : struct _KDEVICE_QUEUE_ENTRY, 3 elements, 0x10 bytes +0x000 DriverContext : [4] ???? +0x010 Thread : ???? +0x014 AuxiliaryBuffer : ???? +0x018 ListEntry : struct _LIST_ENTRY, 2 elements, 0x8 bytes +0x020 CurrentStackLocation : PIO_STACK_LOCATION +0x020 PacketType : ?? +0x024 OriginalFileObject : ???? +0x000 Apc : struct _KAPC, 14 elements, 0x30 bytes +0x000 Type : ?? +0x002 Size : ?? +0x004 Spare0 : ?? +0x008 Thread : ???? +0x00c ApcListEntry : struct _LIST_ENTRY, 2 elements, 0x8 bytes +0x014 KernelRoutine : ???? +0x018 RundownRoutine : ???? +0x01c NormalRoutine : ???? +0x020 NormalContext : ???? +0x024 SystemArgument1 : ???? +0x028 SystemArgument2 : ???? +0x02c ApcStateIndex : ?? +0x02d ApcMode : ?? +0x02e Inserted : ?? +0x000 CompletionKey : ????
这里需要注意的是偏移0x60的CurrentStackLocation,保存的是_IO_STACK_LOCATION的指针,代表了当前设备的I/O栈。
而_IO_STACK_LOCATION的部分结构体成员如下
kd> dt -v -r 2 _IO_STACK_LOCATION nt!_IO_STACK_LOCATION struct _IO_STACK_LOCATION, 9 elements, 0x24 bytes +0x000 MajorFunction : ?? +0x001 MinorFunction : ?? +0x002 Flags : ?? +0x003 Control : ?? +0x004 Parameters : union __unnamed, 38 elements, 0x10 bytes +0x000 DeviceIoControl : struct __unnamed, 4 elements, 0x10 bytes +0x000 OutputBufferLength : ULONG +0x004 InputBufferLength : ULONG +0x008 IoControlCode : ULONG +0x00c Type3InputBuffer : PVOID +0x014 DeviceObject : ???? +0x018 FileObject : ???? +0x01c CompletionRoutine : ???? +0x020 Context : ????
在这个结构体中要关注的是偏移为0x04的Parameters,它保存的是一个联合体,其中的一个是DeviceIoControl。DeviceIoControl中又保存了4个成员,这4个成员的含义如下
在_IO_STACK_LOCATION中的偏移 | 成员 | 含义 |
0x04 | OutputBufferLength | 输出缓冲区的长度 |
0x08 | InputBufferLength | 输入缓冲区长度 |
0x0C | IoControlCode | IOCTL |
0x10 | Type3InputBuffer | 输入缓冲区地址 |
有了上面的基础应该就可以理解AfdDispatchDeviceControl的反汇编代码,
PAGEAFD:0001B290 ; __stdcall AfdDispatchDeviceControl(x, x) PAGEAFD:0001B290 _AfdDispatchDeviceControl@8 proc near ; CODE XREF: AfdDispatch(x,x)+2B1↓p PAGEAFD:0001B290 ; DATA XREF: DriverEntry(x,x)+21A↓o PAGEAFD:0001B290 PAGEAFD:0001B290 arg_4 = dword ptr 0Ch PAGEAFD:0001B290 PAGEAFD:0001B290 mov edi, edi PAGEAFD:0001B292 push ebp PAGEAFD:0001B293 mov ebp, esp PAGEAFD:0001B295 mov ecx, [ebp+arg_4] ; 取出pIrp赋给ecx PAGEAFD:0001B298 mov edx, [ecx+60h] ; 取出CurrentStackLocation赋给edx PAGEAFD:0001B29B push esi PAGEAFD:0001B29C push edi PAGEAFD:0001B29D mov edi, [edx+0Ch] ; 取出IoControlCode赋给edi PAGEAFD:0001B2A0 mov eax, edi ; 将IOCTL赋值给eax PAGEAFD:0001B2A2 shr eax, 2 ; 将eax右移两位 PAGEAFD:0001B2A5 and eax, 3FFh ; 只保留eax的第0位到第9位的数 PAGEAFD:0001B2AA cmp eax, 46h PAGEAFD:0001B2AD jnb loc_21BF3 PAGEAFD:0001B2B3 mov esi, eax ; 将eax赋值esi PAGEAFD:0001B2B5 shl esi, 2 ; esi左移两位,也就是乘4,得出整型数组偏移 PAGEAFD:0001B2B8 cmp _AfdIoctlTable[esi], edi PAGEAFD:0001B2BE jnz loc_21BF3 PAGEAFD:0001B2C4 mov [edx+1], al PAGEAFD:0001B2C7 mov esi, _AfdIrpCallDispatch[esi] ; 从_AfdIrpCallDispatch中取出要调用的函数地址赋给esi PAGEAFD:0001B2CD test esi, esi PAGEAFD:0001B2CF jz loc_21BF3 PAGEAFD:0001B2D5 call esi ; 对函数进行调用 PAGEAFD:0001B2D7 PAGEAFD:0001B2D7 loc_1B2D7: ; CODE XREF: PAGEAFD:00021C09↓j PAGEAFD:0001B2D7 pop edi PAGEAFD:0001B2D8 pop esi PAGEAFD:0001B2D9 pop ebp PAGEAFD:0001B2DA retn 8 PAGEAFD:0001B2DA _AfdDispatchDeviceControl@8 endp
这段代码就是取出IOCTL并计算保留了低9位的控制码。由于POC给出的操作码是(0x82E),所以这里得到的是0x2E,这个数字就作为索引去整型数组_AfdIrpCalldispatch中取出要执行的函数的地址。
根据偏移可以算出这个函数是AfdJoinLeaf,所以产生漏洞的函数就是这个函数。
.data:000121B8 _AfdIrpCallDispatch dd offset @AfdBind@8 .data:000121B8 ; DATA XREF: AfdDispatchDeviceControl(x,x)+37↓r .data:000121B8 ; AfdBind(x,x) .data:000121BC dd offset @AfdConnect@8 ; AfdConnect(x,x) ..... .data:00012268 dd offset @AfdDispatchImmediateIrp@8 ; AfdDispatchImmediateIrp(x,x) .data:0001226C dd offset @AfdAddressListChange@8 ; AfdAddressListChange(x,x) .data:00012270 dd offset @AfdJoinLeaf@8
继续跟进这个函数查看漏洞成因,但要注意此时的edx保存的依然是CurrStackLocation,ecx保存的是IRP的指针,即pIrp。
PAGE:00016C9D ; __fastcall AfdJoinLeaf(x, x) PAGE:00016C9D @AfdJoinLeaf@8 proc near ; DATA XREF: .data:00012270↑o PAGE:00016C9D PAGE:00016C9D var_58 = dword ptr -58h PAGE:00016C9D var_54 = dword ptr -54h PAGE:00016C9D Object = dword ptr -50h PAGE:00016C9D Handle = dword ptr -4Ch PAGE:00016C9D AccessMode = byte ptr -48h PAGE:00016C9D var_44 = dword ptr -44h PAGE:00016C9D var_40 = dword ptr -40h PAGE:00016C9D var_3C = dword ptr -3Ch PAGE:00016C9D var_38 = dword ptr -38h PAGE:00016C9D var_34 = dword ptr -34h PAGE:00016C9D var_30 = dword ptr -30h PAGE:00016C9D var_2C = dword ptr -2Ch PAGE:00016C9D P = dword ptr -28h PAGE:00016C9D var_pIrp = dword ptr -24h PAGE:00016C9D var_20 = dword ptr -20h PAGE:00016C9D var_1C = dword ptr -1Ch PAGE:00016C9D ms_exc = CPPEH_RECORD ptr -18h PAGE:00016C9D PAGE:00016C9D ; __unwind { // __SEH_prolog PAGE:00016C9D push 48h PAGE:00016C9F push offset stru_11668 PAGE:00016CA4 call __SEH_prolog PAGE:00016CA9 mov ebx, edx ; 将edx赋给ebx PAGE:00016CAB mov [ebp+var_pIrp], ecx ; 将pIrp保存到局部变量中 PAGE:00016CAE xor esi, esi ; esi清0 PAGE:00016CB0 mov [ebp+var_20], esi PAGE:00016CB3 mov [ebp+var_AllocateMemory], esi PAGE:00016CB6 mov eax, [ebx+_IO_STACK_LOCATION.Parameters.DeviceIoControl.InputBufferLength] PAGE:00016CB9 cmp eax, 18h PAGE:00016CBC jb loc_1716E ; 小于0x18则跳转 PAGE:00016CC2 mov edx, [ebx+_IO_STACK_LOCATION.Parameters.DeviceIoControl.OutputBufferLength] PAGE:00016CC5 cmp edx, esi PAGE:00016CC7 jz short loc_16CD2 ; 等于0x0则跳转 PAGE:00016CC9 cmp edx, 8 PAGE:00016CCC jb loc_1716E ; 小于0x8则跳转
函数最开始就对输入缓冲区和输出缓冲区进行判断,如果输入缓冲区的长度小于0x18或者输出缓冲区的长度大于0小于0x8,就会跳转到loc_1716E处,而这个地方的代码是函数执行失败以后的退出代码,如下
PAGE:0001716E loc_1716E: ; CODE XREF: AfdJoinLeaf(x,x)+1F↑j PAGE:0001716E ; AfdJoinLeaf(x,x)+2F↑j PAGE:0001716E mov [ebp+var_1C], STATUS_INVALID_PARAMETER ; 返回值表明函数执行失败 PAGE:00017175 PAGE:00017175 loc_17175: ; CODE XREF: AfdJoinLeaf(x,x)+4CF↑j PAGE:00017175 mov edi, [ebp+var_pIrp] PAGE:00017178 PAGE:00017178 loc_17178: ; CODE XREF: AfdJoinLeaf(x,x)+171↑j PAGE:00017178 ; AfdJoinLeaf(x,x)+434↑j PAGE:00017178 xor esi, esi PAGE:0001717A cmp [ebp+P], esi PAGE:0001717D jz short loc_17190 PAGE:0001717F push 0C9646641h ; Tag PAGE:00017184 push [ebp+P] ; P PAGE:00017187 call ds:__imp__ExFreePoolWithTag@8 ; ExFreePoolWithTag(x,x) PAGE:0001718D mov [edi+0Ch], esi PAGE:00017190 PAGE:00017190 loc_17190: ; CODE XREF: AfdJoinLeaf(x,x)+4E0↑j PAGE:00017190 mov eax, [ebp+var_20] PAGE:00017193 cmp eax, esi PAGE:00017195 jz short loc_171AB PAGE:00017197 add eax, 20h PAGE:0001719A or ecx, 0FFFFFFFFh PAGE:0001719D lock xadd [eax], ecx PAGE:000171A1 jnz short loc_171AB PAGE:000171A3 push [ebp+var_20] PAGE:000171A6 call _AfdCloseConnection@4 ; AfdCloseConnection(x) PAGE:000171AB PAGE:000171AB loc_171AB: ; CODE XREF: AfdJoinLeaf(x,x)+4F8↑j PAGE:000171AB ; AfdJoinLeaf(x,x)+504↑j PAGE:000171AB mov [edi+1Ch], esi PAGE:000171AE mov eax, [ebp+var_1C] PAGE:000171B1 mov [edi+18h], eax PAGE:000171B4 mov dl, _AfdPriorityBoost PAGE:000171BA mov ecx, edi PAGE:000171BC call ds:__imp_@IofCompleteRequest@8 ; IofCompleteRequest(x,x) PAGE:000171C2 mov eax, [ebp+var_1C] PAGE:000171C5 PAGE:000171C5 loc_171C5: ; CODE XREF: AfdJoinLeaf(x,x)+394↑j PAGE:000171C5 ; AfdJoinLeaf(x,x)+493↑j PAGE:000171C5 call __SEH_epilog PAGE:000171CA retn PAGE:000171CA ; } // starts at 16C9D PAGE:000171CA @AfdJoinLeaf@8 endp
那么接着看函数正常执行,也就是loc_16CD2的代码,这段代码主要做的就是对输入缓冲区进行检查。如果地址非法,程序就会抛出异常
PAGE:00016CD2 loc_16CD2: ; CODE XREF: AfdJoinLeaf(x,x)+2A↑j PAGE:00016CD2 ; __try { // __except at loc_17165 PAGE:00016CD2 mov [ebp+ms_exc.disabled], esi PAGE:00016CD5 cmp [ecx+IRP.RequestorMode], 0 PAGE:00016CD9 jz short loc_16D06 PAGE:00016CDB cmp eax, esi PAGE:00016CDD jz short loc_16D06 PAGE:00016CDF test byte ptr [ebx+_IO_STACK_LOCATION.Parameters.DeviceIoControl.Type3InputBuffer], 3 PAGE:00016CE3 jz short loc_16CEB PAGE:00016CE5 call ds:__imp__ExRaiseDatatypeMisalignment@0 ; ExRaiseDatatypeMisalignment() PAGE:00016CEB loc_16CEB: ; CODE XREF: AfdJoinLeaf(x,x)+46↑j PAGE:00016CEB mov eax, [ebx+_IO_STACK_LOCATION.Parameters.DeviceIoControl.Type3InputBuffer] PAGE:00016CEE mov ecx, [ebx+_IO_STACK_LOCATION.Parameters.DeviceIoControl.InputBufferLength] PAGE:00016CF1 add ecx, eax ; 获得输入缓冲区的结束地址 PAGE:00016CF3 cmp ecx, eax PAGE:00016CF5 jb short loc_16D00 ; 判断结束地址是否小于开始地址,是的话异常 PAGE:00016CF7 mov eax, ds:__imp__MmUserProbeAddress PAGE:00016CFC cmp ecx, [eax] ; 判断输入缓冲区的地址是否正常,否则抛异常 PAGE:00016CFE jbe short loc_16D06 PAGE:00016D00 PAGE:00016D00 loc_16D00: ; CODE XREF: AfdJoinLeaf(x,x)+58↑j PAGE:00016D00 call ds:__imp__ExRaiseAccessViolation@0 ; ExRaiseAccessViolation()
接下来的代码就是对局部变量进行赋值。这里需要注意的是
Handle变量,它的值是输入缓冲区偏移为0x8的地址中的内容
InputBufferLength变量,保存了输入缓冲区的长度
esi保存了输入缓冲区0x0C偏移的地址
PAGE:00016D06 loc_16D06: ; CODE XREF: AfdJoinLeaf(x,x)+3C↑j PAGE:00016D06 ; AfdJoinLeaf(x,x)+40↑j ... PAGE:00016D06 mov eax, [ebx+_IO_STACK_LOCATION.Parameters.DeviceIoControl.Type3InputBuffer] PAGE:00016D09 mov ecx, [eax+8] PAGE:00016D0C mov [ebp+Handle], ecx ; 将缓冲区偏移0x8的内容赋值到句柄变量Handle中,这里需要记得后面要用 PAGE:00016D0F mov ecx, [eax+4] PAGE:00016D12 mov [ebp+var_38], ecx PAGE:00016D15 lea esi, [eax+0Ch] ; 将输入缓冲区偏移0x0C的地址赋给esi PAGE:00016D18 mov [ebp+var_58], esi PAGE:00016D1B mov eax, [ebx+_IO_STACK_LOCATION.Parameters.DeviceIoControl.InputBufferLength] PAGE:00016D1E sub eax, 0Ch ; 获取输入缓冲区的长度-0x0C的大小 PAGE:00016D21 mov [ebp+var_InputBufferLength], eax
然后程序就申请了一块内存,这块内存的大小是0x30加上输入缓冲区的长度-0x0C的大小。注意此时eax在上面有减去0xC的大小。随后程序就将申请到的内存的前0x30的大小初始化为0,在把输入缓冲区0x0C开始的内容复制到申请的内存偏移0x30的地址开始处。
PAGE:00016D21 ; } // starts at 16CD2 PAGE:00016D24 or [ebp+ms_exc.disabled], 0FFFFFFFFh PAGE:00016D28 ; __try { // __except at loc_1714C PAGE:00016D28 mov [ebp+ms_exc.disabled], 1 PAGE:00016D2F push 0C9646641h ; Tag PAGE:00016D34 add eax, 30h ; 将输入缓冲区的长度加0x30 PAGE:00016D37 push eax ; NumberOfBytes PAGE:00016D38 push 10h ; PoolType PAGE:00016D3A call ds:__imp__ExAllocatePoolWithQuotaTag@12 ; ExAllocatePoolWithQuotaTag(x,x,x) PAGE:00016D40 mov edx, eax ; 申请得到的地址赋给edx PAGE:00016D42 mov [ebp+var_AllocateMemory], edx ; 将地址保存在局部变量中 PAGE:00016D45 mov eax, [ebp+var_pIrp] PAGE:00016D48 mov [eax+_IO_STACK_LOCATION.Parameters.DeviceIoControl.IoControlCode], edx PAGE:00016D4B push 0Ch PAGE:00016D4D pop ecx PAGE:00016D4E xor eax, eax PAGE:00016D50 mov edi, edx PAGE:00016D52 rep stosd ; 对申请的前0x30字节的数据初始化为0 PAGE:00016D54 lea eax, [edx+30h] ; 得到edx+0x30处的地址 PAGE:00016D57 mov ecx, [ebp+var_InputBufferLength] ; 获取输入缓冲区的长度,不过这个时候的长度已经减掉了0x0C PAGE:00016D5A mov edi, eax PAGE:00016D5C mov edx, ecx ; 将长度复制到edx中 PAGE:00016D5E shr ecx, 2 PAGE:00016D61 rep movsd PAGE:00016D63 mov ecx, edx ; 由于esi保存输入缓冲区偏移0x0C的地址 PAGE:00016D65 and ecx, 3 ; edi保存申请的内存偏移0x30的地址 PAGE:00016D68 rep movsb ; 所以这段代码是把输入缓冲区偏移0x0C的内容复制到申请的内存偏移0x30的
接着函数判断eax中保存的内容是否等于1,不等于1则抛出异常。因为这个时候eax保存的是edx+0x30,也就是申请的内存地址偏移0x30处的地址。所以eax中保存的内容是输入缓冲区偏移0x0C处的内容,那这里就是判断输入缓冲区偏移0x0C保存的是否是1。
随后在判断edx是否大于等于eax,不是的话继续向下执行抛出异常。而这里的ecx的值是上面输入缓冲区长度减去0xC,而edx经过分析则是输入缓冲区偏移0x10处保存的内容在加8的值。也就是说输入缓冲区的长度减去0xC要大于等于输入缓冲区偏移0x10处保存的内容加8。
PAGE:00016D6A cmp dword ptr [eax], 1 PAGE:00016D6D jnz short loc_16D7D ; 不等于1则抛出异常 PAGE:00016D6F mov eax, [ebp+var_AllocateMemory] ; 得到申请的内存的地址 PAGE:00016D72 movzx eax, word ptr [eax+34h] ; 偏移0x34的地方,保存的是输入缓冲区偏移0xC+0x4=0x10的内容 PAGE:00016D76 add eax, 8 ; 将内容加8 PAGE:00016D79 cmp edx, eax PAGE:00016D7B jge short loc_16D88 PAGE:00016D7D PAGE:00016D7D loc_16D7D: ; CODE XREF: AfdJoinLeaf(x,x)+D0↑j PAGE:00016D7D push STATUS_INVALID_PARAMETER ; Status PAGE:00016D82 call ds:__imp__ExRaiseStatus@4 ; ExRaiseStatus(x)
接下去的内容就是该产生该漏洞的内容。在这段代码中,程序首先会判断输出缓冲区的长度是否为0,为0的话则跳转到loc_16DC5继续执行。如果不为0,程序才会接着对UserBuffer的地址进行合法性检查。
PAGE:00016D88 loc_16D88: ; CODE XREF: AfdJoinLeaf(x,x)+DE↑j PAGE:00016D88 mov eax, [ebp+var_AllocateMemory] PAGE:00016D8B lea ecx, [eax+30h] PAGE:00016D8E mov [eax+14h], ecx PAGE:00016D91 mov ecx, [ebp+var_InputBufferLength] PAGE:00016D94 mov [eax+10h], ecx PAGE:00016D97 mov edi, [ebp+var_pIrp] PAGE:00016D9A cmp [ebx+_IO_STACK_LOCATION.Parameters.DeviceIoControl.OutputBufferLength], 0 PAGE:00016D9E jbe short loc_16DC5 ; 小于等于则跳转 PAGE:00016DA0 cmp [edi+IRP.RequestorMode], 1 PAGE:00016DA4 jnz short loc_16DC5 PAGE:00016DA6 mov eax, ds:__imp__MmUserProbeAddress PAGE:00016DAB mov eax, [eax] PAGE:00016DAD cmp [edi+IRP.UserBuffer], eax PAGE:00016DB0 jb short loc_16DB8 ; 判断UserBuffer的地址是否合法 PAGE:00016DB2 mov dword ptr [eax], 0 ; 不合法就使用向不合法的地址写入数据的方式触发异常 PAGE:00016DB8 PAGE:00016DB8 loc_16DB8: ; CODE XREF: AfdJoinLeaf(x,x)+113↑j PAGE:00016DB8 mov eax, [edi+IRP.UserBuffer] PAGE:00016DBB mov ecx, [eax] PAGE:00016DBD mov [eax], ecx PAGE:00016DBF mov ecx, [eax+4] PAGE:00016DC2 mov [eax+4], ecx ; 对UserBuffer进行读写检查
接着程序会调用ObReferenceObjectByHandle,而这个Handle参数在前面被赋值为输入缓冲区+0x8的内容。由此可知,输入缓冲区+0x8的地址中的内容不可以为空。以及,此时要记得esi保存的是FsContext,而FsContext保存的是套接字的状态。
PAGE:00016DC5 or [ebp+ms_exc.disabled], 0FFFFFFFFh PAGE:00016DC9 mov eax, [ebx+_IO_STACK_LOCATION.FileObject] PAGE:00016DCC mov [ebp+var_FileObject], eax PAGE:00016DCF mov esi, [eax+FILE_OBJECT.FsContext] PAGE:00016DD2 cmp word ptr [esi], 0AAFDh PAGE:00016DD7 jnz short loc_16E27 PAGE:00016DD9 mov al, [edi+IRP.RequestorMode] PAGE:00016DDC mov [ebp+AccessMode], al PAGE:00016DDF mov eax, ds:_IoFileObjectType PAGE:00016DE4 mov ecx, [eax] PAGE:00016DE6 mov eax, [ebx+_IO_STACK_LOCATION.Parameters.DeviceIoControl.IoControlCode] PAGE:00016DE9 shr eax, 0Eh PAGE:00016DEC and eax, 3 ; 这一段在计算IOCTL的Access PAGE:00016DEF push 0 ; HandleInformation PAGE:00016DF1 lea edx, [ebp+Object] PAGE:00016DF4 push edx ; Object PAGE:00016DF5 push dword ptr [ebp+AccessMode] ; AccessMode PAGE:00016DF8 push ecx ; ObjectType PAGE:00016DF9 push eax ; DesiredAccess PAGE:00016DFA push [ebp+Handle] ; Handle PAGE:00016DFD call ds:__imp__ObReferenceObjectByHandle@24 ; ObReferenceObjectByHandle(x,x,x,x,x,x) PAGE:00016E03 mov ecx, [ebp+Object] PAGE:00016E06 mov [ebp+var_FileObject], ecx PAGE:00016E09 mov [ebp+var_1C], eax PAGE:00016E0C test eax, eax PAGE:00016E0E jl loc_17178 PAGE:00016E14 mov eax, [ecx+FILE_OBJECT.DeviceObject] PAGE:00016E17 cmp eax, _AfdDeviceObject PAGE:00016E1D jnz short loc_16E9C PAGE:00016E1F mov esi, [ecx+FILE_OBJECT.FsContext] ; 将FsContext赋给esi PAGE:00016E22 mov [ebx+_IO_STACK_LOCATION.FileObject], ecx PAGE:00016E25 jmp short loc_16E30
在后面程序就会对套接字的状态进行检查,如果不是CONNECTING状态,则会触发异常。
PAGE:00017076 cmp byte ptr [esi+2], 2 ; 套接字的状态是否为CONNECTING(0x2) PAGE:0001707A jz short loc_17085 PAGE:0001707C mov [ebp+var_1C], STATUS_INVALID_PARAMETER PAGE:00017083 jmp short loc_170C2 PAGE:00017085 ; --------------------------------------------------------------------------- PAGE:00017085 PAGE:00017085 loc_17085: ; CODE XREF: AfdJoinLeaf(x,x)+3DD↑j PAGE:00017085 lea eax, [ebp+var_20] PAGE:00017088 push eax PAGE:00017089 push dword ptr [esi+18h] PAGE:0001708C mov eax, [esi+0Ch] PAGE:0001708F shr eax, 8 PAGE:00017092 and eax, 1 PAGE:00017095 push eax PAGE:00017096 mov eax, [esi+4] PAGE:00017099 shr eax, 9 PAGE:0001709C and eax, 0FFFFFF01h PAGE:000170A1 push eax PAGE:000170A2 push dword ptr [esi+88h] PAGE:000170A8 mov eax, [esi+8Ch] PAGE:000170AE add eax, 10h PAGE:000170B1 push eax PAGE:000170B2 call _AfdCreateConnection@24 ; AfdCreateConnection(x,x,x,x,x,x)
虽然在AfdJoinLeaf中存在了不对UserBuffer地址进行合法性检查的漏洞,但是对该值进行赋值操作的代码却不再这个函数中。它在以下的AfdRestartJoin函数中
该函数将会调用AfdConnectApcKernelRoutine来执行代码
在该函数中可以看到,程序将IRP的状态赋给了UserBuffer中保存的地址
PAGE:0001539F mov eax, [esi+IRP.UserBuffer] ; 将UserBuffer的地址赋给eax PAGE:000153A2 mov ecx, [esi+IRP.IoStatus.anonymous_0.Status] ; 将IRP的状态赋给ecx PAGE:000153A5 mov [eax], ecx ; 将UserBuffer中保存的内容赋值为IRP的状态
四.漏洞利用
由上面分析可以得出下面的结论,在AfdJoinLeaf中最终会调用到afdConnectApcKernelRoutine,在这个函数中会对UserBuffer的地址赋值为IRP的状态。而要调用到这个函数则需要满足下面的条件
输入缓冲区的长度必须大于0x18
输入缓冲区偏移0x8的地址中保存的数据不可以为0
输入缓冲区偏移为0xC的值必须为1,并且输入缓冲区的长度减去0xC的值需要大于输入缓冲区偏移为0x10处保存的内容+8的值
套接字需要连接到开放的端口以保持CONNECTING状态
具体的利用代码请参考:MS11-080、MS11-046两个提权代码
五.参考资料
《漏洞战争》
[CTF入门培训]顶尖高校博士及硕士团队亲授《30小时教你玩转CTF》,视频+靶场+题目!助力进入CTF世界