農民鬥地主——Binder fuzz安全研究
吧力作,歡迎轉載,轉載請註明來自http://colbert337.github.io
2016.01.25
最近扣吧忙成狗了,好久沒更新博客,對不住大家了,今天趁天氣暖和點,來一篇乾貨。
由於好久沒搞Android了,寫得不專業的地方,請見諒哈。
0.為什麼要研究Binder fuzz
以目前最熱門的指紋方案為例。
TZ:Trustzone(請自行百度)
CA:Trustzone框架中的Clinet AppTA:Trustzone框架中的Trustzone APP
上層APP通過Binder機制調用keystore和FingerprintService兩個底層系統服務,來獲取密鑰存儲和指紋的能力。底層服務再通過CA跟TZ驅動通信,調用TZ中TA提供的服務,如指紋識別等安全性要求較高的服務。
我們今天只看Android側的Binder體系。
Binder其實是提供了一種進程間通信(IPC)的功能。這些系統服務,通過binder協議抽象出一個個的「介面」,供其他進程調用,是一個重要的潛在的攻擊面。如果沒有做好許可權控制,會讓低許可權的第三方應用/病毒/木馬利用,後果不堪設想。
其次,做Android的同學都知道,Binder是android一個非常重要的機制,誇張一點可以說是「Android的靈魂」,非常有必要進行細緻的分析和漏洞挖掘。
插播一個扣吧總結的知識點,系統服務的分類
1.Binder體系的java服務(有Stub介面,也就是AIDL封裝)
2.Binder體系的Native服務
3.socket體系的init服務(通常見於init.rc)4.其他服務
OK,再談談為什麼使用fuzz技術呢?
總的來說,是因為fuzz在協議和介面安全測試中比較簡單粗暴,試錯成本低。所以,「不管什麼介面,先fuzz一把看看」。
Fuzzing是一種基於缺陷注入的自動軟體測試技術。通過編寫fuzzer工具向目標程序提供某種形式的輸入並觀察其響應來發現問題,這種輸入可以是完全隨機的或精心構造的。Fuzzing測試通常以大小相關的部分、字元串、標誌字元串開始或結束的二進位塊等為重點,使用邊界值附近的值對目標進行測試。
主要有兩種類型的fuzzing技術 :
1)dumb fuzzing 這種測試無需了解協議或文件本身格式,通過提供完全隨機的輸入或簡單改變某些位元組去發現問題。這種方法實現起來較簡單,容易快速觸發錯誤,但它的完全隨機性會導致產生大量無效的輸入或格式。
2)Intelligent fuzzing 研究目標應用程序的協議或文件格式、功能配置,了解各類漏洞的成因,有目的地編寫fuzzer。編寫有效的fuzzer需要花費時間,但能夠對某些感興趣的部分集中測試,因此更有效。
1.什麼是Binder(有基礎的可以略過這一部分)
Android系統採用Binder機製作為進程間通信機制,類似於COM和CORBA分散式組件架構,通俗來講其實就是提供遠程過程調用(RPC)功能。
在Binder機制中,由Client、Server、ServiceManger、Binder驅動這四個部分組成,其中Client、Server、ServiceManager運行在用戶空間,Binder驅動運行在內核空間。Binder就是把這四個組件粘合在一起的粘合劑,核心組件是Binder驅動,ServiceManager提供了輔助管理的功能。Client和Server正是在Binder驅動和ServiceManager提供的基礎設施上,進行CS通信。
下面這個流程圖可以簡單說明Client通過binder調用Server的一個過程,Client會通過Proxy(這裡的Proxy不是單一實體,實際上是一系列的BpInterface、BpBinder等代理組件)去跟binder驅動通信,Proxy把數據打包成parcel類型數據再進行傳輸。
那麼數據具體是怎麼傳輸的呢?
我們繼續深究一下,筆者總結了一個比較全的圖。Java層服務其實也是在Native層服務BpBinder和BBinder的一個封裝。如果屏蔽底層驅動來看,整個Binder代理的核心就是BpBinder和BBinder。
其中,BpBinder最重要的職責就是實現跨進程傳輸的傳輸機制,至於具體傳輸的是什麼語義,它並不關心。我們觀察它的transact()函數的參數,可以看到所有的語義都被打包成Parcel類型數據。(Parcel是輕量級的高效的對象序列化和反序列化機制,Android在Java空間和C++都實現了Parcel,由於它在C/C++中,直接使用了內存來讀取數據,因此,它更有效率)
請記住這個偉大的函數——transact()
舉一個例子:上層APP調用MediaRecorder對外提供的API,名字叫setCamera,實際上是執行了BpMediaRecorder中的setCamera方法中,remote()返回的就是BpBinder對象,這裡會組裝好parcel數據包,會傳給BpBinder的transact函數。transact函數就會把數據發給對端,也就是另一個BBinder對象。
我們看一下具體是如何發送數據?
BpBinder的transact函數,通過層層調用,最終通過ioctl和binder驅動通信
嗯,上述的就是發送請求的過程。
下面來看接收方,Binder遠程通信的目標端實體必須繼承於BBinder類,該類和BpBinder相對,主要關心的只是傳輸方面的東西,不太關心所傳輸的語義。當收到回復後,會執行IPCTHreadState::waitForRespaonse函數的邏輯,並執行executeCommand(cmd)
executeCommand中,會取得一個合法的BBinder對象,並執行BBinder的transact函數。
(是不是有點奇怪,BBinder也有一個transact函數,請繼續往下看吧)BBinder::transact中會調用onTransact,這個onTransact才是真正處理業務的。需要注意的是,因為我們的binder實體在本質上都是繼承於BBinder的,而且我們一般都會重載onTransact()函數,所以上面的onTransact()實際上調用的是具體binder實體的onTransact()成員函數。也就是說,onTransact的具體實現一般在上層的binder實體,而不在BBinder。
上面說了,BBinder沒有實現一個默認的onTransact()成員函數,所以在遠程通信時,BBinder::transact()調用的onTransact()其實是Bnxxx或者BnInterface的某個子類的onTransact()成員函數,舉個例子,BnMediaRecorder中實現了一個onTransact函數,通過switch-case,根據不同code進行分發處理。
switch(code)中的code,其實就是前面說的BpBinder中transact函數傳過來的int型的方法號。
2.Binder fuzz怎麼作
經過上面的分析,我們已經對Binder有個全局的了解。fuzz的關鍵是選擇好fuzz的目標和fuzz切入點(介面),那麼應該如何選擇呢?
思路就是農民鬥地主!
前面也說了,系統服務(地主)具有高許可權,是我們需要重點關注的對象,而低許可權進程(農民)可以利用binder call去調用系統服務,從低許可權到高許可權,存在一個跨安全域的數據流,這裡就是一個典型的攻擊界面。所以,我們選擇系統服務作為fuzz的目標。
那麼Fuzz介面呢?選擇fuzz介面需要滿足這幾個要求:
1)這個介面是開放的,是可以被低許可權進程調用的
2)這個介面距離fuzz目標(系統服務)比較接近,中間路徑最好透傳,這樣比較容易分析異常3)從簡原則
根據上面的分析,BpBinder中的transact函數就是一個很好的fuzz介面,但這貨在底層無法直接調用。
怎麼辦呢?
我們從BpBinder往上層找,很容易發現,Java層IBinder的transact函數最終調用到BpBinder,且參數是原封不動的「透傳」到底層,考慮到java層的可視化和擴展性,我決定選擇IBinder的公有方法transact作為fuzz介面。
下圖就是這個介面的定義:
請大家認真看看上圖注釋的說明:
code是int類型,指定了服務方法號
data是parcel類型,是發送的數據,滿足binder協議規則,下面會有詳述reply也是parcel類型,是通信結束後返回的數據flag是標記位,0為普通RPC,需要等待,調用發起後處於阻塞狀態直到接收到返回,1為one-way RPC,表示「不需要等待回復的」事務,一般為無返回值的單向調用。
下面開始講重點了,額。
介面不是你想fuzz就能fuzz。我們來解決幾個關鍵問題:
1)如何取得服務的IBinder對象?
我們要取到對端的IBinder對象,才可以調用這個服務。系統其實有一些隱藏API可以利用。先通過反射出ServiceManager(hide屬性)中的listServices獲取所有運行的服務名稱:
獲取到String類型的服務名稱後,再反射getService獲取對應的服務IBinder對象:
是不是很犀利,其實是借用了上文說的ServiceManager的強大力量。
2)code如何生成?
code也稱為TransactionID,標定了服務端方法號。
每個服務對外定義的方法都會分配方法號,而且是有規律的,第一個服務方法code使用1,第二個是2,,第三個使用3,依次類推,如果有N個方法,就分別分配1-N個連續的服務號。
有個小技巧,對於Java服務,必定有Stub類,可以通過反射出mInterfaceToken+」$Stub」類中所有成員屬性,其中以」TRANSACTION_」開頭的int型就是該方法對應的。
如下圖的例子,服務端greet方法對應的code就是TRANSACTION_greet:
如果是Native服務,就比較悲劇了,目前還沒有好的自動化方法直接獲取code。一般服務方法數不會太多,所以確定一個上限如50,從1到50循環生成code就可以把所有方法遍歷。當然可以通過人工逆向分析出code,但這樣成本比較高。
3)data如何構造?
通過大量的源碼review和分析得知,data由「RPC header+參數1+參數2+….」來構成的。
舉個例子,如下圖,setDataSource這個API,首先調用data.writeInterfaceToken會寫入一個RPC header,然後會依次寫入調用方法的參數,比如setdataSource有3個參數,這裡就會依次寫入三個數據:
是不是很有規律!!
通過review writeInterfaceToken的實現,我們可以發現這個RPC header是由一個int型數據加上String類型的interface name來構成。
但我們不需要自己去構造RPC header,直接調用writeInterfaceToken函數,傳入interface name就可以了。最後抽象出來的parcel類型的data應該是這樣的:
那大家可能會問interface name是什麼東西,如何獲取?很簡單,interface name是介面名稱,只要取得IBinder對象,就可以直接getInterfaceDescriptor來獲取interface name,也就是介面方法的描述符。
再看如何獲取一個方法的參數和類型呢?
對於Java層服務的方法,可以通過反射獲取method對象,然後用getParameterTypes獲取所有的類型:
對於native層服務,無法直接獲取方法參數類型,可以用過review調用者實現和反編譯分析等方法來作。
4)fuzz系統和邏輯怎麼設計?
直接上圖吧。如下圖,整個fuzz系統分為4個模塊,分別是數據產生器,fuzz引擎,監視器和日誌模塊。
1)數據產生器就是用上述方法產生transact需要用到的數據
2)fuzz引擎用於執行具體的transact過程
3)監視器用於監控fuzz結果和異常
4)日誌模塊用於記錄fuzz結果
這裡筆者採用了3種fuzz方法
1)dumb fuzz:構造好RPC header後,直接塞入大量隨機數據,code範圍為1-100,比較暴力。
2)intelligent fuzz:構造好RPC header後,精準識別出code,並根據不同的code構造出類型正確的隨機參數
3)simple fuzz:構造好RPC header後,精準識別出code,但每次請求只寫入int類型「0」,通過返回值,快速識別fuzz目標的介面是否有許可權校驗
5)如何判斷fuzz結果和識別安全漏洞?
一般來說,要做到「許可權判斷+數據有效性判斷」兩層防護才是安全的。
通過監控transact的返回值和系統log和系統狀態,可以看到的fuzz現象主要有以下幾種:
1)有SecurityException,則說明該介面有進行許可權判斷,做了一層防護
2)無Exception,說明該介面沒有進行許可權校驗,默認對外暴露,是不安全的,可以深挖
3)異常現象,如系統重啟、指紋服務掛死、屏幕無響應等,說明該介面不僅沒有進行許可權判斷,而且fuzz數據導致了緩存區溢出/進程crash等異常,這類現象要再去進行人工分析,很有可能會嚴重的提權漏洞(比如root)
舉個例子,看看到底是哪裡出現安全漏洞。下圖,ontransact函數中switch-case結構里,其中一個case中沒有對數據進行判斷就讀到*device_address,而這個指針直接當成參數直接使用,當指針地址異常就會引起系統服務進程crash,從而導致系統重啟,是一個典型的拒絕服務漏洞。也就是說,任意一個低許可權的進程可以隨時進行攻擊,導致系統重啟。
再舉個例子,假設某手機廠家的系統指紋服務有個介面叫DeleteFingerPrint(),用於刪除用戶指紋,該服務的實現沒有進行許可權判斷和參數校驗,惡意攻擊者就有可能構造參數,非法調用該服務的方法,把用戶的指紋信息刪除。
重要的事情要說三遍!
參數要做檢查
參數要做檢查
參數要做檢查
今天先寫到這裡,寫得有點亂,後續再更新一下。如果你喜歡扣吧的文章,請多多留言支持~
推薦閱讀: