使用androguard生成方法控制流圖CFG

使用androguard生成方法控制流圖CFG

瞭解了 androguard 的 基本方法調用XREF 之後,進一步學習其方法的控制流圖 Control Flow Graph (CFG),控制流圖可以通過 androguard 工具 decompile 來生成。

其語法使用規則如下

Usage: androguard decompile [OPTIONS] [FILE_]

  Decompile an APK and create Control Flow Graphs.

Options:
  -i, --input FILE       APK to parse (legacy option)
  -o, --output TEXT      output directory. If the output folder already
                         exsist, it will be overwritten!  [required]
  -f, --format TEXT      Additionally write control flow graphs for each
                         method, specify the format for example png, jpg, raw
                         (write dot file), ...
  -j, --jar              Use DEX2JAR to create a JAR file
  -l, --limit TEXT       Limit to certain methods only by regex (default:
                         '.*')
  -d, --decompiler TEXT  Use a different decompiler (default: DAD)
  --help                 Show this message and exit.

使用該方式能以 graphviz 的樣式輸出每個方法的控制流圖,並直接輸出爲圖片格式。
(什麼是graphviz?graphviz的輸入是一個用dot語言編寫的繪圖腳本,通過對輸入腳本的解析,分析出其中的點,邊以及子圖,然後根據屬性進行繪製。用graphviz來繪圖的時候,主要工作就是編寫dot腳本,關注圖中各個點之間的關係,而不需要考慮各個節點的位置及整體的佈局。)

除了.java格式的反編譯類之外,每種方法均以類似於 SMALI 的格式(.ag文件)給出。

ubuntu@ubuntu:~$ androguard decompile -o /home/ubuntu/Desktop/outputfolder -f png -i /home/ubuntu/Desktop/meeting.apk
[INFO    ] androguard.apk: Starting analysis on AndroidManifest.xml
[INFO    ] androguard.apk: APK file was successfully validated!
[INFO    ] androguard.analysis: Adding DEX file version 35
[INFO    ] androguard.analysis: Reading bytecode took : 0min 00s
[INFO    ] androguard.analysis: Adding DEX file version 35
[INFO    ] androguard.analysis: Reading bytecode took : 0min 00s
[INFO    ] androguard.analysis: End of creating cross references (XREF) run time: 0min 00s
Dump information /home/ubuntu/Desktop/meeting.apk in /home/ubuntu/Desktop/outputfolder
Create directory /home/ubuntu/Desktop/outputfolder
Decompilation ... End
Dump Lcom/example/helloworld/BuildConfig; <init> ()V ... png ... source codes ... bytecodes ... 
Dump Lcom/example/helloworld/R$attr; <init> ()V ... png ... source codes ... bytecodes ... 
Dump Lcom/example/helloworld/R$dimen; <init> ()V ... png ... source codes ... bytecodes ... 
……

等待程序執行結束,即可到指定的文件夾中查看生成的圖片。
文件名爲:MyWrapperProxyApplication initProxyApplication (Context)V.png
在這裏插入圖片描述
其對應的 .ag 文件爲 MyWrapperProxyApplication initProxyApplication (Context)V.ag

這裏以官方文檔中給出的典型進行說明
在這裏插入圖片描述
上圖所示中每一個矩形都是一個 ~androguard.core.analysis.analysis.DVMBasicBlock 類,並通過箭頭指明瞭流向,在上圖中 switch 命令有 6 個不同的流向,分別用綠色和紫色的箭頭表示出來,每個綠色箭頭是指 switch 指令內的特定檢查標記,即哪個值對應哪個代碼塊,紫色箭頭對應 default 情況。如圖 6 個箭頭僅指向4個不同的模塊,此外還有一個黃色箭頭指向的特殊模塊,用來保存 switch 有效載荷的僞指令。

