温馨提示:如果想看速看完文章和评论,请开启弹幕模式

Android应用程序逆向入门详解

我们每天都会在Google Play商店看到一系列新的Android应用程序,从游戏到公用设施,再到物联网设备客户端等等,几乎我们生活中的每一个方面都可以通过一个app以某种方式进行控制。我们有智能家居,智能健身设备和智能咖啡机 ……但是这种APP是否智能或者是否安全:)

逆向Android APP可以用一个(相对)简单而有趣的方式来进行讲解,这就是为什么我决定写这篇博客文章,我将尝试解释基础知识,并给大家一些我的“技巧”来让大家更快,更有效的逆向APP。

我不会深入研究技术细节,你可以自学Android的工作原理,Dalvik VM是如何工作等等,这将是一个非常基本的实用指南,而不是一个充满理论且都是无用东西的文章。

让我们开始吧!

前提准备

为了遵循APK 逆向的介绍,有几个前提准备:

一个工作的大脑(我不再认为这是理所当然的……)

Android智能手机(doh!)。

您有Java编程语言的基本知识(如果您阅读它,您会理解它)。

您的计算机上安装了JRE。

您安装了adb。

您的智能手机上已启用安卓开发者模式并开启USB调试。

什么是APK?

Android应用程序打包为APK(Android Package)文件,该文件实质上是一个ZIP文件,其中包含编译代码,资源,签名,清单(manifest)以及软件运行所需的所有其他文件。作为ZIP文件,我们可以使用unzip命令(或者使用其他解压软件)来查看其内容:

unzip application.apk -d application

以下是您可以在APK中找到的内容。

/AndroidManifest.xml (file)

这是描述应用程序请求什么权限的XML清单文件的二进制表示形式(请记住,某些权限可能会在APP运行时请求并且未在此处声明),那里有什么活动(GUI),什么服务(在没有UI的情况下在后台运行的东西)以及什么接收器(可以接收和处理系统事件的类,例如设备启动或传入SMS)。

一旦反编译(稍后会更多),它将如下所示:

<?xml version="1.0" encoding="utf-8" standalone="no"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.company.appname" 
                                                                     platformBuildVersionCode="24" 
                                                                     platformBuildVersionName="7.0">
    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
    <uses-permission android:name="android.permission.INTERNET"/>
    <application android:allowBackup="true" android:icon="@mipmap/ic_launcher" 
        android:label="@string/app_name" 
        android:supportsRtl="true" android:theme="@style/AppTheme">
        <activity android:name="com.company.appname.MainActivity">
            <intent-filter>
                <action android:name="android.intent.action.MAIN"/>
                <category android:name="android.intent.category.LAUNCHER"/>
            </intent-filter>
        </activity>
    </application>

</manifest>

请记住,这是隔离应用程序“入口点”的完美起点,即为了理解整个软件的逻辑而首先要逆向的类。例如,在这种情况下,我们将开始检查被声明为应用程序主UI的类com.company.appname.MainActivity。

