• XSS.stack #1 – первый литературный журнал от юзеров форума

GSM Обзор проекта Rayhunter

evdo

Главный редактор
Модератор
Регистрация
18.08.2024
Сообщения
95
Реакции
79
Обзор проекта Rayhunter.

Проект Rayhunter представляет собой открытый инструмент, разработанный Electronic Frontier Foundation (EFF) для детекции имитаторов базовых станций сотовой связи (cell-site simulators, CSS), также известных как IMSI-catchers или "Stingrays".
Эти устройства маскируются под легитимные базовые станции, принуждая мобильные терминалы в радиусе действия подключаться к ним вместо аутентичных базовых станций, что позволяет перехватывать идентификаторы (IMSI, IMEI) и потенциально осуществлять мониторинг трафика.

Rayhunter ориентирован на пассивный анализ сигнального трафика в реальном времени для выявления аномалий, указывающих на присутствие таких имитаторов.
Проект реализован на языке программирования Rust и предназначен для работы на мобильном хотспоте Orbic Speed RC400L стоимостью 30 USD, что делает его доступным для широкого круга пользователей, включая технических специалистов и активистов в области приватности данных.
1. Возможности решения.
Rayhunter предоставляет следующие ключевые функциональные возможности:
  • мониторинг сигнального трафика в реальном времени: устройство захватывает контрольные сообщения (signaling data) между модемом хотспота и базовой станцией, анализируя их на предмет подозрительных событий;
  • определение аномалий: алгоритмы выявляют индикаторы компрометации, включая NAS-сообщения (Non-Access Stratum), где фиксируются запросы идентификаторов или принудительное снижение уровня безопасности (например, отсутствие щифрования);
  • захват и хранение данных: при обнаружении подозрительной активности Rayhunter автоматически сохраняет PCAP-файлы (Packet Capture) с контрольным трафиком для последующего оффлайн-анализа, без перехвата пользовательских данных (payload), что обеспечивает этичность и соответствие нормам приватности;
  • оповещение пользователя: через веб-интерфейс или индикаторы на устройстве (LED) уведомляет о потенциальной угрозе;
  • поддержка сетей: фокусировка на LTE (4G), с потенциалом расширения на другие стандарты через будущие обновления; тестирование проводилось с открытыми базовыми станциями на базе srsRAN или OpenBTS;
  • интеграция с сообществом: открытый исходный код позволяет пользователям вносить улучшения, такие как новые критерии аномалий на основе исследований.
2. Особенности реализации.
Реализация Rayhunter отличается минималистичным и эффективным дизайном, ориентированным на портативность:
  • используется модифицированный мобильный хотспот Orbic RC400L с чипсетом Qualcomm или совместимый (поддержка AT-команд для доступа к модему). Устройство компактно (размеры сопоставимы с карманным гаджетом), энергопотребление низкое, что обеспечивает автономную работу от встроенной батареи.
  • написан на Rust для обеспечения безопасности памяти и производительности; включает модули для парсинга протоколов 3GPP (например, NAS, RRC – Radio Resource Control). Захват трафика осуществляется через интерфейс модема без необходимости в дополнительном оборудовании, таком как RTL-SDR.
  • процесс установки включает скачивание бинарного релиза с GitHub, подключение устройства к ПК и прошивку через скрипт. После установки доступен веб-интерфейс на локальном IP для мониторинга.
3. Оценка эффективности критериев выявления индикаторов компрометации в сетях GSM/UMTS/LTE, скрытности и удобства применения.
Критерии выявления основаны на анализе характерных аномалий имитаторов базовых станций, описанных в стандартах 3GPP и исследованиях:
  • запрос идентификатора IMSI через сообщение NAS Identity Request от базовой станции, что обычно происходит только при начальном подключении или невозможности идентификации по временным идентификаторам (TMSI/GUTI);
  • разрыв соединения с последующей переадресацией на базовую станцию стандарта 2G;
  • вещание текущей базовой станцией сообщений SIB Type 6 и 7 с приоритетами для 2G/3G-частот, потенциально принуждающими к переходу на них;
  • предложение базовой станцией null-шифрования (EEA0) в слое RRC, отключающего криптозащиту между устройством и базовой станцией, и/или в команде security mode на уровне NAS после успешной аутентификации;
  • неполный перечень SIB в сообщении SIB1 (например, отсутствие SIB3, SIB5 и прочих), поскольку фальшивые базовые станции часто вещают только SIB1 и один дополнительный SIB.
В LTE эффективность работы высока для типичных сценариев: инструмент фиксирует до 80–90% известных индикаторов, как показано в тестах на DEF CON 33 с открытыми базовыми станциями.
Стандарты GSM/UMTS анализируются лишь косвенно по факту принудительного перевода обслуживания хотспота на них.

Rayhunter обладает высокой степенью скрытности, поскольку функционирует в пассивном режиме: не генерирует дополнительный трафик, не модифицирует сигнальные сообщения и не раскрывает свое присутствие.
Модем работает как стандартный UE (User Equipment), что делает его неотличимым от обычного хотспота.
Портативность (вес < 100 г), автономность (>8 часов), простой веб-интерфейс для визуализации данных.
Установка занимает < 5 минут, не требует специализированных навыков за пределами базового знания Linux/ADB.

Ограничения проекта: зависимость от конкретного аппаратного обеспечения и необходимость SIM-карты для работы.
4. Освещение в прессе и публичность проекта.
Проект получил значительное освещение в специализированных СМИ в период март–май 2025 г., с пиком в марте после релиза EFF: публикации в Hackaday, BleepingComputer, Schneier on Security, Reddit (r/linux, r/rust), Slashdot, Android Authority и презентация на DEF CON 33 (август 2025).
Проект ориентирован на энтузиастов информационной безопасности, активистов и исследователей, а не на массового потребителя, из-за технической специфики и фокуса на нишевых угрозах (CSS используются преимущественно правоохранительными органами).
5. Практический опыт применения.
В качестве мобильного хотспота использован TP-Link M7350, доступный в открытой продаже в ЕС и России.
Данная модель также позволяет менять IMEI встроенного модема специальной командой через SSH-сессию, а также устанавливать конкретные диапазоны LTE для работы.
Проект интересен с точки зрения возможности реализации собственных критериев выявления аномалий и автоматического реагирования на них, например отключения радиомодуля.
5.1 Порядок установки дистрибутива "Rayhunter" и подготовка устройства TP-Link M7350 к работе.

1. Отформатировать карту памяти в файловую систему FAT, вставить в устройство;
2. Включить устройство, дождаться завершения его загрузки;
3. Подключиться к устройству кабелем или через беспроводной интерфейс, запустить программу установки.​
Код:
test@test:~$ ./installer tplink
Launching telnet on the device
Got a 404 trying to run exploit for hardware revision v3, trying v5 exploit
Listening on http://127.0.0.1:4000
Please open above URL in your browser and log into the router to continue.
Connecting via telnet to 192.168.0.1
Mounting sdcard on /media/card
sdcard already mounted
Sending file /media/card/config.toml ... ok
Sending file /media/card/rayhunter-daemon ... ok
Sending file /etc/init.d/rayhunter_daemon ... ok
Done. Rebooting device. After it's started up again, check out the web interface at http://192.168.0.1:8080

Для удобства дальнейшего использования - активации SSH и позможности смены IMEI, выполняем следующие действия:
1. Прилагаемые архивы dropbear.tar и imei_band.tar копируем на карту памяти;
2. Подключаемся к устройству через telnet, выполняем команды:​
Код:
cd /sdcard
tar -C / -xvf dropbear.tar
tar -C /bin -xvf imei_band.tar
cd /bin
chmod 755 imei
chmod 755 band
chmod 755 diagcmd
reboot

3. После перезагрузки подключаемся к устройству по SSH, вводим пароль oelinux123 и меняем его на нужный:​
Код:
ssh -oHostKeyAlgorithms=+ssh-rsa root@192.168.0.1
root@mdm9625:~# passwd

4. Меняем imei и/или активируем поддержку нужных band'ов модемом посредством одноименных команд:​
Код:
ssh -oHostKeyAlgorithms=+ssh-rsa root@192.168.0.1
root@mdm9625:~# imei
root@mdm9625:~# band
Роутер перезагрузится.

5. Заходим в графический интерфейс роутера по адресу http://192.168.0.1 , выполняем следующие действия:
  • в разделе "Advanced->NATForwarding->PortTriggering" блокируем удаленный доступ к устройству по telnet;
  • назначаем требуемые параметры безопасности Wi-Fi сети в разделе Wireless и выключаем WPS, если планируем подключаться к устройству по Wi-Fi в дальнейшем.

(Опционально)
6. Если необходимо управлять модемом при помощи AT-команд для собственных сценариев, не связанных с проектом, необходимо дополнительно установить qterminal:​
Код:
root@mdm9625:~# cd /sdcard
root@mdm9625:~# tar -C / -xvf qterminal_m7350.tar
root@mdm9625:~# qterminal

>ati

ati
Manufacturer: QUALCOMM INCORPORATED
Model: 4108
Revision: MPSS.JO.1.2.c1-00145-9607_GEN_PACK-2.73921.1  1  [Oct 17 2016 02:00:00]
SVN: 01
IMEI: 86019905*******
+GCAP: +CGSM

OK
Ссылка на репозиторий проекта: https://github.com/EFForg/rayhunter
Текущая версия ПО: Rayhunter v0.8.0
 

Вложения

  • Addons.zip
    350.8 КБ · Просмотры: 20
Последнее редактирование:
Интересно было бы гайды посмотреть, практическое применение, опыт автора, что то такое =)
 
Сами забанили gliderexpert , теперь тут нет связистов от слова совсем.
справедливости ради - он сам ушел, с призывом уйти всем остальным, с массовой рассылкой по личкам - не забыл?
Очень надеюсь, что вернется, под другим ником, видимо...=)

По теме. Добавлю "отсебятины" в неакадемическом стиле. как в буржунете: "Хакни копов!" ("Hack" the Cops with Rayhunter )
Кратко, что есть РэйХантер? :
инструмент обнаружения атак на ваше мобильное устройство
цена вопроса 25-30$,
opensource
довольно подробная статья здесь: https://micahflee.com/hunting-street-level-cell-phone-surveillance-with-rayhunter/ ниже ее перевод
==================================================================================================================
Похоже, сейчас самое время защитить наши сообщества, организовав собственную контрразведку. Поэтому я с нетерпением ждал, когда узнал о новом инструменте с открытым исходным кодом от EFF для обнаружения сотовой слежки под названием Rayhunter . Rayhunter — это прошивка, которую можно установить на недорогую точку доступа Wi-Fi, способную обнаруживать имитаторы сотовых станций, также называемые IMSI-ловушками или Stingray.

Я купил собственную точку доступа, чтобы установить Rayhunter. Вот мой опыт настройки и то, как можно самостоятельно обнаружить эти атаки.

Что такое симулятор сотовой связи?​


Имитаторы сотовой связи — это устройства, используемые полицией (а может, и другими организациями, кто знает) для беспорядочной слежки за всеми мобильными телефонами, находящимися в определённом месте, например, на акции протеста. Имитаторы сотовой связи, которые обычно устанавливаются в автомобилях, имитируют настоящие вышки сотовой связи, обманным путём заставляя находящиеся поблизости телефоны подключаться к ним вместо настоящих. По сути, они осуществляют атаку «человек посередине» между мобильными телефонами и вышкой, с которой они должны обмениваться данными.

Они могут видеть уникальные идентификаторы (IMSI) всех SIM-карт в этом районе, что позволяет им отслеживать, какие IMSI используются на различных протестах в разное время, и, возможно, идентифицировать отдельных активистов. Они также потенциально могут шпионить за любыми незашифрованными телефонными звонками и текстовыми сообщениями (используйте Signal, чтобы избежать этого!). Они также потенциально могут шпионить за интернет-трафиком, но только если смогут понизить мобильный интернет до 2G (поддержка которого в США прекращена) или если у них есть ключи шифрования для 3G или 4G от операторов связи. Неясно, есть ли у местной полиции доступ к ним, но, возможно, он есть у некоторых служб.

Что купить​


Во-первых, вам понадобится мобильная точка доступа Orbic RC400L. В блоге EFF есть ссылки на Amazon и eBay . Я нашёл подержанную на eBay за 15,77 доллара, но после доставки она стоила около 23 долларов.
orbic-hotspot-and-sim.jpg


Во-вторых, вам нужна SIM-карта. Я не до конца понимаю, как работает Rayhunter (или симуляторы сотовой связи). Но, насколько я понимаю, вам, похоже, не нужно платить за телефонную связь для вашей SIM-карты. Вам просто нужно подключить SIM-карту к устройству Orbic.


Подойдет любая SIM-карта. Их часто можно найти в обычных магазинах, у стоек с подарочными картами. Я проверил местные магазины CVS и Safeway, и, к сожалению, у них их не оказалось. Слышал, что их продаёт Target, но сейчас я бойкотирую Target . В итоге я заказал предоплаченную SIM-карту Tello Mobile (без тарифного плана) на Amazon за 3 доллара. (Я избегаю покупок на Amazon, но пока не бойкотирую его. Фу, всё ужасно.)


Вам также понадобится кабель USB-C (в комплекте с моим Orbic его не было, но он у меня завалялся) и компьютер под управлением Linux, macOS или, если вы готовы потрудиться, Windows.


Установка Rayhunter​


После того, как вы достанете Orbic и SIM-карту, переверните точку доступа Orbic и подденьте правый нижний угол, чтобы получить доступ к аккумулятору. Извлеките аккумулятор. Под ним вы увидите слот для SIM-карты. Мне показалось, что вставить его довольно сложно. Нужно сдвинуть металлическую защёлку вправо, чтобы разблокировать телефон, открыть телефон, вставить SIM-карту, нажать на него и снова заблокировать телефон, сдвинув металлическую защёлку влево.

inserting-sim.jpg

Установка SIM-карты в точку доступа Orbic
Подключите аккумулятор и защёлкните заднюю панель. Затем нажмите и удерживайте кнопку питания, пока Orbic не загрузится.


Чтобы установить прошивку Rayhunter, следуйте инструкциям в файле README.md Rayhunter на GitHub.
Rayhunter не поддерживает скрипт автоматической установки для Windows. Если вы используете Windows, вам необходимо следовать инструкциям по настройке среды разработки.
По сути, мои шаги были такими:

Сначала я попробовал сделать это на ноутбуке с Ubuntu 24.04. Сначала всё работало, но затем процесс завис на этапе перезагрузки Orbic и ожидания его повторного запуска. Я попытался устранить неполадки и опубликовал несколько комментариев в GitHub.

Но в конце концов я решил попробовать тот же процесс на Mac, и на этот раз все прошло без проблем.
Возможно, вы столкнётесь с похожими проблемами. Если это произойдёт с вами, рекомендую поискать информацию о проблемах на GitHub или создать новую, если не найдёте подходящую.


Использование Rayhunter​


Rayhunter сохраняет тот же пользовательский интерфейс по умолчанию для точки доступа Orbic WiFi (а если вы платите за обслуживание своей SIM-карты, вы даже можете использовать ее как точку доступа WiFi, если захотите), но добавляет тонкую зеленую линию в верхней части дисплея.

screenshot_2025-03-04_at_9.20.10_am.png

Фотография Orbic, работающего на Rayhunter, взята из записи в блоге EFF (спасибо)
Если линия вверху становится красной, значит, Рэйхантер нашёл что-то подозрительное. Пока что я не видел, чтобы она краснела, но и протестовать пока не стал.


Если Rayhunter обнаружит что-то подозрительное, вы можете подключиться к Orbic и загрузить логи произошедшего. Обратите внимание, что в блоге EFF говорится следующее:


Если вы получили предупреждение Rayhunter и хотите помочь нам в нашем исследовании, отправьте нам ваши данные Rayhunter (журналы QMDL и PCAP) на наше имя пользователя Signal ElectronicFrontierFoundation.90 , указав следующую информацию: дату захвата, место захвата, устройство, модель устройства и версию Rayhunter.

Существует два способа подключения к Orbic, описанные в файле README.md. Вы можете подключиться к сети Wi-Fi, где работает Orbic, и открыть http://192.168.1.1:8080/, или подключить его к компьютеру по USB и использовать adbкоманду для переадресации порта 8080 через USB.


Поскольку Wi-Fi проще, я так и сделаю. На Orbic можно нажать кнопку меню несколько раз, пока не появится надпись «Информация о Wi-Fi 2,4 ГГц» (на вашем устройстве частота может быть меньше 5 ГГц, в зависимости от настроек), а затем нажать кнопку питания. После этого на экране отобразятся имя и пароль для сети Wi-Fi, вот так.

wifi-info.jpg
Просмотр информации о WiFi на Orbic
Затем подключите компьютер к этой сети WiFi.

Если вы не оплачиваете услуги SIM-карты, на этом этапе ваш компьютер не будет иметь доступа в интернет, пока вы не подключитесь к другой сети. Но вам это и не нужно. Сайт, который вы загружаете, http://192.168.1.1:8080/, размещен непосредственно на устройстве Orbic.

Наконец, откройте http://192.168.1.1:8080/ в браузере. Это должно выглядеть примерно так:
Screenshot-from-2025-04-08-18-53-34.png


Здесь вы можете посмотреть информацию о том, что собирает Rayhunter. Вы можете скачать логи в формате PCAP или QMDL, представляющие собой дампы пакетов. При обнаружении подозрительного события они будут содержать доказательства.

Я не очень хорошо знаком с файлами QMDL, которые являются собственным форматом Qualcomm. Но если вы хотите самостоятельно изучить файлы PCAP, вы можете открыть их с помощью программы с открытым исходным кодом Wireshark .
 
Последнее редактирование:
Не смеши народ! Какую контрразведку? О чем ты вообще? Тебя на любой станции прослушают и запишут, что бы ты не делала.
для тех кто в танке\под_солями:
модератор попросил практический опыт, которого нет, скорее всего ни у кого. Я потратила время, чтобы содержательно дополнить ответ чужим опытом
какого-то америкоса, который захотел использовать инструмент по-своему. Дальше перевод его статьи. Провокации уже надоели.
Да, мне тоже очень жаль, что нет глайдера на этом форуме.

По теме. Жаль, что обсуждается непонятный инструмент с непонятной эффективностью.
Смысл многих интернет сми - обзор новинок. Смысл форума - в чем? в идеале, найти решение проблемы.А получается много обзоров, тулзы, статьи, мнения, много реплик оффтопных, ошибочных, вопросительных. А результат? а хз...

Проблема. Безопасность соединения wi-fi.
Обзор
Методы, технологии атак Детектирование (софт+девайс) ПокрытиеВыводы
ааполное
ббчастичное


А по этому инструменту. Насколько он детектирует все аномалии при попытках перехвата трафика? Можно ли работать с другими роутерами? Методика прошивки других роутеров?
Испытания в белую (атака-защита) на стенде?

Вот это было бы круто...:-(
 
Последнее редактирование:
А ты в курсе, что стандарты телефонии в Америке и в России разные. Почитай тогда.
А потом из за таких как ты в барыжном отделе вовсю доступ к ОКС-7 продают.
Стандарты связи GSM, UMTS, LTE, работу которых поддерживает обсуждаемое устройство, так же как и протоколы SS7 и DIAMETER в опорных сетях соответствующих операторов связи в России, Америке и множестве других стран одинаковые, потому что они стандартизованы согласно спецификациям 3GPP. Отличаются диапазоны рабочих частот. Вы не имеете компетенций, чтобы упрекать в этом других, но продолжаете это делать публично. Хорошо, что это позволяет сформировать о Вас корректное мнение другим участникам форума. Плохо, что нет предметного разговора.​
И где это место. Просвятите пожалуйста.
Болталка.
 
Стандарты связи GSM, UMTS, LTE, работу которых поддерживает обсуждаемое устройство, так же как и протоколы SS7 и DIAMETER в опорных сетях соответствующих операторов связи в России, Америке и множестве других стран одинаковые, потому что они стандартизованы согласно спецификациям 3GPP. Отличаются диапазоны рабочих частот. Вы не имеете компетенций, чтобы упрекать в этом других, но продолжаете это делать публично. Хорошо, что это позволяет сформировать о Вас корректное мнение другим участникам форума. Плохо, что нет предметного разговора.​

Болталка.

Прям ответ в духе gliderexpert =)
 
Несмотря на то, что раздел не мой - раз уж Veil тут устроил филиал "болталки", я взял на себя смелость, и почистил тему от его сообщений.

evdo, lisa99 - спасибо за труд, сейчас это вдвойне ценно)
 
для тех кто в танке\под_солями:
модератор попросил практический опыт, которого нет, скорее всего ни у кого. Я потратила время, чтобы содержательно дополнить ответ чужим опытом
какого-то америкоса, который захотел использовать инструмент по-своему. Дальше перевод его статьи. Провокации уже надоели.
Да, мне тоже очень жаль, что нет глайдера на этом форуме.

По теме. Жаль, что обсуждается непонятный инструмент с непонятной эффективностью.
Смысл многих интернет сми - обзор новинок. Смысл форума - в чем? в идеале, найти решение проблемы.А получается много обзоров, тулзы, статьи, мнения, много реплик оффтопных, ошибочных, вопросительных. А результат? а хз...

Проблема. Безопасность соединения wi-fi.
Обзор
Методы, технологии атак Детектирование (софт+девайс) ПокрытиеВыводы
ааполное
ббчастичное


А по этому инструменту. Насколько он детектирует все аномалии при попытках перехвата трафика? Можно ли работать с другими роутерами? Методика прошивки других роутеров?
Испытания в белую (атака-защита) на стенде?

Вот это было бы круто...:-(
Проверка на тестовом стенде обязательно будет проведена, необходимое оборудование в наличии.
Материал будет дополняться по мере готовности, с примерами живого трафика и комментариями.
 
Давайте говорить по существу. Есть проблема. Чисто техническая. Как убедится что за твоим трафиком не следят. И всё. Но некоторым хочется погорячее?

Вопрос по теме (ностальгия по железу, мне приходилось в далекие годы возится с пайкой, платами, микросхемами и лапшой). Было бы востребованно такое устройство на рынке?
Есть ли перспективы сделать его более универсальным? Если да, то с чем совместить? Насколько оно получится легальным? (дельфина запретили, вроде)?
В целом я готова выйти на мелкосерийное производство специализированных гаджетов, при понимании рентабельности затеи. Не вижу ниши.
 
Последнее редактирование модератором:
Давайте говорить по существу. Есть проблема. Чисто техническая. Как убедится что за твоим трафиком не следят. И всё. Но некоторым хочется погорячее?
Трафиком чего именно следят, или не следят? Я не так что бы мало писал об этом здесь, но затрагивая вскольз темы, которые возможно были бы интересны. Если это интересно, выберу ссылки на эти сообщения. Я не пытаюсь умничать, или подавать прочитанное в интернете, просто затрагивал то, что возможно было бы интересным кому-то и обсуждение с подачей информации могли бы быть полезными и не вредить чему либо в форме неправильной трактовки и использования этих сведений.
Лично меня интересует не столько WhoFi, сколько карты сети, устройств, их идентификаторы и перемещения девайсов. Bluetooth, WiFi. Плюс ряд других технологий дополняющий и расширяющий возможности практического использования этого. Так не объяснить, но это не пустая трата времени.
Вопрос по теме (ностальгия по железу, мне приходилось в далекие годы возится с пайкой, платами, микросхемами и лапшой). Было бы востребованно такое устройство на рынке?
Есть ли перспективы сделать его более универсальным? Если да, то с чем совместить? Насколько оно получится легальным? (дельфина запретили, вроде)?
В целом я готова выйти на мелкосерийное производство специализированных гаджетов, при понимании рентабельности затеи. Не вижу ниши.
Чего вы хотите производить?
Скажу как есть. То что востребовано производится. СТС тоже самое.
Я знал крутых дядек из СПб и Севастополя, у них там был бизнес на полупроводниках, микроконтроллерах (hard& soft firmware). Это последние из могикан, в году 2012 они не свернулись, а просто развернулись в Шеньчжэне, это выгодней и интересней. Я не знаю как у них пошло, но других по-моему и нет микроэлектронщиков. Им предлагали и мощности и заводы, не стали рассматривать. К чему это, если можно отправлять и легально доупаковывать говоря что это местное. Это не запрещено и нет строгого регламента, например полного цикла производства. Даже господдержу некому будет оказать, его просто нет и никто не вложится, это противоречит экономической целе(со)образности.
Возможно нечто авторское или уникальное. Но это совсем другая тема.
Примеры? Есть крафтовые производители системных плат, при чем это не гаражные пацаны, а люди с договоренностями и соглашениями производителей чипов. У них уникальные материнки, что-то под видеокарты. Это вечная hi end++ электроника. Выглядят они космически и стоят несколько тысяч долларов. Но это для ценителей, богачи не покупают это массово, как аудиофилы заморачиваютя. Четыре вида колонок и динамиков, два усилителя и три вида кабеля по 250 зелени метр, не во всех самых дорогих аудиостудиях такой есть.
Покупают это мало, может и имеет смысл. Я знаю статистику продаж по Украине и Польше, очень мало. Но и каналы ввоза и поставок могут быть разными. В РФ возможно больше пошло бы.
У меня как-то была причудливая идея придумать решение для уничтожения ПЗУ носителей данных. Я изучал вопросы по химии и уровню классифицируемой опасности, логику и прикидывал маркетинг план. Ну там в массовое производство электро-магнито-импульсная оснастка тоже не будут безобидными. Вывести из строя флешку например, там горит контроллер зачастую да и данные можно поднять иногда. А с химией это физически безвозратность. Ну прикинул что это венчур штук на 30 зелени. Маржа приличная, спрос неизучен. Потом забылось.
К тому что было бы желание чем-то заняться. Если оно твое то достигнешь успеха, вопрос времени. Интуиция подскажет если это твое.
 
Последнее редактирование:
Вопрос по теме (ностальгия по железу, мне приходилось в далекие годы возится с пайкой, платами, микросхемами и лапшой). Было бы востребованно такое устройство на рынке?
Есть ли перспективы сделать его более универсальным? Если да, то с чем совместить? Насколько оно получится легальным? (дельфина запретили, вроде)?
В целом я готова выйти на мелкосерийное производство специализированных гаджетов, при понимании рентабельности затеи. Не вижу ниши.
Изложу свои соображения.
1. Rayhunter работает на сертифицированном оборудовании (например, Orbic RC400L с CE-маркировкой), не модифицирует аппаратные характеристики устройства и не меняет его функции при установке.
Назначение Rayhunter - запись и анализ общедоступных сигнальных данных от базовых станций сетей сотовой связи - не подпадает под действующие правовые ограничения ЕС и России.
Открытый исходный код проекта обеспечивает проверяемость этого функционала при возможной экспертизе со стороны правоохранительных органов.
2. Для оценки востребованности устройства на рынке необходимо сначала определить страну сбыта и целевую аудиторию. В отчётах EFF фигурируют активисты, журналисты, IT-специалисты ЕС.
Исходя из этого можно дополнять и комплексировать решение, от интеграции в системы контроля периметра (в виде датчиков) до реализации на встроенном Wi-Fi адаптере доступного инструментария этичных атак аналогично Pineapple (возможность не изучена).
Реализация конкретных функций может потребовать переоценки легальности решения.
3. Планирую доработать проект с учётом тематики нашей площадки: доработать критерии на основе собственных экспериментов, реализовать дополнительные функции анонимизации в контексте использования (принудительное подлючение к выбранной БС; автоматическое выключение модема при выявлении аномалий; проверка сетевого трафика устройства, его туннелирование). Если есть ещё идеи - прошу в личные сообщения.​
 
Скажу как есть. То что востребовано производится.
да. например, смартфоны. Но мы же имеем ввиду что-то специализированное - тот же Rayhunter не для всех. Это не массмаркет, узкая ниша - не производится.

решение для уничтожения ПЗУ носителей данных
так как для данной темы это оффтоп, я спрячу под спойлер (открывать новую тему в данных обстоятельствах не хочу)
Скрытый контент для пользователей: stepany4.


Планирую доработать проект с учётом тематики нашей площадки: доработать критерии на основе собственных экспериментов, реализовать дополнительные функции анонимизации в контексте использования (принудительное подлючение к выбранной БС; автоматическое выключение модема при выявлении аномалий; проверка сетевого трафика устройства, его туннелирование).
звучит круто, но сколько человек этим воспользуются? сколько готовы искать гаджет и потом долбаться с софтом, с портами, стабильностью...?
И главное - все останется на уровне самоделки - без гарантий, обновлений, качественных испытаний.

Если есть ещё идеи - прошу в личные сообщения.

Странно. Я еще могу понять почему меня ссыкливо слили местному хаму. Но почему тему по усовершенствованию, развитию устройства нужно обсуждать в ЛС - не понимаю никаким образом.
 
Последнее редактирование:
Но мы же имеем ввиду что-то специализированное - тот же Rayhunter не для всех. Это не массмаркет, узкая ниша - не производится.
Именно об этом я и писал. Есть ряд проектов и нишу определяют заинтересованные лица. Которые не слишком нуждаются в решениях из коробки.
Плюс ко всему есть тонкие моменты как административной, так и уголовной ответственности, они немного разняться в разных странах. Некоторые из них не будут очевидны обывателю.
Например, если в устройстве GPS/GSM трекера есть pin для микрофона и коробочка с дырочкой, он не будет признан СТС, не суть скрытого, или открытого он монтажа, так же как и схема его электропитания (т.е. конструкционная логика использования), хотя очевидна программно-аппаратная возможность голосового мониторинга. Только это не будет СТС, но если ты впаяешь туда микрофон, который на радиобазаре тут же может продать барыга с пояснениями что он паять этого не будет. )) То общая методика судэкспертизы отнесёт такой объект-изделие к СТС. Это 138.1 УК РФ (до 4 ЛС), или 359 УК Украины (ч.1 - до 4 ЛС, ч.2 - в группе/повторно 4-7 ЛС, ч.3 - существенный вред 7-10 ЛС). Более того, в Украине сама методика отнесения является информацией ограниченного использования с грифом ДСП и не может быть открыто представлена. Это еще одна статья УК если что. Раньше так было, сейчас не знаю. Ну это нонсенс, есть ИСТЭ СБ Украины который в закрытом режиме определяет груда ли это запчастей, или СТС. Нарушаются не только право подозреваемого\обвиняемого по ч. 2 ст. 101 УПК Украины в праве на требование\предоставление (результатов) альтернативной экспертизы, но сама методология госэкспертизы ограничена полной закрытостью, когда ее результаты являются основанием для обвинения в уголовном преступлении средней тяжести, а при доказательствах существенного вреда от 7 до 10 ЛС это тяжкое преступление. В заключении эксперта не всегда может быть очевидна логика отнесения, или попросту достоверность изложенного.
Есть много других проектов кроме Hack RF, Blade RF, Yate BTS, Open BTS. Кроме фрикерских связок Wireshark + Motorola c117/c118 на osmocomBB, можно использовать Realtek RTL2832U s SDR и набором скриптов, он подходит не только для DVB-T, но и для GSM, стоит это 7-12 долларов + отдельно хорошая анетенна. Более того, не только motorola, есть ряд старых мобильных вплоть до nokia с java-апплетами с возможностями не менее. Просто тема узкая, все за hello moto знают и всё. В реале существуют апплеты на java которые фиксировали по частотно-мощностным характеристикам аномалии подключений к БС. По сути делали то же самое еще до эры смартфонов, конечно это костыли, но я не думаю что они сильно уебищней конструкционно нежели приложения для android и ios. ))
так как для данной темы это оффтоп, я спрячу под спойлер (открывать новую тему в данных обстоятельствах не хочу)
Скрытый контент для пользователей: lisa99.
 
Последнее редактирование:
Которые не слишком нуждаются в решениях из коробки.
ну как сказать. Если готовить о корпах, им нужна "коробка" - сертификация, разрешения - в РФ мб ФСТЭК (так было) и проч. А если мы говорим о форумах, то
Скрытый контент для пользователей: stepany4.
 
звучит круто, но сколько человек этим воспользуются? сколько готовы искать гаджет и потом долбаться с софтом, с портами, стабильностью...?
И главное - все останется на уровне самоделки - без гарантий, обновлений, качественных испытаний.
С одной стороны, Вы критикуете практическую сторону: сомневаетесь в аудитории, подчёркиваете сложности и называете всё "самоделкой" без гарантий, обновлений и испытаний.
С другой стороны, подчёркиваете странность того, почему такие обсуждения нужно переносить в личные сообщения, подразумевая, что тема заслуживает открытого диалога на площадке.
Но почему тему по усовершенствованию, развитию устройства нужно обсуждать в ЛС - не понимаю никаким образом.
Жду Ваших идей для проработки.
 
