[0x00]. IntroductionFirst, 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 overviewOur 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:

8d524b44 8876bbba 05244d2d 000120c3 8876ba8c afd!AfdTliGetTpInfo+0×89

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

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 CALLa. Analysis of AfdTransmitFileAfdTransmitFile 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.

tpInfo = AfdGetTpInfo ( 3 ) // if we satisfiy all the cnoditions above, we’ll reach the call to AfdGetTpInfo

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

b. Analysis of AfdTliGetTpInfoThe 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:

{

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 analysisa. AfdTransmitPacketsThe 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) + 0x1Cfor (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 HandleTableEntryWe 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.

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

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] LastFor 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 PDFNtSetInformationWrokerFactory and NtQueryEaFile are all treasure !