:突破現(xiàn)代樹莓派安全啟動)
干硬件安全這行的人遲早會碰上一個繞不開的話題故障注入。這幾年我拿現(xiàn)代樹莓派Raspberry PiRPi做過不少安全研究Voltage Fault Injection電壓故障注入是我個人認為最適合入門也最容易出成果的一個方向。因為現(xiàn)代RPi已經(jīng)不再是那個“裸奔”的開發(fā)板了——安全啟動、OTP熔絲、獨立安全處理器全都有常規(guī)firmware改動、啟動日志抓拍這些辦法基本都不好使想要繞過去就得回到物理層面跟芯片硬碰硬。這篇文章我想完整講一遍怎么用電壓故障注入對一個現(xiàn)代樹莓派單板計算機SBC做安全測試。主題就是給供電線上“砸”一個精確的毛刺讓CPU在特定時刻算錯一步從而繞過簽名校驗或安全啟動的邏輯。這個過程聽起來像玄學但只要你理解了目標、參數(shù)和時間窗口它其實是一套非常工程化的流程完全可以穩(wěn)定復現(xiàn)。我會把環(huán)境搭建、參數(shù)掃描、串口檢測、問題排查全部分享出來。適合正在做IoT安全研究、產(chǎn)品安全評估或者單純想拿RPi練手的人看一眼看完至少能少踩我踩過的八成坑。1. 為什么是電壓故障注入為什么是現(xiàn)代RPi1.1 故障注入到底在攻擊什么故障注入不是什么新鮮的“黑客大招”本質(zhì)是利用芯片在物理極限條件下會產(chǎn)生非預期行為的特性去干擾程序執(zhí)行流。比如一個if判斷條件成立才跳轉(zhuǎn)執(zhí)行關(guān)鍵代碼如果我在CPU執(zhí)行比較指令的瞬間讓電壓驟降幾十納秒比較單元可能出現(xiàn)“兩位輸出同時為1”的電路混亂導致條件被錯誤判斷原本不該走的路徑就這么走了過去。電壓故障注入就是在供電引腳上人為制造一個極短的高精度電壓跌落或毛刺讓芯片在執(zhí)行特定指令時“算錯一次”。跟電磁故障注入EMFI、時鐘毛刺Clock Glitch相比電壓注入最大的優(yōu)勢有三個不需要把探頭懟到芯片正面的精密位置只碰供電平面就行可重復性好只要電路穩(wěn)定同一參數(shù)多次注入成功率很可觀成本低一兩百塊錢的元件就能搭出一臺能用的注入器。當然它也有明顯的短板毛刺會順著電源平面擴散影響的是整個核心域精度不如聚焦的EMFI加上現(xiàn)代SoC內(nèi)部大量使用降壓穩(wěn)壓器DVFS、LDO外部電壓毛刺到了核心內(nèi)部可能被“削掉”不少高頻分量所以參數(shù)掃描范圍必須覆蓋得很寬。1.2 現(xiàn)代RPi為什么值得折騰“Modern RPi”我指的是Raspberry Pi 4B和5這兩個平臺它們已經(jīng)配備了完整的啟動鏈安全措施。樹莓派的啟動流程從BootROM開始BootROM只信任用密鑰簽名的固件下一級bootloader會校驗固件簽名如果校驗失敗就直接停在那里。這個特性讓入侵變得非常難你不能隨手換一個SD卡里的bootcode.bin就實現(xiàn)代碼執(zhí)行所有代碼路徑都被簽名約束住了。這對攻擊者來說是好事對安全研究者也是好事。意味著你有一個明確、可測的“攻擊目標點”簽名校驗代碼的某個時間窗口。只要在錯誤時刻注入一次毛刺讓CPU跳過“校驗失敗”分支就有機會從SD卡或USB設備加載未簽名固件拿到Next Stage代碼執(zhí)行。而且樹莓派官方文檔對BootROM、UART日志的公開程度相當好這省去了大量逆向工作板子上也能很方便地引出UART引腳觀察注入前后的狀態(tài)。還有人問為什么不直接燒EEPROM、改eFuse這種永久性改動需要專門的硬件工具而且改一次就烙在芯片里容易把板子搞廢。電壓故障注入是瞬時的、可重復的每一次“瞎試”的成本極低非常適合做啟動鏈安全評估。2. 先想清楚目標你要攻擊的是哪個環(huán)節(jié)2.1 從Reset到Kernel的整條信任鏈拿樹莓派4B舉例上電后大致經(jīng)過這么幾個階段BootROM片上ROM不可改BootROM加載bootcode.bin從EEPROM或SD卡二級bootloader也就是EEPROM里的固件初始化DDR、加載start4.elfstart4.elf引導device tree、kernel安全啟動模式下每一步的關(guān)鍵鏡像都要用PKG密鑰做RSA簽名校驗老版本樹莓派的BootROM階段比較多、鏈上可物理干擾的面也大但到了4B/5很多關(guān)鍵步驟已經(jīng)被硬件安全模塊接管軟件層能動的空間很小。所以“硬件攻擊者”真正能開槍的位置基本就在簽名校驗邏輯正在執(zhí)行的那幾百納秒里。我一開始犯的錯誤是以為隨便在啟動的某個階段打毛刺都行。實際不是。你要是不確定目標窗口可以先把UART接到板子上觀察正常啟動時每個階段打印的耗時。比如啟動日志從“Raspberry Pi Boot”到“Secure Boot: enabled”再到“DMA32”之類的提示往往就隔了幾十到幾百毫秒。這時候毛刺注入的時間范圍就好框定了大概在BootROM加載bootcode.bin、二級bootloader計算簽名的區(qū)間。2.2 判斷成功條件什么算“打穿了”攻擊成不成功不能靠猜。通常兩類信號能判斷UART日志出現(xiàn)異常比如簽名失敗的報錯沒有出現(xiàn)、直接跳到下一個階段、出現(xiàn)未預期的內(nèi)存dump。板子進入一個“非正常但可控”的狀態(tài)比如start4.elf加載了帶有明顯未簽名標識的custom鏡像或者kernel啟動后我們能拿到root shell且這個root shell是通過未簽名固件進入的。我用的是更粗暴的方式準備一個自定義修改過的start4.elf或bootcode.bin只是加了一個可見的UART打印如果注入成功日志里會出現(xiàn)自定義打印。這樣就不需要在內(nèi)存里翻找“有沒有執(zhí)行到shell”靠一條日志就能判斷注入點是否命中。另外RPi的secure boot模式開啟之后UART默認打印量大減有些版本連啟動細節(jié)都不顯示。這時候可以把HDMI接上接一個攝像頭對著屏幕或者用邏輯分析儀監(jiān)控某個GPIO翻轉(zhuǎn)來輔助判斷。邏輯分析儀其實是最可靠的我后續(xù)的復現(xiàn)實驗基本都用GPIO輸出作為“成功標志”。2.3 為什么有時候需要重復上萬次電壓故障注入是概率性事件。即便你確定了時間窗口、電壓寬度、偏移平均成功率也可能只有百分之幾。所以工業(yè)界做這類測試時都不是搞一槍命中的而是“以量取勝”控制器比如ESP32或RP2040循環(huán)給RPi斷電、上電在上電后的某個可配置延遲處注入毛刺每次注入前記錄注入?yún)?shù)注入后等待UART輸出如果UART沒有輸出期望結(jié)果則再循環(huán)。這個自動化循環(huán)很重要。手動按鈕注入一兩次玩玩行真要做參數(shù)掃描一晚上跑幾十萬次也不稀奇。我見過有些團隊拿FPGA專門做故障注入生成器但RPi平臺開發(fā)時用便宜的MCU就能起步完全夠用。3. 動手搭建必需的硬件和關(guān)鍵參數(shù)原理3.1 硬件清單和連接方式下面是我現(xiàn)在一直在用的方案成本不高但效果穩(wěn)定硬件用途備注樹莓派4B或5被測試目標4B的供電點比5更好接ESP32開發(fā)板觸發(fā)控制、自動復位與注入口也可以換成RP2040、STM32邏輯分析儀或USB串口模塊采集UART日志建議買支持3.3V電平的MOSFET大功率開關(guān)如IRF540N或NCE6005產(chǎn)生電壓毛刺導通電流、開關(guān)速度要足夠直流電源5V/3A以上給RPi供電不要用普通手機充電器紋波太大采樣電阻若干、電容若干濾波和電流檢測一般用1Ω、10Ω、100μF組合連接方式是這樣的ESP32通過GPIO控制MOSFET柵極MOSFET漏極接RPi的5V供電點或3.3V核心供電測試點源極接地。ESP32在RPi上電后延遲指定時間輸出一個寬度可控的“低電平脈沖”到柵極MOSFET瞬間導通把電源對地短一下形成一個電壓毛刺。有一點要特別注意直接短接電源到地短路電流很大MOSFET和供電網(wǎng)絡發(fā)熱非常快。所以占空比必須控制好毛刺寬度通常只有幾百納秒到幾微秒不要長時間導通否則板子會燒。3.2 最關(guān)鍵的三個參數(shù)寬度、偏移、電壓幅度參數(shù)才是電壓故障注入的靈魂。就三個毛刺寬度、觸發(fā)偏移、毛刺電壓幅度。毛刺寬度Glitch Width從MOSFET導通到關(guān)斷的時間。對于RPi這樣跑在1.5GHz量級的SoC核心指令周期在幾百皮秒到幾納秒。一個幾十納秒的毛刺可能影響幾十個時鐘周期屬于“粗粒度”干擾幾百納秒的毛刺則可能直接導致CPU掉電重啟。所以寬度是要從小往大掃的我常從10ns開始掃每個步進5ns或10ns一直到1μs。觸發(fā)偏移Offset上電開始到毛刺注入之間的延遲。這個時間差要精確因為它決定毛刺落在啟動鏈的哪一步。偏移掃描范圍通常是啟動總時長的前十分之一比如整體啟動到kernel約2秒那偏移掃0~300ms就夠了重點看BootROM到bootcode校驗那一段。毛刺電壓幅度Glitch Voltage這個定義要分清楚。對MOSFET開關(guān)短路到地的情況不是“給一個超壓”而是“把電壓拉低到一個中間值”。實踐方法是給MOSFET源極串聯(lián)一個可調(diào)的并聯(lián)電阻或穩(wěn)壓管限制短路時的最低電壓。如果直接短路到底毛刺就是一次深度掉電很多情況下不是最理想的狀態(tài)很多時候只需要把3.3V拉到1.5~2.5V區(qū)間干擾邏輯判斷但不至于復位成功率反而更高。給一個我當時掃描范圍的參考表參數(shù)掃描范圍步進說明Glitch Width10ns ~ 5μs5~50ns對數(shù)步進先粗掃命中后再細掃Offset0 ~ 500ms0.1ms根據(jù)UART日志估算Voltage Level0.8V ~ 2.8V0.1V用可調(diào)負載限制最低電壓Injection Count每次參數(shù)組合跑2000~5000次—統(tǒng)計成功率3.3 為什么“毛刺寬度/偏移/電壓”要一起掃單獨掃某一個參數(shù)成功率往往低得可憐。因為故障注入本質(zhì)是三維空間里的一個“故障成功區(qū)域”三個參數(shù)必須同時落在某個范圍里才行。比如偏移對了寬度太短則毛刺不產(chǎn)生作用寬度對了偏移跑偏了則毛刺打在別的指令上寬度偏移都對了電壓拉得太深直接復位板子重啟當然也不會成功。所以我的做法是分階段第一輪粗掃描鎖定一個“成功率有明顯凸起”的區(qū)間第二輪在凸起附近做三維細掃每次固定電壓掃寬度×偏移二維矩陣第三輪把命中率最高的那個參數(shù)組合反復驗證確認不是偶然。這里有個容易被忽略的參數(shù)觸發(fā)基準。RPi上電后如果直接用“ESP32給RPi上電”作為時間零點它和BootROM開始執(zhí)行之間的延遲抖動可能高達幾十微秒。為了降低抖動我會從UART的TXD引腳抓一個“BootROM啟動打印”信號作為觸發(fā)基準這樣從該信號到目標校驗點的時間抖動就可以壓縮到微秒級以內(nèi)。實現(xiàn)上可以給ESP32加一個比較器電路檢測UART引腳的起始位再延時指定的偏移。早期沒有這個基準時成功率經(jīng)常忽高忽低就是因為時間基準本身在抖。3.4 穩(wěn)定供電是“能復現(xiàn)”的前提有句話我說了無數(shù)次故障注入實驗室里最大的敵人不是芯片是電源紋波。如果供電本身就有一兩百毫伏的紋波那你掃出來的“成功點”很可能只是紋波疊加后的巧合換個環(huán)境就完全失效。建議做三件事用線性穩(wěn)壓電源別用廉價開關(guān)電源。起碼是實驗室用的或高品質(zhì)的5V/5A線性電源。在RPi供電入口并聯(lián)足夠大的濾波電容100μF鉭電容10μF陶瓷電容0.1μF陶瓷電容組合但注意電容太大會吃掉毛刺所以要留一個專用注入點注入點附近不要放太大的濾波電容。所有測試線盡量短、粗接地回路要單點接地。我用的是銅箔膠帶鋪地盡可能降低回路電感。毛刺寬度到百納秒級別時回路電感稍微大一點毛刺形狀就畸形了成功率直接斷崖。4. 完整實操流程跑一次自動化電壓注入掃描4.1 第一步準備“可觀測目標固件”我先準備了一個修改過的bootcode.bin樹莓派4B用在里面加了一行串口輸出“CUSTOM_PAYLOAD_LOADED”。因為現(xiàn)代RPi開啟了secure boot默認不會加載所有未簽名固件所以如果我的注入成功跳過了校驗這個自定義bootcode就會被加載執(zhí)行UART上就會打印這行內(nèi)容。如果沒有這個打印說明要么沒注入成功要么鏡像被卡在更早的校驗階段。把“觀察點”做成一行固定字符串日志比純靠“它能不能繼續(xù)啟動”判斷方便太多因為“繼續(xù)啟動”可能是意外復位不一定就是故障注入導致的。4.2 第二步為ESP32寫好注入腳本這里我放一個ESP32的簡化偽代碼實際工程里加上錯誤處理和日志統(tǒng)計#define TRIGGER_GPIO 25 // MOSFET柵極控制 #define RESET_GPIO 26 // 控制RPi復位信號 #define UART_RX 34 // 讀取RPi UART TX struct glitch_param { long width_ns; long offset_us; float voltage; }; void inject_glitch(glitch_param p) { // 設置MOSFET柵極拉低電平模擬導通到地 gpio_set_level(TRIGGER_GPIO, 0); esp_rom_delay_us(p.offset_us); // 精確控制寬度這里用硬件定時器或esp_timer hw_timer_start(p.width_ns); } void scan_loop() { for (float v 0.8; v 2.8; v 0.1) { set_load_voltage(v); for (long off 0; off 500000; off 100) { for (long w 10; w 1000; w 10) { reset_rpi(); delay(50); // 等RPi穩(wěn)定上電 inject_glitch({w, off, v}); wait_for_uart(500); // 預期500ms內(nèi)出現(xiàn)目標日志 if (uart_buffer_contains(CUSTOM_PAYLOAD_LOADED)) { log_success(w, off, v); } } } } }不要一上來就跑完整三維掃描那樣可能要跑幾天。先用等寬、等電壓的二維粗掃把范圍往可能區(qū)域壓然后再細掃。我通常的做法是先固定電壓1.8V常用中間值掃offset×width圈出成功率最高的幾個點固定這幾個點里成功率最高的offset和width掃電壓觀察最低電壓區(qū)間縮小電壓范圍再回掃一遍offset×width找到更精確的窗口。4.3 第三步用UART和邏輯分析儀判斷命中UART的3.3V電平可以直接用邏輯分析儀采。我見過不少人在這一步翻車RPi的UART默認可能是關(guān)閉的需要在config.txt里開啟uart有些版本把miniuart和PL011 UART搞混引腳接錯就沒輸出。我建議先從確認正常啟動日志開始接到BootROM打印后再跑攻擊。看到一個穩(wěn)定的“Raspberry Pi Boot”或類似輸出說明串口通路是好的這時候再給測試注入器通電。千萬別一上來就邊注入邊查串口出了問題分不清是注入的影響還是串口本身沒接好。邏輯分析儀采樣率至少25MHz否則幾十納秒寬的毛刺根本抓不到。我常用的做法是在MOSFET柵極和RPi VDD上都掛探頭一個通道看注入時刻一個通道看電源電壓跌落波形另一個通道看UART TX。這樣每次攻擊記錄下來的不止“成功/失敗”還有真實的毛刺波形做復現(xiàn)時能快速排查是不是波形畸變了。4.4 第四步從“一次成功”到“穩(wěn)定復現(xiàn)”第一次掃出成功點后別高興太早故障注入的成功率不是100%。通常要連續(xù)注入20~50次看成功率是否穩(wěn)定在10%以上才算真正找到了有效的參數(shù)點。我在RPi上最常遇到的情況是掃到一個點連續(xù)成功三次然后在同一參數(shù)下突然連續(xù)失敗50次。這種“偽命中”多半是參數(shù)選在故障區(qū)域的邊緣抖動稍大就掉出去了。解決辦法是往回找成功率曲線上的“平臺區(qū)”而不是峰值點。峰值看著高但周圍梯度大稍微偏移就失敗平臺區(qū)成功率可能只有20%但很寬抗抖動能力強更適合出成果和寫論文。成功復現(xiàn)以后我建議把每個成功參數(shù)的完整環(huán)境記錄寫清楚包括RPi型號、固件版本、eFuse狀態(tài)重定時間boot code version電源型號和輸出電壓MOS管型號、柵極驅(qū)動電阻注入點位置、外部電容值環(huán)境溫度影響不大但影響復現(xiàn)最好記錄。這些細節(jié)能讓你三個月后看到成果的時候還知道自己到底做了什么。5. 常見問題與排查技巧實錄5.1 問題速查表我在跑故障注入時踩過不少坑下面這些比較典型給一張速查表供參考現(xiàn)象常見原因排查辦法注入后板子總重啟但沒有目標日志毛刺深度過大觸發(fā)復位降低毛刺深度調(diào)高限制電壓縮窄毛刺寬度某組參數(shù)之前命中后來再也不命中供電或環(huán)境變化檢查電源是否漂移MOSFET是否損壞重新校準觸發(fā)基準UART完全無輸出串口沒開或引腳接錯先跑一次正常啟動確認UART鏈路檢查config.txt成功率忽高忽低觸發(fā)基準抖動大用UART起始位做觸發(fā)不要用外部上電信號特定偏移區(qū)間成功率凸起但立刻消失可能是目標窗口極窄把該區(qū)間步進調(diào)到10μs配合多電壓細掃毛刺波形嚴重失真回路電感和電容效應縮短測試線、單點接地、在注入點附近用盡量小的電容注入器發(fā)熱嚴重MOS管導通時間過長降低單次注入寬度調(diào)大循環(huán)間隔5.2 怎么排查“成功率莫名下降”如果你有一個參數(shù)點昨天還好好的今天怎么試都不出來最常見的兇手就是供電。電源的熱漂移或負載變化會讓實際電壓偏離0.1V以上而故障注入的成功區(qū)對電壓非常敏感可能就因為這0.1V整個窗口消失了。所以我每次長時間掃描前都會做一次“參考點測試”固定一組已知成功率高的參數(shù)連續(xù)注入50次確認成功率在正常范圍再開始掃。如果這個參考點都過了那掃描環(huán)境就是好的如果沒過先別急著掃去找環(huán)境原因。另一個隱蔽的原因MOSFET的柵極驅(qū)動波形劣化。ESP32的GPIO驅(qū)動能力有限如果MOSFET柵極電容比較大上升沿可能拖幾微秒導致實際毛刺形狀和預設值差很遠。解決方法是在柵極加一個專門的門極驅(qū)動芯片如TC4420或者換一個Qg更小的MOSFET。5.3 判斷“真的繞過校驗”還是“只是運氣好”在secure boot開啟的情況下如果你注入成功后直接跳過了校驗那個自定義bootcode或elf會被加載UART會看到你的標記。但如果只是把CPU打進了異常處理狀態(tài)它也可能繼續(xù)往下執(zhí)行一些指令有時候也能打印出一些奇怪的日志容易被誤判為成功。我的判斷標準是成功標志必須明確且一致比如每次看到同一串“CUSTOM_PAYLOAD_LOADED”而不是隨機亂碼必須能連續(xù)復現(xiàn)至少三次獨立注入中成功兩次成功時要有可觀察的波形邏輯分析儀上能看到毛刺出現(xiàn)在目標窗口有條件的話驗證一下注入成功后系統(tǒng)是否真的執(zhí)行了自定義代碼而不是只打印了一行隨機字符。如果不滿足這些我只能認為“不知道發(fā)生了什么”而不會寫進結(jié)論。6. 從結(jié)果反推防護思路6.1 現(xiàn)代系統(tǒng)怎么防電壓故障注入你拿RPi做攻擊實驗看完了成功點其實反過來也是對固件設計的一種壓力測試。現(xiàn)代SoC應對故障注入的思路主要有這幾層硬件層加固供電監(jiān)控器檢測電壓跌落一旦低于閾值立即復位縮小攻擊窗口。缺點是窗口再怎么縮也不是零只要毛刺落在那段區(qū)間內(nèi)還有機會。軟件層冗余校驗關(guān)鍵判斷不只看一次而是多次比對。比如簽名校驗失敗分支被跳過一次流程里還會在別處再校驗一次哈希或RSA簽名。增加一次校驗攻擊者就要在極短時間窗口內(nèi)連續(xù)打中兩個漏洞難度指數(shù)級上升。時間隨機化啟動鏈每隔一段時間加一個隨機延遲讓攻擊者無法穩(wěn)定預測目標指令的執(zhí)行時刻。對固定偏移的掃描來說這非常克制。多源信號交叉驗證比如安全子系統(tǒng)獨立監(jiān)控主CPU狀態(tài)一旦發(fā)現(xiàn)主CPU存在未授權(quán)跳轉(zhuǎn)直接拉高復位信號。錯誤計數(shù)器檢測到異常復位或異常跳轉(zhuǎn)時把計數(shù)器累加超過閾值就進入鎖定或擦除密鑰狀態(tài)。這樣想靠“百萬次掃描”碰運氣也變得不現(xiàn)實。樹莓派這套Secure Boot雖然還不是最頂級的硬件安全方案但已經(jīng)明顯比以前好打很多了我周圍不少研究團隊都在上面做過類似的測試。隨著SBC這類單板機在邊緣設備里的地位越來越高它們被針對的頻率也會明顯上升這就是大家常說的“SBC暴雷”背后的現(xiàn)實不是SBC本身不安全而是過去沒人認真打它現(xiàn)在開始被關(guān)注了。6.2 對我們自己的項目有什么啟發(fā)如果你正在做產(chǎn)品固件或硬件設計我建議至少做三件事在啟動鏈的每一個關(guān)鍵校驗點設計至少二重校驗而且校驗之間的代碼路徑不要緊挨著隔開一段再驗證一次啟用硬件隨機延遲如果芯片支持打散固定的啟動時間線把“多次異常復位”視為安全事件觸發(fā)密鑰銷毀或進入恢復模式而不是簡單重啟。從攻擊者的角度看一次成功的電壓故障注入往往只需要找到一處薄弱校驗從防御者角度看你要做的不是讓攻擊變得不可能而是讓攻擊成本高到對方不愿意嘗試。多一層校驗、多一次延遲、多一個監(jiān)控都可能讓攻擊成功率從幾十分之一降到幾十萬分之一。6.3 最后再分享一點實操體會這套流程我在樹莓派4B和5上都跑通了最大的感受故障注入的成功率雖然低但它是“可測量、可控制、可復現(xiàn)”的遠沒有很多文章寫得那么玄。關(guān)鍵就是把每個參數(shù)當成科學實驗變量去對待一次只改一個變量并完整記錄結(jié)果。另外一個小技巧如果你和我一樣不是每次都能拿到貴的商用故障注入設備先用ESP32MOS管搭建的方案足夠起步。等你在RPi上跑通了整套掃描邏輯再去玩更復雜的EMFI、激光注入那只是換了一個“激勵源”而已思路完全是相通的。硬件安全研究的樂趣恰恰在于它逼著你去理解芯片底層的執(zhí)行細節(jié)而不是在軟件層面堆工具。電壓故障注入只是一個入口但它能讓你對“信任邊界”有完全不一樣的理解。