1. 程式人生 > >FlowDroid: 精確的上下文,流,欄位, 物件敏感和生存週期感知的汙染分析

FlowDroid: 精確的上下文,流,欄位, 物件敏感和生存週期感知的汙染分析

一個Android app可包含多個元件,如activity, service, content-provider 和broadcast receiver。其中activity負責成像,也就是人機互動,是我們分析的主要入口和場所。不像傳統的Java程式,Android app裡不帶main函式,我們無法簡單的像之前一樣找到程式的入口和出口來畫控制流圖,但app裡每個元件都有函式來反應此元件的生命週期(lifecycle),我們可以依託lifecycle來畫出控制流圖。我們先來看下一個activity裡的lifecycle是什麼樣的,

(圖片擷取自AppIntent, CCS'13)

從圖中可以看出activity可以從任何結點按任何順序執行,並沒有傳統的單入口單出口結構。除了lifecycle函式activity裡還可以帶有各種event handler的callbacks,用於處理像UI互動之類的事件。callbacks可以看作居住在lifecycle函式間。

為了生成給定app的控制流圖,我們可以對其生成一個虛擬main函式。通過一個例子我們能很清楚的看清背後的思想。


例子中的app只有一個activity用於把使用者輸在密碼框中的內容通過簡訊傳送出去。畫橫線的語句分別代表了source和sink。現在我們來生成它的控制流圖


可以看到dummy main中把onCreatea當作入口,把onDestory作為出口,把原lifecycle圖中的帶分支的狀態看作是抽象的模糊判斷語句p,把啟用callbacks的條件也同樣看作是抽象的判斷語句p。

說完了控制流圖生成,接下來正式步入static analysis。整個analysis屬於flow-sensitive(執行順序相關),分為兩部分,向前的taint analysis用於找出被汙染的變數傳遞到了何處和向後的on-demand alias analysis用處查詢source之前所有對同一被汙染的heap 位置的別名。來看個小例子


1) w作為被汙染了的變數,被向前傳遞給了heap中的x.f。2) 繼續追蹤x.f 3)每當heap中有成員被汙染,使用向後分析找出相關物件的各種別名。如發現x和z.g是引用的heap中的同一位置 4-6)z是作為引數傳遞進來的,繼續向後找看到caller的a即callee的z,再往後發現b也是別名。7)對b.f做前向分析,判斷出其被傳遞到sink,從而展現了從source到sink的整個路徑。

FlowDroid基於IFDS框架,得益於其path不敏感性(由於複合distributive,無論內部怎麼傳過來的,只要檢視JOIN結點的結果即可),FlowDroid不需要考慮所有可能的在lifecycle中的執行順序。實際上考慮所有可能執行順序也是不可能的。

為了進一步理解,我們先看看比較formal的東西。


VarId表宣告出的object,同一個Object可以有多個VarId。Object 的field(instance, FieldId)儲存在heap中,heap儲存在記憶體中。除非刻意刪除或者GB, 否則在heap中的元素不會自動消失。Environments 是區域性變數和其值之間的對映集合,而Heap: (Loc, FieldId) -> Val是heap中元素與其值之間的對映的集合。物件的值和物件所在位置是等價的,因為物件不像基本型別,其本身沒有值,它的值反映在其所在位置下的成員field中,而且即使對於基礎型別從其所在位置即可獲得其的值。


semantics中的sigma表States := <E, H> E屬於Env表當前environment, H屬於Heaps表當前heap狀態。語義反應了語句的行為帶來的影響,除了第一個new,其它都相當於對同一loc增加別名的行為。

再引入一些輔助函式

arrayElem(x) : VarId -> Boolean 判斷x是否引用的陣列成員

static(x) : FieldId -> Boolean 判斷x是否是個全域性field

immut(x) : VarId -> Boolean 判斷x是否是基礎型別,如int, String等

source(s) : Stmt -> P (V arId) 返回被s語句汙染的變數名集合

