Пароль у нас, ключ у модема

Опубликовано 2026-09-19
Пароль у нас, ключ у модема
Серия: Чей это компьютер?Часть 7 из 8

Из серии заметок о bring‑up‑е собственной ОС на Samsung Galaxy Tab S6 (SM‑T865, Snapdragon 855). «Паразит в планшете» объяснял, что Wi‑Fi‑прошивка здесь исполняется на Hexagon сотового модема и шифрует трафик сама; «23 миллисекунды» — как она проверяет хоста. Эта заметка — о том, что мы получили, написав WPA‑супликант на хосте, и чего не получили, потому что получить это на таком чипе нельзя.


1. Вопрос

Мы написали 802.11i сами: 4‑way handshake WPA2‑PSK, групповое рукопожатие и SAE для WPA3‑Personal — чистый крейт crates/wpa, no_std, без радио и часов, автомат над байтами, который драйвер кормит EAPOL‑кадрами и из которого читает ключи. Естественно спросить, зачем: прошивка WCN3990 всё равно шифрует каждый кадр (hwcrypto), и ключ трафика уезжает в неё. Что защищает супликант на хосте, если шифрует не хост? Короткий ответ — учётные данные, а не трафик. Дальше — почему, что это даёт и почему граница проходит здесь у всех, а не только у нас.

2. Что построено

crates/wpa — пять модулей и эталон:

unsafe в крейте нет: разбор кадров, которые прислал AP — или прошивка от его имени, — идёт в safe Rust, и ошибка там — паника, а не запись мимо буфера.

3. Как это проходит через прошивку

Путь пароля. Он хранится в сторе ядра под ключом sys/net/wifi (WifiSettings {ssid, password}, JSON, открытым текстом; тул net.settings показывает лишь password_set). Станция (tools::wifi::station) отдаёт его вниз как LinkAsk::Connect {bssid, password}; задача радио добавляет SNonce и rand ‖ mask для SAE из Kernel::random и строит Credentials. Link::connect тут же считает из пароля PMK (wpa::psk) либо элемент пароля и commit SAE. Ни пароль, ни PMK дальше Link не идут.

Путь ключей — на схеме: слева хост, справа то, что видит прошивка.

 хост (safe Rust)                                    прошивка (Hexagon, hwcrypto)
 ───────────────────────────────                     ────────────────────────────
 пароль  ──PBKDF2──▶ PMK (WPA2)                      —
 пароль  ──SAE/P‑256──▶ PMK (WPA3)   commit/confirm ▶ видит кадры Auth (публичные
                                     MGMT_TX_SEND     scalar/Element), не пароль
 PMK + ANonce + SNonce ──PRF/KDF──▶ PTK = KCK ‖ KEK ‖ TK
                                     EAPOL M1..M4  ▶ видит нонсы, MIC‑и и key data
                                     TX_FRM NO_ENCRYPT (завёрнутую под KEK) — открытым
                                                      текстом, но без PMK/KCK/KEK
 TK (16 байт) ──────────────────── VDEV_INSTALL_KEY ▶ ключ трафика пары
 GTK (из M3, RSC=0) ────────────── VDEV_INSTALL_KEY ▶ ключ группы
 IGTK (из M3) — принят, не ставится                   —
 ──────────────────────────────── PEER_SET_PARAM ▶ authorize: данные пошли
                                                      шифрует и расшифровывает
                                                      каждый кадр этой сессии

Факты из кода, на которых схема держится.

EAPOL идёт через прошивку в открытом виде и до ключей. Первый кадр данных, который станция вообще слышит после ассоциации, — M1 из приёмного кольца. Ответы уходят HTT‑командой TX_FRM с флагом NO_ENCRYPT — ровно так mac80211 отправляет EAPOL до ключей. Прошивке рукопожатие объявлено заранее: PEER_ASSOC несёт NEED_PTK_4_WAY, а authorize в конце значит «данные могут идти».

В прошивку уезжают только TK и GTK. install_ptk берёт из Keys ровно ptk.tk — 16 байт для CCMP — и ставит его VDEV_INSTALL_KEY {PAIRWISE, AES_CCM, idx 0} на адрес BSSID. KCK и KEK, которыми подписаны кадры и завёрнута key data, остаются на хосте; после M4 объект Supplicant вместе с ними отбрасывается. GTK ставится {GROUP, шифр группы, idx}; RSC, который M3 принёс, хост читает, но в команду пишет ноль — «RSC оставлен прошивке, как ath10k_install_key оставляет его». Затем PEER_SET_PARAM AUTHORIZE — и линк connected. В ячейке wifi.link это keys_set 2.