首先,看一下該方法的整體反彙編:

 METHOD LTestDefaultPackage; public static main ([Ljava/lang/String; v9)V
    main-BB@0x00000000 :
            0  (00000000) const/4             v8, 0
            1  (00000002) const/4             v7, 4
            2  (00000004) const/4             v6, 3
            3  (00000006) const/4             v0, 5
            4  (00000008) packed-switch       v0, 80 [ D:main-BB@0x0000000e 1:main-BB@0x00000078 2:main-BB@0x00000078 3:main-BB@0x00000088 4:main-BB@0x0000000e 5:main-BB@0x00000098 ]
            5  (0000000e) sget-object         v4, Ljava/lang/System;->out Ljava/io/PrintStream;
            6  (00000012) const-string        v5, '4'
            7  (00000016) invoke-virtual      v4, v5, Ljava/io/PrintStream;->println(Ljava/lang/String;)V [ main-BB@0x0000001c ]
            8  (0000001c) new-instance        v1, LTestDefaultPackage;
            9  (00000020) invoke-direct       v1, LTestDefaultPackage;-><init>()V
            10 (00000026) new-instance        v2, LTestDefaultPackage$TestInnerClass;
            11 (0000002a) invoke-virtual      v1, Ljava/lang/Object;->getClass()Ljava/lang/Class;
            12 (00000030) invoke-direct       v2, v1, v6, v7, v8, LTestDefaultPackage$TestInnerClass;-><init>(LTestDefaultPackage; I I LTestDefaultPackage$TestInnerClass;)V
            13 (00000036) new-instance        v3, LTestDefaultPackage$TestInnerClass$TestInnerInnerClass;
            14 (0000003a) invoke-virtual      v2, Ljava/lang/Object;->getClass()Ljava/lang/Class;
            15 (00000040) invoke-direct       v3, v2, v6, v7, v8, LTestDefaultPackage$TestInnerClass$TestInnerInnerClass;-><init>(LTestDefaultPackage$TestInnerClass; I I LTestDefaultPackage$TestInnerClass$TestInnerInnerClass;)V
            16 (00000046) sget-object         v4, Ljava/lang/System;->out Ljava/io/PrintStream;
            17 (0000004a) new-instance        v5, Ljava/lang/StringBuilder;
            18 (0000004e) const-string        v6, 't.a = '
            19 (00000052) invoke-direct       v5, v6, Ljava/lang/StringBuilder;-><init>(Ljava/lang/String;)V
            20 (00000058) invoke-static       v2, LTestDefaultPackage$TestInnerClass;->access$1(LTestDefaultPackage$TestInnerClass;)I
            21 (0000005e) move-result         v6
            22 (00000060) invoke-virtual      v5, v6, Ljava/lang/StringBuilder;->append(I)Ljava/lang/StringBuilder;
            23 (00000066) move-result-object  v5
            24 (00000068) invoke-virtual      v5, Ljava/lang/StringBuilder;->toString()Ljava/lang/String;
            25 (0000006e) move-result-object  v5
            26 (00000070) invoke-virtual      v4, v5, Ljava/io/PrintStream;->println(Ljava/lang/String;)V
            27 (00000076) return-void
            28 (00000078) sget-object         v4, Ljava/lang/System;->out Ljava/io/PrintStream;
            29 (0000007c) const-string        v5, '1 || 2'
            30 (00000080) invoke-virtual      v4, v5, Ljava/io/PrintStream;->println(Ljava/lang/String;)V
            31 (00000086) goto                -53 [ main-BB@0x0000001c ]
            32 (00000088) sget-object         v4, Ljava/lang/System;->out Ljava/io/PrintStream;
            33 (0000008c) const-string        v5, '3 || '
            34 (00000090) invoke-virtual      v4, v5, Ljava/io/PrintStream;->print(Ljava/lang/String;)V
            35 (00000096) goto                -68 [ main-BB@0x0000000e ]
            36 (00000098) sget-object         v4, Ljava/lang/System;->out Ljava/io/PrintStream;
            37 (0000009c) const-string        v5, '5'
            38 (000000a0) invoke-virtual      v4, v5, Ljava/io/PrintStream;->println(Ljava/lang/String;)V
            39 (000000a6) goto                -69 [ main-BB@0x0000001c ]
            40 (000000a8) packed-switch-payload

可以通過第二列給出字節碼內的偏移量與CFG中給出的偏移量匹配的方式找出反彙編中的基本塊。

字節碼中的指令順序與執行順序並不是匹配的。例如,return 操作碼位於字節碼的中間,而它是執行的結尾。因此,某些部分必須具有 goto 指令以在正確的位置恢復執行。例如,對於 switch 操作碼的參數爲 5 的情況,基本塊以偏移量 0xa6 結尾,因爲有 goto 命令所以從當前偏移量中減去 0x45 ,但最終偏移量是 0x61 嗎?

操作碼的偏移量參數始終以16位爲單位,而 androguard 使用的偏移量以8位爲單位。這就是說,必須減去 0x8a,這實際上返回到字節碼中的偏移量是 0x1c

結合 CFG 和反彙編代碼,再來看一下該段代碼的源 java 代碼

	public static void main(String [] z) {
        int a = 5;
        switch(a)
        {
        case 1:
        case 2:
            System.out.println("1 || 2");
            break;
        case 3:
            System.out.print("3 || ");
        case 4:
        default:
            System.out.println("4");
            break;
        case 5:
            System.out.println("5");
        }
        TestDefaultPackage p = new TestDefaultPackage();
        TestInnerClass t = p.new TestInnerClass(3, 4);
        TestInnerClass.TestInnerInnerClass t2 = t.new TestInnerInnerClass(3, 4);
        System.out.println("t.a = " + t.a);
    }
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章