談Fuzz技術挖掘Android漏洞

本文首發於《程序員》雜誌,轉載須註明出處

談Fuzz技術挖掘Android漏洞

Android系統服務即由Android提供的各種服務,比如WIFI,多媒體,短信等等,幾乎所有的Android應用都要使用到系統服務。系統服務在爲用戶提供便利的同時,也存在着一些風險。比如,如果一個應用獲取到了系統服務中的短信服務,那麼他就可能會查看用戶的短信信息,用戶隱私就有可能暴露。此外,如果在使用系統服務的過程中,使用了異常的外部數據,有可能會導致系統服務崩潰,甚至是遠程代碼執行,內存破壞等等嚴重後果。因此Android系統服務的安全問題需要重視。
在以前的工作發現主要的漏洞和攻擊主要包括特權提升攻擊,惡意軟件攻擊,重打包,組件劫持攻擊等類型。儘管安全研究人員已經針對Android上層app的漏洞挖掘做了大量的工作,但是針對Android系統服務的漏洞挖掘一直被安全人員所普遍忽視。
通過Binder機制可以對Android的系統服務漏洞進行深入的挖掘。本文基於Android的Binder機制編寫了一套漏洞挖掘框架。
下面我們首先介紹一下先驗知識。
#1 基礎知識

1.1 Android的Binder機制

1.1.1 Binder概述
Binder其實也不是Android提出來的一套新的進程間通信機制,它是基於OpenBinder來實現的。Binder是一種進程間通信機制,它是一種類似於COM和CORBA分佈式組件架構,是提供遠程過程調用(RPC)功能。
什麼是Binder
1. 直觀來說,Binder是Android中的一個類,它繼承了IBinder接口
2. 從IPC角度來說,Binder是Android中的一種跨進程通信方式,Binder還可以理解爲一種虛擬的物理設備,它的設備驅動是/dev/binder,該通信方式在Linux中沒有
3. 從Android Framework角度來說,Binder是ServiceManager連接各種Manager(ActivityManager、WindowManager,etc)和相應ManagerService的橋樑
4. 從Android應用層來說,Binder是客戶端和服務端進行通信的媒介,當你bindService的時候,服務端會返回一個包含了服務端業務調用的Binder對象,通過這個Binder對象,客戶端就可以獲取服務端提供的服務或者數據,這裏的服務包括普通服務和基於AIDL的服務
在Android系統的Binder機制中,由一系統組件組成,分別是Client、Server、Service Manager和Binder驅動程序,其中Client、Server和Service Manager運行在用戶空間,Binder驅動程序運行內核空間,如圖1-1所示。Binder就是一種把這四個組件粘合在一起的粘結劑,其中核心組件便是Binder驅動程序了,Service Manager提供了輔助管理的功能,Client和Server正是在Binder驅動和Service Manager提供的基礎設施上,進行Client-Server之間的通信。Service Manager和Binder驅動已經在Android平臺中實現好,開發者只要按照規範實現自己的Client和Server組件就可以了。
這裏寫圖片描述
圖1-1 Binder架構圖
1.1.2 爲什麼使用Binder
Android中有大量的CS(Client-Server)應用方式,這就要求Android內部提供IPC方法,而linux所支持的進程通信方式有兩個問題:性能和安全性。
目前linux支持的IPC包括傳統的管道,System V IPC(消息隊列/共享內存/信號量),以及socket,但只有socket支持Client-Server的通信方式,由於socket是一套通用的網絡通信方式,其傳輸效率低下切有很大的開銷,比如socket的連接建立過程和中斷連接過程都是有一定開銷的。消息隊列和管道採用存儲-轉發方式,即數據先從發送方緩存區拷貝到內核開闢的緩存區中,然後再從內核緩存區拷貝到接收方緩存區,至少有兩次拷貝過程。共享內存雖然無需拷貝,但控制複雜,難以使用。
在安全性方面,Android作爲一個開放式,擁有衆多開發者的的平臺,應用程序的來源廣泛,確保智能終端的安全是非常重要的。終端用戶不希望從網上下載的程序在不知情的情況下偷窺隱私數據,連接無線網絡,長期操作底層設備導致電池很快耗盡等等。傳統IPC沒有任何安全措施,完全依賴上層協議來確保。首先傳統IPC的接收方無法獲得對方進程可靠的UID/PID(用戶ID/進程ID),從而無法鑑別對方身份。Android爲每個安裝好的應用程序分配了自己的UID,故進程的UID是鑑別進程身份的重要標誌。使用傳統IPC只能由用戶在數據包裏填入UID/PID,但這樣不可靠,容易被惡意程序利用。可靠的身份標記只有由IPC機制本身在內核中添加。其次傳統IPC訪問接入點是開放的,無法建立私有通道。比如命名管道的名稱,system V的鍵值,socket的ip地址或文件名都是開放的,只要知道這些接入點的程序都可以和對端建立連接,不管怎樣都無法阻止惡意程序通過猜測接收方地址獲得連接。
基於以上原因,Android需要建立一套新的IPC機制來滿足系統對通信方式,傳輸性能和安全性的要求,這就是Binder。Binder基於 Client-Server通信模式,傳輸過程只需一次拷貝,爲發送發添加UID/PID身份,既支持實名Binder也支持匿名Binder,安全性高。

