├── README.md ├── botHunter.py └── requirements.txt /README.md: -------------------------------------------------------------------------------- 1 | # botHunter 2 | Scans the internet for open FTP servers looking for common malware bot droppers and grabs them for analysis. Downloads stored in output/ dir. 3 | 4 | Scanning based on https://github.com/kennell/ftpknocker 5 | Copyright (c) 2014, kevin@fileperms.org All rights reserved. 6 | 7 | Bot Hunting capabilities added by Hunter Gregal 8 | 9 | ##Requirements 10 | ------------ 11 | 12 | The **netaddr** module must be installed, on Debian/Ubuntu systems simply run: 13 | 14 | ``` 15 | sudo apt-get install python-pip 16 | sudo pip install -r requirements.txt 17 | ``` 18 | 19 | 20 | ##Install 21 | ------- 22 | 23 | Clone this repository or save botHunter.py on your machine and make it executable: 24 | 25 | ``` 26 | wget https://github.com/huntergregal/botHunter/botHunter.py 27 | chmod +x ./botHunter.py 28 | ``` 29 | 30 | ##Usage 31 | ----- 32 | 33 | ``` 34 | usage: botHunter.py [-h] [-t MAXTHREADS] [-w TIMEOUT] [-s] 35 | [targets [targets ...]] 36 | 37 | positional arguments: 38 | targets 39 | 40 | optional arguments: 41 | -h, --help show this help message and exit 42 | -t MAXTHREADS, --threads MAXTHREADS 43 | Number of threads to use, default is 10 44 | -w TIMEOUT, --wait TIMEOUT 45 | Seconds to wait before timeout, default is 2 46 | -s, --shuffle Shuffle the target list 47 | ``` 48 | 49 | ##Examples 50 | -------- 51 | 52 | The syntax for specifying targets is similar to nmap. Here are some examples: 53 | 54 | Scan three individual IPs: 55 | ``` 56 | ./botHunter.py 192.168.1.1 192.168.1.2 192.168.1.3 57 | ``` 58 | 59 | Scan an entire IP-block using CIDR notation (in this example, all hosts from 192.168.1.1 - 192.168.1.254 will be scanned, a total of 254 hosts): 60 | ``` 61 | ./botHunter.py 192.168.1.0/24 62 | ``` 63 | 64 | Feed targets from a other programm using a pipe (must be IPs, seperated by newlines!): 65 | ``` 66 | cat mytargets.txt | ./botHunter.py 67 | ``` 68 | 69 | 70 | 71 | -------------------------------------------------------------------------------- /botHunter.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | ''' 3 | Scanning code based on https://github.com/kennell/ftpknocker 4 | ''' 5 | 6 | 7 | import ftplib 8 | import sys, os, urllib 9 | import threading 10 | from argparse import ArgumentParser 11 | from netaddr import IPSet 12 | from random import shuffle 13 | 14 | KNOWN_BOTS = ["pbot.php", "pma.php", "lol.php", "kok.php", "bot", "bot2", "nmlt1.sh", "bobo", "dxd2.txt", 15 | "bot.php", "go.php"] 16 | COMMON_DIRS = ["pub", "bots", "bot"] 17 | #check for known bots 18 | def bot_check(ftp, host): 19 | fileList = ftp.nlst() 20 | for fileName in fileList: 21 | if fileName.lower() in KNOWN_BOTS: 22 | outputFile = "output/%s-%s_%s" % (host, urllib.quote_plus(ftp.pwd()), fileName) 23 | print("[+] Potential bot found: %s @@ %s") % (ftp.pwd()+"/"+fileName, host) 24 | with open(outputFile, 'w') as f: 25 | try: 26 | ftp.retrbinary('RETR %s' % fileName, f.write) 27 | except Exception as e: 28 | print("Error getting file: %s" % repr(e)) 29 | if fileName.lower() in COMMON_DIRS: 30 | try: 31 | ftp.cwd(fileName) 32 | bot_check(ftp, host) 33 | except Exception as e: 34 | print repr(e) 35 | 36 | #output dir 37 | def check_output(): 38 | if not os.path.exists("output"): 39 | os.makedirs("output") 40 | # Split list 41 | def split_list(l, parts): 42 | newlist = [] 43 | splitsize = 1.0/parts*len(l) 44 | for i in range(parts): 45 | newlist.append(l[int(round(i*splitsize)):int(round((i+1)*splitsize))]) 46 | return newlist 47 | 48 | # Try anonymous FTP login 49 | def try_ftp_login(hosts): 50 | for host in hosts: 51 | host = host.strip() 52 | try: 53 | ftp = ftplib.FTP() 54 | ftp.connect(host=host, timeout=args.timeout) 55 | if '230' in ftp.login(): 56 | #check for bots, if so download the bot 57 | bot_check(ftp, host) 58 | ftp.quit() 59 | except ftplib.all_errors: 60 | pass 61 | 62 | 63 | # Init Argument parser 64 | argparser = ArgumentParser(description="Scans targets for anonymous ftp servers - looking for known botnet files.") 65 | argparser.add_argument('targets', 66 | nargs='*') 67 | argparser.add_argument('-t', '--threads', 68 | action='store', 69 | default=10, 70 | type=int, 71 | dest='maxThreads', 72 | help='Number of threads to use, default is 10') 73 | argparser.add_argument('-w', '--wait', 74 | action='store', 75 | default=2, 76 | type=int, 77 | dest='timeout', 78 | help='Seconds to wait before timeout, default is 2') 79 | argparser.add_argument('-s', '--shuffle', 80 | action='store_true', 81 | default=False, 82 | dest='shuffle', 83 | help='Shuffle the target list') 84 | args = argparser.parse_args() 85 | 86 | # Check if we are running in a pipe and read from STDIN 87 | if not sys.stdin.isatty(): 88 | args.targets = sys.stdin.readlines() 89 | 90 | # Add target IPs/Networks to a netaddr-IPSet 91 | targetSet = IPSet() 92 | for t in args.targets: 93 | targetSet.add(t) 94 | 95 | #output dir 96 | check_output() 97 | 98 | # Render IPSets to a list 99 | targetlist = list() 100 | for ip in targetSet: 101 | targetlist.append(str(ip)) 102 | 103 | # Check for shuffle argument 104 | if args.shuffle: 105 | shuffle(targetlist) 106 | 107 | # Split list into [maxThreads] smaller batches 108 | targetlist = split_list(targetlist, args.maxThreads) 109 | 110 | # Launch threads 111 | for batch in targetlist: 112 | threading.Thread(target=try_ftp_login, args=(batch,)).start() 113 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | appdirs==1.4.0 2 | netaddr==0.7.19 3 | packaging==16.8 4 | pkg-resources==0.0.0 5 | pyparsing==2.1.10 6 | six==1.10.0 7 | --------------------------------------------------------------------------------