/assets/* ( folder )

该文件夹将包含特定的应用程序文件,例如APP可能需要播放的wav文件,自定义字体文件等。反过来说,这通常不是很重要,除非你在软件内部找到对这些文件的函数引用.

/res/* ( folder )

所有资源,如活动xml文件,图像和自定义样式都存储在此处。

/resources.arsc ( file )

这是所有资源的“索引”,长话短说,在每个资源文件中都分配了一个数字标识符,应用程序将使用该标识符来标识特定条目,并将resources.arsc文件映射到它们的标识符……没有什么值得关注的。

/classes.dex ( file )

该文件包含APP的Dalvik(运行Android应用程序的虚拟机)字节码,请让我更好地解释它。Android应用程序(大多数情况下)是使用Java编程语言开发的。然后将java源文件编译成Dalvik虚拟机最终将执行的字节码……类似于Java源文件编译为.class文件。

长话短说,这个文件包含了APP的业务逻辑,这就是我们感兴趣的内容。

有时你也会找到一个classes2.dex文件,这是由于DEX格式,它可以限制你在单个dex文件中声明的类的数量,在历史的某个时刻,Android APP变得越来越大,所以Google必须调整这种格式,支持可以声明其他类的辅助.dex文件。

从我们的角度来看,这并不重要,我们要使用的工具能够检测到它并将其附加到反编译管道。

/libs/ ( folder )

有时候应用程序需要执行native代码,它可以是图像处理库,游戏引擎等等。在这种情况下,这些.so ELF库将在该libs文件夹中找到,分为特定于体系结构的子文件夹(因此该APP将在ARM,ARM64,x86等上运行)。

/META-INF/ ( folder )

每个Android APP都需要使用开发人员证书进行签名才能在设备上运行,即使调试版本由调试证书签署,该META-INF文件夹也包含有关APK内部和开发人员的文件信息。

在这个文件夹里面,你通常会发现:

MANIFEST.MF文件列出了apk的所有文件,以及这些文件对应的SHA-1或SHA-256哈希值

CERT.SF文件非常像MANIFEST.MF,但是用RSA密钥签名。

CERT.RSA文件包含用来给CERT.SF文件签名的公钥和摘要

这些文件对于保证APK完整性和代码的所有权非常重要。有时候检查这样的签名可以非常方便地确定谁开发了一个给定的APK。如果你想获得关于开发者的信息,你可以使用openssl命令行工具:

openssl pkcs7 -in /path/to/extracted/apk/META-INF/CERT.RSA -inform DER -print

这将打印一个输出,如:

PKCS7: 
  type: pkcs7-signedData (1.2.840.113549.1.7.2)
  d.sign: 
    version: 1
    md_algs:
        algorithm: sha1 (1.3.14.3.2.26)
        parameter: NULL
    contents: 
      type: pkcs7-data (1.2.840.113549.1.7.1)
      d.data: <ABSENT>
    cert:
        cert_info: 
          version: 2
          serialNumber: 10394279457707717180
          signature: 
            algorithm: sha1WithRSAEncryption (1.2.840.113549.1.1.5)
            parameter: NULL
          issuer: C=TW, ST=Taiwan, L=Taipei, O=ASUS, OU=PMD, CN=ASUS AMAX Key/emailAddress=admin@asus.com
          validity: 
            notBefore: Jul  8 11:39:39 2013 GMT
            notAfter: Nov 23 11:39:39 2040 GMT
          subject: C=TW, ST=Taiwan, L=Taipei, O=ASUS, OU=PMD, CN=ASUS AMAX Key/emailAddress=admin@asus.com
          key: 
            algor: 
              algorithm: rsaEncryption (1.2.840.113549.1.1.1)
              parameter: NULL
            public_key:  (0 unused bits)
              ...
              ...
              ...

这对我们来说可能是黄金,例如我们可以使用这些信息来确定某个应用程序是否真的由(比方说)Google签名,或者是由第三方重新签名或修改的。

我是如何获得应用程序的APK文件?

现在我们已经对我们应该在APK中找到的内容有了一个基本概念,我们需要一种方法来真正获取我们感兴趣的应用程序的APK文件。有两种方法,1,将其安装在设备上并用于adb获取它。2,使用在线服务下载。

用ADB拉一个应用程序

首先,让我们将我们的智能手机插入电脑的USB端口,并获取已安装软件包及其命名空间列表:

adb shell pm list packages

这将列出智能手机上的所有软件包,一旦找到要逆向的软件包命名空间(比如:com.android.systemui),我们来看看它的物理路径是什么:

adb shell pm path com.android.systemui

最后,我们有APK路径:

package:/system/priv-app/SystemUIGoogle/SystemUIGoogle.apk

让我们从设备中拉出它:

adb pull /system/priv-app/SystemUIGoogle/SystemUIGoogle.apk

到这里,你拿到了你想要逆向的APK!

使用在线服务

如果您不想在设备上安装应用程序,则可以使用多种在线服务(例如,如果您想要逆向恶意软件,您希望先获得这个文件,然后再安装在干净的设备上),这里有我使用的列表:

Apk-DL

Evozi Downloader

Apk Leecher

请记住,一旦您从这些服务下载APK,最好先检查开发人员证书,以便100%确定您下载了正确的APK,而不是一些重新打包和重新签名的掺杂很多广告的恶意软件。

网络分析

现在我们开始进行一些测试以了解APP在运行时功能。我的第一个测试通常是检测APP本身产生的网络流量,而为了做到这一点,我的首选工具是bettercap ……好吧,这就是为什么我首先开发它。

确保你已经安装了bettercap,并且你的电脑和Android设备都在同一个wifi网络上,那么你可以开始中间人攻击智能手机(IP地址:192.168.1.5),并从终端实时看到它的流量:

sudo bettercap -T 192.168.1.5 -X

-X选项将使嗅探器,只要你启动APP,你应该看到一堆HTTP and/or HTTPS请求的服务器,现在你知道APP正在发送数据到那个服务器,让我们来看看发送了什么数据。

sudo bettercap -T 192.168.1.5 --proxy --proxy-https --no-sslstrip

这将从被动嗅探模式切换到代理模式。所有的HTTP和HTTPS流量都会被bettercap拦截(如果需要的话,可以修改)。如果APP正确使用公钥固定(因为每个APP都应该),您将无法看到其HTTPS流量,但不幸的是,根据我的经验,这只发生在极少数APP中。

从现在开始,在检查流量的同时继续在APP上触发操作(您也可以Wireshark一起捕获PCAP文件以便稍后检查),过了一段时间后,您应该对其使用的协议,出于什么目的,有或多或少的完整的认识。

静态分析

在网络分析之后,我们收集了大量的URL和数据包,我们可以使用这些信息作为我们的出发点,这就是我们在对APP进行静态分析时要查找的内容。“静态分析”意味着你现在不用运行这个APP程序,你只需要研究它的代码。大多数时候,这是你需要逆向某种关键点的所有东西。

您可以使用不同的工具来实现此目的,我们来看看最受欢迎的工具。

apktool

APKTool是您想要使用的第一个工具,它能够将AndroidManifest文件反编译为其原始XML格式的resources.arsc文件,并且还会将classes.dex(classes2.dex如果存在)文件转换为称为SMALI。看起来像:

.super  Ljava / lang / Object;
.method public static main([ Ljava / lang / String;)V
    .registers 2
   sget-object v0,Ljava / lang / System; - > out:Ljava / io / PrintStream;
   const-string     v1,“Hello World!”
   invoke-virtual {v0,v1},Ljava / io / PrintStream; - > println(Ljava / lang / String;)V
    返回void的
.end方法

但是不要担心,在大多数情况下,这不是您要读取的最终语言来逆向应用程序;)

给定一个APK,这个命令行会反编译它:

apktool d application.apk

一旦完成,application文件夹就会被创建,你会在那里找到所有apktool的输出。

您也可以使用apktool反编译APK,修改它然后重新编译它(例如,我做的:为了让Nike + APP显示更多的调试日志实例。),除非其他工具反编译失败,否则您不太可能需要阅读smali代码,现在让我们来看看其他工具;)

jADX

jADX套件让您只需加载APK并查看其Java源代码。后台发生的是,jADX正在将APK反编译为smali,然后将smali转换回Java。不用说,阅读Java代码比阅读smali更容易,正如我已经提到的:)

APK加载后,您会看到如下的用户界面:

jADX最好的特性之一就是字符串/符号搜索(这个按钮),它允许你搜索URL,字符串,方法以及你想要在应用程序的代码库中找到的任何内容。

此外,还有Find Usage菜单选项,只需突出显示某个符号并右键单击它,此功能将为您提供该符号的每个引用的列表。

Dex2Jar and JD-Gui

类似于jADX的是dex2jar和JD-GUI工具,一旦安装完成,您将使用dex2jar将APK转换为JAR文件:

/path/to/dex2jar/d2j-dex2jar.sh application.apk

一旦你有了JAR文件,只需用JD-GUI打开它,你就会看到它的Java代码,非常像jADX:

不幸的是,JD-GUI并不像jADX那样功能丰富,但有时当一个工具失败时,你必须尝试另一个工具,希望能够更幸运。

JEB

作为最后的手段,你可以尝试JEB反编译器。这是一款非常优秀的软件,但不幸的是它不是免费的,如果你想试一试,有一个试用版,下面是它的样子:

JEB还具有一个ARM反汇编程序(在APK中有本地库时很有用)和一个调试器(对于动态分析非常有用),但它又不是免费的,并且不便宜。

本地二进制文件的静态分析

如前所述,有时您会在APK文件夹中的lib目录下找到本地库(.so共享对象),并在阅读Java代码时找到native方法如下声明:

public native String stringFromJNI();

native关键字表示这个方法的实现不是在dex的文件里,但是,相反,它是声明从本地代码即通过所谓的Java Native InterfaceJNI执行。

在native方法附近,你通常也会找到这样的东西:

System.loadLibrary("hello-jni");

这将告诉你在哪个本地库中实现了该方法。在这种情况下,您需要一个ARM(或x86,如果libs文件夹内有x86子文件夹)反汇编以逆向native对象。

IDA

每个合格的逆向人员应该知道的第一个反汇编器和反编译器是Hex-Rays IDA,它是用于native代码的最牛的逆向工具。除了IDA许可证之外,您还可以购买decompiler许可证,在这种情况下,IDA还可以重建伪C类代码,从而允许您读取更高级别表示的so文件的逻辑。

不幸的是,IDA是一款非常昂贵的软件,除非你是专业地逆向native代码,否则真的不值得花费所有的资金用于单个工具。

Hopper

如果您没有足够的资金,但您需要逆向native代码,则可以尝试使用Hopper代替IDA。它绝对不如IDA好,但它便宜很多,对大多数情况来说都足够好。

Hopper支持GNU / Linux和macOS(没有Windows!),就像IDA一样,它有一个内置的反编译器,考虑到它的价格是相当合适的:

动态分析

当静态分析还不够时,可能是因为应用程序被混淆了,或者代码库太大而且复杂,无法快速找到您感兴趣的点,因此需要动态调试。动态分析仅仅意味着你将执行应用程序(就像我们在执行网络分析时所做的那样)并以某种方式使用不同的工具,策略和方法跟踪其执行情况。

沙箱

沙箱是一个黑箱动态分析策略,这意味着你不会主动跟踪到应用程序代码(像你这样在调试时),但你将执行该应用程序到某个容器中,该容器将为您记录最相关的操作,并在执行结束时显示报告。

Cuckoo-Droid

Cuckoo-Droid是自动化的Android恶意软件分析工具,一旦安装和配置好后,它会为您提供一个活动报告,其中包含APP包括的所有URL,所有DNS查询,API调用等等:

Joe Sandbox

移动Joe Sandbox是一款出色的在线服务,可让您上传APK并获取其活动报告,而无需安装或配置任何内容。

这是一个样本报告,您可以看到,这些信息与Cuckoo-Droid非常相似,此外还有一些启发式信息正在执行,以便将样本与其他已知应用程序进行行为关联。

调试

如果沙箱不能满足要求,你需要深入了解应用程序的行为,那么你需要调试它。假设你不会动态调试,调试一个应用程序意味着用调试器软件附加到正在运行的进程,设置断点,这些断点允许你停止执行并检查内存状态,并逐个地进入代码行,以便非常接近执行图。

启用调试模式

当应用程序编译并最终发布到Google Play商店时,通常是您正在查看的发布版本,这意味着开发人员已禁用调试功能,并且无法直接附加它。为了再次启用调试,我们需要使用apktool反编译应用程序:

apktool d application.apk

然后,您需要编辑生成的AndroidManifest.xml文件,并将该android:debuggable="true"属性添加到其applicationXML节点:

<?xml version="1.0" encoding="utf-8" standalone="no"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.company.appname" 
                                                                     platformBuildVersionCode="24" 
                                                                     platformBuildVersionName="7.0">
    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
    <uses-permission android:name="android.permission.INTERNET"/>
    <application android:allowBackup="true" android:icon="@mipmap/ic_launcher" 
        android:label="@string/app_name" 
        android:supportsRtl="true" 
        android:theme="@style/AppTheme"
        android:debuggable="true"> <-- !!! NOTICE ME !!! -->
        <activity android:name="com.company.appname.MainActivity">
            <intent-filter>
                <action android:name="android.intent.action.MAIN"/>
                <category android:name="android.intent.category.LAUNCHER"/>
            </intent-filter>
        </activity>
    </application>

</manifest>

一旦你更新了manifest,让我们重新编译应用程序:

apktool b -d application_path output.apk

现在让我们给他重新签名:

git clone https://github.com/appium/sign
java -jar sign/dist/signapk.jar sign/testkey.x509.pem sign/testkey.pk8 output.apk signed.apk

并将其重新安装到设备上(确保您保留原始版本):

adb install signed.apk

现在您可以继续调试应用程序^ _ ^

Android Studio

Android Studio是官方的Android IDE,一旦您的应用程序启用了调试模式,您可以使用此IDE直接附加到它并开始调试:

IDA

如果您有一个支持Dalvik调试的IDA许可证,您可以附加到正在运行的进程并通过smali代码调试,本文档介绍了如何执行此操作,但基本上这个想法是您上传ARM调试服务器(本地ARM二进制文件)在您的设备,用adb上启动它,并最终从IDA开始您的调试会话。

动态检测

动态检测意味着您想要在运行时修改应用程序行为,并且为了这样做,您可以在应用程序中注入一些“代理程序”,并最终用它来检测它。

您可能想要这样做是为了让应用程序跳过一些检查(例如,如果强制实施公钥固定,您可能希望通过动态检测来禁用它以轻松检查HTTPS流量),让它显示本来不应该显示的信息(解锁“Pro”功能或调试/管理活动)等。

Frida

Frida是一款非常好用的免费工具,可以将整个Javascript引擎注入Android,iOS和其他平台上的正在运行的进程中......但为何使用Javascript?因为一旦引擎被注入,您就可以以非常酷且简单的方式来测试应用程序,如下所示:

from __future__ import print_function
import frida
import sys
# let's attach to the 'hello process
session = frida.attach("hello")
# now let's create the Javascript we want to inject
script = session.create_script("""
Interceptor.attach(ptr("%s"), {
    onEnter: function(args) {
        send(args[0].toInt32());
    }
});
""" % int(sys.argv[1], 16))
# this function will receive events from the js
def on_message(message, data):
    print(message)
# let's start!
script.on('message', on_message)
script.load()
sys.stdin.read()

在这个例子中,我们只是在检查一些函数参数,但是您可以使用Frida做几百件事情,就是RTFM!并使用你的想象力:D

这里有一份很酷的Frida资源清单,尽情享受吧!

XPosed

我们的另一个选项是使用XPosed Framework。XPosed基本上是整个Dalvik虚拟机的一个仪器层,它需要你有一个已经root的手机才能安装它。

来自XPosedwiki

There is a process that is called "Zygote". This is the heart of the Android runtime. Every application is started as a copy ("fork") of it. This process is started by an /init.rc script when the phone is booted. The process start is done with /system/bin/app_process, which loads the needed classes and invokes the initialization methods.

This is where Xposed comes into play. When you install the framework, an extended app_process executable is copied to /system/bin. This extended startup process adds an additional jar to the classpath and calls methods from there at certain places. For instance, just after the VM has been created, even before the main method of Zygote has been called. And inside that method, we are part of Zygote and can act in its context.

The jar is located at /data/data/de.robv.android.xposed.installer/bin/XposedBridge.jar and its source code can be found here. Looking at the class XposedBridge, you can see the main method. This is what I wrote about above, this gets called in the very beginning of the process. Some initializations are done there and also the modules are loaded (I will come back to module loading later).

一旦你在你的智能手机上安装了XPosed,你就可以开始开发你自己的模块了(详细见:project wiki)),下面是一个例子,如何hookSystemUI应用程序的updateClock方法以便对其进行检测:

package de.robv.android.xposed.mods.tutorial;
import static de.robv.android.xposed.XposedHelpers.findAndHookMethod;
import de.robv.android.xposed.IXposedHookLoadPackage;
import de.robv.android.xposed.XC_MethodHook;
import de.robv.android.xposed.callbacks.XC_LoadPackage.LoadPackageParam;
public class Tutorial implements IXposedHookLoadPackage {
    public void handleLoadPackage(final LoadPackageParam lpparam) throws Throwable {
        if (!lpparam.packageName.equals("com.android.systemui"))
            return;
        findAndHookMethod("com.android.systemui.statusbar.policy.Clock", lpparam.classLoader, "updateClock", 
            new XC_MethodHook() {
            @Override
            protected void beforeHookedMethod(MethodHookParam param) throws Throwable {
                // this will be called before the clock was updated by the original method
            }
            @Override
            protected void afterHookedMethod(MethodHookParam param) throws Throwable {
                // this will be called after the clock was updated by the original method
            }
    });
    }
}

已经有很多用户提供的模块可供您根据自己的需要使用,学习和修改。

结论

我希望你发现这本参考指南对你的Android逆向很有用,请记住逆向时最重要的不是你正在使用的工具,而是你如何使用它,所以你必须学习如何为您的场景选择适当的工具,这是您只能借助经验学习的东西,足够的阅读并开始逆向!:D


发表评论

游客 表情
看不清楚?点图切换 送你一朵小花花~

众说纷纭(0)