Skip to content

Commit 94241b2

Browse files
author
HD Moore
committed
First attempt at rewiring HTTP handlers to use UUIDs
1 parent 858d9b1 commit 94241b2

File tree

4 files changed

+101
-72
lines changed

4 files changed

+101
-72
lines changed

lib/msf/core/handler/reverse_http.rb

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -215,12 +215,16 @@ def on_request(cli, req, obj)
215215

216216
print_status("#{cli.peerhost}:#{cli.peerport} Request received for #{req.relative_resource}...")
217217

218-
uri_match = process_uri_resource(req.relative_resource)
218+
info = process_uri_resource(req.relative_resource)
219+
220+
conn_id = nil
221+
if info[:mode] && info[:mode] != :connect
222+
conn_id = generate_uri_connect_uuid(info[:uuid], obj.arch, obj.platform)
223+
end
219224

220225
# Process the requested resource.
221-
case uri_match
222-
when /^\/INITPY/
223-
conn_id = generate_uri_checksum(URI_CHECKSUM_CONN) + "_" + Rex::Text.rand_text_alphanumeric(16)
226+
case info[:mode]
227+
when :init_python
224228
url = payload_uri(req) + conn_id + '/'
225229

226230
blob = ""
@@ -254,8 +258,7 @@ def on_request(cli, req, obj)
254258
})
255259
self.pending_connections += 1
256260

257-
when /^\/INITJM/
258-
conn_id = generate_uri_checksum(URI_CHECKSUM_CONN) + "_" + Rex::Text.rand_text_alphanumeric(16)
261+
when :init_java
259262
url = payload_uri(req) + conn_id + "/\x00"
260263

261264
blob = ""
@@ -282,8 +285,7 @@ def on_request(cli, req, obj)
282285
:ssl => ssl?
283286
})
284287

285-
when /^\/A?INITM?/
286-
conn_id = generate_uri_checksum(URI_CHECKSUM_CONN) + "_" + Rex::Text.rand_text_alphanumeric(16)
288+
when :init_native
287289
url = payload_uri(req) + conn_id + "/\x00"
288290

289291
print_status("#{cli.peerhost}:#{cli.peerport} Staging connection for target #{req.relative_resource} received...")
@@ -318,11 +320,9 @@ def on_request(cli, req, obj)
318320
:ssl => ssl?,
319321
})
320322

321-
when /^\/CONN_.*\//
323+
when :connect
322324
resp.body = ""
323-
# Grab the checksummed version of CONN from the payload's request.
324-
conn_id = req.relative_resource.gsub("/", "")
325-
325+
conn_id = req.relative_resource
326326
print_status("Incoming orphaned session #{conn_id}, reattaching...")
327327

328328
# Short-circuit the payload's handle_connection processing for create_session

lib/msf/core/handler/reverse_http/uri_checksum.rb

Lines changed: 76 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,7 @@
11
# -*- coding: binary -*-
2+
3+
require 'msf/core/payload/uuid'
4+
25
module Msf
36
module Handler
47
module ReverseHttp
@@ -9,67 +12,85 @@ module UriChecksum
912
# These are based on charset frequency
1013
#
1114
URI_CHECKSUM_INITW = 92 # Windows
15+
URI_CHECKSUM_INITN = 92 # Native (same as Windows)
1216
URI_CHECKSUM_INITP = 80 # Python
1317
URI_CHECKSUM_INITJ = 88 # Java
14-
URI_CHECKSUM_CONN = 98
18+
URI_CHECKSUM_CONN = 98 # Existing session
1519

