研究者只靠一根舊 wifi 天線和一台舊 Raspberry Pi,就能把 Eufy 門鈴踢出跟自家 HomeBase Station 2 的連線;而拆穿門鈴裡那份加密設定檔的鑰匙,原來只藏在韌體裡一組 64 bytes 的字母表,跟一個三位元組的 XOR 樣式裡。
從門鈴走進你家內網
這篇文章記錄一條完整的攻擊鏈:從一支公開部落格文章、一台 Eufy 影像門鈴加 HomeBase Station 2,一路拆到能讀出門鈴儲存的隱藏網路明文密碼、以及 HomeBase 在區網裡的 IP 位址。整個過程沒有動用任何雲端漏洞或帳號密碼——起點只是作者懷疑的一個沒有額外防護的標準 WPA2 隱藏網路,終點是一把寫死在韌體裡的 AES 金鑰,作者推論這把金鑰在所有裝置間應該通用。作者形容這是暌違兩年後重新回來做的研究——原話是「After this two-year hiatus, we are pleased to preach a new homily from this humble digital pulpit of ours.」用的工具也很樸素:一根舊天線、一台 Raspberry Pi,加上把韌體一行一行反組譯出來的耐心。這類研究示範的是一整套適用於任何「本地閘道加雲端裝置」架構的分析方法——從無線電層開始,一路往下逼近到晶片跟韌體。
隱藏在門鈴背後的 OCEAN 網路
每一台 Eufy 影像門鈴都要跟自己的 HomeBase Station 2 講話,而作者發現這段通訊走的是一個沒有廣播 SSID 的隱藏 Wi-Fi。文章寫得很直接:「the video doorbell is placed at your door. The doorbell (and I guess the rest of products related to Eufy) communicates with the Homebase station through a hidden wifi.」——先別管加密強度,光是「有一個隱藏網路」這件事,就已經是個可以被定位的目標。
推測起來,「隱藏」在 802.11 協定裡未必是真的探測不到——AP 不主動廣播 SSID,不代表封包裡看不到 SSID;只要有裝置嘗試連線,beacon 或 probe request/response 裡就可能把網路名稱用明文帶出來。文章裡,作者的做法是直接送一個帶著 OCEAN_XXXXXX 字樣的 probe request,藉此確認這個隱藏網路確實存在,完全不需要先破解任何加密。
這個隱藏網路的名字不是隨機的。作者確認 SSID 固定是 OCEAN_XXXXXX,而後綴的六碼,「being the suffix the last 24 bits of Homebase's MAC」,換句話說,只要知道裝置的 MAC 位址,就能反推出它的 Wi-Fi 名稱。更方便的是,HomeBase 跟門鈴共用同一段 OUI 前綴 90:bf:d9,作者形容:「Identify the presence of Homebases is easy because the first 24 bits of its MAC are known...We can dust off an old Alfa wifi anntena, connect it to a battery-powered Raspberry Pi, and walk around to locate beacons from stations with a MAC address matching the one we are looking for.」——拿一根舊天線在街上走一圈,就能鎖定目標訊標所在的頻道。
比較可能的解釋是,用 OUI 前綴找同款裝置的做法之所以有效,是因為晶片模組或整機廠商申請到的 MAC 位址區段通常連續——只要抓到一台裝置的 MAC,就能反推整批同型號裝置共用的前 24 bits,再靠這組前綴去掃描、定位其他還在使用中的裝置。文章裡,HomeBase 與門鈴實際共用的前綴正是 90:bf:d9。
剩下的問題只有一個:這個隱藏網路有沒有做額外防護?作者的假設很樸素——「if this uses standard WPA2 without any kind of protection… would it be vulnerable to deauth packets?」——於是寫了一支叫 Baleeiro 的工具,鎖定目標頻道後廣播 500 個 deauth 訊框,把門鈴踢出跟 HomeBase 的連線。這一步本身的目的不是逼門鈴重新配對,而是替接下來的物理操作爭取一個不會被雲端察覺的空檔——作者說得很直接:「You can remotely disconnect the doorbell from the "management" network avoiding it to stream video/audio.」有了這段空檔,才有辦法把門鈴拆開、接上測試點,「dump its memory, and close it so nobody knows」。這一步同時也把攻擊門檻,從「需要在遠端找到雲端服務或帳號的漏洞」降到「只要有一根便宜的 wifi 天線、站在收得到訊號的地方」——作者甚至在程式裡印出一句挑釁的訊息:「Launching 500 harpoons to hunt that whale!」,把整個攻擊過程比喻成獵鯨:先鎖定目標,再送出大量「魚叉」把它逼出水面。下面這條時間軸,把從鎖定訊標到最後解密設定檔的六個階段攤開來看。
拖動時間軸看每一步解鎖了什麼 · 6 個階段
90:bf:d9 這段 OUI 前綴,用一根 wifi 天線走一圈就能找到 HomeBase 的訊標並鎖定頻道。聲波配對協定:把韌體逆向出來的音樂
門鈴要跟 HomeBase 重新配對時,Eufy 的做法很特別——不是走藍牙也不是走 QR code,是靠聲音:「To connect the doorbell to the Homebase, it must be synchronized.」播放一段聲波,門鈴的麥克風聽到之後解碼出設定資訊,這整套機制被作者從韌體裡逆向了出來,跟前一段的 deauth 攻擊是分頭進行的兩條獨立線索。
底層的頻率設計並不複雜。合理的推測是,Reed-Solomon 這類校驗機制通常是用來對付有雜訊的傳輸通道——喇叭、麥克風跟環境音都可能讓聲波訊號失真,校驗 nibble 換來的容錯,可以讓門鈴不用整段重錄就糾正個別符碼的誤判;文章本身沒有明講這個設計動機。作者引用另一位研究者 BatchDrake 的初步分析:「19 Frequencies were used, 150 Hz of difference between them」,而韌體程式碼裡印出的基準頻率是 12000 Hz(printf("---------use base frequence: %d\n",12000));每個符碼(symbol)的長度「had a aprox duration of 65ms」。19 個音準各自扮演不同角色——作者寫道「The tone at position "1" seems to be a marker that split the information blocks」,音準 1 是把資訊切成一個個區塊的分隔記號;「tones 2 to 17 maps nibles 0 to F」,中間 16 個音準剛好對應到十六進位的 0 到 F;剩下兩個音準 18、19,作者的結論是「they are used as "control" tones to indicate repetitions」,用來標示重複傳輸。合理的推測是,聲波配對能省下一組原本要另外配置的藍牙或近場通訊模組——門鈴本來就內建麥克風做語音對講,直接借用來接收配對訊息,硬體成本最低;代價是整個協定只靠聲音的頻率跟時序編碼,一旦韌體被讀出來,等於把規格書攤在陽光下。
每個資訊區塊固定是 15 個符碼(原文寫「15 symols size」),其中留兩個位置不是拿來裝資料——作者確認「So it was Reed-Solomon, meaning that the last two nibbles does not carry information per se」,13 個資料 nibble 配 2 個 Reed-Solomon 校驗 nibble。最後一個比較短的區塊改用另一套驗證方式,「The shortened block is CRC-16 (it is validated at FUN_000b7104 using FUN_000c4370)」,連對應的韌體函式位址都被標了出來。把區塊結構拆到這麼細——13 個資料 nibble、2 個 Reed-Solomon 校驗 nibble、外加專門處理最後一塊的 CRC-16——代表作者把韌體裡負責編解碼的函式讀懂、驗證過了,不然沒辦法解釋為什麼連 FUN_000b7104 這種函式位址都能對得上。BatchDrake 的分析也提到「There was a gap (one frequency was never used)」——19 個音準之間並不是單純等差排列,所以底下這個小工具不重建每一個音準的絕對頻率,只呈現音準編號跟它在協定裡扮演的角色(分隔記號、nibble、重複控制)之間的對應。
拖動音準編號看它扮演的角色 · 19 個音準
光是能解碼還不夠有殺傷力,作者自己也說明為什麼要多走一步:「there is only a way to validate if my decoding is working or not: building the inverse (an "encoder") and create a wifi to see if the doorbell connects to it after parsing my synthetic audio」——能反過來偽造,才算真正讀懂這份規格書,而不只是被動監聽。文章附上一支叫 audio_wave_encoder.py 的工具,指令是「python3 audio_wave_encoder.py "a1b2c3d4" "4d3c2b1a" message.wav」;實測時,作者按住門鈴的 sync 鍵兩秒、拿手機在旁邊播放自己編碼出來的聲波,結果——「I played it using my mobile near to the doorbell after pressing the "sync" button for two seconds and it immediately sent probe requests from the doorbell asking for my wifi」——門鈴立刻照著偽造的聲波內容送出了對應的 wifi probe request,接著「It used a static IP (192.168.32.250)」連上作者架設的測試熱點。要走到這一步,前提是有人先按下門鈴上的實體 sync 鍵;這跟前一段的 deauth 攻擊是兩條分開的研究線索,deauth 負責爭取物理操作的空檔,聲波協定則是另一條被完整逆向、也被完整偽造出來的獨立路徑。
從 SPI Flash 焊點到明文 Wi-Fi 密碼
逆向完聲波協定只是拿到「怎麼跟門鈴講話」的能力,藏著密碼的地方是門鈴本體的快閃記憶體,不是 HomeBase。作者原本也想直接拆 HomeBase,但坦承做不到:「the Homebase flash does not expose "legs" so I can not use test hooks to connect it that's why I did not dump -yet- its firmware」,只好退而求其次,改拆一台先前已經跟自己的 HomeBase 同步過的門鈴:「When I dumped the flash memory the doorbell was previously synced with my homebase, so I knew it contained somewhere the hidden network credentials」。作者的做法很物理:「connecting test hooks to the flash memory and using the SPI0 interface of an old Raspberry Pi」,直接在 SPI flash 的測試點接上探針,把整顆晶片的內容讀出來。
把 mtdparts 列出的每個分割區大小加總——env、idblock、uboot、misc、boot、recovery、meta、system、config、user、nv_user、nv_factory——剛好是 16384 KB,也就是 16 MB。其中的 user 分割區被工具辨識為「Linux jffs2 filesystem data little endian」,加密過的設定檔 es_config 就藏在裡面。整顆快閃記憶體裡不只有這一份設定檔,連同 boot、recovery、完整的應用程式二進位檔都在裡面——聲波協定跟金鑰推導邏輯裡每一個函式的細節,都是從這次完整讀出的韌體反組譯出來的。
es_config 本身是加密過的,但解開它的鑰匙不需要動用任何雲端服務——全部原料都寫死在韌體裡。一般來說,這類「測試點」是 PCB 佈線時留下、方便產線燒錄或除錯用的接腳,量產出貨後不見得會被移除,也不一定有防呆處理——這是通用的硬體佈局慣例,文章本身沒有描述這顆晶片接腳的細節,但找得到測試點,通常就等於繞過了裝置原本設計的韌體存取限制。下面把金鑰推導拆成四步,可以一步一步照著看。
切換分頁看金鑰推導的四個步驟
韌體裡寫死一組 64 bytes 的字元表,是整條金鑰推導的原始輸入:
abcdefghijklmnopqrstuvwxyz0123456789~_ABCDEFGHIJKLMNOPQRSTUVWXYZ
把這組字表拿去跟一個重複的三位元組樣式做 XOR,原文寫:「XOR with a repeating group of three bytes (0x01, 0x04, 0x06)」。
0x01, 0x04, 0x06, 0x01, 0x04, 0x06, ...
把 XOR 過的結果算一次 MD5,作者記下了結果:「This gives us the string used as key to encrypt/decrypt the configutation file: C0C714B43806EF49 => 43304337313442343338303645463439...It's derived from hardcoded data, so it should be the same between devices.」後面那串 32 個字元的十六進位數字,是把 C0C714B43806EF49 這 16 個 ASCII 字元逐一轉成十六進位編碼的結果——當成位元組讀回去正好是 16 bytes,直接拿來當 AES-128 的金鑰用。
MD5(XOR 結果) = C0C714B43806EF49 → 43304337313442343338303645463439
最後一步是把 es_config 從快閃記憶體裡讀出來、跳過開頭幾個位元組,再用推導出來的金鑰跑 AES-128-ECB 解密:
jffs2reader /tmp/user.bin -f /es_config | tail -c +3 | openssl enc -aes-128-ecb -d -nopad -K 43304337313442343338303645463439 | xxd
es_config 就整個攤開成明文——Wi-Fi SSID 是 OCEAN_E9A5FC、密碼是 F1VSXfNQL_ZCd3k,還有一段十六進位的 IP 欄位(c0a8 2005,換算成十進位是 192.168.32.5),看起來是 HomeBase 在這個網段裡的位址。這是一組格式正確、看起來就是 Wi-Fi 設定的明文字串,不是亂碼。
如果 Eufy 一開始就替每一台裝置燒入不同的種子,或是把金鑰綁定在裝置專屬的序號、甚至一顆便宜的安全元件上,這整條推導鏈就會在「任何裝置都能用同一組字母表跟 XOR 樣式」這一步斷掉。攻擊者頂多解出自己手上這一台的密碼,沒辦法把腳本原封不動套用到別人家的門鈴上。
從其他消費性 IoT 產品的資安研究經驗來看,「量產批次共用同一把邏輯金鑰」這類失誤並不罕見;照這個邏輯推算,原因通常出在量產階段要另外管理每一台裝置的專屬秘密,牽涉產線流程跟供應鏈,成本跟複雜度都比燒一份共用韌體高。這是一般性的工程推論,文章本身沒有談論 Eufy 內部為什麼這樣設計。
這條推導鏈本身沒有用到任何一次性亂數或裝置專屬的種子——字母表、XOR 樣式、最後用的 AES-128-ECB 模式,全部都是常數。這裡有一段值得補充的技術背景(文章本身只寫了 -aes-128-ecb 這個參數,沒有展開討論):ECB 模式是已知的弱點,同樣的明文區塊永遠對應同樣的密文區塊,沒有初始向量把每次加密結果打散。如果這把金鑰真的如作者推論的那樣所有裝置共用,同一支腳本套用在任何一台裝置上都能跑出同樣的結果。
同一把鑰匙,沒有牆的家用網路
把整條鏈條走完之後,真正讓這件事變嚴重的不是「密碼被解出來了」,而是這把鑰匙跟這個隱藏網路本身的設計。金鑰的推導完全建立在寫死的常數上——字母表跟 XOR 樣式兩者都不會因為裝置不同而改變。作者自己也只把這一點寫成推論:「It's derived from hardcoded data, so it should be the same between devices.」這是作者在只實際驗證過一台門鈴之後做出的判斷,文章裡沒有拿第二台裝置比對過。
比鑰匙共用更麻煩的是網路本身沒有隔離。作者寫得很清楚:「if you connect...to that hidden network you can browse freely. Also it gives you access to any other element in the network (for example, your router web interface).」連上 OCEAN_XXXXXX 之後,不只能上網,還能直接碰到同一段區網裡的其他裝置——包括家用路由器的管理介面。也就是說,一旦有人拿到這把鑰匙,他拿到的是一張直通全家內網的門票。
這不是紙上談兵的推論——作者實際連上 OCEAN_XXXXXX 之後跑了一次 port scan,結果列出十幾個開放的 tcp 埠,包括 53/tcp 的 dnsmasq、554/tcp 的 rtsp 串流,還有 9000、10400 系列等一整串;接著直接對外 ping google.es,「2 packets transmitted, 2 received, 0% packet loss, time 1001ms」,平均延遲落在 40 ms 上下,證明這個隱藏網路能完整通到外部網際網路,不是被關在一個封閉的沙盒裡。更關鍵的是分段測試:作者寫道「Some testing devices/platforms I could reach that are deployed at the same router than the Homebase」,接著對 192.168.90.1 送出 curl -I 請求就拿到「HTTP/1.1 200 OK」——文章沒有進一步指認這個位址背後是什麼裝置,只確認它跟同一台路由器底下的其他測試裝置一樣,位址跟先前解密出來、看起來是 HomeBase 自己的 192.168.32.5 完全不同網段,證明隱藏網路確實能一路碰到同一個路由器底下的其他裝置,不只是理論上的推測。port scan 秀出來的服務也不只 dnsmasq 這種基礎設施元件——還有 554/tcp 的 rtsp,這個埠通常用來做即時影像串流。文章沒有進一步深入測試這個埠背後接的是什麼,但光是掃出這一長串開放埠,就足以說明這個網段遠遠不只是「幫你轉個網路封包出去」這麼單純。下面這張圖把門鈴、攻擊者、HomeBase 跟家用路由器擺在同一張拓樸上,點每個節點看它在這條攻擊鏈裡扮演的角色。
點任一節點看它在攻擊鏈裡的角色 · 4 個節點
門鈴 · 角色
透過隱藏 Wi-Fi 跟 HomeBase 講話——「the video doorbell...communicates with the Homebase station through a hidden wifi」。自己的快閃記憶體裡就存著連回 HomeBase 用的隱藏網路帳密,重新配對則要靠實體 sync 鍵加聲波,不是被 deauth 踢斷線就會自動觸發。
不知道:自己所在的這段隱藏網路有沒有跟家裡其他裝置隔開。
攻擊者(Baleeiro) · 角色
只需要知道 OUI 前綴 90:bf:d9,就能用一根天線定位 HomeBase 訊標、鎖定頻道,再廣播 500 個 deauth 訊框逼門鈴斷線重新配對。
不需要:任何雲端帳號或密碼,全程只在無線電層面操作。
HomeBase Station 2 · 角色
同時廣播隱藏網路 OCEAN_XXXXXX、也是家用網路對外的閘道。作者確認:「the Homestation acts as a gateway and if you connect...to that hidden network you can browse freely.」
沒有做到:把這個隱藏管理網路跟家用網路的其他裝置分段隔離。
家用路由器與其他 LAN 裝置 · 角色
一旦有人連上 OCEAN_XXXXXX,這裡就直接暴露出來——「it gives you access to any other element in the network (for example, your router web interface)」。
不知道:進來的連線是不是真的來自 HomeBase 本身。
把三條線索串起來看:一根天線加 500 個 deauth,換來的是一段不會被雲端注意到的物理操作空檔;聲波協定的逆向與偽造,證明門鈴的本地配對機制完全可以被外人模仿;而 SPI flash 加上寫死的常數金鑰,才是讓明文密碼掉出來的最後一塊拼圖。三段研究各自獨立成立,合在一起才是完整的攻擊鏈。
這種「閘道裝置背後開一個沒有隔離的管理網路」的設計,並不是 Eufy 獨有的失誤模式——只要是要讓一群裝置彼此發現、彼此配對的家用 IoT 生態系,都會面臨類似的取捨:管理網路開得越方便,攻擊者要跨進來就越容易。比較穩妥的做法是把這類管理/配對用的網路,用 VLAN 或至少獨立的子網段跟使用者的主要區網隔開,即使金鑰外洩,攻擊者也只會被困在那個管理網路裡,碰不到路由器或其他裝置。
這條攻擊鏈也提醒一件容易被忽略的事:只要牽涉到本地配對協定跟閘道裝置,安全模型就不能只算「雲端帳號有沒有被盜」,還得算進「有沒有人能靠近你家、能不能拆開裝置」這個維度。門鈴、HomeBase 這類產品的攻擊面,很大一部分其實落在射頻層跟硬體層,而不是應用層或雲端 API。整篇文章從頭到尾都沒有動用到 Eufy 的雲端服務或帳號系統,即使廠商加強了雲端那一側的防護,這條走實體層跟本地協定的攻擊鏈依然成立。作者自己也用「暫時」(-yet-)這個字眼形容拆不了 HomeBase 這件事,這也留下一個開放的可能:如果之後找到繞過缺腳測試點的辦法,這條攻擊鏈很可能還能再往前推一段,直接從 HomeBase 本身下手。
這不是 Eufy 第一次被這樣拆穿。作者提到「There was a USENIX talk about this same ecosystem called Reverse Engineering the Eufy Ecosystem: A Deep Dive into Security Vulnerabilities and Proprietary Protocols」,而那份研究本身是 2023 年做的。作者也承認廠商並非什麼都沒改——「Eufy changed a lot of stuff (for example the PSK is not 8 bytes anymore, we will talk about it later)」,文中沒有給出新的 PSK 長度,只確認不再是 8 bytes。緊接著作者補了一句:「something it still true: the hidden network is called OCEAN_XXXXXX」——這句「還是一樣」只涵蓋隱藏網路的命名規則。金鑰推導寫死在韌體常數上、隱藏網路缺乏分段隔離,是這篇文章自己新挖出來的發現,2023 年那份研究並沒有觸及,也就談不上「同一波修補涵蓋到」與否。
So what:如果你家裝了 Eufy 門鈴,該記住的是這個模式本身:如果金鑰的推導真的只靠韌體裡寫死的常數,同一套邏輯就可能套用在同一型號的其他裝置上——儘管作者也只在一台門鈴上驗證過;而一個沒有跟家用網路做隔離的隱藏 Wi-Fi,一旦被連上,就不只是那一台門鈴的問題。