专栏文章 Android 逆向工程-解析 apk

opentest-oper@360.cn · July 23, 2020 · 2235 hits

Android 逆向工程 - 解析 apk

在 Android 开发中, 我们很少使用 Android 逆向去分析 apk 文件的, 但是作为一个测试人员,我们要对这个 apk 文件进行一系列的分析,审核,测试。
这篇文章讲解如何解析一个 apk 文件,主要从下面几方面介绍:
● 解析前准备环境介绍
● 解析出 apk 的一些基本信息
● 解析出方法调用图

解析前环境介绍

使用语言:python
使用的 python 库:androguard
本章使用的 apk 文件:自己编写一个 apk,apk 文件最好不要混淆

我们先对 APK 文件进行一些简单的介绍

我们解压一个 apk 文件,解压后的目录如上图所示
● META-INF 目录
   信息描述,签名等用途。
● res 目录
   工程的资源文件,以主工程为主,其他文件 (jar 包) 会合并到该目录下;但 values 文件将不会出现在此目录下,因为已经将其编译 resource.arsc 文件中;raw 文件会保持原有内容不会被编译。
● resources.arsc
   编译后的二进制资源文件, 在网上查了说,很多做汉化补丁就是修改这个资源包进行汉化的。
● classes.dex
   虚拟机执行的文件。
● AndroidManifest.xml
   清仓文件

解压 APK 文件代码如下:

def unzip_file(zip_src, dst_dir):
    print(dst_dir)
    r = zipfile.is_zipfile(zip_src)
    if r:
        fz = zipfile.ZipFile(zip_src, 'r')
        for file in fz.namelist():
        fz.extract(file, dst_dir)
        searchDirFile(dst_dir)
    else:
        print('This is not zip')

解析 APK 的一些基本信息

这边我们使用 androguard 库文件,
通过 androguard 的 AnalyzeAPK 方法, 会返回三个对象:apk, dex, dx

from androguard.misc import AnalyzeAPK
apk, dex, dx = AnalyzeAPK(filePath)

这三个对象对应的类别是:下面会分别介绍

androguard.core.bytecodes.apk.APK
androguard.core.bytecodes.dvm.DalvikVMFormat
androguard.core.analysis.analysis.Analysis

apk:APK 文件对象
这个 apk 文件对象,其实就是读取 AndroidManifest.xml 文件, 了解过 Android 的程序员应该知道,这个文件中就是清仓文件, 我们申请一些权限,注册 Activity, Service, Broadcast,ContentProvader 都在清仓文件中申请。

注意:我们一般在编写代码的时候,有一些注册文件可能只注册了,但是没有实际的实现,我们在编译项目的时候,这些文件注册虽然会报错,但是不影响编译,运行的时候也不会报错,所以 apk 只是单纯的读取 AndroidManifest.xml 文件,并不会验证读取文件的有效性。解决方法我们下面在具体介绍。

我们获取到的一些信息代码:

permissions = apk.get_permissions() # APK 获取权限信息
activities = apk.get_activities() # APK 全部的 activity
service = apk.get_services() # APK 全部的 service
receiver = apk.get_receivers() # APK 全部的 广播
provider = apk.get_providers() # APK 全部的 providers
packagename = apk.get_package() # 获取当前 APK 的名字
appname = apk.get_app_name() # 获取当前 appName

APK 文件对象可以直接获取到一些基本信息,但是我们如何解析类的信息,或者更复杂的信息呢?我们下面说下其它两个文件对象,并解决上面提到的信息不准确的问题。

解析出方法调用图

dex 文件对象
Android 在编写代码的时候,新建 .java 文件,编译为 .class 文件,要在 Android 虚拟机上执行,需要对 .class 文件 dx 处理,生成 .dex 文件。

有兴趣可以参考
浅谈 Android Dex 文件

我们在上图解压 apk 文件后,生成的压缩文件夹中,可以看到有两个 .dex 文件,这是因为 一个 .dex 文件最大方法数是 65535,对 apk 进行的分包。

我把之前 apk 中的 dex 文件进行反汇编,可以看到,第一个 .dex 文件中,包含的一些依赖包的代码,第二个 .dex 文件中就是我们自己编写的文件。

我们可以使用 dex 对象, 获取文件中所有类的,所有方法,所有的成员变量和字符串。注意, 这边获取的 dex 对象是一个 list

类似于上图,我获取了我们自己编写的代码的 dex 对象, 获取了所有的类和方法,这边只是单纯的获取,只存在一个关系,就是可以看出继承的父类是什么。

看起来很完美了,也可以解决我们上面提到的问题。但是和我们最终目标还有有点远,就是获取的方法调用图。我们最后一个 dx 即将登场。

dx 分析结果对象
主要作用, 就是分析 .dex 文件,它不是一个列表,它是把所有的 .dex 对象合并起来分析,我们就利用 dx 来解析出方法调用图。我们先按照一个类来说明。

这里使用:

classAnalysis = dx.classes['Lcom/example/songzekun/myapplication/MainActivity;']
    for meth in classAnalysis.get_methods():
        for _, call, _ in meth.get_xref_from():
            print("from -> {} -- {}".format(call.class_name, call.name))
        for _, call, _ in meth.get_xref_to():
            print("to -> {} -- {}".format(call.class_name, call.name))

我们先拿去其中的一个类, 获取当前类中所有的方法。

上图中总共有三个方法, 一个是类的初始化方法, 一个是 getNumber 方法, 还有一个是 getAndroidID 方法,

from 是方法的调用来源, to 是当前方法中执行了那些方法, 我们可以根据这样的关系, 来构建整个应用的类,方法之间的调用图。

当然你还需要对一些 api 的方法进行一些过滤。下面代码是我对一些基本信息的过滤,过滤结束后,就是我们真正实现的方法。

# 我们排除一下
def screenClass(packagename, activities, service, receiver, provider):
    packagename = packagename.replace('.', '/')
    classAnalysis = dx.get_internal_classes()
    className = []
    activityName = []
    serviceName = []
    receiverName = []
    providerName = []
    for item in classAnalysis:
        if packagename in item.name:
           className.append(item.name)
    for item in activities:
        item = changeClass(item)
        if item in className:
            activityName.append(item)
    for server in service:
        item = changeClass(item)
        if server in className:
            serviceName.append(server)
    for rece in receiver:
        item = changeClass(item)
        if rece in className:
            receiverName.append(rece)
    for pro in provider:
        item = changeClass(item)
        if pro in className:
            providerName.append(pro)
    return className, activityName, serviceName, receiverName, providerName

总结

到这里,我也介绍完毕,总体说来,就是 apk 的静态扫描技术,我个人就以这些技术做一些应用场景,欢迎大家留言讨论。

APK 代码结构展示

扫描整个 apk 的代码结构,可以检查冗余方法,未被使用的方法。生成 apk 结构图,增加测试人员测试深度,降低测试人员技术强度。

精准测试用例推

依据原有的执行流程,可以匹配新建出一些 CASE,可以增加功能覆盖度。

APK 上架自动检测

根据 APK 文件直接提取代码特征,应用特征,对当前的 APK 做分类(比如一些木马病毒的的 APK), 对 APK 进行风险评估, 也可以作 APK 错误扫描,崩溃预测,敏感权限申请等等。

如果觉得我的文章对您有用,请随意打赏。您的支持将鼓励我继续创作!
No Reply at the moment.
需要 Sign In 后方可回复, 如果你还没有账号请点击这里 Sign Up