SAE прошивка не касается. Commit и confirm ходят кадрами Authentication через MGMT_TX_SEND/MGMT_RX, как открытая аутентификация; SAE‑offload у ath10k нет, и у нас тоже. Элемент пароля, rand, mask и весь вывод PMK — на хосте; прошивка видит только публичные scalar и Element в эфире.

MFP — наполовину. RSN‑элемент для SAE объявляет MFPC|MFPR и BIP‑CMAC‑128, IGTK из M3 читается в Keys.igtk («IGTK id 5 received» в живом прогоне), но в прошивку не ставится: ath10k возвращает BIP в софт, а проверка защищённых Deauth/Disassoc на нашей стороне ещё не написана; флаг PMF в PEER_ASSOC не ставим — поддержка MFP_SUPPORT прошивкой неизвестна. Пробел зафиксирован.

4. Что защищает супликант на хосте

Пароль и PMK. Прошивка не получает ни пароля, ни PMK, ни KCK/KEK — ни по одному из протокольных путей. То, что она видит в EAPOL (нонсы, MIC‑и, завёрнутая key data), и так публично в эфире; по нему PTK не выводится без PMK. Следствие: скомпрометированная прошивка не может войти в сеть как другое устройство с нашими учётными данными и не может вывести ключи чужих сессий на той же сети — для WPA2‑PSK это потребовало бы PSK, для SAE — пароля. Она получает свой TK для этой сессии, и только его.

Свежий PMK в WPA3. В WPA2‑Personal PMK равен PSK — один на все сессии и все устройства сети, и перехваченное рукопожатие перебирается офлайн. В SAE PMK выводится из обмена Dragonfly и уникален для сессии; из перехвата ничего перебрать нельзя, разглашение одного PMK не открывает другие сессии. На планшете PWE и два скалярных умножения заняли ~60 мс вместе с эфиром.

Логика и разбор — у нас. Проверка MIC, развёртка key data, сравнение RSN‑элемента с маячком (даунгрейд шифра посередине), проверка скаляра и точки в SAE, отражения, статусов — всё в safe Rust на хосте, против независимого эталона, а не в вендорском блобе. Что супликант заключает о «неверном пароле» (bad_password), мы знаем, а не гадаем по поведению прошивки.

Две оговорки, обе из наших же документов. Первая: «прошивка не может узнать пароль» верно по протоколу. Пока поток DMA 0x640 идёт без трансляции SMMU, прошивка читает всю физическую RAM — в том числе sys/net/wifi открытым текстом и PMK в Link. Граница WPA становится полной только после включения stage‑1 из модели угроз; до этого супликант защищает учётные данные от эфира, но не от копроцессора. Вторая: SNonce и rand/mask SAE берутся из Kernel::random, а на sdm855 устройства энтропии пока нет — «the nonces are the clock's», SplitMix64 от uptime. Это записано как некриптостойкое; «свежесть» PMK сегодня опирается на предсказуемый источник, и исправить это надо до того, как сеть станет чем‑то большим, чем тест.

Та же граница — на фрагменте общей карты устройства; схема выше показывала, как секреты выводятся один из другого, эта — где каждый из них физически лежит. Пароль — в куче, в сторе; PMK, KCK и KEK — там же, в Link, и недолго; TK и GTK — в хранилище ключей прошивки; EAPOL и кадры данных — открытым текстом в LENT_WLAN, куда копирующие движки ходят DMA. Жирные стрелки — путь ключа; пунктир — то, что прошивка может, если захочет.

Схема: Пароль у нас, ключ у модема

Полная карта всех жильцов, теми же именами узлов, — в «Чей это компьютер?», §2a.

5. Чего супликант на хосте не защищает

Трафик сессии. TK в прошивке — значит, каждый кадр этой сессии для неё открытый текст в обе стороны, по определению: она его и шифрует. Исходящие Ethernet‑кадры хост кладёт в LENT_WLAN незашифрованными, входящие получает уже расшифрованными в native‑Wi‑Fi decap. Никакая логика на хосте этого не меняет.

Что реально уходит в эфир. Прошивка держит хранилище ключей и сама решает, чем шифровать. Она может поставить себе любой другой ключ, отправить кадр без шифрования или под чужим ключом — хост этого не увидит. Признак encrypted в дескрипторе приёма, по которому Link::on_eth судит, расшифрован ли защищённый кадр, — её слово, как и всё содержимое дескрипторов. Здесь нет проверяемого инварианта, только доверие.

Forward secrecy против прошивки. SAE даёт свежий PMK на сессию, но TK каждой сессии вручается прошивке. Скомпрометированная в момент T, она знает трафик всех сессий с T и дальше; прошлых — только если записывала их заранее, что упирается в её собственную персистентность, о которой мы ничего не знаем. Свежесть PMK защищает от пассивного перехвата в эфире, не от шифрующего копроцессора.

