在python中直接调用androguard

androguard是Android恶意软件分析工具,主要用于APK的静态分析。相比于IDA/VTS之类的工具,androguard的扩展性更强,开发者可以将其作为自己项目中的一个模块。本文详细讲解怎么在自己的py代码中调用androguard。

androguard目前最新的2.0版本还不兼容python3。所以下面的过程都在python2.7上进行。python3的用户可以参考这篇文章配置一个virtualenv的pythong2环境。

安装androguard

本文的环境为:Windows 10 + python 2.7.11 + androguard 2.0 
安装androguard的步骤为:

  • (1) 下载androguard2.0
  • (2) 安装androguard2.0
python setup.py install
  • (3) 安装其他依赖

python中导入androguard

从androguard源码中可以看出,其androguard/就是一个包(从目录下含有init.py可以看出)。而包是可以在python导入,并使用其方法的。 
从androguard的源码androlyze.py中也可以看到,这个包是可以被导入的。

所以我们只要在代码中声明androguard的位置,并将需要的androguard模块导入,即可使用androguard的函数。

import os
import sys
# 根据自己py代码的位置,确定Androguard-2.0的路径
androguard_module_path = os.path.join( os.path.dirname(os.path.abspath(__file__)), 'Androguard-2.0/androguard' )
if not androguard_module_path in sys.path:
    sys.path.append(androguard_module_path)
# 导入核心的三个模块
from androguard.core.bytecodes import apk
from androguard.core.bytecodes import dvm
from androguard.core.analysis import analysis

使用androguard

def get_androguard_obj(apkfile):
    a = apk.APK(apkfile, False, "r", None, 2)
    #获取APK文件对象
    d = dvm.DalvikVMFormat(a.get_dex())
    #获取DEX文件对象
    x = analysis.VMAnalysis(d)
    #获取分析结果对象
    return (a,d,x)

sp = '1.apk'
    
if __name__=='__main__':
    ao = get_androguard_obj(sp)
    x = ao[2]
    pkgs = x.get_tainted_packages()
    for pkg in pkgs.get_packages():
        print(pkg)
        #打印package name
-------------------------------------------------------------------------

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)# 分析结果对象
  
  
  • 1
  • 2
  • 3
  • 4
  • 1
  • 2
  • 3
  • 4

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;
  
  
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17

注意结果中的$是用于分隔外部类内部类的,按照[外部类]$[内部类]的格式。

根据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
  
  
  • 1
  • 2
  • 3
  • 4
  • 1
  • 2
  • 3
  • 4

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
####################
  
  
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19

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;')
  
  
  • 1
  • 2
  • 3
  • 4
  • 1
  • 2
  • 3
  • 4

但其得到的Ljava/io/FileNotFoundException是一个class,并不是package。

  • get_tainted_packages()获取的是所有 tainted package,并非所有package。关于什么是tainted package,在stackoverflowandroguard开源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)
  
  
  • 1
  • 2
  • 3
  • 4
  • 5
  • 1
  • 2
  • 3
  • 4
  • 5

调用某个特殊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

  
  
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15

可知这个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()
  
  
  • 1
  • 2
  • 3
  • 1
  • 2
  • 3

获取类的Java代码

In [21]: class_list = d.get_classes()#获取APK中的所有class
In [45]: class_item = class_list[10]# 第10个class
In [87]: class_item.source()
  
  
  • 1
  • 2
  • 3
  • 1
  • 2
  • 3

获取类的直接方法个数

In [45]: class_item = class_list[10]
In [47]: class_item.get_class_data().get_direct_methods_size()
Out[47]: 8
  
  
  • 1
  • 2
  • 3
  • 1
  • 2
  • 3

注意这里方法只包括类自己创建的方法,里面当然是包括了构造函数的,但不包括继承的方法。

获取类的虚拟方法个数

class_item.get_class_data().get_virtual_methods_size()
  
  
  • 1
  • 1

获取类的静态变量个数

class_item.get_class_data().get_static_fields_size()
  
  
  • 1
  • 1

获取类的实例变量个数

class_item.get_class_data().get_instance_fields_size()
  
  
  • 1
  • 1

获取类的(private, public)信息

class_item.get_access_flags()
  
  
  • 1
  • 1

获取类的名称

class_item.get_name()
  
  
  • 1
  • 1

获取父类的名称

class_item.get_superclassname()
  
  
  • 1
  • 1

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]
  
  
  • 1
  • 2
  • 3
  • 4
  • 1
  • 2
  • 3
  • 4

获取method的访问属性(public static)

In [14]: m.get_access_flags_string()
Out[14]: 'public static'
  
  
  • 1
  • 2
  • 1
  • 2

获取method的大小

函数对象在dex文件中占的空间。

In [24]: m.get_length()
Out[24]: 5
  
  
  • 1
  • 2
  • 1
  • 2

获取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;
    }
  
  
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13

获取method的描述符

m.get_descriptor()

(I I)V # 说明有两个形参,都是int型,返回值是void
  
  
  • 1
  • 2
  • 3
  • 1
  • 2
  • 3

对比它的Java代码,就能知道其含义。

获取method的基本信息

In [21]: m.get_information()
Out[21]:
{'params': [(1, 'android.accessibilityservice.AccessibilityServiceInfo')],
 'registers': (0, 0),
 'return': 'boolean'}
  
  
  • 1
  • 2
  • 3
  • 4
  • 5
  • 1
  • 2
  • 3
  • 4
  • 5

获取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')

转自: http://blog.csdn.net/ybdesire/article/details/52629142

  • 6
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值