Androguard的文档写的很粗略,很多API的用法都是查源码得到的,真是源码里的注释都比文档写的详细。
APK静态分析,主要用Androguard里的androlyze.py。下面从APK级别,到package级别,和class级别,讲解用androlyze.py静态分析APK最常用的方法。
androlyze.py使用方法
使用androlyze.py之前,要先安装配置好Androguard环境(参考这里)。
然后,在终端提示符下,执行androlyze.py -s
就会进入androlyze的Shell交互环境。一般就是在这个交互环境中执行不同的命令,来分析APK文件。
分析之前,首先要初始化三个对象:APK文件对象
,DEX文件对象
,分析结果对象
。
# androlyze.py -s # 进入androlyze的Shell交互环境
In [1]: a = APK("./1.apk")# APK文件对象
In [6]: d = DalvikVMFormat(a.get_dex())# DEX文件对象
In [7]: dx = VMAnalysis(d)# 分析结果对象
APK文件对象
从Androguard源码中可以看出,创建APK文件对象
的类其实就是androguard.core.bytecodes.apk.APK
。这个类用于访问APK文件中的所有元素。从类中的一些方法中也可以看出它的功能:
- 获取manifest文件:get_AndroidManifest()
- 判断APK是否有效:is_valid_APK()
- 获取APK文件名:get_filename()
- 获取APP名:get_app_name()
- 获取package名:get_package()
- 获取android版本名:get_androidversion_code()
- 获取APK中的文件列表:get_files()
- 获取APK中所有activity名称列表:get_activities()
- 获取APK中的主activity名称:get_main_activity()
- 获取APK中所有service名称列表:get_services()
- 获取APK中所有receiver名称列表:get_receivers()
- 获取APK中所有provider名称列表:get_providers()
- 获取APK中签名:get_signature()
除了这些信息,还能获取APK权限相关的信息,看看下面这些函数就知道它的重要性了。
- get_permissions()
- get_requested_permissions()
- get_declared_permissions()
- get_certificate()
如果只想大致看看APK的基本信息,有一个简单的函数就能满足你,show()
。它会将APK内部的文件、权限、Activity相关的信息显示给你。
DEX文件对象
创建该对象的类为androguard.core.bytecodes.dvm.DalvikVMFormat
,源码位于这里。这个类的主要功能是解析APK文件中classes.dex
,并获取其相关信息。同样的,只需要用创建的DEX文件对象的show()
方法,就能显示classes.dex
中的基本信息。
- 获取APK/DEX文件中的所有类:get_classes()
- 获取APK/DEX文件中的所有方法:get_methods()
- 获取APK/DEX文件中的所有成员变量:get_all_fields()
- 获取APK/DEX文件中的所有字符串:get_strings()
分析结果对象
创建该对象的类为androguard.core.analysis.analysis.VMAnalysis
,源码位于这里。VMAnalysis
类的主要功能,就是分析DEX文件对象。比如根据字符串,搜索APK中的package:dx.get_tainted_packages().search_packages('')
。该类其它功能详见源码。
APK级别的分析
APK级别的分析,可以用APK文件对象
提供的方法,来获取manifest文件、APK的签名、权限等相关信息。除了直接用APK文件对象
,还能根据下面的例子,来获取、查找APK中含有的类和package。
获取整个APK中所有的class
# androlyze.py -s
In [1]: a = APK("./1.apk")
In [6]: d = DalvikVMFormat(a.get_dex())
In [7]: dx = VMAnalysis(d)
In [21]: class_list = d.get_classes()
In [47]: for i in range(4750,4757):
...: class_item = class_list[i]
...: class_name = class_item.get_name()
...: print(class_name)
...:
Lcom/google/android/gms/internal/ib$3;
Lcom/google/android/gms/internal/ib$4;
Lcom/google/android/gms/internal/ib$5;
Lcom/google/android/gms/internal/jf$1;
Lcom/google/android/gms/internal/jf$2;
Lcom/google/android/gms/internal/jf$3;
Lcom/google/android/gms/internal/jf$4;
注意结果中的$
是用于分隔外部类
和内部类
的,按照[外部类]$[内部类]
的格式。
根据string,来search package,并打印search得到的结果
In [1]: a,d,dx = AnalyzeAPK("1.apk", decompiler="dad")
In [3]: res = dx.get_tainted_packages().search_packages('Landroid/support/v4/widget')
In [13]: analysis.show_Path(d, res[0])
1 Landroid/support/v4/widget/SearchViewCompatHoneycomb;->newOnQueryTextListener(Landroid/support/v4/widget/SearchViewCompatHoneycomb$OnQueryTextListenerCompatBridge;)Ljava/lang/Object; (0x4) ---> Landroid/support/v4/widget/SearchViewCompatHoneycomb$1;-><init>(Landroid/support/v4/widget/SearchViewCompatHoneycomb$OnQueryTextListenerCompatBridge;)V
res[0]
就是搜索结果中的第一个package。由show_Path()结果可知,搜索到的Landroid/support/v4/widget/SearchViewCompatHoneycomb
(注意这其实是一个class),就是我们需要的结果。
获取整个APK中所有的methods
In [40]: f = d.get_methods()[0]
In [41]: f.pretty_show()
########## Method Information
Landroid/support/v4/net/TrafficStatsCompat$BaseTrafficStatsCompatImpl$SocketTags;-><init>()V [access_flags=private constructor]
########## Params
local registers: v0...v1
- return: void
####################
***************************************************************************
<init>-BB@0x0 :
0 (00000000) invoke-direct v1, Ljava/lang/Object;-><init>()V
1 (00000006) const/4 v0, -1
2 (00000008) iput v0, v1, Landroid/support/v4/net/TrafficStatsCompat$BaseTrafficStatsCompatImpl$SocketTags;->statsTag I
3 (0000000c) return-void
***************************************************************************
########## XREF
F: Landroid/support/v4/net/TrafficStatsCompat$BaseTrafficStatsCompatImpl$SocketTags; <init> (Landroid/support/v4/net/TrafficStatsCompat$1;)V 0
####################
package级别的分析
根据分析结果对象
类的说明,package是可以用get_tainted_packages()
来获取的。但在我本机上实验发现两个问题:
-
get_tainted_packages()
获取的是class级别的信息,比如:
In [17]: pkg = dx.get_tainted_packages()
In [20]: for i in pkg.get_packages():
...: print(i)
(<androguard.core.analysis.analysis.TaintedPackage object at 0x1efbbd10>, 'Ljava/io/FileNotFoundException;')
但其得到的Ljava/io/FileNotFoundException
是一个class,并不是package。
-
get_tainted_packages()
获取的是所有tainted package
,并非所有package。关于什么是tainted package
,在*, androguard开源github, 看雪论坛都发帖问了这个问题,给Androgurad的作者也发了邮件询问,已经得到回复。
根据我的理解,只要将class信息(比如Ljava/io/FileNotFoundException
)中的class(FileNotFoundException
)去掉,就是package了(Ljava/io
)。所以我用下面的方式来获取APK中的所有package。
根据class,获取所有package。
pkgs = set()
for c in d.get_classes():
s = c.get_name()
pkg_without_classinfo = s.replace('/{}'.format(s.split('/')[-1]), '')
pkgs.add(pkg_without_classinfo)
调用某个特殊API的class-method信息
假设要查询这个API(class: ContextCompat, method: buildPath)被APK中哪些类调用
In [76]: paths = dx.get_tainted_packages().search_methods('ContextCompat', 'buildPath', '.')
In [77]: for p in paths:
...: i = p.get_src_idx()
...: m = d.get_method_by_idx(i)
...: print(m.get_class_name())
...: print(m.get_name())
...:
...:
Landroid/support/v4/content/ContextCompat;
getExternalCacheDirs
Landroid/support/v4/content/ContextCompat;
getExternalFilesDirs
Landroid/support/v4/content/ContextCompat;
getObbDirs
可知这个API被三个地方调用:
* 类Landroid/support/v4/content/ContextCompat中的方法getExternalCacheDirs代码中调用过这个API
* 类Landroid/support/v4/content/ContextCompat中的方法getExternalFilesDirs代码中调用过这个API
* 类Landroid/support/v4/content/ContextCompat中的方法getObbDirs代码中调用过这个API
class级别的分析
获得整个类的反编译后的samli代码
In [21]: class_list = d.get_classes()#获取APK中的所有class
In [45]: class_item = class_list[10]# 第10个class
In [46]: class_item.get_class_data().pretty_show()
获取类的Java代码
In [21]: class_list = d.get_classes()#获取APK中的所有class
In [45]: class_item = class_list[10]# 第10个class
In [87]: class_item.source()
获取类的直接方法个数
In [45]: class_item = class_list[10]
In [47]: class_item.get_class_data().get_direct_methods_size()
Out[47]: 8
注意这里方法只包括类自己创建的方法,里面当然是包括了构造函数的,但不包括继承的方法。
获取类的虚拟方法个数
class_item.get_class_data().get_virtual_methods_size()
获取类的静态变量个数
class_item.get_class_data().get_static_fields_size()
获取类的实例变量个数
class_item.get_class_data().get_instance_fields_size()
获取类的(private, public)信息
class_item.get_access_flags()
获取类的名称
class_item.get_name()
获取父类的名称
class_item.get_superclassname()
function级别的分析
获取method对象
In [2]: class_list = d.get_classes()
In [3]: class_item = class_list[10]
In [4]: methods = class_item.get_methods()
In [6]: m = methods[1]
获取method的访问属性(public static)
In [14]: m.get_access_flags_string()
Out[14]: 'public static'
获取method的大小
函数对象在dex文件中占的空间。
In [24]: m.get_length()
Out[24]: 5
获取method的Java代码
m.source()
public void setStyle(int p3, int p4)
{
this.mStyle = p3;
if ((this.mStyle == 2) || (this.mStyle == 3)) {
this.mTheme = 16973913;
}
if (p4 != 0) {
this.mTheme = p4;
}
return;
}
获取method的描述符
m.get_descriptor()
(I I)V # 说明有两个形参,都是int型,返回值是void
对比它的Java代码,就能知道其含义。
获取method的基本信息
In [21]: m.get_information()
Out[21]:
{'params': [(1, 'android.accessibilityservice.AccessibilityServiceInfo')],
'registers': (0, 0),
'return': 'boolean'}
获取method中的const string
In [41]: v = dx.get_tainted_variables()
In [42]: for c in class_list:
...: ms = c.get_methods()
...: for m in ms:
...: d = v.get_strings_by_method(m)
...: if(len(d)>0):
...: for k in d:
...: print( 'STRING: {}\n'.format(k.get_info()) )
...: print( m.show() )
...: print('\n\n')