1.1.3 Aidl機制
AIDL (Android Interface Definition Language) 是一種IDL 語言,用於生成可以在Android設備上兩個進程之間進行進程間通信(interprocess communication, IPC)的代碼。如果在一個進程中(例如Activity)要調用另一個進程中(例如Service)對象的操作,就可以使用AIDL生成可序列化的參數。
AIDL IPC機制是面向接口的,像COM或CORBA一樣,但是更加輕量級。它是使用代理類在客戶端和服務端傳遞數據。只有你允許客戶端從不同的應用程序爲了進程間的通信而去訪問你的service,以及想在你的service處理多線程。如果不需要進行不同應用程序間的併發通信(IPC),或者你想進行IPC,但不需要處理多線程的。使用AIDL前,必須要理解如何綁定service。
AIDL IPC機制是面向接口的,像COM或Corba一樣,但是更加輕量級。它是使用代理類在客戶端和實現端傳遞數據。

1.2 Fuzz技術

模糊測試定義爲“通過嚮應用提供非預期的輸入並監控輸出中的異常來發現軟件中的故障(faults)的方法”。典型而言,模糊測試利用自動化或是半自動化的方法重複地嚮應用提供輸入。顯然,上述定義相當寬泛,但這個定義闡明瞭模糊測試的基本概念。
用於模糊測試的模糊測試器(fuzzer)分爲兩類:一類是基於變異(mutation-based)的模糊測試器,這一類測試器通過對已有的數據樣本進行變異來創建測試用例;而另一類是基於生成(generation-based)的模糊測試器,該類測試器爲被測系統使用的協議或是文件格式建模,基於模型生成輸入並據此創建測試用例。這兩種模糊測試器各有其優缺點

模糊測試各階段

採用何種模糊測試方法取決於衆多因素。沒有所謂的一定正確的模糊測試方法,決
定採用何種模糊測試方法完全依賴於被測應用、測試者擁有的技能、以及被進行模糊測
試的數據的格式。但是,不論對什麼應用進行模糊測試,不論採用何種模糊測試方法,
模糊測試執行過程都包含相同的幾個基本階段。

<1>確定測試目標

只有有了明確的測試目標後,我們才能決定使用的模糊測試工具或方法。如果要在安全審計中對一個完全由內部開發的應用進行模糊測試,測試目標的選擇必須小心謹慎。但如果是要在第三方應用中找到安全漏洞,測試目標的選擇就更加靈活。要決定第三方應用模糊測試的測試目標,首先需要參考該第三方應用的供應商歷史上曾出現過的安全漏洞。在一些典型的安全漏洞聚合網站如 SecurityFocus 18 和 Secunia 19 上可以查找到主要軟件供應商歷史上曾出現過的安全漏洞。如果某個供應商的歷史記錄很差,很可能意味着這個供應商的代碼實踐 (code practice)能力很差,他們的產品有仍有很大可能存在未被發現的安全漏洞。除應用程序外,應用包含的特定文件或庫也可以是測試目標。
如果需要選擇應用包含的特定文件或者庫作爲測試目標,你可以把注意力放在多個應用程序之間共享的那些二進制代碼上。因爲如果這些共享的二進制代碼中存在安全漏洞,將會有非常多的用戶受到影響,因而風險也更大。

