Realtek rtl8821ae 芯片
wifi 芯片位于 m.2 模塊上,可以筆記本電腦中被更換。它具有 2.4GHz 和 5GHz wifi 支持等功能,與 wifi 芯片的通信通過 PCIe 進行。
要找到這些固件很容易,它在 Linux 上啟動時從 /lib/firmware/rtlwifi 加載。實際上有兩套固件:一套用于正常使用(rtl8821aefw_29.bin),另一套稍小一些用于遠程喚醒 (rtl8821aefw_wowlan.bin),這是一種通過 wifi 喚醒設備的技術,英文名為Wake-on-WLAN)。…_29.bin 在 ip link set dev wlan0 up 上加載,…_wowlan.bin 在 ip link set dev wlan0 down 上加載。它們不是永久固件,而只是加載到芯片的 RAM 中。
該芯片還有一個由realtek編寫的Linux內核中的上行wifi驅動程序,這并不能說明它的質量,因為它仍然是由realtek編寫的驅動程序。
在同一芯片上還有一個藍牙芯片,可以通過 USB 2.0 進行通話。內核中似乎實現了一些 BT-Coexistence 協議,以確保 wifi 和 BT 部分不會干擾。
與rtl8821ae的所有通信都是內存映射的:有一個4K大小的配置空間,可以寫入和讀取。此外,還有一個64K的tx緩沖區和一個64K的rx緩沖區用于發送和接收數據包。
基本固件結構
查看固件,它顯然是基于 8051 的。但是,它不會在 0x0000 處加載。當直接在固件映像上運行時,at51 base 返回 0x3fe0。結果發現有一個 0x20 字節的標頭,因此固件本身在 0x4000 處加載。
從 0x4000 開始的代碼路徑也通向 main 函數:在典型的 Keil 編譯器方式中,它跳轉到 ?C_START 函數,該函數用于初始化由 Keil C(X)51 編譯器發出的用于初始化靜態變量的靜態變量,之后它跳轉到 MAIN 函數。
MAIN 函數有點奇怪:它設置一些內存,然后將一個地址壓入堆棧,設置一個計時器并返回到它剛剛壓入堆棧的地址。事實證明,realtek 正在使用 RTX51 tiny,這是一個非常小的實時內核,用于管理任務和信號。內核基本上會跟蹤任務狀態并在切換任務堆棧時重新定位它們。at51 libfind 會找出各種 Keil 庫的位置,作為其中的一部分,它還會找到 rtx51 內核函數,如 OS_SWITH_TASK 或 _ISR_SEND_SIGNAL。
普通固件有兩個任務(另外一個只用于初始化):一個只在特定信號時被激活,另一個在無限循環中運行。
雖然沒有公開數據表記錄 8051 端的 I/O 寄存器,但大部分內容很容易找到:Linux 驅動程序從主機端定義配置空間中的寄存器。通過查看 8051 未訪問的寄存器,可以看到當映射到 XDATA 偏移量 0x0 時,這些寄存器與驅動程序源中未定義的寄存器類似。然后很容易對定義寄存器的 reg.h 文件進行一些處理,以將所有名稱導入 ghidra。
轉儲Mask ROM
MASK ROM,是制造商為了要大量生產,事先制作一顆有原始數據的ROM或EPROM當作樣本,然后再大量生產與樣本一樣的 ROM。固件在 0x4000 處加載,但它仍然會調用 0x4000 以下的函數,這意味著芯片上有一個永久Mask ROM,它也負責將固件下載到芯片上。不知道這些功能會使逆向工程有點困難,因此獲得該固件會很好。
最簡單的方法是修改固件并將字節從 CODE 空間復制到配置空間寄存器中,因為我們已經知道這些是如何映射的。但是有一個障礙:固件的最后兩個字節似乎是一個校驗和,這意味著我們需要知道校驗和是如何計算的。然而,校驗和將在Mask ROM 內計算,所以我們不能只看固件來了解它是如何完成的。幸運的是,delsum 很快就表明它只是一個 16 位 XOR 校驗和,相當于 poly=0x0001 的 CRC。
從用戶空間讀取和寫入配置空間通常是不可能的,所以我需要重新編譯內核,以添加對/dev/ mems進行此操作的支持。我選擇通過在VM中執行內核并使用VFIO將wifi卡映射到其中來完成此操作,因為重新編譯傳播服務器內核對我來說時間太長,并且標準配置幾乎只適用于 VM。另外,我知道 modconfig 可用,但是 VM 方法在標準配置下仍然可以正常工作,并且不會花費太多時間。
內核與芯片上的固件通信主要使用的是所謂的H2C,它基本上把一些簡短的(比如8字節)命令放入配置空間,并讓固件讀取。通過在ghidra中查看從固件到該點的引用,很容易找到讀取該點的固件代碼。不幸的是,當我自己處理這些事務時,我無法得到固件的響應。
于是我決定硬編碼讀取到固件的地址,修改 0x4000 處的跳轉,將字節從硬編碼地址復制到未使用的配置空間寄存器 (REG_ROM_VERSION),然后繼續執行主程序。對于每個讀取地址,固件都會被修改,內核模塊會重新加載使用新地址修改的固件。這種方法的讀取速度大約為每秒 3 個字節。雖然花了幾個小時,但可能仍然比找出固件不響應 H2C 命令的原因要快。
藍牙固件
有一件事我一開始沒有意識到,但回顧起來似乎很明顯,那就是芯片還有一個單獨的藍牙固件(在/lib/firmware/rtlbt中)。這一次它不是基于8051的,但是我不能輕易地找出它的架構是什么。為了解決這個問題,我制作了一個工具,比較不同的版本的固件通過比較他們使用成對對齊,以便在不同的固件版本之間的相同但在不同地址的部分仍然顯示并排。
需要注意的是,在許多地方,都插入了字節00 65,在此之后,兩個地址都以4個字節對齊,這意味著它被用作一種填充。當然,用于填充的完美指令應該是NOP。所以我進行了搜索,但“0065 NOP”并沒有真正產生任何結果,但是搜索“6500 NOP”(相反的字節序)確實產生了一些有趣的結果,表明 mips16e(mips 的拇指擴展)使用該操作碼進行 NOP,事實上,這確實正確地分解了固件,使之變得有意義。
然而,我對藍牙固件本身不是那么感興趣,因為它不是基于8051,所以我繼續我的wifi固件的分析。首先,我并沒有真正擁有任何藍牙設備。
驅動程序如何發送數據包
wifi的工作方式是在802.11規范中規定的。打開它,你會看到3500頁密密麻麻的縮寫詞。
802.11 幀具有以太網幀的某種作用,但除了承載數據之外,還有各種各樣的控制幀來執行某些操作,例如與 AP 關聯、與 AP 進行身份驗證、電源管理等。數據幀(以及一些控制幀)也可以使用各種密碼和協議進行加密。
在加密幀的情況下,還有一些額外的數據,如序列號,其目的是重放保護,這也意味著 802.11 數據幀可以根據是否加密而具有不同的大小。加密通常由 rtl8821ae 本身完成,驅動程序只是將未加密的內容放入加密通常所在的幀中,然后芯片使用硬件對其進行加密。
在 802.11 數據幀內部有一個 LLC 標頭,在大多數情況下,它僅用于告知內容的 EtherType。
發送數據包時,驅動程序將整個幀放入 tx 緩沖區,在其前面有一個 40 字節的標頭。標頭告訴 wifi 硬件幀的大小、是否使用加密、發送的速率和類似的東西。802.11 幀內還有一個持續時間字段,指示數據包發送所需的時間,這也是由硬件計算的。
硬件有 8 個用于不同目的的隊列,可以在標頭中指定,一個用于信標幀,一個 MGQ(管理隊列?)無論隊列如何,數據包都以循環方式在 256 字節邊界上放入 tx 緩沖區。
歡迎使用 Realtek RealWoW Tech
在 Linux 驅動程序中,有一個名為 rtl8821ae_set_fw_rsvdpagepkt 的函數,它會在加載新固件時調用。它將信標幀等幀和包含 ARP 響應的數據幀加載到 tx 緩沖區的高端,驅動程序將其視為保留區域,然后這些幀的偏移量通過一些H2C命令發送到固件。
這顯然也是出于wowlan的目的,這樣固件就可以在主機休眠時響應ARP請求,這樣數據包就可以通過IP發送到wifi芯片。wowlan固件負責的另一件事是GTK握手,當設備離開或加入網絡時,AP 向所有設備發送一個新的組加密密鑰,用于廣播/多播目的。
要了解固件如何處理這些,可以查看保存這些偏移量的 H2C 命令。它將它們寫入一些 XDATA 位置,因此通過查看引用這些位置的內容,可以找出固件的哪個部分發送了幀。
結果是在XDATA 0xfc00-0xfcff的tx緩沖區中有一個256字節的窗口,地址的更高的8位可以用0xfd10的XDATA寄存器設置。類似地,可以從XDATA 0xfb00-0xfbff訪問rx緩沖區,0xfd11有更高的8位。
發送數據包時,很難弄清楚具體是什么發送了數據包,因為有很多事情要做。固件在 tx 標頭中設置一些字段,還在 802.11 標頭中設置一些位,計算是否加密以找出內容的偏移量,對于 ARP 數據包,它還檢查諸如 EtherType 之類的內容并進行響應。當然,解析數據包時會出現更高的復雜性。發送數據包的關鍵部分似乎是將REG_TXPKTBUF_MGQ_BDNY設置為txbuffer地址的高8位。注意,數據包是256字節對齊的,然后向REG_CPU_MGQ_INFORMATION + 3寫入0x20。
查看wowlan固件,可以發現還有一個H2C命令,它似乎為額外的數據包設置了更多配置。在解析代碼中,還有一個地方是在幀數據內容的開始處檢查字節0x45,這就是IPv4數據包的開始。解析代碼然后檢查由上述 H2C 命令給出的目標地址和 UDP 端口,并根據應該放入 tx 緩沖區的模式檢查數據。如果一切都匹配,則將字節 0x30 作為 wowlan 喚醒原因并喚醒主機。
在Linux驅動程序源代碼中,這個原因被命名為FW_WOW_V2_REALWOW_V2_WAKEUPPKT(并沒有真正處理)。那么這個所謂的 RealWoW 是什么呢?
不幸的是,Linux 驅動程序沒有實現 Realtek RealWoW Tech,并且不清楚如何從 realtek 獲得新 ID,所以我放棄了對它的嘗試。
但是對固件的分析表明它可能通過定期向服務器發送 UDP 數據包(由驅動程序提供)來工作,服務器以某種 ACK 響應。當在網站中輸入正確的 ID 后,服務器會向設備發送一個喚醒 UDP 數據包。可以進行此操作,由于定期發送UDP數據包,所以連接已經打開,wifi固件然后通過PCIe喚醒設備。
一個純粹基于 8051 的鍵盤記錄器
既然固件已經解決了一些問題,那么我們自己做些改變怎么樣?計劃是讓 EC 獲取筆記本電腦鍵盤的按鍵,并將它們發送到 wifi 芯片,然后由 wifi 芯片將它們發送到網絡。
DMA 不能從 EC 工作,因為它位于 LPC 總線后面,這意味著它將依賴 ISA DMA,這非常糟糕,并且只能訪問最低 的16MB 的內存。因此,無法通過 DMA 進行通信。
在上一篇文章中,我分析了EC固件,讓我們看一下上一篇文章中的圖表:
現在我寫到 wifi m.2 芯片上沒有連接 EC_TX 和 EC_RX 跟蹤(EC 和 rtl8821ae 之間的右下方)。但請注意,EC_RX 跟蹤也通過另一個引腳連接到 rfkill(bt) 線,并且在通往 CPU 的路上有一個電阻器,以便 EC 可以有效地覆蓋 CPU 而不會造成短路,可以試著用它來傳輸數據。
經過一些測試,似乎該方法也阻止了wifi的射頻能力。但事實證明,如果在使用后再次將其拉低,那就不是什么大的問題,因為主機和固件似乎只看到一些數據包被下載。
因為只有一條跡線,所以控制器必須以某種方式同步,而不是單獨的時鐘線。我決定采用 UART-ish 方法,這意味著位只是一個接一個地傳輸,它們之間有一個設定的時間間隔。
EC 已經使用了 1ms 計時器,因此我只是修補了計時器中斷以檢查 16 字節密鑰緩沖區中的新密鑰,并以 1 位/毫秒的速度通過 EC_RX 引腳發送它們。
在wifi端,執行固件時出現了問題。事實證明,wifi固件只是在短時間內停止執行以節省電量。與其嘗試讀取 realtek 驅動程序以了解電源管理的工作方式,還不如定期寫入似乎使其保持清醒的配置空間。
現在wifi固件也需要知道一些時間概念,其中有標準的 8051 外設,但它們似乎不會產生中斷,因此必須在主循環中進行輪詢。一個快速的實驗表明,它的頻率大約為6.67 MHz,這意味著8051使用了一個80MHz時鐘源,這似乎是由驅動程序源中提到的80MHz所備份的。
另一個實驗表明,更改 rfkill(bt) 行會更改 REG_GPIO_PIN_CTRL_2 的第 3 位,這樣就可以實現UART的另一端。我只是將定時器設置為每 1/3 ms 產生一次溢出,以便有一個更高的采樣率使UART正常工作。一旦傳輸了一個字節,EC會在下一個字節之前暫停一段時間。
在 EC 等待期間,wifi芯片將發送一個包含一個字節的UDP數據包到配置的IP地址和端口,這是通過未加密的wifi實現的。
雖然加密可能是可行的,但它也需要付出一些努力。序列號必須更新,如果主機也在傳輸數據包,則必須通過tx緩沖區來找到當前的數據包。由于重復的序列號,它也無法從主機發送一些數據包。固件也有可能掃描周圍未加密的wifi網絡,與它們關聯,然后通過DNS發送數據,這通常會繞過潛在的強制門戶。
在樹莓派上,我設置了一個小程序,它接受 UDP 數據包并將 PS/2 代碼轉換為 uinput 事件。這樣,鍵盤就可以有效地充當樹莓派的鍵盤了。
有趣的是,這實際上是一個鍵盤記錄器,它在運行時不會在 CPU 上運行任何代碼。只需要刷新一次EC的固件,更換wifi固件即可加載修改后的版本。