Broadcom是全球無線設備的主要供應商之一。由於這些芯片用途廣泛,因此構成了攻擊者的高價值目標,因此,在其中發現的任何漏洞都應視為帶來了很高的風險。在此博客文章中,我記錄了我在Quarkslab實習的情況,其中包括獲取,反轉和Fuzzing固件,以及發現一些新漏洞。
0x00 介紹Broadcom是全球無線設備的主要供應商之一,他們生產帶有43系列標籤的無線芯片,從智能手機到筆記本電腦,智能電視和物聯網設備,你幾乎可以在任何地方找到這些芯片。你可能會在不知道的情況下就使用了它,例如,如果你有戴爾筆記本電腦,則可能正在使用bcm43224或bcm4352卡;如果你擁有iPhone,Mac筆記本,Samsumg手機或Huawei手機等,也可能使用Broadcom WiFi芯片。
由於這些芯片用途廣泛,因此變成了攻擊者的高價值目標,因此,在其中發現的任何漏洞都應視為會帶來很高的風險。
我研究了很長一段時間的Broadcom芯片,將已知漏洞複製並移植到其他易受攻擊的設備,以學習和改進幾種常見的信息安全慣例,在此文章中,我記錄了我的研究過程,包括獲取,逆向和Fuzzing固件,以及分析發現的一些新漏洞。
0x01 關於WLAN和Linux在開始之前,讓我們看一下802.11無線標準。
1997年創建的第一個IEEE 802.11標準[1]標準化了PHY和MAC層,這是最低的兩個OSI層。
對於PHY層,選擇了兩個頻帶:紅外(IR)頻帶和微波頻帶(2.4GHz);之後,其他標準(例如802.11a [2])帶來了另一個頻率範圍(5GHz)。
MAC層使用三種類型的幀:管理,數據和控制;802.11標頭幀的幀控製字段標識任何給定幀上的類型。
管理幀由MLME(MAC子層管理實體)實體進行管理。根據處理MLME的內核的位置,我們可以得到兩種主要類型的無線芯片實現:SoftMAC(其中MLME在內核驅動程序中運行)和HardMAC(也稱為FullMAC),其中MLME在固件中嵌入在嵌入式固件中。芯片並不是像生活中看到的那麼簡單,存在一些混合實現,例如,探測響應和請求由驅動程序管理,而關聯請求和身份驗證則由芯片的固件處理。
FullMAC設備在功耗和速度方面提供了更好的性能,這就是為什麼它們在智能手機中得到大量使用,並且往往成為市場上使用最多的芯片的原因。它們的主要缺點是限制了用戶發送特定幀或將其設置為監視模式的能力,為此,將需要直接編輯芯片上運行的固件。
從Linux操作系統的角度來看,以上內容為我們提供了無線堆棧中組件的兩種主要佈局:當無線設備是SoftMAC設備時,內核將使用稱為'mac80211'的特定Linux內核模塊(LKM)。該驅動程序公開MLME API以便管理管理幀,否則內核將直接使用硬件驅動程序並將MLME處理卸載到芯片的固件中。
0x02 Broadcom 的bcm43xxx芯片Broadcom bcm43xxx系列同時具有HardMAC和SoftMAC卡。不幸的是,我們找不到所分析所有芯片的所有數據表,賽普拉斯收購Broadcom的“ IoT業務”分支後,已經發布了一些可用的數據表;但是,有些芯片同時集成了WLAN和藍牙函數,例如bcm4339或bcm4330。
分析的所有芯片都將ARM Cortex-M3或ARM Cortex-R4用作非關鍵時間操作的主要MCU,因此我們需要處理兩個類似的指令集:armv7m和armv7r。這些MCU具有一個ROM和一個RAM,其大小取決於芯片組的版本。
所有時間緊迫的操作都由D11內核的Broadcom專有處理器實現,該處理器主要負責PHY層。
這些芯片使用的固件分為兩部分:一部分寫入ROM,不能修改,另一部分由驅動程序上傳到芯片的RAM。這樣,供應商僅通過更改固件的RAM部分就可以為其芯片添加新函數或編寫更新。
FullMAC芯片非常有趣,首先如在固件代碼中實現MLME層之前所述,但是它們還提供卸載函數,例如ARP緩存,mDNS,EAPOL等。這些芯片還具有一些硬件加密模塊,可以加密和解密密碼,流量,管理密鑰等。
所有卸載函數都增加了攻擊面,為我們提供了一個不錯的研究空間。
為了與主機(應用處理器)進行通信,b43系列使用了幾種總線接口:USB,SDIO和PCIe。
在驅動程序方面,我們可以將bcm43xxx驅動程序集分為兩類:開源和專有。
開源:
马云惹不起马云b43 (reversed from proprietary wl/old SoftMAC/Linux)
马云惹不起马云brcmsmac(SoftMAC/Linux)
马云惹不起马云brcmfmac(FullMAC/Linux)
马云惹不起马云bcmdhd(FullMAC/Android)
專有:
马云惹不起马云broadcom-sta aka'wl'(SoftMAC FullMAC/Linux)
“ wl”驅動程序在諸如路由器之類的嵌入式系統上最常用。它通常也用在筆記本電腦上,而筆記本電腦的驅動程序不支持brcmfmac/brcmsmac,例如Dell XPS上的bcm4352芯片。另外,wl驅動程序使用其自己的MLME,不需要LKM'mac80211'處理管理幀,從而為攻擊者擴大了攻擊面。
Broadcom發行的版本通常稱為“混合”驅動程序,因為代碼的主要部分來自兩個已編譯的ELF(在編譯時使用的對象)。為什麼兩個?因為一個用於x86_64體系結構,另一個用於i386。這些對象保存了驅動程序的主要代碼,因此公開了許多Broadcom API的函數。
芯片的固件和wl驅動程序共享許多代碼,因此在一個中發現的漏洞也可能在另一個中存在。
0x03 獲取固件1)第一部分:RAM固件
如前所述,固件分為兩部分。最容易抓住的部分是RAM部分,該部分由驅動程序加載到RAM中,這部分包含主MCU使用的代碼和數據,以及D11內核使用的微代碼。
固件的此部分未簽名,並且使用CRC32校驗和“驗證”完整性。這導致了一些固件修改,以便添加諸如監控器模式之類的函數;例如,SEEMO Lab發布了NEXMON項目[3],這是一個了不起的框架,用於通過用C編寫補丁來修改這些固件。
在我們的研究中,我們遇到了兩種可能的RAM固件映像格式:第一個也是最常遇到的是沒有特定結構的簡單二進制blob;第二種是TRX格式,在bcm43236芯片上工作時很容易解析。
使用.bin RAM固件時,通常在文件末尾有一個字符串,用於顯示:
马云惹不起马云芯片版本
马云惹不起马云芯片用於主機進行加密狗通信的總線
马云惹不起马云固件提供的函數;p2p,TDLS等
马云惹不起马云固件版本
马云惹不起马云CRC校驗和
马云惹不起马云創建日期。
當使用的驅動程序是brmfmac或bcmdhd時,我們可以直接從主機文件系統獲取RAM固件。在Linux上,我們可以在/lib/firmware/brcm中找到它,在Android上可以在/system/vendor/firmware中找到它。
在其他情況下,它會根據我們使用的系統而有所不同:
如果使用的驅動程序是專有wl,我們可以在LKM的.data部分中找到固件的RAM部分,可以使用LIEF輕鬆提取[8]。
wl=lief.parse('wl.ko')
data=wl.get_section('.data')
forsymbolinwl.symbols:
.if'dlarray_'insymbol.name:
.print(symbol.name)
.
dlarray_4352pci
dlarray_4350pci
b4352=wl.get_symbol('dlarray_4352pci')
bcm4352_fw=data.content[b4352.value:b4352.value+b4352.size]
withopen('/tmp/bcm4352_ramfw.bin','wb')asf:
.f.write(bytes(bcm4352_fw))
.
442233
$strings/tmp/bcm4352_ramfw.bin|tail-n1
4352pci-bmac/debug-ag-nodis-aoe-ndoeVersion:6.30.223.0CRC:ff98ca92Date:Sun2013-12-1519:30:36PSTFWID01-9413fb21發布的bcm4352固件最新採用WL上的Linux驅動程序的日期2013年
2)第二部分:ROM簡介
固件的ROM部分是了解這些芯片內部的最重要的部分。
為了拿到ROM部分,我們需要知道它的映射位置。查找基址的最佳方法是讀取驅動程序的頭文件,例如在bcmdhd的頭文件/include/hndsoc.h 中;另一種替代方法是讀取Nexmon項目README,該項目根據我們的MCU型號為我們提供了其他基址,精明的讀者可能會發現這些地址不同。 Nexmon項目指定具有Cortex-M3的芯片的ROM加載為0x800000,bcmdhd的標頭顯示為0x1e000000,兩者都是正確的,似乎ROM和RAM映射了兩次。此外,知道基址可以為我們提供有關所使用的MCU的線索,例如,如果將ROM轉儲到0x000f0000,則表明該芯片正在使用ARM Cortex-R4。
3)在Android系統上獲取ROM
在Android上,我們可以使用dhdutil工具,該工具是舊wlctl實用程序的Android開源改進分支,通過使用此工具的“內存字節”函數,我們可以轉儲芯片組的RAM,在某些情況下還可以轉儲ROM。
adbshell/data/local/tmp/dhdutil-iwlan0membytes-r0x00xa0000rom.bin例如,在依賴Cortex-R4的Nexus 5中使用的bcm4339芯片上,ROM被直接轉儲。不幸的是,在較舊的bcm4330(Cortex-M3)上,此函數無效;但是,只要你可以與RAM交互,就可以Hook一個函數,該存根將把ROM逐片複製到RAM中的空區域,之後,我們可以轉儲所有ROM的分片。
4)恢復Linux系統上的ROM
在具有brcmfmac驅動程序的Linux上,我們無法直接訪問ROM。因此,我們需要找到一種直接在ROM或RAM中與芯片內存交互的方法。幸運的是,當芯片使用SDIO總線與主機進行通信時,開源brcmfmac驅動程序將公開brcmf_sdiod_ramrw函數,此函數使我們可以從主機讀取和寫入芯片組的RAM。
如果我們修改驅動程序以便在此函數周圍添加一個ioctl包裝器,則可以從一個很小的userspace實用程序讀取和寫入芯片組的RAM。
在調用brcmf_sdiod_ramrw之前,我們必須調用sdio_claim_host以便回收SDIO總線的利用率;請注意,如果該設備未連接到任何接入點,則該設備可能處於低功耗模式,並且總線可能處於空閒狀態,因此我們需要通過調用bcmf_sdio_bus_sleep和brcmf_sdio_clkctl來確保設備的總線正常運行。
intbrcmf_ioctl_entry(structnet_device*ndev,structifreq*ifr,intcmd)
{
.
sdiobk-alp_only=true;
sdio_claim_host(sdiobk-sdiodev-func[1]);
brcmf_sdio_bus_sleep(sdiobk,false,false);
brcmf_sdio_clkctl(sdiobk,CLK_AVAIL,false);
res=brcmf_sdiod_ramrw(sdiobk-sdiodev,margs-op,margs-addr,buff,margs-len);
if(res)
{
printk(KERN_DEFAULT'[!]Dumpmemfailedforaddr%08x.\n',margs-addr);
sdio_release_host(sdiobk-sdiodev-func[1]);
kfree(buff);
return(-1);
}
if(copy_to_user(margs-buffer,buff,margs-len)!=0)
printk(KERN_DEFAULT'[!]Can'tcopybuffertouserland.\n');
.
}我們需要編寫一個小程序來與用戶領域的ioctl進行交互,有了它,我們能夠讀寫設備RAM:
.
memset(margs,0,sizeof(t_broadmem));
margs.addr=strtol(ar[1],NULL,16);
margs.op=1;
if(errno==ERANGE)
prt_badarg(ar[1]);
len=strtol(ar[2],NULL,10);
if(errno==ERANGE)
prt_badarg(ar[2]);
margs.buffer=hex2byte((unsignedchar*)ar[3],len);
if((s=socket(AF_INET,SOCK_DGRAM,0))0)
return(-1);
strncpy(ifr.ifr_name,ar[0],IFNAMSIZ);
margs.len=len;
ifr.ifr_data=(char*)margs;
if(!(ret=ioctl(s,SIOCDEVPRIVATE,ifr)))
printf('[+]Writesuccesfull!\n');
else
printf('[!]Failedtowrite.\n');
close(s);
free(buf);
return(ret);
.現在我們可以讀寫芯片的RAM,我們可以通過以下方式轉儲ROM:
马云惹不起马云Hook位於RAM中並由動作X調用的函數
马云惹不起马云將ROM逐片複製到RAM中的空白區域
马云惹不起马云轉儲所有新復制的ROM片並將其串聯。
此協議與我們在芯片的MCU是Android上的Cortex-M3時使用的協議相同;但是,這次我們不得不修改驅動程序並構建自己的工具以使用新驅動程序的ioctl。
在RPI3芯片(bcm43430)上工作時,我們選擇了這種方法。
5)在特定情況下獲取ROM部分
還有許多其他可能的方案:
如果你的芯片將brcmfmac驅動程序與PCIe總線一起使用怎麼辦?如果你的芯片使用專有驅動程序“ wl”在嵌入式系統中怎麼辦?如果主機操作系統上沒有shell,該怎麼辦?或者,如果你沒有權限?等等.
在所有其他情況下,你都有幾種可能:如果可以訪問硬件,則可以尋找UART訪問,或者可以掛接wl驅動程序,在“ SFR微型解碼器”(bcm43236)上工作時,我們選擇了UART訪問。
RTE(usbrdl)v5.90(TOB)runningonBCM43235r3@20/96/96MHz.
rdl0:BroadcomUSBRemoteDownloadAdapter
ei1,ebi2,ebo1
RTE(USB-CDC)6.37.14.105(r)onBCM43235r3@20.0/96.0/96.0MHz
000000.007ei1,ebi2,ebo1
000000.054wl0:BroadcomBCM43235802.11WirelessController6.37.14.105(r)
000000.060nodisconnect
000000.064reclaimsection1:Returned91828bytestotheheap
000001.048bcm_rpc_buf_recv_mgn_low:HostVersion:0x6250e69
000001.054ConnectedSession:69!
000001.057revinfo
000063.051rpcuptime1minutes
?
000072.558reboot
000072.559rmwk
000072.561dpcdump
000072.563wlhist
000072.564rpcdump
000072.566md
000072.567mw
000072.569mu
000072.570?
波特率為115200 b/s,命令md允許將內存轉儲到特定地址,你應該指定地址以及要轉儲的DWORD數,有了一個很小的PySerial腳本,我們就能夠轉儲ROM並獲得實時RAM。
#!/usr/bin/envpython3
importserial
importbinascii
nb=65535
baseaddr=0
uart=serial.Serial('/dev/ttyUSB0',115200)
uart.write(b'md0x%08x4%d\n'%(baseaddr,nb))
i=0
dump=b''
whilei!=nb:
read=uart.readline().split(b'')
ifb''inread[0]:
continue
ifb'rpc'inread[2]:
continue
print('Dump%s%s\r'%(read[1][:-1],read[2]),end='')
dump+=binascii.unhexlify(read[