Управляющие кадры. Пока IGTK не применяется, Deauth от имени AP принимается без проверки — и из эфира, и от прошивки, у которой для потери линка есть и собственное событие PEER_STA_KICKOUT.

6. Альтернатива: шифровать на хосте

Логичный выход — не отдавать TK: шифровать CCMP на хосте, отдавать прошивке готовые 802.11‑кадры «как есть» и принимать нерасшифрованные. Нужен режим, в котором прошивка не трогает криптографию ни на передаче, ни на приёме.

Наши источники говорят об этом немногое. В htt.rs есть decap::RAW — формат приёма «как пришло из эфира: заголовок 802.11, параметры шифрования, нагрузка, FCS» — и флаг MAC_HDR_PRESENT на передаче; но WMI_INIT запрашивает RX_DECAP_NATIVE_WIFI, и весь путь данных построен на нём. Ни ATH10K_HW_TXRX_RAW, ни «raw mode» в коде и документах не упоминаются; в Linux у ath10k есть параметр rawmode, но поддерживает ли его прошивка WLAN.HL.3.2.0 на WCN3990 и что при этом теряется, мы не проверяли. Это опция, требующая живой проверки, а не доступный путь — и даже при удаче она меняет мало: прошивка перестаёт видеть содержимое кадров, но остаётся с DMA в наше окно, эфиром и сотовым uplink’ом.

7. А в Linux иначе?

Нет — граница та же по форме. wpa_supplicant в userspace считает PMK и PTK, отдаёт временный ключ ядру через nl80211 (NL80211_CMD_NEW_KEY), mac80211 вызывает set_key драйвера, ath10k_set_key собирает WMI_VDEV_INSTALL_KEY — ту же команду, что и install_ptk у нас. Поэтому модель угроз в столбце «ключи трафика» ставит одно и то же для стокового Android, mainline Linux и нас: «в прошивке». Программное шифрование mac80211 включается там, где драйвер отказывается от ключа — как ath10k для BIP, — а не по выбору пользователя. Супликант на хосте — не наша особенность и не наш недостаток, а универсальная форма границы для любого радио, чья прошивка держит hwcrypto.

8. Итог

WPA на хосте защищает учётные данные, а не трафик. Пароль, PMK и ключи рукопожатия не выходят за пределы хоста ни по одному протокольному пути; прошивка получает ровно 16 байт TK и GTK для одной сессии и не может ни войти в сеть как кто‑то другой, ни открыть чужие сессии, ни — в WPA3 — перебрать пароль по перехвату. Трафик этой сессии она видит целиком, потому что шифрует его сама, и это не исправляется ни одной строкой в супликанте.

Отсюда следствие, к которому ведёт вся серия: конфиденциальность содержимого против радиопрошивки строится выше канала — TLS с проверкой сертификата и сквозное шифрование. Как это выглядит на маленькой плате — в «Доверяй проводу?»; что остаётся после всех заборов — в «Паразите в планшете» (§2.4, §9); карта всех жильцов устройства — во вводной серии.


Источники в репозитории: супликант — crates/wpa/src/lib.rs, supplicant.rs (Ptk, Keys, Step, derive_ptk), authenticator.rs, eapol.rs, crypto.rs (psk), sae.rs (документация модуля, password_element), vectors.rs; путь ключей — crates/ath10k/src/link.rs (документация модуля, Credentials, Secret, connect, on_data, send_eapol, install_ptk, install_gtk, connected, on_eth), crates/ath10k/src/wmi.rs (key, peer_param, InstallKey), crates/ath10k/src/htt.rs (tx_flag0::NO_ENCRYPT, decap::RAW); пароль и станция — crates/tools/src/net.rs (WIFI_KEY, WifiSettings), crates/tools/src/wifi.rs, wifi/station.rs, wifi/target.rs (LinkAsk::Connect, Kernel::random). Стадии 8, 9, 9b с живыми прогонами — docs/wifi-sm8150.md §6e–§6g; что остаётся после stage‑1 и сравнение с Linux — docs/wifi-threat-model.md §6.1, §7.

Часть 7 из 8
  1. 1Чей это компьютер? Ваш, или…
  2. 2Сброс без свидетелей
  3. 3Мегабайт, который убил модем
  4. 4Паразит в планшете
  5. 523 миллисекунды, чтобы солгать
  6. 6Гипервизор сказал «нет»
  7. 7Пароль у нас, ключ у модема
  8. 8Доверяй проводу?

Продукты из статьи

0xd-ossecuritywificryptography
Попробовать