Skip to content

Commit fa016de

Browse files
author
Brent Cook
committed
Land rapid7#7634, Implement universal HTTP/S handlers for Meterpreter payloads
2 parents 082a894 + 2839b19 commit fa016de

File tree

70 files changed

+1320
-675
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

70 files changed

+1320
-675
lines changed
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
# -*- coding: binary -*-
2+
3+
require 'msf/base/sessions/meterpreter'
4+
5+
module Msf
6+
module Sessions
7+
8+
###
9+
#
10+
# This class creates a platform-independent meterpreter session type
11+
#
12+
###
13+
class Meterpreter_Multi < Msf::Sessions::Meterpreter
14+
def initialize(rstream, opts={})
15+
super
16+
self.base_platform = 'multi'
17+
self.base_arch = ARCH_ANY
18+
end
19+
20+
def self.create_session(rstream, opts={})
21+
# TODO: fill in more cases here
22+
case opts[:payload_uuid].platform
23+
when 'python'
24+
require 'msf/base/sessions/meterpreter_python'
25+
return Msf::Sessions::Meterpreter_Python_Python.new(rstream, opts)
26+
when 'java'
27+
require 'msf/base/sessions/meterpreter_java'
28+
return Msf::Sessions::Meterpreter_Java_Java.new(rstream, opts)
29+
when 'android'
30+
require 'msf/base/sessions/meterpreter_android'
31+
return Msf::Sessions::Meterpreter_Java_Android.new(rstream, opts)
32+
when 'php'
33+
require 'msf/base/sessions/meterpreter_php'
34+
return Msf::Sessions::Meterpreter_Php_Java.new(rstream, opts)
35+
when 'windows'
36+
if opts[:payload_uuid].arch == ARCH_X86
37+
require 'msf/base/sessions/meterpreter_x86_win'
38+
return Msf::Sessions::Meterpreter_x86_Win.new(rstream, opts)
39+
end
40+
require 'msf/base/sessions/meterpreter_x64_win'
41+
return Msf::Sessions::Meterpreter_x64_Win.new(rstream, opts)
42+
end
43+
44+
# TODO: what should we do when we get here?
45+
end
46+
end
47+
48+
end
49+
end
50+

lib/msf/base/sessions/meterpreter_options.rb

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -67,9 +67,7 @@ def on_session(session)
6767
end
6868

6969
if session.platform == 'android'
70-
if datastore['AutoLoadAndroid']
71-
session.load_android
72-
end
70+
session.load_android
7371
end
7472

7573
[ 'InitialAutoRunScript', 'AutoRunScript' ].each do |key|

lib/msf/core/exploit/browser_autopwn2.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -323,7 +323,7 @@ def start_payload_listeners
323323
multi_handler.datastore['PAYLOAD'] = payload_name
324324
multi_handler.datastore['LPORT'] = wanted[:payload_lport]
325325