<2>.確定輸入向量

幾乎所有可被利用的安全漏洞都是因爲應用沒有對用戶的輸入進行校驗或是進行必要的非法輸入處理。是否能找到所有的輸入向量(input vector)是模糊測試能否成功的關鍵。如果不能準確地找到輸入向量,或是不能找到預期的輸入值,模糊測試的作用就會受到很大的侷限。有些輸入向量是顯而易見的,有些則不然。尋找輸入向量的原則是:從客戶端向目標應用發送的任何東西,包括頭(headers)、文件名(file name)、環
境變量(environment variables),註冊表鍵(registry keys),以及其他信息,都應該被看做是輸入向量。所有這些輸入向量都可能是潛在的模糊測試變量。

<3>.生成模糊測試數據

一旦識別出輸入向量,就可以依據輸入向量產生模糊測試數據了。究竟是使用預先確定的值、使用基於存在的數據通過變異生成的值、還是使用動態生成的值依賴於被測應用及其使用的數據格式。但是,無論選擇哪種方式,都應該使用自動化過程來生成數據。

<4>.執行模糊測試數據

該步驟緊接上一個步驟,正是在這個步驟,“模糊測試”變成了動詞。在該步驟中,一般會向被測目標發送數據包、打開文件、或是執行被測應用。同上一個步驟一樣,這個步驟必須是自動化的。否則,我們就不算是真正在開展模糊測試。

<5>.監視異常

一個重要但經常容易被忽略的步驟是對異常和錯誤進行監控。設想我們在進行一次模糊測試,在測試中,我們向被測的 Web 服務器發送了 10000 個數據包,最終導致了服務器崩潰。但服務器崩潰後,我們卻怎麼也找不到導致服務器崩潰的數據包了。如果這種事真的發生了,我們只能說這個測試毫無價值。模糊測試需要根據被測應用和所決定採用的模糊測試類型來設置各種形式的監視。

<6>.判定發現的漏洞是否可能被利用

如果在模糊測試中發現了一個錯誤,依據審計的目的,可能需要判定這個被發現的錯誤是否是一個可被利用的安全漏洞。這種判定過程是典型的手工過程,需要操作者具有特定的安全知識。這個步驟不一定要由模糊測試的執行者來進行,也可以交給其他人來進行。

2.漏洞挖掘思路

fuzz在協議和接口安全測試中比較簡單粗暴,試錯成本低。Fuzzing是一種基於缺陷注入的自動軟件測試技術。通過編寫fuzzer工具向目標程序提供某種形式的輸入並觀察其響應來發現問題,這種輸入可以是完全隨機的或精心構造的。Fuzzing測試通常以大小相關的部分、字符串、標誌字符串開始或結束的二進制塊等爲重點,使用邊界值附近的值對目標進行測試。

2.1 fuzz的切入點和目標

2.1.1 fuzz的切入點

爲了更好的挖掘漏洞,選擇fuzz接口需要滿足這幾個要求:

1)這個接口是開放的,是可以被低權限進程調用的
2)這個接口距離fuzz目標(系統服務)比較接近,中間路徑最好透傳,這樣比較容易分析異常
3)從簡原則

根據上面的分析,BpBinder中的transact函數就是一個很好的fuzz接口,但這個函數在底層無法直接調用。

底層transact方法介紹
在c層中,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進行分發處理。

我們從BpBinder往上層找,很容易發現,Java層IBinder的transact函數最終調用到BpBinder,且參數是原封不動的“透傳”到底層,考慮到java層的可視化和擴展性,可以選擇IBinder的公有方法transact作爲fuzz接口。

transact的四個參數介紹。我們可以構造這四個參數進行測試。

