使用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);
}