Hi 大家好,我是梅森,當初國小和國中就對無人機非常感興趣,因此入手了相對平價的 Tello,在國中遙控飛機社也作為飛 DJI 空拍機之前的主力練習機。最近因為戰爭因素等等,讓我對於無人機安全很感興趣,因此我把我不知道摔了幾次的戰損版 Tello 與 Android App 開始研究。
研究歷程與測試環境
題目、樣本與分析範圍
這個題目的起點很單純。我手上有一台 Tello,也想知道它的 Android App 有沒有人仔細看過。當時關於 DJI 的資安討論大多集中在 GO 4 與 Pilot,控制通道的研究也已經相當多。相較之下,Tello App 的第三方 SDK、資料流與隱私政策幾乎沒有公開分析,於是成了這次研究的主要範圍。
一開始要下載 App 時,Google Play 的頁面已經無法取得。不過 Ryze 官方下載頁仍提供 APK,這表示下架的是 Play 商店通路,不是廠商停止散布。我蒐集了 2019 至 2024 年的六個版本,並驗證各版本由同一把簽章金鑰簽發。後來取得官方網站的 1.6.8.0 後,也確認它與鏡像樣本逐位元組相同。樣本來源先確認清楚,後面的比對才有意義。
動態分析環境
首先我撈出了已經很久沒有使用的 Pixel 6 Pro,當作 Android 的目標載具。這個題目需要直接連上 Tello 的 Wi-Fi,模擬器不適合。它沒有真實的無線網路介面,加殼 App 也可能偵測到模擬器環境。
接著是解鎖 bootloader、刷入 Magisk,並處理 Windows 的 fastboot 驅動程式。Root 裝好後,su 一開始仍被拒絕,原因不是全域設定,而是 Magisk 的 Superuser 分頁裡已有一筆針對 com.android.shell 的拒絕政策。把那筆個別規則改掉後,才終於能使用 Root 權限。
我再加上 Frida、mitmproxy 與系統憑證,讓手機連著 Tello Wi-Fi 時仍可經由 USB 轉送 HTTPS 流量。這段環境建置比預期久得多。Frida 也不能一開始就掛載,過早加入 Java hook 會讓 App 在啟動時停止運作。後來改成延遲掛載,研究後期再改為 attach 已啟動的行程。這也留下了一項限制,啟動期的行為不一定能被觀測到。
手機準備好後,我才回頭處理 APK。愛加密抽走了大量方法主體,反編譯結果不足以還原所有執行路徑。不過 DEX 的字串池與類別清單仍可用,因此靜態分析仍能用來確認端點、SDK 與程式碼是否存在。
測試條件的確認
實機測試也有自己的干擾因素。飛控傳感器警告必須先完成校正,否則無法判斷異常反應來自測試還是機體狀態。連續測試時的高溫保護、Android 對無網際網路 Wi-Fi 的斷線判定,以及 VPN 看似中斷但出口仍在海外的情況,都曾讓資料不能直接採用。
因此,我後來把環境狀態列入每次測試的前置條件。測試前確認出口 IP、Tello Wi-Fi 連線與機體溫度,對與既有機制矛盾的單次結果重新測試。這也是本文多次保留「未測試」與「未驗證」的原因,不是每一段封包或一次失敗都能直接寫成結論。
Tello SDK 控制通道的測試結果
Tello 同時使用兩條控制路徑。官方 App 使用私有的二進位協定,而官方 SDK 則以純文字 UDP 指令操作 192.168.10.1 的 8889 埠,狀態資料經 8890 埠廣播,影像則走 11111 埠。後者原本是讓教育與程式設計工具使用的介面,但協定規格沒有描述認證、授權或裝置配對。
我先讓官方 App 維持正常連線與影像串流,再從另一個來源埠送出下列指令。為了避免讓機體產生動作,這次只送出了進入 SDK 模式與查詢用途的內容。
command |
封包紀錄顯示,飛機在 18 毫秒後回覆 ok。接著,App 的影像串流停止,飛機開始向另一端廣播明文遙測資料。這不是 App 自己離線造成的錯覺,同一段時間中 App 的控制流仍持續運作,合計送出 2,893 個封包,至 59 秒仍維持 49–51 Hz。螢幕上的操控元件仍在,但即時影像已經消失。
影像封包的計數也很直接。攻擊前 0–12.1 秒有 1,766 個封包,攻擊後的 0–10 秒、10–25 秒與 25–47 秒都為 0。此時另一端收到的資料包含姿態、速度、溫度、高度與電量,如下。
pitch:0;roll:1;yaw:8;vgx:0;vgy:0;vgz:0;templ:70;temph:73; |
我另外驗證了恢復情形。47 秒的觀測期間沒有新的影像封包,從 App 的連線畫面重新操作也沒有恢復。這次結果唯一有效的復原方式是關機重開。不過「飛行中會有相同結果」仍是依同一機制作出的推論,未在實際飛行中測試。
已公開的控制通道問題與研究範圍
測完之後,我先去查了前人研究。Tello Wi-Fi 預設無密碼、SDK 協定沒有認證,以及以 SDK 指令讓已連線手機失去連線,早已分別出現在 Alias Robotics 的 RVDP#1208 案例研究、同儕審查論文〈Analysis of Control Channel Cybersecurity of the Consumer-Grade UAV by the Example of DJI Tello〉,以及公開的技術文章。Alias Robotics 對預設開放 Wi-Fi 的案例給出 CVSS 9.4。
所以我不會把它寫成「本研究發現 Tello 可被未認證接管」。我做的是複現已知的 SDK 劫持,另外補上三個前人研究沒有講清楚的邊界。
第一,兩條協定的安全模型不同。App 的二進位控制封包雖然序號恆為 0,我仍以滿舵右轉、持續 21.5 秒、共 250 個封包的方式測試。飛手的搖桿全程維持中位,飛機沒有旋轉。這表示二進位路徑具工作階段綁定,不能只因封包格式可重建就推論任意來源可以控制飛機。相對地,SDK 純文字路徑沒有這個限制。
第二,設定 Wi-Fi 密碼無法成為協定層的解法。研究中將 Wi-Fi 由 open 設為 wpa2-psk / wpa3-sae,再以已合法連網的手機從不同來源埠重送 command。飛機在 13 毫秒後回覆 ok,App 影像由 1,499 個封包降為 6 個後歸零。密碼可以阻擋未取得密碼的人加入網路,卻不會為已在網路內的裝置加入 SDK 認證。
第三,影像中斷無法由 App 自行恢復。這點使問題不只是一段短暫的串流干擾,但也不代表我已經驗證飛行控制可以被接管。官方 SDK 文件列有 rc a b c d、takeoff、land 與 emergency,其中 emergency 的說明是「Stop motors immediately.」我從未對飛行中的機體送出 rc、takeoff、land 或 emergency。它們對飛行中機體的實際效果未測試,任何墜落或完整劫持的說法都只能是依官方文件的推論。
Tello Android App 的資料流與隱私政策
控制通道已有相當完整的研究,Tello App 本身卻很少被拆開檢視。我把範圍轉到六個 APK 版本的第三方 SDK、內建隱私政策、端點與執行期流量,希望區分廠商說明了什麼、App 實際做了什麼,以及哪些結果仍然需要保留不確定性。
動態分析比靜態檢索多了幾個現實限制。六個版本都以愛加密加固,部分 Java 方法主體被抽取到執行期載入的元件。單靠反編譯結果不能說明所有執行路徑。我在研究裝置上使用 Frida 觀察特定 API 與 HTTPS 流量,同時保留原始封包與測試條件。掛載時機也需要控制,過早加入 Java hook 會使 App 在首次啟動階段停止運作,這類失敗不是漏洞證據,而是研究工具與目標程式交互作用的限制。
動態測試在隔離的研究裝置上完成。為了讀取所需的系統狀態與控制測試環境,我先完成 Magisk 的安裝與授權設定。這些畫面只記錄研究環境,不涉及 Tello App 的使用者資料或飛行影像。
最明確的例子來自內嵌的 GameSir 手把 SDK。當我把 gamesir_lastTime 歸零後重新啟動 App,SDK 向 https://client.vgabc.com/clientapi/ 發出以下請求。端點解析至中國境內的阿里雲。
POST https://client.vgabc.com/clientapi/ |
這筆資料包含裝置廠牌、機型、套件名稱與 SDK 版本,未見 Android ID、MAC 位址、IMEI、廣告 ID、位置或帳號資訊。問題不在於把它說成高敏感度資料,而是這個第三方傳輸沒有出現在我檢索的 1.3.0.0、1.6.0.1、1.6.4.0、1.6.8.0 中英文隱私政策。gamesir、vgabc、小鸡 與 xj 的命中數皆為 0。
我接著做了權限 A/B 對照。位置、相片、影片與儲存空間的六項權限全部拒絕時,仍有 1 筆請求。全部授予時也有 1 筆。兩組 body 都是 110 bytes,逐位元組相同,回應大小皆為 2053 bytes。原因並不複雜,這些欄位可由 android.os.Build 取得,不需危險權限。使用者既無法從權限設定得知這筆傳輸,也不能以這些設定阻止它。
這個實驗也提醒我,流量觀測不能只做一次。GameSir SDK 有節流欄位。若不先歸零 gamesir_lastTime,兩組都可能沒有流量,進而把「節流尚未到期」誤解成「權限沒有影響」。結論碰巧正確並不代表測試設計正確。
客服 WebView 提供了第二個不同的資料面。從 App 開啟技術支援後,頁面載入阿里雲 ARMS 與兩組 Google Analytics,ARMS 信標含持久性使用者識別碼、工作階段識別碼、螢幕解析度、像素密度與連線類型。這筆請求的目的地是 us-west-1,因此不能寫成傳往中國。至於 VPN 未關閉時取得的地區分流現象,我沒有把它當作結論。
政策的問題也不是單向缺漏。中文政策曾具名揭露 Bugly 與 BaiduLBS,卻未提 Google Analytics。英文政策提到 Google Analytics,卻未列前兩者。兩種語言的使用者各自拿到不完整的第三方清單,而 GameSir、阿里雲 ARMS 與 retcode 在各版本、各語言政策中皆未被揭露。App 的繁中資源另有 316 條字串,12 組台灣與中國用語對照中,中國用語出現 57 次,台灣用語出現 4 次,占 93.4%。這不是我對翻譯品質的印象,而是資源檔的計數結果。
我進一步比較內建政策,發現英文版從 2019 至 2024 的六個版本逐位元組相同,大小為 205,402 bytes。中文隱私政策則改過三次。這不代表英文版完全沒有資料所在地的說明,英文文件其實提到伺服器可能位於美國、中國與香港。我曾用過窄的關鍵字搜尋而漏掉這段內容,之後已改正。仍然成立的結論是,具名第三方 SDK 的揭露在語言之間不對等,而且最新 APK 內建的政策副本可能落後於線上版本。
飛行日誌、原生程式庫與未驗證事項
我也檢查了飛行日誌的保存與上傳。飛行後,App 在 droneLog/ 目錄留下 DJI_LOG_V3 檔案。針對一個 3.3 MB 的日誌檔,我獨立求得其每筆記錄的混淆方式如下。
plain[i] = cipher[i] ^ cipher[2] |
金鑰就放在酬載的第 3 個位元組,與密文一同送出。全量驗證中,解碼後為 0 的位元組比例為 30.5%,位元組熵由 7.962 / 8.0 降為 6.356 / 8.0。不過 DJI DAT 格式已有第三方工具,我尚未查證這種混淆方式是否已有公開文件,也未驗證那些工具是否適用於 Tello。所以這頂多算我自己獨立解出來的,稱不上首次發現。
資料留在本機不等於不會上傳。我在未登入、切回有對外網路的 Wi-Fi 後觀測 5 分鐘,連續 20 次、每 15 秒檢查一次,Tello App 都沒有對外請求,也未觀測到飛行紀錄上傳。這是對廠商有利的結果,但它的邊界同樣明確。登入後的行為未測試,批次或特定頁面觸發的上傳也未測試,不能把它延伸成「App 不會上傳飛行紀錄」。
原生程式庫的結果則替研究排除了一個方向。liblocSDK8a.so 在 1.6.8.0 仍隨 APK 出貨,大小為 43,112 bytes,18 個 JNI 進入點都指向 com.baidu.location.Jni 或 com.baidu.serverLoc.Jni。但該版本 dex 中 com/baidu 開頭的類別數是 0,位置權限拒絕或授予後等待 20 秒,這個程式庫都沒有載入。這些證據指向它是 Java 端根本叫不到的殘留程式碼。要當成隱藏資料傳輸的直接證據,還差得遠。
四項測試結果的修正
研究做到最後,我手上沒有漏洞清單,倒是有四次被自己推翻的推論。
第一,我曾認為二進位協定的序號恆為 0,就足以注入飛行指令。21.5 秒的滿舵測試沒有任何可見反應後,我把結論下修為二進位協定具工作階段綁定,嚴重度由 High 下修為 Low。
第二,我一度以為設定 Wi-Fi 密碼能阻擋 SDK 路徑。第一次測試沒有回應,但那是 n=1,且與 WPA2 位於連結層、UDP 協定位於應用層的機制不符。重測後攻擊完整重現。首次無回應可能與高溫保護有關,但這個原因本身也未驗證。若我停在第一次結果,就會給出方向相反的安全建議。
第三,我曾把英文隱私政策寫成未提 Google Analytics。交叉檢索後發現英文版三個版本確實有揭露,應修正為中英文揭露各自缺漏、形成對稱落差。否定性主張尤其需要多組關鍵字與原文段落交叉確認。
第四,日誌的高熵一開始讓我以為內容接近隨機而無法解讀。找到與密文同在的單位元組金鑰後,資料結構才開始清楚。高熵只表示目前的假設看起來像隨機,不能直接當成強加密的證明。
教室多機環境也值得另外驗證。官方的教育工具與 SDK 使用同一組 UDP 埠和文字指令,但我只有一台機器,沒有在真實教室情境實地測試。多機同時運作時的連線、SSID 區分與學生裝置行為仍是未測試事項。
研究結論
我原本以為最有價值的成果會是那 7 個位元組。回頭看,那段測試真正的用處是逼我去查前人研究,然後把問題移到還沒有被仔細看過的 App 層。
隱私政策裡沒有提到的 GameSir 傳輸、客服 WebView 載入的第三方遙測、中英文政策各自缺漏的第三方清單、飛行日誌的獨立求解,還有那個 Java 端叫不到卻仍隨 APK 出貨的原生程式庫,這幾件事比控制通道更值得往下追。
只是每一項都還有沒測完的地方。登入後的日誌上傳、教室多機情境,以及 rc 與 emergency 對飛行中機體的實際效果,都還停在未測試。這篇留下的是一份標好邊界的紀錄,該補的測試還在後面。