code是int類型,指定了服務方法號
data是parcel類型,是發送的數據,滿足binder協議規則,下面會有詳述
reply也是parcel類型,是通信結束後返回的數據
flag是標記位,0爲普通RPC,需要等待,調用發起後處於阻塞狀態直到接收到返回,1爲one-way RPC,表示“不需要等待回覆的”事務,一般爲無返回值的單向調用。

2.1.2fuzz的目標

Binder其實是提供了一種進程間通信(IPC)的功能。這些系統服務,通過binder協議抽象出一個個的“接口”,供其他進程調用,是一個重要的潛在的攻擊面。如果沒有做好權限控制,會讓低權限的第三方應用/病毒/木馬利用,後果不堪設想。

系統服務具有高權限,是我們需要重點關注的對象,而低權限進程(農民)可以利用binder call去調用系統服務,從低權限到高權限,存在一個跨安全域的數據流,這裏就是一個典型的攻擊界面。所以,我們選擇系統服務作爲fuzz的目標。

系統服務的分類

1.Binder體系的java服務(有Stub接口,也就是AIDL封裝)
2.Binder體系的Native服務
3.socket體系的init服務(通常見於init.rc)
4.其他服務

2.2 fuzz引擎

fuzz引擎實際是構造transact(int code,Parcel data,Parcel reply,int Flags)函數的四個函數,然後調用Ibinder.transact()來調用系統服務。
2.2.1如何獲取Ibinder對象

我們要取到對端的IBinder對象,纔可以調用這個服務。系統其實有一些隱藏API可以利用。先通過反射出ServiceManager(hide屬性)中的listServices獲取所有運行的服務名稱。獲取到String類型的服務名稱後,再反射getService獲取對應的服務IBinder對象。
2.2.2 code如何生成

code也稱爲TransactionID,標定了服務端方法號。

每個服務對外定義的方法都會分配方法號,而且是有規律的,第一個服務方法code使用1,第二個是2,,第三個使用3,依次類推,如果有N個方法,就分別分配1-N個連續的服務號。

對於Java服務,必定有Stub類,可以通過反射出mInterfaceToken+”$Stub”類中所有成員屬性,其中以”TRANSACTION_”開頭的int型就是該方法對應的。

2.2.3 data如何構造

data由“RPC header+參數1+參數2+….”來構成的。但我們不需要自己去構造RPC header,直接調用writeInterfaceToken函數,傳入interface name就可以了。interface name是接口名稱,只要取得IBinder對象,就可以直接getInterfaceDescriptor來獲取interface name,也就是接口方法的描述符。對於Java層服務的方法,可以通過反射獲取method對象,然後用getParameterTypes獲取所有的類型。
2.2.4 fuzz系統和邏輯設計
一共分爲四個部分:

1)測試數據產生器產生器就是用上述方法產生transact需要用到的的四個參數

2)fuzz引擎用於執行具體的transact調用過程,調用ibinder的transact()函數

3)監視器用於監控fuzz結果和異常

4)日誌模塊用於記錄fuzz結果,通過對異常日誌的分析可以發現漏洞

3.漏洞挖掘的結果

通過對Android系統服務的漏洞挖掘,目前一共發現了32個漏洞,其中在AOSP版本的虛擬機上發現了20個,在第三方廠商定製的系統服務中發現了12個,目前漏洞已經提交Google,小米,魅族等廠商,並且得到了高危漏洞的確認。這些漏洞主要是造成重啓,這樣就可以構造拒絕服務攻擊(dos),還有一些會導致顯示進程崩潰等干擾性破壞。
我個人覺得Android系統在安全方面的提升主要體現在以下幾個方面:

1.建立更加完善的Android漏洞,提交相應完善制度,加快補丁發佈。
2.完善Android文件的加密,同時在硬件上完善,比如TrustZone。
3.通過更加細粒度的授權機制,來保護用戶的安全和隱私。
4.縮小Android的碎片化。
5.提高開發者的審覈門檻,應用市場加強惡意應用的檢查。

隨着Android的版本升級和對漏洞的不斷完善,Android系統正在變得越來越安全。

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