native(s) : Stmt ->  Boolean 判斷s是否是呼叫了原生函式 (C/C++)

nativeTaint(s): Stmt -> P(VarId) 返回被s所呼叫native函式所汙染的變數名集合

nativeAlias(s): Stmt -> P(VarId)返回被s呼叫的native函式所建立的別名集合

為了更精確的表示field, 引入access path概念。access path更精確的表達了物件的field,如x.f.g.h用access path x.f^3表示,表明x後長度為3的訪問路徑。T是被汙染了的access path的集合,於是更具體的程式狀態sigma用三元<E, H, T>表示。

先來談談前向taint analysis。FlowDroid只把函式呼叫(如 API call)看作是可能的source 。taint var和uninit var問題很相似,同樣是以並集作為confluence operator, trans func有id, gen, kill(和複製)這麼幾種。不同在於在uninit var問題中與gen最直接相關的是變數宣告語句,而taint是source函式呼叫;在uninit var中與kill最直接相關的語句是變數被初始化,而taint var則是變數與變數的重初始化(new)或sink函式呼叫相連。 和unit var一樣,data flow的值(uninit和init, untained 和tained)是通過賦值語句在變數間傳遞的。IFDS框架把結點分為四類,分別是普通,call, return 和call-to-return。我們將對這四種列出相應的transfer func (flow func)。

從普通結點說起,因為source只能是函式呼叫,所以普通結點不可能純產生新的汙染相關的資料流事實(如a = source() ),只能是傳遞(= )或者"kill"(new)。

FlowDroind為了保證sound, 對陣列索引並不敏感,只要陣列內一個元素被汙染,則整個陣列皆被汙染。我們來看下複製的trans func: 


對於「=」語句,1) 第一條是gen, 檢查y.f^m下有沒有被汙染的了,有的話把要引用的x.f^n下的相應訪問路徑也加入T中 2) 下面是kill,當y,f^m下不包含任何汙染且x.f^n不處於被汙染過的陣列中,則把x.f^n從T中刪除(原來在T的話)3)其它情況維持不變。「=」語句右邊可能是一個算術表示式,則右邊任何變數被汙染則左邊的變數也會被汙染。

接著是new的

new表示該field被重新初始化,不再保留taint,因此與kill直接相關,與複製中的kill一樣。

下面講Call(函式呼叫)的trans func。Call代表了caller對callee 的影響。一個函式呼叫的形式可以表達為c.m(a0, .., an),其中c表一個物件名(變數名),m是函式名,a0到an是實參。場景可以表達為class test {x; c.m(a0, .., an)}。FlowDriod在基於有關呼叫函式的汙染集合Tcaller上,通過trans func為被呼叫函式建立汙染集合Tcallee。trans fun定義如下,s表示了一個函式呼叫語句,pi表示與ai相對應的形參

如果被呼叫函式所在物件中有成員被汙染了,在被呼叫函式的T中以this來表示;呼叫者所在物件中的被汙染的全域性變數如果出現在callee中的話,則同樣傳遞給callee所關聯的集合中。

Return 與call相對於,代表了被呼叫函式對caller的影響。它trans func需要對基本型別(String, int)特殊處理,因為這些型別儲存在臨時stack內而不是heap中,在被呼叫函式體內汙染並不會影響到呼叫者。因此對於b = c.m(a0, .., an)語句s相應的trans func為

r表示callee返回的變數。

Call-to-return 用來1)處理native call 2) 遇到source生成純新的tainted 變數 (之前都是傳遞) 3) 傳遞與call無關的汙染資訊

On-demand Aliasing Analysis:處理別名,即向後找出source出現前的對同一(被source汙染了的)heap位置的引用(變數);每當找到一個別名,則觸發向前的taint分析,找出此別名的汙染傳遞。實際上與taint analysis的transfer func基本一致,我就貼出來但不細說了。