├── 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 |
--------------------------------------------------------------------------------