Проверка на тестовом стенде обязательно будет проведена, необходимое оборудование в наличии.
Материал будет дополняться по мере готовности, с примерами живого трафика и комментариями.
я тоже проведу, как раз под рукой tp link завалялся подходящий
 
Анализ исходного кода проекта Rayhunter.

1. Структура репозитория и обзор архитектуры решения.

Репозиторий проекта Rayhunter (commit SHA 69260d21ac291d55dcbcf86f932c8f15a5feb027) организован как рабочее пространство Rust (через корневой файл Cargo.toml) с модульной структурой, где разделены основные библиотеки, инструменты, службы демона, установщик и документация.

lib - фундаментальная библиотека, которая явдяется основой проекта и повторно используется в других компонентах, таких как daemon для runtime-анализа и check для CLI-проверок, обеспечивая единый подход к обработке данных и минимизируя дублирование кода.
Модуль lib отвечает за низкоуровневый разбор данных (формата QMDL для Qualcomm-диагностики, PCAP для захвата пакетов, GSMTAP для GSM-трафика и HDLC для протоколов передачи данных).
Кроме этого, в нем реализованы эвристические алгоритмы анализа для выявления потенциальных угроз безопасности, таких как запросы IMSI, отключение шифрования в радиоинтерфейсе и понижение уровня сети до 2G (что упрощает атаки типа "man-in-the-middle").
Библиотека использует спецификации ASN.1 (Abstract Syntax Notation One) через интеграцию с модулем telcom-parser для декодирования телекоммуникационных протоколов.

telcom-parser - специализированный модуль, предназначенный для генерации обработчиков на основе ASN.1-спецификаций телекоммуникационных стандартов.
Преобразует формальные описания протоколов стандарта LTE в исполняемый Rust-код, результатом чего является сгенерированный файл lte_rrc.rs, который обеспечивает точное декодирование сообщений, таких как системные информационные блоки (SIB) или NAS-сообщения (Non-Access Stratum). Данный подход позволяет проекту оставаться в актуальном состоянии с эволюцией стандартов 3GPP, минимизируя ручной код и повышая точность анализа.

daemon - основной сервис, который интегрирует все компоненты для непрерывной работы.
Он включает модули, специфичные для аппаратного обеспечения, такие как управление батареей и дисплеем для устройств вроде Orbic (мобильные хотспоты Verizon), TMobile (T-Mobile hotspots), Wingtech и других, обеспечивая портативность на embedded-устройствах.
Демон запускает циклы анализа в реальном времени, хранит логи в формате QMDL/PCAP, отправляет уведомления о выявленных аномалиях и экспонирует веб-API/сервер на базе Axum для взаимодействия.
Поддиректория web/ представляет собой полноценное приложение на фреймворке Svelte (с использованием Vite, Tailwind и SvelteKit), которое служит дашбордом: здесь пользователи могут просматривать результаты анализа (в таблице), редактировать конфигурации (через формы), мониторить системную статистику (например, уровень батареи, сетевую активность) и управлять записью логов модема.

installer - установщик, независимый от конкретных устройств, но с логикой, адаптированной под аппаратное обеспечение.
Автоматически обнаруживает подключенные устройства (через USB или ADB), настраивает режим диагностики (diag mode для Qualcomm-чипов) и выполняет конфигурацию, включая установку драйверов и скриптов.
Поддержка ОС Windows реализована через PowerShell-скрипт (install.ps1), что обеспечивает кросс-платформенность.
Модуль упрощает развертывание на поддерживаемых устройствах, делая проект доступным для неспециалистов, интегрируется с документацией для пошаговых инструкций.

check - утилита командной строки (CLI) для валидации захваченных данных или выполнения быстрых проверок.
Использует функциональность lib для анализа файлов PCAP/QMDL на наличие ошибок или аномалий без необходимости запуска полного демона.

rootshell: Минималистичный инструмент для получения root-доступа на устройствах (через уязвимости или ADB-команды), используемый в основном для установки или отладки.
Он состоит из простого исполняемого файла, который запускает оболочку с повышенными привилегиями, необходимую для модификации системных настроек на embedded-устройствах.
Вспомогательный компонент.

2. Алгоритм работы системы модулей Rayhunter.
2.1 Установка (модуль installer).

На первом этапе алгоритма проводится настройка системы - разбор аргументов командной строки, активация диагностического интерфейса модема для захвата QMDL-логов, создание конфигурационного файла (config.toml) и установка демона в качестве системного сервиса при инициализации системы (для обеспечения автозапуска).
Процедура учитывает аппаратные особенности устройств, идентифицируемых по USB VID/PID.
Обработка исключений (с использованием конструкций try_exists и anyhow) включает вывод сообщений для пользователя.

(installer/src/main.rs): парсинг аргументов с использованием библиотеки clap направляет выполнение к функциям, специфичным для модели устройства.
C-подобный:
use anyhow::{Context, Error, bail};[/JUSTIFY]
use clap::{Parser, Subcommand};
use env_logger::Env;

mod orbic;
mod orbic_network;
mod pinephone;
mod tmobile;
mod tplink;
mod util;
mod uz801;
mod wingtech;

pub static CONFIG_TOML: &str = include_str!("../../dist/config.toml.in");
pub static RAYHUNTER_DAEMON_INIT: &str = include_str!("../../dist/scripts/rayhunter_daemon");

#[derive(Parser, Debug)]
#[command(version, about)]
struct Args {
    #[command(subcommand)]
    command: Command,
}

