Doop學習 part 1

語言: CN / TW / HK

doop倉庫是一個gradle專案, ./doop 其實就是一個bash去呼叫gradle命令

#!/usr/bin/env bash

ARGS=""
while [ "$1" != "" ]; do
    case "$1" in
        # Quote arguments with spaces.
        *\ * )
            ARGS="${ARGS} '$1'"
            ;;
        *)
            ARGS="${ARGS} $1"
            ;;
    esac
    shift
done

# Export number of terminal columns for help display.
if command -v 'tput' &> /dev/null
then
    export COLUMNS=`tput cols`
fi
eval "./gradlew :run -Pargs=\"$ARGS\""

還提供了一個 ./doopOffline ,就是離線模式的doop。通常在每次呼叫Doop時,底層構建系統都會檢查所有依賴庫的更新版本。有時可能需要在離線模式下呼叫doop。為此目的有一個替代指令碼。

#!/bin/bash
eval './gradlew :run -Pargs="'$@'" --offline'

其實就是gradle加了一個offline引數

Doop 執行流程大致可以分為幾步:

  1. 使用soot生成jimple檔案
  2. 使用 --generate-jimple 引數可以輸出jimple檔案,在 output/$(uuid)/database/jimple 資料夾下
  3. 將jimple檔案轉換為datalog引擎的輸入事實(.facts)
  4. 使用souffle引擎執行選定的分析,將關係輸出為 .csv ,即分析結果

以長城杯b4bycoffee為例,解壓springboot專案,將class檔案打包為jar包

unzip b4bycoffee.jar
cd BOOT-INF/classes
jar -cvf classes.jar *

然後執行

./doop -a context-insensitive --information-flow spring --fact-gen-cores 16 --souffle-jobs 16  -i /tmp/BOOT-INF/lib/classes.jar --stats none

解釋一下各個引數

-a context-insensitive
--information-flow spring
--fact-gen-cores 16 --souffle-jobs 16
-i /tmp/BOOT-INF/lib/classes.jar
--stats none

如果是第一次執行會比較慢,因為他會去 http://centauri.di.uoa.gr:8081/ 拉一些jar包,等著就行了,第二次就快了。

構建完之後可見整個構建過程分為幾部分

out/uuid/database

分析輸出的結果如圖

ubuntu@ubuntu:~/doop$ cat  last-analysis/MockObject.csv
javax.servlet.http.HttpServletRequest::MockObject       javax.servlet.http.HttpServletRequest
javax.servlet.http.HttpServletResponse::MockObject      javax.servlet.http.HttpServletResponse
com.example.b4bycoffee.controller.indexController::MockObject   com.example.b4bycoffee.controller.indexController
com.example.b4bycoffee.controller.coffeeController::MockObject  com.example.b4bycoffee.controller.coffeeController
com.example.b4bycoffee.model.CoffeeRequest::MockObject  com.example.b4bycoffee.model.CoffeeRequest

可見spring的controller會被自動進行汙點分析。

除去 --information-flow 指定為spring以外還支援一些其他的比如webapps javaee專案,這裡不再演示了。

新增自己的規則

doop的規則是基礎規則,只給你了腳手架,針對我們的實際應用我們不得不寫一些自定義規則,比如我們想要呼叫圖,那麼可以將如下規則儲存為my.dl

.decl CG(?caller:Method, ?callee:Method)

CG(?caller, ?callee) :-
  mainAnalysis.AnyCallGraphEdge(?invocation, ?callee),
  Instruction_Method(?invocation, ?caller).

.output CG

然後加上引數 --extra-logic my.dl 重新構建,檢視last-analysis下的CG.csv即可。

另一種自定義規則的方式

加引數 --extra-logic 仍然會很慢,會更新依賴包、重新編譯、生成facts等固定步驟,有沒有更快的方式?

在doop分析的時候可見是用gcc編譯成二進位制檔案來分析

這個二進位制檔案是souffle編譯完的可執行檔案。

可以直接執行這個可執行檔案會按照之前的規則重新輸出結果,規則檔案是 gen_xxx.dl

我們寫的my.dl被追加到最後面去執行了。

那麼我們想要改自定義的規則可以直接編輯這個 gen_xx.dl ,在最後面追加即可。然後用souffle去執行,畢竟facts事實檔案都有了。

souffle -F database/ gen_1755044251944027223.dl -j32

這裡提到bytecodedl是把doop的幾部分拆出來做了。用soot-facts-generator生成facts,編寫規則之後直接用souffle進行查詢。

而doop的好處就是內建的規則比bytecodedl多。

缺點很明顯就是慢,每次查詢都需要下依賴並重新生成facts。

官方也提到了可以生成facts之後用souffle執行自定義規則,我直接複製過來。

在檔案 temp.dl 中放入程式碼:

#!java
.decl Var_DeclaringMethod(v: symbol, m: symbol)
.input Var_DeclaringMethod(IO="file", filename="Var-DeclaringMethod.facts", delimiter="\t")

.decl VarPointsTo(c1: symbol, h: symbol, c2: symbol, v: symbol)
.input VarPointsTo(IO="file", filename="VarPointsTo.csv", delimiter="\t")

.decl Temp(v: symbol, h: symbol)
Temp(v, h) :-
  VarPointsTo(_, h, _, v),
  Var_DeclaringMethod(v, "<Example: void test(int)>").

.output Temp

複製 Var-DeclaringMethod.facts,使它們與輸出關係 VarPointsTo 位於同一目錄中(替換$id為您的分析 ID):

#!bash
$ cp out/$id/facts/Var-DeclaringMethod.facts out/$id/database/

執行查詢並檢視其結果:

#!bash
$ souffle -F out/$id/database/ temp.dl
$ cat Temp.csv
<Example: void test(int)>/@this <Example: void main(java.lang.String[])>/new Example/0
<Example: void test(int)>/l0#_0 <Example: void main(java.lang.String[])>/new Example/0
<Example: void test(int)>/l3#_32        <Example: void test(int)>/new Cat/1
<Example: void test(int)>/l4#_33        <Example: void test(int)>/new Cat/2
<Example: void test(int)>/$stack5       <Example: void test(int)>/new Dog/0
<Example: void test(int)>/$stack6       <Example: void test(int)>/new Cat/1
<Example: void test(int)>/$stack7       <Example: void test(int)>/new Cat/2
<Example: void test(int)>/$stack8       <Example: void test(int)>/new Cat/0
<Example: void test(int)>/l2#_26        <Example: void test(int)>/new Cat/0
<Example: void test(int)>/l2_$$A_1#_28  <Example: void test(int)>/new Dog/0
<Example: void test(int)>/l2_$$A_2#_29  <Example: void test(int)>/new Cat/0
<Example: void test(int)>/l2_$$A_2#_29  <Example: void test(int)>/new Dog/0

這種方式需要你寫import facts的規則,比較麻煩。bytecodedl就是這樣。

可能會碰到的報錯

soot生成facts事實的時候可能會報oom異常,這是因為記憶體給小了,修改build.gradle中

def factGenXmx='32G'
def factGenStack='16G'

給大一點就行了。

文末

其實doop最精華的應該是他的dl規則,我再看明白一點再寫文章把。

說是最牛逼的指標分析框架,但實際學習的時候文件不全、資料太少、莫名其妙的報錯等各種原因導致學習門檻太高了。

對實際挖洞而言,具體怎麼用得再學一學再寫,個人傾向於像bytecodedl那樣拆出來改一改,加上危險函式sink規則,配合doop原有的spring、webapp的source,輸出一條準確的汙點分析過後的路徑圖是最好的。

學無止境啊,共勉吧。