16-
#
17-
# Precalculated checkums as fallback
18-
#
19-
URI_CHECKSUM_PRECALC = [
20-
"Zjjaq", "pIlfv", "UvoxP", "sqnx9", "zvoVO", "Pajqy", "7ziuw", "vecYp", "yfHsn", "YLzzp",
21-
"cEzvr", "abmri", "9tvwr", "vTarp", "ocrgc", "mZcyl", "xfcje", "nihqa", "40F17", "zzTWt",
22-
"E3192", "wygVh", "pbqij", "rxdVs", "ajtsf", "wvuOh", "hwRwr", "pUots", "rvzoK", "vUwby",
23-
"tLzyk", "zxbuV", "niaoy", "ukxtU", "vznoU", "zuxyC", "ymvag", "Jxtxw", "404KC", "DE563",
24-
"0A7G9", "yorYv", "zzuqP", "czhwo", "949N8", "a1560", "5A2S3", "Q652A", "KR201", "uixtg",
25-
"U0K02", "4EO56", "H88H4", "5M8E6", "zudkx", "ywlsh", "luqmy", "09S4I", "L0GG0", "V916E",
26-
"KFI11", "A4BN8", "C3E2Q", "UN804", "E75HG", "622eB", "1OZ71", "kynyx", "0RE7F", "F8CR2",
27-
"1Q2EM", "txzjw", "5KD1S", "GLR40", "11BbD", "MR8B2", "X4V55", "W994P", "13d2T", "6J4AZ",
28-
"HD2EM", "766bL", "8S4MF", "MBX39", "UJI57", "eIA51", "9CZN2", "WH6AA", "a6BF9", "8B1Gg",
29-
"J2N6Z", "144Kw", "7E37v", "9I7RR", "PE6MF", "K0c4M", "LR3IF", "38p3S", "39ab3", "O0dO1",
30-
"k8H8A", "0Fz3B", "o1PE1", "h7OI0", "C1COb", "bMC6A", "8fU4C", "3IMSO", "8DbFH", "2YfG5",
31-
"bEQ1E", "MU6NI", "UCENE", "WBc0E", "T1ATX", "tBL0A", "UGPV2", "j3CLI", "7FXp1", "yN07I",
32-
"YE6k9", "KTMHE", "a7VBJ", "0Uq3R", "70Ebn", "H2PqB", "83edJ", "0w5q2", "72djI", "wA5CQ",
33-
"KF0Ix", "i7AZH", "M9tU5", "Hs3RE", "F9m1i", "7ecBF", "zS31W", "lUe21", "IvCS5", "j97nC",
34-
"CNtR5", "1g8gV", "7KwNG", "DB7hj", "ORFr7", "GCnUD", "K58jp", "5lKo8", "GPIdP", "oMIFJ",
35-
"2xYb1", "LQQPY", "FGQlN", "l5COf", "dA3Tn", "v9RWC", "VuAGI", "3vIr9", "aO3zA", "CIfx5",
36-
"Gk6Uc", "pxL94", "rKYJB", "TXAFp", "XEOGq", "aBOiJ", "qp6EJ", "YGbq4", "dR8Rh", "g0SVi",
37-
"iMr6L", "HMaIl", "yOY1Z", "UXr5Y", "PJdz6", "OQdt7", "EmZ1s", "aLIVe", "cIeo2", "mTTNP",
38-
"eVKy5", "hf5Co", "gFHzG", "VhTWN", "DvAWf", "RgFJp", "MoaXE", "Mrq4W", "hRQAp", "hAzYA",
39-
"oOSWV", "UKMme", "oP0Zw", "Mxd6b", "RsRCh", "dlk7Q", "YU6zf", "VPDjq", "ygERO", "dZZcL",
40-
"dq5qM", "LITku", "AZIxn", "bVwPL", "jGvZK", "XayKP", "rTYVY", "Vo2ph", "dwJYR", "rLTlS",
41-
"BmsfJ", "Dyv1o", "j9Hvs", "w0wVa", "iDnBy", "uKEgk", "uosI8", "2yjuO", "HiOue", "qYi4t",
42-
"7nalj", "ENekz", "rxca0", "rrePF", "cXmtD", "Xlr2y", "S7uxk", "wJqaP", "KmYyZ", "cPryG",
43-
"kYcwH", "FtDut", "xm1em", "IaymY", "fr6ew", "ixDSs", "YigPs", "PqwBs", "y2rkf", "vwaTM",
44-
"aq7wp", "fzc4z", "AyzmQ", "epJbr", "culLd", "CVtnz", "tPjPx", "nfry8", "Nkpif", "8kuzg",
45-
"zXvz8", "oVQly", "1vpnw", "jqaYh", "2tztj", "4tslx"
20+
# Mapping between checksums and modes
21+
URI_CHECKSUM_MODES = Hash[
22+
URI_CHECKSUM_INITN, :init_native,
23+
URI_CHECKSUM_INITP, :init_python,
24+
URI_CHECKSUM_INITJ, :init_java,
25+
URI_CHECKSUM_CONN, :connect
4626
]
4727

28+
URI_CHECKSUM_MIN_LEN = 5
29+
30+
# Limit how long :connect URLs are to stay within 256 bytes when including
31+
# the hostname, colon, port, and leading slash
32+
URI_CHECKSUM_CONN_MAX_LEN = 128
33+
34+
URI_CHECKSUM_UUID_MIN_LEN = URI_CHECKSUM_MIN_LEN + Msf::Payload::UUID::UriLength
35+
4836
# Map "random" URIs to static strings, allowing us to randomize
4937
# the URI sent in the first request.
5038
#
51-
# @param uri_match [String] The URI string to convert back to the original static value
52-
# @return [String] The static URI value derived from the checksum
53-
def process_uri_resource(uri_match)
54-
55-
# This allows 'random' strings to be used as markers for
56-
# the INIT and CONN request types, based on a checksum
57-
uri_strip.sub!(/^\//, '')
58-
uri_check = Rex::Text.checksum8(uri_strip)
59-
60-
# Match specific checksums and map them to static URIs
61-
case uri_check
62-
when URI_CHECKSUM_INITW
63-
uri_match = "/INITM"
64-
when URI_CHECKSUM_INITP
65-
uri_match = "/INITPY"
66-
when URI_CHECKSUM_INITJ
67-
uri_match = "/INITJM"
68-
when URI_CHECKSUM_CONN
69-
uri_match = "/CONN_" + ( uri_conn || Rex::Text.rand_text_base64url(16) )
39+
# @param uri_match [String] The URI string from the HTTP request
40+
# @return [Hash] The attributes extracted from the URI
41+
def process_uri_resource(uri)
42+
43+
# Ignore non-base64url characters in the URL
44+
uri_bare = uri.gsub(/[^a-zA-Z0-9_\-]/, '')
45+
46+
# Figure out the mode based on the checksum
47+
uri_csum = Rex::Text.checksum8(uri_bare)
48+
49+
uri_uuid = nil
50+
51+
if uri_bare.length >= URI_CHECKSUM_UUID_MIN_LEN
52+
uri_uuid =
53+
Msf::Payload::UUID.payload_uuid_parse_raw(
54+
Rex::Text.decode_base64url(
55+
uri_bare[0, Msf::Payload::UUID::UriLength]))
56+
57+
# Verify the uri_uuid fields and unset everything but
58+
# the unique ID itself unless it looks wonky.
59+
if uri_uuid[:timestamp] > (Time.now.utc.to_i + (24*3600*365)) ||
60+
uri_uuid[:timestamp] < (Time.now.utc.to_i - (24*3600*365)) ||
61+
(uri_uuid[:arch].nil? && uri_uuid[:platform].nil?)
62+
uri_uuid = { puid: uri_uuid[:puid] }
63+
end
7064
end
7165

72-
uri_match
66+
uri_mode = URI_CHECKSUM_MODES[uri_csum]
67+
68+
# Return a hash of URI attributes to the caller
69+
{
70+
uri: uri_bare,
71+
sum: uri_csum,
72+
uuid: uri_uuid,
73+
mode: uri_mode
74+
}
75+
end
76+
77+
# Create a URI that matches the :connect mode with optional UUID, Arch, and Platform
78+
#
79+
# @param uuid [Hash] An optional hash with the UUID parameters
80+
# @param arch [String] An optional architecture name to use if no UUID is provided
81+
# @param platform [String] An optional platform name to use if no UUID is provided
82+
# @return [String] The URI string that checksums to the given value
83+
def generate_uri_connect_uuid(uuid=nil, arch=nil, platform=nil)
84+
curl_uri_len = URI_CHECKSUM_UUID_MIN_LEN+rand(URI_CHECKSUM_CONN_MAX_LEN-URI_CHECKSUM_UUID_MIN_LEN)
85+
curl_prefix = Rex::Text.encode_base64url(
86+
Msf::Payload::UUID.payload_uuid_generate_raw(
87+
uuid: uuid[:puid],
88+
arch: uuid[:arch] || arch,
89+
platform: uuid[:platform] || platform,
90+
timestamp: uuid[:timestamp] ))
91+
92+
# Pad out the URI and make the checksum match :connect
93+
"/" + generate_uri_checksum(URI_CHECKSUM_CONN, curl_uri_len, curl_prefix)
7394
end
7495

7596
# Create an arbitrary length URI that matches a given checksum
@@ -83,13 +104,13 @@ def generate_uri_checksum(sum, len=5, prefix="")
83104
# Lengths shorter than 4 bytes are unable to match all possible checksums
84105
# Lengths of exactly 4 are relatively slow to find for high checksum values
85106
# Lengths of 5 or more bytes find a matching checksum fairly quickly (~80ms)
86-
if len < 5
87-
raise ArgumentError, "Length must be 5 bytes or greater"
107+
if len < URI_CHECKSUM_MIN_LEN
108+
raise ArgumentError, "Length must be #{URI_CHECKSUM_MIN_LEN} bytes or greater"
88109
end
89110

90111
gen_len = len-prefix.length
91-
if gen_len < 5
92-
raise ArgumentError, "Prefix must be at least 5 bytes smaller than total length"
112+
if gen_len < URI_CHECKSUM_MIN_LEN
113+
raise ArgumentError, "Prefix must be at least {URI_CHECKSUM_MIN_LEN} bytes smaller than total length"
93114
end
94115

95116
# Brute force a matching checksum for shorter URIs

lib/msf/core/payload/uuid.rb

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
# This module provides methods for calculating, extracting, and parsing
99
# unique ID values used by payloads.
1010
#
11-
module Msf::Payload::UUID
11+
class Msf::Payload::UUID
1212

1313
Architectures = {
1414
0 => nil,
@@ -62,13 +62,17 @@ module Msf::Payload::UUID
6262
23 => 'firefox'
6363
}
6464

65+
RawLength = 16
66+
UriLength = 22
67+
6568
#
6669
# Generate a raw 16-byte payload UUID given a seed, platform, architecture, and timestamp
6770
#
6871
# @options opts [String] :seed A optional string to use for generated the unique payload ID, deterministic
6972
# @options opts [String] :arch The hardware architecture for this payload
7073
# @options opts [String] :platform The operating system platform for this payload
7174
# @options opts [String] :timestamp The timestamp in UTC Unix epoch format
75+
# @return [String] The encoded payoad UUID as a binary string
7276
#
7377
def self.payload_uuid_generate_raw(opts={})
7478
plat_id = find_platform_id(opts[:platform]) || 0
@@ -97,15 +101,15 @@ def self.payload_uuid_generate_raw(opts={})
97101
# Parse a raw 16-byte payload UUID and return the payload ID, platform, architecture, and timestamp
98102
#
99103
# @param raw [String] The raw 16-byte payload UUID to parse
100-
# @return [Array] The Payload ID, platform, architecture, and timestamp
104+
# @return [Hash] A hash containing the Payload ID, platform, architecture, and timestamp
101105
#
102106
def self.payload_uuid_parse_raw(raw)
103107
puid, plat_xor, arch_xor, plat_id, arch_id, tstamp = raw.unpack('A8C4N')
104108
plat = find_platform_name(plat_xor ^ plat_id)
105109
arch = find_architecture_name(arch_xor ^ arch_id)
106110
time_xor = [plat_xor, arch_xor, plat_xor, arch_xor].pack('C4').unpack('N').first
107111
time = time_xor ^ tstamp
108-
[puid, plat, arch, time]
112+
{ puid: puid, platform: plat, arch: arch, timestamp: time }
109113
end
110114

111115
# Alias for the class method
@@ -133,6 +137,7 @@ def self.find_platform_id(platform)
133137
end
134138

135139
def self.find_architecture_id(name)
140+
name = name.first if name.kind_of? ::Array
136141
Architectures.keys.select{ |k|
137142
Architectures[k] == name
138143
}.first || Architectures[0]

lib/rex/post/meterpreter/packet_dispatcher.rb

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -66,9 +66,12 @@ def initialize_passive_dispatcher
6666
self.waiters = []
6767
self.alive = true
6868

69+
# Ensure that there is only one leading and trailing slash on the URI
70+
resource_uri = "/" + self.conn_id.to_s.gsub(/(^\/|\/$)/, '') + "/"
71+
6972
self.passive_service = self.passive_dispatcher
70-
self.passive_service.remove_resource("/" + self.conn_id + "/")
71-
self.passive_service.add_resource("/" + self.conn_id + "/",
73+
self.passive_service.remove_resource(resource_uri)
74+
self.passive_service.add_resource(resource_uri,
7275
'Proc' => Proc.new { |cli, req| on_passive_request(cli, req) },
7376
'VirtualDirectory' => true
7477
)

0 commit comments

Comments
 (0)