使用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);
    }
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章