// A note on stylisation of device names: strip special characters and spell like This regardless
// of the manufacturer's capitalisation.
#[derive(Subcommand, Debug)]
enum Command {
    /// Install rayhunter on the Orbic Orbic RC400L.
    Orbic(InstallOrbic),
    /// Install rayhunter on the Orbic RC400L or Moxee Hotspot via network.
    ///
    /// This is an experimental installer for Orbic that does not require USB drivers on Windows.
    OrbicNetwork(OrbicNetworkArgs),
    /// Install rayhunter on the TMobile TMOHS1.
    Tmobile(TmobileArgs),
    /// Install rayhunter on the Uz801.
    Uz801(Uz801Args),
    /// Install rayhunter on a PinePhone's Quectel modem.
    Pinephone(InstallPinephone),
    /// Install rayhunter on the TP-Link M7350.
    Tplink(InstallTpLink),
    /// Install rayhunter on the Wingtech CT2MHS01.
    Wingtech(WingtechArgs),
    /// Developer utilities.
    Util(Util),
}
//
async fn run() -> Result<(), Error> {
    env_logger::Builder::from_env(Env::default().default_filter_or("off")).init();
    let Args { command } = Args::parse();

    match command {
        Command::Tmobile(args) => tmobile::install(args).await.context("Failed to install rayhunter on the Tmobile TMOHS1. Make sure your computer is connected to the hotspot using USB tethering or WiFi.")?,
        Command::Uz801(args) => uz801::install(args).await.context("Failed to install rayhunter on the Uz801. Make sure your computer is connected to the hotspot using USB.")?,
        Command::Tplink(tplink) => tplink::main_tplink(tplink).await.context("Failed to install rayhunter on the TP-Link M7350. Make sure your computer is connected to the hotspot using USB tethering or WiFi.")?,
        Command::Pinephone(_) => pinephone::install().await
            .context("Failed to install rayhunter on the Pinephone's Quectel modem")?,
        Command::Orbic(_) => orbic::install().await.context("\nFailed to install rayhunter on the Orbic RC400L")?,
        Command::OrbicNetwork(args) => orbic_network::install(args.admin_ip).await.context("\nFailed to install rayhunter on the Orbic RC400L via network exploit")?,
        Command::Wingtech(args) => wingtech::install(args).await.context("\nFailed to install rayhunter on the Wingtech CT2MHS01")?,
        Command::Util(subcommand) => match subcommand.command {
///...

(installer/src/orbic.rs): Функции для активации ADB, установки rootshell для привилегированного доступа и проверки функционирования.

C-подобный:
pub async fn install() -> Result<()> {
    #[cfg(target_os = "windows")]
    {
        let confirmation = confirm().await?;
        if confirmation != true {
            println!("Install aborted. Your device has not been modified.");
            return Ok(());
        }
    }

    let mut adb_device = force_debug_mode().await?;
    echo!("Installing rootshell... ");
    setup_rootshell(&mut adb_device).await?;
    println!("done");
    echo!("Installing rayhunter... ");
    let mut adb_device = setup_rayhunter(adb_device).await?;
    println!("done");
    echo!("Testing rayhunter... ");
    test_rayhunter(&mut adb_device).await?;
    println!("done");
    Ok(())
}

async fn force_debug_mode() -> Result<ADBUSBDevice> {
    println!("Forcing a switch into the debug mode to enable ADB");
    enable_command_mode()?;
    echo!("ADB enabled, waiting for reboot... ");
    let mut adb_device = get_adb().await?;
    adb_setup_serial(&mut adb_device).await?;
    println!("it's alive!");
    echo!("Waiting for atfwd_daemon to startup... ");
    adb_command(&mut adb_device, &["pgrep", "atfwd_daemon"])?;
    println!("done");
    Ok(adb_device)
}

async fn setup_rootshell(adb_device: &mut ADBUSBDevice) -> Result<()> {
    let rootshell_bin = include_bytes!(env!("FILE_ROOTSHELL"));

    install_file(adb_device, "/bin/rootshell", rootshell_bin).await?;
    tokio::time::sleep(Duration::from_secs(1)).await;
    adb_at_syscmd(adb_device, "chown root /bin/rootshell").await?;
    tokio::time::sleep(Duration::from_secs(1)).await;
    adb_at_syscmd(adb_device, "chmod 4755 /bin/rootshell").await?;
    let output = adb_command(adb_device, &["/bin/rootshell", "-c", "id"])?;
    if !output.contains("uid=0") {
        bail!("rootshell is not giving us root.");
    }
    Ok(())
}
Файл config.toml впоследствии используется для настройки хранилища QMDL, rootshell обеспечивает необходимые привилегии.

2.2 Запуск демона (модуль daemon).
Демон реализован на базе Tokio для асинхронной обработки следующих задач:
  • чтение диагностических данных (diag_read_thread);
  • анализ данных (analysis_thread);
  • обработка HTTP-запросов (Axum сервер);
  • мониторинг батареи и уведомлений;
  • обработка ввода с клавиш (key_input_thread);
  • обновление UI (display).
При его запуске производится загрузка конфигурации, формирование каналов для обмена в многозадачной среде и запуск вышеуказанных задач.

(daemon/src/main.rs): координация процессов с использованием shared state.

C-подобный:
async fn run_with_config(
    args: &config::Args,
    config: config::Config,
) -> Result<bool, RayhunterError> {
    // TaskTrackers give us an interface to spawn tokio threads, and then
    // eventually await all of them ending
    let task_tracker = TaskTracker::new();
    println!("R A Y H U N T E R 🐳");

    let store = init_qmdl_store(&config).await?;
    let analysis_status = AnalysisStatus::new(&store);
    let qmdl_store_lock = Arc::new(RwLock::new(store));
    let (diag_tx, diag_rx) = mpsc::channel::<DiagDeviceCtrlMessage>(1);
    let (ui_update_tx, ui_update_rx) = mpsc::channel::<display::DisplayState>(1);
    let (analysis_tx, analysis_rx) = mpsc::channel::<AnalysisCtrlMessage>(5);
    let restart_token = CancellationToken::new();
    let shutdown_token = restart_token.child_token();

    let notification_service = NotificationService::new(config.ntfy_url.clone());

    if !config.debug_mode {
        info!("Using configuration for device: {0:?}", config.device);
        let mut dev = DiagDevice::new(&config.device)
            .await
            .map_err(RayhunterError::DiagInitError)?;
        dev.config_logs()
            .await
            .map_err(RayhunterError::DiagInitError)?;

        info!("Starting Diag Thread");
        run_diag_read_thread(
            &task_tracker,
            dev,
            diag_rx,
            diag_tx.clone(),
            ui_update_tx.clone(),
            qmdl_store_lock.clone(),
            analysis_tx.clone(),
            config.analyzers.clone(),
            notification_service.new_handler(),
        );
        info!("Starting UI");

        let update_ui = match &config.device {
            Device::Orbic => display::orbic::update_ui,
            Device::Tplink => display::tplink::update_ui,
            Device::Tmobile => display::tmobile::update_ui,
            Device::Wingtech => display::wingtech::update_ui,
            Device::Pinephone => display::headless::update_ui,
            Device::Uz801 => display::uz801::update_ui,
        };
        update_ui(&task_tracker, &config, shutdown_token.clone(), ui_update_rx);

        info!("Starting Key Input service");
        key_input::run_key_input_thread(
            &task_tracker,
            &config,
            diag_tx.clone(),
            shutdown_token.clone(),
        );
    }

    let analysis_status_lock = Arc::new(RwLock::new(analysis_status));
    run_analysis_thread(
        &task_tracker,
        analysis_rx,
        qmdl_store_lock.clone(),
        analysis_status_lock.clone(),
        config.analyzers.clone(),
    );

    run_shutdown_thread(
        &task_tracker,
        diag_tx.clone(),
        shutdown_token.clone(),
        qmdl_store_lock.clone(),
        analysis_tx.clone(),
    );

    run_battery_notification_worker(
        &task_tracker,
        config.device.clone(),
        notification_service.new_handler(),
        shutdown_token.clone(),
    );

    run_notification_worker(
        &task_tracker,
        notification_service,
        config.enabled_notifications.clone(),
    );

    let state = Arc::new(ServerState {
        config_path: args.config_path.clone(),
        config,
        qmdl_store_lock: qmdl_store_lock.clone(),
        diag_device_ctrl_sender: diag_tx,
        analysis_status_lock,
        analysis_sender: analysis_tx,
        daemon_restart_token: restart_token.clone(),
        ui_update_sender: Some(ui_update_tx),
    });
    run_server(&task_tracker, state, shutdown_token.clone()).await;

    task_tracker.close();
    task_tracker.wait().await;

    info!("see you space cowboy...");
    Ok(restart_token.is_cancelled())
}

Как можно видеть, Shared state (Arc<ServerState>) распределяется между задачами для доступа к конфигурации и блокировкам.

2.3 Захват данных (модули daemon/diag.rs, qmdl_store.rs, pcap.rs).
После запуска демона осуществляется получение пакетов от модема посредством DiagDevice, одновременный сбор сетевого трафика в PCAP-формате и сохранение в форматах NDJSON (для анализа) и QMDL (для исходных данных).
Циклическое чтение обеспечивает мониторинг в реальном времени; сериализация в NDJSON с ротацией файлов управляет объемом данных; манифест обновляется для метаданных; обработка исключений предотвращает повреждения; PCAP дополняет анализ сетевыми данными для эвристик (например, connection_redirect_downgrade).
Захваченные данные передаются на обработку и анализ в реальном масштабе времени.

(daemon/src/diag.rs): интегрированный подход: захват и анализ происходят в одной задаче для реального времени.

C-подобный:
pub fn run_diag_read_thread(
    task_tracker: &TaskTracker,
    mut dev: DiagDevice,
    mut qmdl_file_rx: Receiver<DiagDeviceCtrlMessage>,
    qmdl_file_tx: Sender<DiagDeviceCtrlMessage>,
    ui_update_sender: Sender<display::DisplayState>,
    qmdl_store_lock: Arc<RwLock<RecordingStore>>,
    analysis_sender: Sender<AnalysisCtrlMessage>,
    analyzer_config: AnalyzerConfig,
    notification_channel: tokio::sync::mpsc::Sender<Notification>,
) {
    task_tracker.spawn(async move {
        let mut diag_stream = pin!(dev.as_stream().into_stream());
        let mut diag_task = DiagTask::new(ui_update_sender, analysis_sender, analyzer_config, notification_channel);
        qmdl_file_tx
            .send(DiagDeviceCtrlMessage::StartRecording)
            .await
            .unwrap();
        loop {
            tokio::select! {
                msg = qmdl_file_rx.recv() => {
                    match msg {
                        Some(DiagDeviceCtrlMessage::StartRecording) => {
                            let mut qmdl_store = qmdl_store_lock.write().await;
                            diag_task.start(qmdl_store.deref_mut()).await;
                        },
                        Some(DiagDeviceCtrlMessage::StopRecording) => {
                            let mut qmdl_store = qmdl_store_lock.write().await;
                            diag_task.stop(qmdl_store.deref_mut()).await;
                        },
                        // None means all the Senders have been dropped, so it's
                        // time to go
                        Some(DiagDeviceCtrlMessage::Exit) | None => {
                            info!("Diag reader thread exiting...");
                            diag_task.stop_current_recording().await;
                            return Ok(())
                        },
                        Some(DiagDeviceCtrlMessage::DeleteEntry { name, response_tx }) => {
                            let mut qmdl_store = qmdl_store_lock.write().await;
                            let resp = diag_task.delete_entry(qmdl_store.deref_mut(), name.as_str()).await;
                            if response_tx.send(resp).is_err() {
                                error!("Failed to send delete entry respons, receiver dropped");
                            }
                        },
                        Some(DiagDeviceCtrlMessage::DeleteAllEntries { response_tx }) => {
                            let mut qmdl_store = qmdl_store_lock.write().await;
                            let resp = diag_task.delete_all_entries(qmdl_store.deref_mut()).await;
                            if response_tx.send(resp).is_err() {
                                error!("Failed to send delete all entries respons, receiver dropped");
                            }
                        },
                    }
                }
                maybe_container = diag_stream.next() => {
                    match maybe_container.unwrap() {
                        Ok(container) => {
                            let mut qmdl_store = qmdl_store_lock.write().await;
                            diag_task.process_container(qmdl_store.deref_mut(), container).await
                        },
                        Err(err) => {
                            error!("error reading diag device: {err}");
                            return Err(err);
                        }
                    }
                }
            }
        }
    });
}

(daemon/src/qmdl_store.rs): представлена структура хранилища с метаданными и функциями проверки существования, загрузки, создания и восстановления.
C-подобный:
pub struct RecordingStore {
    pub path: PathBuf,
    pub manifest: Manifest,
    pub current_entry: Option<usize>, // index into manifest
}

#[derive(Deserialize, Serialize, Clone, PartialEq, Debug)]
pub struct Manifest {
    pub entries: Vec<ManifestEntry>,
}

#[derive(Deserialize, Serialize, Clone, PartialEq, Debug)]
pub struct ManifestEntry {
    pub name: String,
    pub start_time: DateTime<Local>,
    pub last_message_time: Option<DateTime<Local>>,
    pub qmdl_size_bytes: usize,
    pub rayhunter_version: Option<String>,
    pub system_os: Option<String>,
    pub arch: Option<String>,
}

impl ManifestEntry {
    fn new() -> Self {
        let now = Local::now();
        let metadata = RuntimeMetadata::new();
        ManifestEntry {
            name: format!("{}", now.timestamp()),
            start_time: now,
            last_message_time: None,
            qmdl_size_bytes: 0,
            rayhunter_version: Some(metadata.rayhunter_version),
            system_os: Some(metadata.system_os),
            arch: Some(metadata.arch),
        }
    }

    pub fn get_qmdl_filepath<P: AsRef<Path>>(&self, path: P) -> PathBuf {
        let mut filepath = path.as_ref().join(&self.name);
        filepath.set_extension("qmdl");
        filepath
    }

    pub fn get_analysis_filepath<P: AsRef<Path>>(&self, path: P) -> PathBuf {
        let mut filepath = path.as_ref().join(&self.name);
        filepath.set_extension("ndjson");
        filepath
    }
}

impl RecordingStore {
    // Returns whether a directory with a "manifest.toml" exists at the given
    // path (though doesn't check if that manifest is valid)
    pub async fn exists<P>(path: P) -> Result<bool, RecordingStoreError>
    where
        P: AsRef<Path>,
    {
        let manifest_path = path.as_ref().join("manifest.toml");
        let dir_exists = try_exists(path)
            .await
            .map_err(RecordingStoreError::OpenDirError)?;
        let manifest_exists = try_exists(manifest_path)
            .await
            .map_err(RecordingStoreError::ReadManifestError)?;
        Ok(dir_exists && manifest_exists)
    }

    // Loads an existing RecordingStore at the given path. Errors if no store exists,
    // or if it's malformed.
    pub async fn load<P>(path: P) -> Result<Self, RecordingStoreError>
    where
        P: AsRef<Path>,
    {
        let path: PathBuf = path.as_ref().to_path_buf();
        let manifest = RecordingStore::read_manifest(&path).await?;
        Ok(RecordingStore {
            path,
            manifest,
            current_entry: None,
        })
    }

    // Creates a new RecordingStore at the given path. This involves creating a dir
    // and writing an empty manifest.
    pub async fn create<P>(path: P) -> Result<Self, RecordingStoreError>
    where
        P: AsRef<Path>,
    {
        fs::create_dir_all(&path)
            .await
            .map_err(RecordingStoreError::OpenDirError)?;

        let mut store = RecordingStore {
            path: path.as_ref().to_owned(),
            manifest: Manifest {
                entries: Vec::new(),
            },
            current_entry: None,
        };

        store.write_manifest().await?;
        Ok(store)
    }

    // Does a best-effort attempt to recover the manifest from a directory of
    // QMDL files. We expect these files to be named like "<timestamp>.qmdl",
    // and skip any files which don't match that pattern.
    pub async fn recover<P>(path: P) -> Result<Self, RecordingStoreError>
    where
        P: AsRef<Path>,
    {
        let mut dir_entries = fs::read_dir(path.as_ref())
            .await
            .map_err(RecordingStoreError::OpenDirError)?;
        let mut manifest_entries = Vec::new();

        while let Some(entry) = dir_entries
            .next_entry()
            .await
            .map_err(RecordingStoreError::OpenDirError)?
        {
            let os_filename = entry.file_name();
            let Some(filename) = os_filename.to_str() else {
                continue;
            };

            if !filename.ends_with(".qmdl") {
                continue;
            }

            let stem = filename.trim_end_matches(".qmdl");
            let Ok(start_timestamp) = stem.parse::<i64>() else {
                warn!("QMDL file has invalid name {os_filename:?}, skipping");
                continue;
            };

            let metadata = match entry.metadata().await {
                Ok(metadata) => metadata,
                Err(err) => {
                    warn!("failed to read QMDL file metadata: {err:?}, skipping");
                    continue;
                }
            };

            let Some(start_time) = DateTime::from_timestamp(start_timestamp, 0) else {
                warn!("QMDL filename {os_filename:?} gave an invalid timestamp, skipping");
                continue;
            };

            let Ok(last_message_time) = metadata.modified() else {
                warn!("failed to get modified time for QMDL file {os_filename:?}, skipping");
                continue;
            };

            info!("successfully recovered QMDL entry {os_filename:?}!");
            manifest_entries.push(ManifestEntry {
                name: stem.to_string(),
                start_time: start_time.into(),
                last_message_time: Some(last_message_time.into()),
                qmdl_size_bytes: metadata.size() as usize,
                rayhunter_version: None,
                system_os: None,
                arch: None,
            });
        }

        // sort chronologically
        manifest_entries.sort_by(|a, b| a.start_time.cmp(&b.start_time));

        let mut store = RecordingStore {
            path: path.as_ref().to_path_buf(),
            manifest: Manifest {
                entries: manifest_entries,
            },
            current_entry: None,
        };
        store.write_manifest().await?;

        Ok(store)
    }

(daemon/src/pcap.rs): генерация PCAP-файла из уже захваченных QMDL-данных с обеспечением синхронизации по времени из QMDL.
C-подобный:
pub async fn generate_pcap_data<R, W>(
    writer: W,
    qmdl_file: R,
    qmdl_size_bytes: usize,
) -> Result<(), Error>
where
    W: AsyncWrite + Unpin + Send,
    R: AsyncRead + Unpin,
{
    let mut pcap_writer = GsmtapPcapWriter::new(writer).await?;
    pcap_writer.write_iface_header().await?;

    let mut reader = QmdlReader::new(qmdl_file, Some(qmdl_size_bytes));
    while let Some(container) = reader.get_next_messages_container().await? {
        if container.data_type != DataType::UserSpace {
            continue;
        }

        for maybe_msg in container.into_messages() {
            match maybe_msg {
                Ok(msg) => {
                    let maybe_gsmtap_msg = gsmtap_parser::parse(msg)?;
                    if let Some((timestamp, gsmtap_msg)) = maybe_gsmtap_msg {
                        pcap_writer
                            .write_gsmtap_message(gsmtap_msg, timestamp)
                            .await?;
                    }
                }
                Err(e) => error!("error parsing message: {e:?}"),
            }
        }
    }

    Ok(())
}

2.4 Обработка данных (модули lib/gsmtap_parser, telcom-parser).
На данном этапе логи QMDL преобразуются в GSMTAP-формат, затем в структуры сообщений RRC (Radio Resource Control) на основе спецификаций ASN.1 (согласно 3GPP TS 36.331).
Парсинг извлекает ключевые элементы (тип сообщения, PDU); соответствие версиям заголовков обеспечивает совместимость, неподдерживаемые пакеты пропускаются для предотвращения сбоев.

(lib/src/gsmtap_parser.rs): классификация сообщений по версиям и PDU.

C-подобный:
pub fn parse(msg: Message) -> Result<Option<(Timestamp, GsmtapMessage)>, GsmtapParserError> {
    if let Message::Log {
        timestamp, body, ..
    } = msg
    {
        match log_to_gsmtap(body)? {
            Some(msg) => Ok(Some((timestamp, msg))),
            None => Ok(None),
        }
    } else {
        Ok(None)
    }
}

fn log_to_gsmtap(value: LogBody) -> Result<Option<GsmtapMessage>, GsmtapParserError> {
    match value {
        LogBody::LteRrcOtaMessage {
            ext_header_version,
            packet,
        } => {
            let gsmtap_type = match ext_header_version {
                0x02 | 0x03 | 0x04 | 0x06 | 0x07 | 0x08 | 0x0d | 0x16 => match packet.get_pdu_num()
                {
                    1 => GsmtapType::LteRrc(LteRrcSubtype::BcchBch),
                    2 => GsmtapType::LteRrc(LteRrcSubtype::BcchDlSch),
                    3 => GsmtapType::LteRrc(LteRrcSubtype::MCCH),
                    4 => GsmtapType::LteRrc(LteRrcSubtype::PCCH),
                    5 => GsmtapType::LteRrc(LteRrcSubtype::DlCcch),
                    6 => GsmtapType::LteRrc(LteRrcSubtype::DlDcch),
                    7 => GsmtapType::LteRrc(LteRrcSubtype::UlCcch),
                    8 => GsmtapType::LteRrc(LteRrcSubtype::UlDcch),
                    pdu => {
                        return Err(GsmtapParserError::InvalidLteRrcOtaHeaderPduNum(
                            ext_header_version,
                            pdu,
                        ));
                    }
                },
                0x09 | 0x0c => match packet.get_pdu_num() {
                    8 => GsmtapType::LteRrc(LteRrcSubtype::BcchBch),
                    9 => GsmtapType::LteRrc(LteRrcSubtype::BcchDlSch),
                    10 => GsmtapType::LteRrc(LteRrcSubtype::MCCH),
                    11 => GsmtapType::LteRrc(LteRrcSubtype::PCCH),
                    12 => GsmtapType::LteRrc(LteRrcSubtype::DlCcch),
                    13 => GsmtapType::LteRrc(LteRrcSubtype::DlDcch),
                    14 => GsmtapType::LteRrc(LteRrcSubtype::UlCcch),
                    15 => GsmtapType::LteRrc(LteRrcSubtype::UlDcch),
                    pdu => {
                        return Err(GsmtapParserError::InvalidLteRrcOtaHeaderPduNum(
                            ext_header_version,
                            pdu,
                        ));
                    }
                },
                0x0e..=0x10 => match packet.get_pdu_num() {
                    1 => GsmtapType::LteRrc(LteRrcSubtype::BcchBch),
                    2 => GsmtapType::LteRrc(LteRrcSubtype::BcchDlSch),
                    4 => GsmtapType::LteRrc(LteRrcSubtype::MCCH),
                    5 => GsmtapType::LteRrc(LteRrcSubtype::PCCH),
                    6 => GsmtapType::LteRrc(LteRrcSubtype::DlCcch),
                    7 => GsmtapType::LteRrc(LteRrcSubtype::DlDcch),
                    8 => GsmtapType::LteRrc(LteRrcSubtype::UlCcch),
                    9 => GsmtapType::LteRrc(LteRrcSubtype::UlDcch),
                    pdu => {
                        return Err(GsmtapParserError::InvalidLteRrcOtaHeaderPduNum(
                            ext_header_version,
                            pdu,
                        ));
                    }
                },
                0x13 | 0x1a | 0x1b => match packet.get_pdu_num() {
                    1 => GsmtapType::LteRrc(LteRrcSubtype::BcchBch),
                    3 => GsmtapType::LteRrc(LteRrcSubtype::BcchDlSch),
                    6 => GsmtapType::LteRrc(LteRrcSubtype::MCCH),
                    7 => GsmtapType::LteRrc(LteRrcSubtype::PCCH),
                    8 => GsmtapType::LteRrc(LteRrcSubtype::DlCcch),
                    9 => GsmtapType::LteRrc(LteRrcSubtype::DlDcch),
                    10 => GsmtapType::LteRrc(LteRrcSubtype::UlCcch),
                    11 => GsmtapType::LteRrc(LteRrcSubtype::UlDcch),
                    45 => GsmtapType::LteRrc(LteRrcSubtype::BcchBchNb),
                    46 => GsmtapType::LteRrc(LteRrcSubtype::BcchDlSchNb),
                    47 => GsmtapType::LteRrc(LteRrcSubtype::PcchNb),
                    48 => GsmtapType::LteRrc(LteRrcSubtype::DlCcchNb),
                    49 => GsmtapType::LteRrc(LteRrcSubtype::DlDcchNb),
                    50 => GsmtapType::LteRrc(LteRrcSubtype::UlCcchNb),
                    52 => GsmtapType::LteRrc(LteRrcSubtype::UlDcchNb),
                    pdu => {
                        return Err(GsmtapParserError::InvalidLteRrcOtaHeaderPduNum(
                            ext_header_version,
                            pdu,
                        ));
                    }
                },
                0x14 | 0x18 | 0x19 => match packet.get_pdu_num() {
                    1 => GsmtapType::LteRrc(LteRrcSubtype::BcchBch),
                    2 => GsmtapType::LteRrc(LteRrcSubtype::BcchDlSch),
                    4 => GsmtapType::LteRrc(LteRrcSubtype::MCCH),
                    5 => GsmtapType::LteRrc(LteRrcSubtype::PCCH),
                    6 => GsmtapType::LteRrc(LteRrcSubtype::DlCcch),
                    7 => GsmtapType::LteRrc(LteRrcSubtype::DlDcch),
                    8 => GsmtapType::LteRrc(LteRrcSubtype::UlCcch),
                    9 => GsmtapType::LteRrc(LteRrcSubtype::UlDcch),
                    54 => GsmtapType::LteRrc(LteRrcSubtype::BcchBchNb),
                    55 => GsmtapType::LteRrc(LteRrcSubtype::BcchDlSchNb),
                    56 => GsmtapType::LteRrc(LteRrcSubtype::PcchNb),
                    57 => GsmtapType::LteRrc(LteRrcSubtype::DlCcchNb),
                    58 => GsmtapType::LteRrc(LteRrcSubtype::DlDcchNb),
                    59 => GsmtapType::LteRrc(LteRrcSubtype::UlCcchNb),
                    61 => GsmtapType::LteRrc(LteRrcSubtype::UlDcchNb),
                    pdu => {
                        return Err(GsmtapParserError::InvalidLteRrcOtaHeaderPduNum(
                            ext_header_version,
                            pdu,
                        ));
                    }
                },
                _ => {
                    return Err(GsmtapParserError::InvalidLteRrcOtaExtHeaderVersion(
                        ext_header_version,
                    ));
                }
            };
            let mut header = GsmtapHeader::new(gsmtap_type);
            header.arfcn = packet.get_earfcn().try_into().unwrap_or(0);
            header.frame_number = packet.get_sfn();
            header.subslot = packet.get_subfn();
            Ok(Some(GsmtapMessage {
                header,
                payload: packet.take_payload(),
            }))
        }
        LogBody::Nas4GMessage { msg, direction, .. } => {
            // currently we only handle "plain" (i.e. non-secure) NAS messages
            let mut header = GsmtapHeader::new(GsmtapType::LteNas(LteNasSubtype::Plain));
            header.uplink = matches!(direction, Nas4GMessageDirection::Uplink);
            Ok(Some(GsmtapMessage {
                header,
                payload: msg,
            }))
        }
        _ => {
            error!("gsmtap_sink: ignoring unhandled log type: {value:?}");
            Ok(None)
        }
    }
}
Полученные структуры используются для дальнейшего анализа.

2.5 Анализ (модуль lib/analysis).
На данном этапе посредством интерфейса Analyzer задействуются эвристики для выявления аномалий в сообщениях, за которыми следует создание уведомлений.
Агрегация проверок (null cipher, connection_redirect_downgrade, imsi_requested, incomplete_sib, nas_null_cipher, priority_2g_downgrade) классифицирует угрозы; инспекция вложенных структур (critical_extensions) минимизирует ложные срабатывания; уведомления передаются в реальном времени для оперативной реакции пользователя.

(lib/src/analysis/null_cipher.rs): проверка алгоритмов шифрования в соответствии с 3GPP TS 33.401.
C-подобный:
pub struct NullCipherAnalyzer {}

impl NullCipherAnalyzer {
    fn check_rrc_connection_reconfiguration_cipher(
        &self,
        reconfiguration: &RRCConnectionReconfiguration,
    ) -> bool {
        let RRCConnectionReconfigurationCriticalExtensions::C1(c1) =
            &reconfiguration.critical_extensions
        else {
            return false;
        };
        let RRCConnectionReconfigurationCriticalExtensions_c1::RrcConnectionReconfiguration_r8(c1) =
            c1
        else {
            return false;
        };
        if let Some(handover) = &c1.security_config_ho {
            let maybe_security_config = match &handover.handover_type {
                telcom_parser::lte_rrc::SecurityConfigHOHandoverType::IntraLTE(lte) => {
                    lte.security_algorithm_config.as_ref()
                }
                telcom_parser::lte_rrc::SecurityConfigHOHandoverType::InterRAT(rat) => {
                    Some(&rat.security_algorithm_config)
                }
            };
            if let Some(security_config) = maybe_security_config
                && security_config.ciphering_algorithm.0 == CipheringAlgorithm_r12::EEA0
            {
                return true;
            }
        }
        // Use map/flatten to dig into a long chain of nested Option types
        let maybe_v1250 = c1
            .non_critical_extension
            .as_ref()
            .and_then(|v890| v890.non_critical_extension.as_ref())
            .and_then(|v920| v920.non_critical_extension.as_ref())
            .and_then(|v1020| v1020.non_critical_extension.as_ref())
            .and_then(|v1130| v1130.non_critical_extension.as_ref());
        let Some(v1250) = maybe_v1250 else {
            return false;
        };

        if let Some(SCG_Configuration_r12::Setup(scg_setup)) = v1250.scg_configuration_r12.as_ref()
        {
            let maybe_cipher = scg_setup
                .scg_config_part_scg_r12
                .as_ref()
                .and_then(|scg| scg.mobility_control_info_scg_r12.as_ref())
                .and_then(|mci| mci.ciphering_algorithm_scg_r12.as_ref());
            if let Some(cipher) = maybe_cipher
                && cipher.0 == CipheringAlgorithm_r12::EEA0
            {
                return true;
            }
        }

        let maybe_v1530_security_config = v1250
            .non_critical_extension
            .as_ref()
            .and_then(|v1310| v1310.non_critical_extension.as_ref())
            .and_then(|v1430| v1430.non_critical_extension.as_ref())
            .and_then(|v1510| v1510.non_critical_extension.as_ref())
            .and_then(|v1530| v1530.security_config_ho_v1530.as_ref());
        let Some(v1530_security_config) = maybe_v1530_security_config else {
            return false;
        };
        let maybe_security_algorithm = match &v1530_security_config.handover_type_v1530 {
            SecurityConfigHO_v1530HandoverType_v1530::Intra5GC(intra_5gc) => {
                intra_5gc.security_algorithm_config_r15.as_ref()
            }
            SecurityConfigHO_v1530HandoverType_v1530::Fivegc_ToEPC(to_epc) => {
                Some(&to_epc.security_algorithm_config_r15)
            }
            SecurityConfigHO_v1530HandoverType_v1530::Epc_To5GC(to_5gc) => {
                Some(&to_5gc.security_algorithm_config_r15)
            }
        };
        if let Some(security_algorithm) = maybe_security_algorithm
            && security_algorithm.ciphering_algorithm.0 == CipheringAlgorithm_r12::EEA0
        {
            return true;
        }
        false
    }

    fn check_security_mode_command_cipher(&self, command: &SecurityModeCommand) -> bool {
        let SecurityModeCommandCriticalExtensions::C1(c1) = &command.critical_extensions else {
            return false;
        };
        let SecurityModeCommandCriticalExtensions_c1::SecurityModeCommand_r8(r8) = &c1 else {
            return false;
        };
        if r8
            .security_config_smc
            .security_algorithm_config
            .ciphering_algorithm
            .0
            == CipheringAlgorithm_r12::EEA0
        {
            return true;
        }
        false
    }
}

impl Analyzer for NullCipherAnalyzer {
    fn get_name(&self) -> Cow<'_, str> {
        Cow::from("Null Cipher")
    }

    fn get_description(&self) -> Cow<'_, str> {
        Cow::from("Tests whether the cell suggests using a null cipher (EEA0)")
    }

    fn get_version(&self) -> u32 {
        1
    }

    fn analyze_information_element(
        &mut self,
        ie: &InformationElement,
        _packet_num: usize,
    ) -> Option<Event> {
        let dcch_msg = match ie {
            InformationElement::LTE(lte_ie) => match &**lte_ie {
                LteInformationElement::DlDcch(dcch_msg) => dcch_msg,
                _ => return None,
            },
            _ => return None,
        };
        let DL_DCCH_MessageType::C1(c1) = &dcch_msg.message else {
            return None;
        };
        let null_cipher_detected = match c1 {
            DL_DCCH_MessageType_c1::RrcConnectionReconfiguration(reconfiguration) => {
                self.check_rrc_connection_reconfiguration_cipher(reconfiguration)
            }
            DL_DCCH_MessageType_c1::SecurityModeCommand(command) => {
                self.check_security_mode_command_cipher(command)
            }
            _ => return None,
        };
        if null_cipher_detected {
            return Some(Event {
                event_type: EventType::High,
                message: "Cell suggested use of null cipher".to_string(),
            });
        }
        None
    }
}

2.6 Отображение информации (модули daemon/server, web).
Активируется HTTP-сервер с API для получения данных (analysis, config); фронтенд на Svelte поддерживает реактивные изменения, отслеживание статистики и повторный анализ.

(daemon/src/main.rs): маршруты API для интеграции с фронтендом.
C-подобный:
type AppRouter = Router<Arc<ServerState>>;

fn get_router() -> AppRouter {
    Router::new()
        .route("/api/pcap/{name}", get(get_pcap))
        .route("/api/qmdl/{name}", get(get_qmdl))
        .route("/api/zip/{name}", get(get_zip))
        .route("/api/system-stats", get(get_system_stats))
        .route("/api/qmdl-manifest", get(get_qmdl_manifest))
        .route("/api/log", get(get_log))
        .route("/api/start-recording", post(start_recording))
        .route("/api/stop-recording", post(stop_recording))
        .route("/api/delete-recording/{name}", post(delete_recording))
        .route("/api/delete-all-recordings", post(delete_all_recordings))
        .route("/api/analysis-report/{name}", get(get_analysis_report))
        .route("/api/analysis", get(get_analysis_status))
        .route("/api/analysis/{name}", post(start_analysis))
        .route("/api/config", get(get_config))
        .route("/api/config", post(set_config))
        .route("/api/debug/display-state", post(debug_set_display_state))
        .route("/", get(|| async { Redirect::permanent("/index.html") }))
        .route("/{*path}", get(serve_static))
}

(daemon/src/analysis.rs): обработка сообщений для анализа.
C-подобный:
async fn perform_analysis(
    name: &str,
    qmdl_store_lock: Arc<RwLock<RecordingStore>>,
    analyzer_config: &AnalyzerConfig,
) -> Result<(), String> {
    info!("Opening QMDL and analysis file for {name}...");
    let (analysis_file, qmdl_file) = {
        let mut qmdl_store = qmdl_store_lock.write().await;
        let (entry_index, _) = qmdl_store
            .entry_for_name(name)
            .ok_or(format!("failed to find QMDL store entry for {name}"))?;
        let analysis_file = qmdl_store
            .clear_and_open_entry_analysis(entry_index)
            .await
            .map_err(|e| format!("{e:?}"))?;
        let qmdl_file = qmdl_store
            .open_entry_qmdl(entry_index)
            .await
            .map_err(|e| format!("{e:?}"))?;

        (analysis_file, qmdl_file)
    };

    let mut analysis_writer = AnalysisWriter::new(analysis_file, analyzer_config)
        .await
        .map_err(|e| format!("{e:?}"))?;
    let file_size = qmdl_file
        .metadata()
        .await
        .expect("failed to get QMDL file metadata")
        .len();
    let mut qmdl_reader = QmdlReader::new(qmdl_file, Some(file_size as usize));
    let mut qmdl_stream = pin::pin!(
        qmdl_reader
            .as_stream()
            .try_filter(|container| future::ready(container.data_type == DataType::UserSpace))
    );

    info!("Starting analysis for {name}...");
    while let Some(container) = qmdl_stream
        .try_next()
        .await
        .expect("failed getting QMDL container")
    {
        let _ = analysis_writer
            .analyze(container)
            .await
            .map_err(|e| format!("{e:?}"))?;
    }

    analysis_writer
        .close()
        .await
        .map_err(|e| format!("{e:?}"))?;
    info!("Analysis for {name} complete!");

    Ok(())
}

(daemon/src/stats.rs): сбор системной статистики.
C-подобный:
#[derive(Debug, Serialize)]
pub struct SystemStats {
    pub disk_stats: DiskStats,
    pub memory_stats: MemoryStats,
    pub runtime_metadata: RuntimeMetadata,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub battery_status: Option<BatteryState>,
}

impl SystemStats {
    pub async fn new(qmdl_path: &str, device: &Device) -> Result<Self, String> {
        Ok(Self {
            disk_stats: DiskStats::new(qmdl_path, device).await?,
            memory_stats: MemoryStats::new(device).await?,
            runtime_metadata: RuntimeMetadata::new(),
            battery_status: match get_battery_status(device).await {
                Ok(status) => Some(status),
                Err(RayhunterError::FunctionNotSupportedForDeviceError) => None,
                Err(err) => {
                    log::error!("Failed to get battery status: {err}");
                    None
                }
            },
        })
    }
}

2.7 Завершение работы по запросу пользователя.
Контролируемое завершение асинхронных задач и процессов в демоне реализовано в main.rs, diag.rs, analysis.rs и key_input.rs для сохранения данных, закрытия файлов, освобождения ресурсов и избежания утечек.
Это достигается комбинацией TaskTracker (отслеживание и ожидание завершения всех задач), CancellationToken, mpsc-каналов и обработки сигнала Ctrl+C (или эквивалентного) для инициации завершения.

Удаление данных реализовано в модулях diag.rs и qmdl_store.rs, процесс инициируется по запросу пользователя через HTTP API:
POST /api/delete-recording/{name}
POST /api/delete-all-recordings

Обработка в структуре DiagTask файла diag.rs реализована двумя методами:
  • "delete_entry": если запись текущая (qmdl_store.is_current_entry(name)), сначала вызывается self.stop(qmdl_store) для остановки записи, затем qmdl_store.delete_entry(name) для удаления файлов и обновления манифеста. Возвращает результат через oneshot-канал.
  • "delete_all_entries": вызывается self.stop(qmdl_store) для текущей записи, затем qmdl_store.delete_all_entries().
(daemon/src/qmdl_store.rs): функции удаления данных.
C-подобный:
pub async fn delete_entry(&mut self, name: &str) -> Result<(), RecordingStoreError> {
        let entry_to_delete_idx = self
            .manifest
            .entries
            .iter()
            .position(|entry| entry.name == name)
            .ok_or(RecordingStoreError::NoSuchEntryError)?;
        match self.current_entry {
            Some(current_entry) if current_entry == entry_to_delete_idx => {
                self.close_current_entry().await?;
            }
            Some(current_entry) => {
                self.current_entry = Some(current_entry - 1);
            }
            None => {}
        };
        let entry_to_delete = self.manifest.entries.remove(entry_to_delete_idx);
        self.write_manifest().await?;
        let qmdl_filepath = entry_to_delete.get_qmdl_filepath(&self.path);
        let analysis_filepath = entry_to_delete.get_analysis_filepath(&self.path);
        remove_file_if_exists(&qmdl_filepath)
            .await
            .map_err(RecordingStoreError::DeleteFileError)?;
        remove_file_if_exists(&analysis_filepath)
            .await
            .map_err(RecordingStoreError::DeleteFileError)?;
        Ok(())
    }

    pub async fn delete_all_entries(&mut self) -> Result<(), RecordingStoreError> {
        if self.current_entry.is_some() {
            self.close_current_entry().await?;
        }

        let mut keep = Vec::new();

        for entry in &self.manifest.entries {
            let qmdl_filepath = entry.get_qmdl_filepath(&self.path);
            let analysis_filepath = entry.get_analysis_filepath(&self.path);

            if let Err(e) = remove_file_if_exists(&qmdl_filepath).await {
                log::warn!("failed to remove {qmdl_filepath:?}: {e:?}");
                keep.push(true);
                continue;
            }

            if let Err(e) = remove_file_if_exists(&analysis_filepath).await {
                log::warn!("failed to remove {analysis_filepath:?}: {e:?}");
                keep.push(true);
                continue;
            }

            keep.push(false);
        }

        let mut keep_iter = keep.into_iter();
        self.manifest.entries.retain(|_| keep_iter.next().unwrap());
        self.write_manifest().await?;
        Ok(())
    }

В результате рассмотрения алгоритма работы системы модулей Rayhunter установлено, что центральным элементом этой системы является этап анализа, на котором эвристические алгоритмы выявляют потенциальные аномалии и угрозы в протоколе LTE, такие как отключение шифрования в радиоинтерфейсе или перевод обслуживания UE в уязвимые стандарты связи.
Указанные алгоритмы опираются на обработку сообщений RRC (Radio Resource Control), извлеченных из QMDL-логов, и позволяют оперативно реагировать на подозрительную активность базовых станций.
Чтобы лучше понять механизм работы этих эвристик, стоит начать с введения в ключевые компоненты системной информации в LTE: Master Information Block (MIB) и System Information Block (SIB).

3. Master Information Block (MIB) и System Information Block (SIB) стандарта LTE.
В стандарте LTE информационные блоки MIB и SIB являются ключевыми элементами, которые транслируются eNodeB (базовой станцией) для помощи UE (User Equipment) в выборе сети, синхронизации и подключении к ней.
Они передаются в формате широковещательной передачи на канале PBCH (Physical Broadcast Channel) и других каналах, обеспечивая доступность для всех UE без предварительной аутентификации.

MIB (Master Information Block) – это основной блок информации, который содержит необходимые для начальной синхронизации и декодирования последующих сообщений eNodeB сведения: ширину полосы канала (cell bandwidth), конфигурацию PHICH (Physical Hybrid ARQ Indicator Channel) и номер системного кадра (SFN – System Frame Number). Без MIB UE не может правильно интерпретировать последующие блоки.
Блок MIB передается с периодичностью 40 мс. При этом в течение этого интервала времени он повторяется 4 раза в нулевом подкадре каждого радиокадра длительностью 10 мс. Таким образом передачи происходят каждые 10 мс
в блоке из 4 последовательных радиокадров.
UE может комбинировать эти повторения для повышения надёжности декодирования (soft combining).

SIB (System Information Block) – это набор дополнительных блоков (SIB1 – SIB13), которые расширяют информацию из MIB и помогают UE в выборе eNodeB, идентификации сети и мобильности.
Блоки MIB и SIB интегрированы в общий процесс RRC (Radio Resource Control) и обеспечивают регистрацию («camping») UE на eNodeB в режиме ожидания.
На основе спецификаций 3GPP TS 36.331 перечислим ключевые поля и цели для каждого типа блоков SIB1 – SIB13.

SIB1 – содержит информацию о доступе к сети и расписании для других SIB. UE сначала декодирует MIB, затем SIB1 для обработки расписания, и только потом остальные SIB по необходимости.
SIB1 передается каждые 80 мс с повторениями в подкадре №5.
Ключевые поля:
– PLMN-IdentityList (до 6 PLMN, первый – primary);
– trackingAreaCode (TAC);
– cellIdentity (Cell ID);
– cellBarred (статус доступа);
– intraFreqReselection (разрешение на выбор другой eNodeB на текущем частотном канале);
– q-RxLevMin (минимальный уровень сигнала);
– p-Max (максимальная мощность UL);
– freqBandIndicator (диапазон частот);
– schedulingInfoList (расписание других SIB, включая si-Periodicity и sib-MappingInfo);
– si-WindowLength (длина окна передачи);
– systemInfoValueTag (тег для проверки изменений SI).

SIB2 – содержит общую конфигурацию радиоресурсов для всех UE, включая конфигурацию общих и разделяемых каналов, RACH, таймеры и управление мощностью UL. Необходим для процедуры ATTACH.
Ключевые поля:
– ac-BarringInfo (барьеры доступа для emergency, MO-signalling, MO-data);
– radioResourseConfigCommon (конфигурация RACH: numberOfRA-Preambles, powerRampingParameters, ra-SupervisionInfo);
– bcch-Config (модификация периода);
– pcch-Config (paging cycle);
– prach-Config (конфигурация PRACH);
– pdsch-Config/pusch-Config/pucch-Config (конфигурация PDSCH/PUSCH/PUCCH);
– soundingRS-UL-Config (SRS);
– uplinkPowerControl (управление мощностью UL);
– ue-TimersAndConstants (таймеры);
– freqInfo (UL частота и ширина полосы сигнала);
– timeAlignmentTimerCommon (таймер выравнивания времени).

SIB3 – содержит общую информацию для перехода UE на новую соту в режимах intra-frequency, inter-frequency и inter-RAT, за исключением соседних сот на текущем частотном канале.
Ключевые поля:
– cellReselectionInfoCommon (q-Hyst для гистерезиса, speedStateReselectionPars для скоростных параметров);
– cellReselectionServingFreqInfo (s-NonIntraSearch, threshServingLow, cellReselectionPriority);
– intraFreqCellReselectionInfo (q-RxLevMin, s-IntraSearch, allowedMeasBandwidth, presenceAntennaPort1, neighCellConfig);
– t-ReselectionEUTRA (таймер выбора eNodeB).

SIB4 – содержит информацию о кандидатах на переход из числа соседних сот на текущем частотном канале (intra-frequency), включая специфические параметры. Все поля опциональны, так как UE может обнаруживать сигналы соседних сот автоматически.
Ключевые поля:
– intraFreqNeighCellList (список соседних сот с параметром q-OffsetCell);
– intraFreqBlackCellList («черный список» сот).

SIB5 – содержит информацию о кандидатах на переход из числа соседних сот на других частотных каналах (intra-frequency) и для процедуры эстафетной передачи вызова.
Ключевые поля:
– interFreqCarrierFreqList (список частот с параметрами q-RxLevMin, thresh-High/Low, q-OffsetFreq);
– interFreqNeighCellList (список соседних сот);
– interFreqBlackCellList («черный список» сот).

SIB6 – содержит информацию о соседних сотах UTRA (UMTS) для перехода.
Ключевое поле: carrierFreqListUTRA-FDD/TDD (частоты UTRA с параметрами q-RxLevMin, cellReselectionPriority, thresh-High/Low);

SIB7 – содержит информацию о соседних сотах GERAN (GSM) для перехода и процедуры эстафетной передачи вызова.
Ключевое поле: carrierFreqsInfoList (GERAN частоты, cellReselectionPriority, ncc-Permitted, q-RxLevMin);

SIB8 – содержит информацию о соседних сотах CDMA2000 для перехода и процедуры эстафетной передачи вызова.
Ключевые поля:
– systemTimeInfo;
– searchWindowSize;
– parametersHRPD (preRegistrationInfoHRPD, cellReselectionParametersHRPD);
– parameters1XRTT (cellReselectionParameters1XRTT).

SIB9 – содержит имя Home eNodeB (HNB name) для femtocell приложений.
Ключевое поле: hnbName (текстовое имя).

SIB10 – содержит первичное уведомление ETWS (Earthquake and Tsunami Warning System).
Ключевые поля:
– messageIdentifier;
– serialNumber;
– warningType (тип предупреждения).

SIB11 – содержит вторичное уведомление ETWS.
Ключевые поля:
– messageIdentifier;
– serialNumber;
– warningMessageSegmentType;
– warningMessageSegmentNumber;
– warningMessageSegment (сегмент сообщения).

SIB12 – содержит уведомления CMAS (Commercial Mobile Alert System) для общественных предупреждений.
Ключевые поля:
– messageIdentifier;
– serialNumber;
– warningAreaCoordinatesSegment (координаты области уведомлений).

SIB13 – содержит информацию о конфигурации MBSFN (Multicast Broadcast Single Frequency Network) для MBMS (Multimedia Broadcast Multicast Service).
Ключевые поля:
– mbsfn-AreaInfoList (информация о зонах MBSFN);
– notificationConfig (конфигурация уведомлений).

4. Анализ эвристических алгоритмов системы Rayhunter.
Материал дополняется.​
 
Последнее редактирование:


Напишите ответ...
  • Вставить:
Прикрепить файлы
Верх