326-
%w(DebugOptions AutoLoadAndroid PrependMigrate PrependMigrateProc
326+
%w(DebugOptions PrependMigrate PrependMigrateProc
327327
InitialAutoRunScript AutoRunScript CAMPAIGN_ID HandlerSSLCert
328328
StagerVerifySSLCert PayloadUUIDTracking PayloadUUIDName
329329
IgnoreUnknownPayloads SessionRetryTotal SessionRetryWait

lib/msf/core/handler.rb

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -199,7 +199,14 @@ def create_session(conn, opts={})
199199
# allocate a new session.
200200
if (self.session)
201201
begin
202-
s = self.session.new(conn, opts)
202+
# if there's a create_session method then use it, as this
203+
# can form a factory for arb session types based on the
204+
# payload.
205+
if self.session.respond_to?('create_session')
206+
s = self.session.create_session(conn, opts)
207+
else
208+
s = self.session.new(conn, opts)
209+
end
203210
rescue ::Exception => e
204211
# We just wanna show and log the error, not trying to swallow it.
205212
print_error("#{e.class} #{e.message}")

lib/msf/core/handler/reverse_http.rb

Lines changed: 19 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -317,77 +317,31 @@ def on_request(cli, req)
317317
pkt.add_tlv(Rex::Post::Meterpreter::TLV_TYPE_TRANS_URL, conn_id + "/")
318318
resp.body = pkt.to_r
319319

320-
when :init_python
321-
print_status("Staging Python payload...")
320+
when :init_python, :init_native, :init_java
321+
# TODO: at some point we may normalise these three cases into just :init
322322
url = payload_uri(req) + conn_id + '/'
323323

324-
blob = ""
325-
blob << self.generate_stage(
326-
http_url: url,
327-
http_user_agent: datastore['MeterpreterUserAgent'],
328-
http_proxy_host: datastore['PayloadProxyHost'] || datastore['PROXYHOST'],
329-
http_proxy_port: datastore['PayloadProxyPort'] || datastore['PROXYPORT'],
330-
uuid: uuid,
331-
uri: conn_id
332-
)
333-
334-
resp.body = blob
335-
336-
# Short-circuit the payload's handle_connection processing for create_session
337-
create_session(cli, {
338-
:passive_dispatcher => self.service,
339-
:conn_id => conn_id,
340-
:url => url,
341-
:expiration => datastore['SessionExpirationTimeout'].to_i,
342-
:comm_timeout => datastore['SessionCommunicationTimeout'].to_i,
343-
:retry_total => datastore['SessionRetryTotal'].to_i,
344-
:retry_wait => datastore['SessionRetryWait'].to_i,
345-
:ssl => ssl?,
346-
:payload_uuid => uuid
347-
})
348-
349-
when :init_java
350-
print_status("Staging Java payload...")
351-
url = payload_uri(req) + conn_id + "/\x00"
352-
353-
blob = self.generate_stage(
354-
uuid: uuid,
355-
uri: conn_id
356-
)
357-
358-
resp.body = blob
359-
360-
# Short-circuit the payload's handle_connection processing for create_session
361-
create_session(cli, {
362-
:passive_dispatcher => self.service,
363-
:conn_id => conn_id,
364-
:url => url,
365-
:expiration => datastore['SessionExpirationTimeout'].to_i,
366-
:comm_timeout => datastore['SessionCommunicationTimeout'].to_i,
367-
:retry_total => datastore['SessionRetryTotal'].to_i,
368-
:retry_wait => datastore['SessionRetryWait'].to_i,
369-
:ssl => ssl?,
370-
:payload_uuid => uuid
371-
})
372-
373-
when :init_native
374-
print_status("Staging Native payload...")
375-
url = payload_uri(req) + conn_id + "/\x00"
324+
# Damn you, python! Ruining my perfect world!
325+
url += "\x00" unless uuid.arch == ARCH_PYTHON
376326
uri = URI(payload_uri(req) + conn_id)
377327

378-
resp['Content-Type'] = 'application/octet-stream'
328+
# TODO: does this have to happen just for windows, or can we set it for all?
329+
resp['Content-Type'] = 'application/octet-stream' if uuid.platform == 'windows'
379330

380331
begin
381-
# generate the stage, but pass in the existing UUID and connection id so that
382-
# we don't get new ones generated.
383332
blob = self.generate_stage(
384-
uuid: uuid,
385-
uri: conn_id,
333+
url: url,
334+
uuid: uuid,
386335
lhost: uri.host,
387-
lport: uri.port
336+
lport: uri.port,
337+
uri: conn_id
388338
)
389339

390-
resp.body = encode_stage(blob)
340+
blob = encode_stage(blob) if self.respond_to?(:encode_stage)
341+
342+
print_status("Staging #{uuid.arch} payload (#{blob.length} bytes) ...")
343+
344+
resp.body = blob
391345

392346
# Short-circuit the payload's handle_connection processing for create_session
393347
create_session(cli, {
@@ -414,11 +368,14 @@ def on_request(cli, req)
414368
url = payload_uri(req) + conn_id
415369
url << '/' unless url[-1] == '/'
416370

371+
# Damn you, python! Ruining my perfect world!
372+
url += "\x00" unless uuid.arch == ARCH_PYTHON
373+
417374
# Short-circuit the payload's handle_connection processing for create_session
418375
create_session(cli, {
419376
:passive_dispatcher => self.service,
420377
:conn_id => conn_id,
421-
:url => url + "\x00",
378+
:url => url,
422379
:expiration => datastore['SessionExpirationTimeout'].to_i,
423380
:comm_timeout => datastore['SessionCommunicationTimeout'].to_i,
424381
:retry_total => datastore['SessionRetryTotal'].to_i,

lib/msf/core/module/platform.rb

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -536,4 +536,12 @@ class Mainframe < Msf::Module::Platform
536536
Rank = 100
537537
Alias = "mainframe"
538538
end
539+
540+
#
541+
# Multi (for wildcard-style platform functions)
542+
#
543+
class Multi < Msf::Module::Platform
544+
Rank = 100
545+
Alias = "multi"
546+
end
539547
end

lib/msf/core/payload.rb

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,9 @@ class Payload < Msf::Module
3232
require 'msf/core/payload/firefox'
3333
require 'msf/core/payload/mainframe'
3434

35+
# Universal payload includes
36+
require 'msf/core/payload/multi'
37+
3538
##
3639
#
3740
# Payload types

lib/msf/core/payload/android.rb

Lines changed: 17 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -24,41 +24,41 @@ def fix_dex_header(dexfile)
2424
# We could compile the .class files with dx here
2525
#
2626
def generate_stage(opts={})
27+
''
28+
end
29+
30+
def generate_default_stage(opts={})
31+
''
2732
end
2833

2934
#
3035
# Used by stagers to construct the payload jar file as a String
3136
#
32-
def generate
33-
generate_jar.pack
37+
def generate(opts={})
38+
generate_jar(opts).pack
3439
end
3540

3641
def java_string(str)
3742
[str.length].pack("N") + str
3843
end
3944

40-
def apply_options(classes, opts)
41-
config = generate_config_bytes(opts)
42-
if opts[:stageless]
43-
config[0] = "\x01"
44-
end
45-
46-
string_sub(classes, "\xde\xad\xba\xad" + "\x00" * 8191, config)
47-
end
48-
49-
def generate_config_bytes(opts={})
45+
def generate_config(opts={})
5046
opts[:uuid] ||= generate_payload_uuid
47+
ds = opts[:datastore] || datastore
5148

5249
config_opts = {
5350
ascii_str: true,
5451
arch: opts[:uuid].arch,
55-
expiration: datastore['SessionExpirationTimeout'].to_i,
52+
expiration: ds['SessionExpirationTimeout'].to_i,
5653
uuid: opts[:uuid],
57-
transports: [transport_config(opts)]
54+
transports: opts[:transport_config] || [transport_config(opts)]
5855
}
5956

6057
config = Rex::Payloads::Meterpreter::Config.new(config_opts)
61-
config.to_b
58+
result = config.to_b
59+
60+
result[0] = "\x01" if opts[:stageless]
61+
result
6262
end
6363

6464
def string_sub(data, placeholder="", input="")
@@ -104,7 +104,8 @@ def generate_jar(opts={})
104104
classes = MetasploitPayloads.read('android', 'apk', 'classes.dex')
105105
end
106106

107-
apply_options(classes, opts)
107+
config = generate_config(opts)
108+
string_sub(classes, "\xde\xad\xba\xad" + "\x00" * 8191, config)
108109

109110
jar = Rex::Zip::Jar.new
110111
files = [
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
# -*- coding: binary -*-
2+
3+
require 'msf/core'
4+
require 'msf/base/sessions/meterpreter_options'
5+
require 'msf/core/payload/uuid/options'
6+
7+
module Msf
8+
9+
###
10+
#
11+
# Common loader for Android payloads that make use of Meterpreter.
12+
#
13+
###
14+
15+
module Payload::Android::MeterpreterLoader
16+
17+
include Msf::Payload::Android
18+
include Msf::Payload::UUID::Options
19+
include Msf::Sessions::MeterpreterOptions
20+
21+
def initialize(info={})
22+
super(update_info(info,
23+
'Name' => 'Android Meterpreter & Configuration',
24+
'Description' => 'Android-specific meterpreter generation',
25+
'Author' => ['OJ Reeves'],
26+
'Platform' => 'android',
27+
'Arch' => ARCH_DALVIK,
28+
'PayloadCompat' => {'Convention' => 'http https'},
29+
'Stage' => {'Payload' => ''}
30+
))
31+
end
32+
33+
def stage_payload(opts={})
34+
stage_meterpreter(opts)
35+
end
36+
37+
def stage_meterpreter(opts={})
38+
clazz = 'androidpayload.stage.Meterpreter'
39+
metstage = MetasploitPayloads.read("android", "metstage.jar")
40+
met = MetasploitPayloads.read("android", "meterpreter.jar")
41+
42+
# Name of the class to load from the stage, the actual jar to load
43+
# it from, and then finally the meterpreter stage
44+
blocks = [
45+
java_string(clazz),
46+
java_string(metstage),
47+
java_string(met),
48+
java_string(generate_config(opts))
49+
]
50+
51+
(blocks + [blocks.length]).pack('A*' * blocks.length + 'N')
52+
end
53+
54+
end
55+
end
56+

0 commit comments

Comments
 (0)