乐于分享
好东西不私藏

安卓逆向草鸟入门

安卓逆向草鸟入门

1、引言

Android 逆向是一种深入分析 Android 应用程序的技术,主要目的是通过分析应用的代码、资源和行为来理解其功能、结果和潜在的安全问题。

关键点

APK 逆向工具

APK 基本机构

Android 逆向分析基础

如何找出APP的程序的入口点?

如何识别是否加壳,并且简单脱壳?

如何寻找一个页面的xml布局文件?

如何确定按钮所绑定的函数?

如何寻找app中涉及到的资源文件?

什么是java层逆向,什么是Native层逆向?

安装系统的四大组件如何应用

最后是安卓体系的梳理

2、APK 逆向工具

雷电模拟器

这里需要一个模拟器,用于模拟软件运行。

ADB

ADB(Android Debug Bridge),安卓开发者和逆向工程师的”瑞士军刀“。适用于调试、安装应用、日志分析。

JADX

APK 反编译工具,能将 APK 中的 DEX 文件(Dalvik Executable 文件)反编译成可读的 JAVA 源代码。JADX 的又是不仅在于他的易用性,还在反编译效果表现的非常优秀,能清晰显示反编译后的 Java 源代码。

GAD (Google Android Disassembler)

专注于 APK 底层字节码分析的工具。GAD 更侧重于字节码级别的反汇编。

JEB

高精度反汇编,可以解析传统的 DEX 文件,还能处理各种复杂的文件格式,包括加固的 APK、混淆处理的代码,甚至非标准的 Android 文件结构。

Frida 脱壳

强大的动态分析工具,广泛应用于逆向和安全测试。
adb -s 127.0.0.1:5555 shell
/data/local/tmp/fs64 &
配置参考文章:

3、APK 解析基础

理解 Android 应用的工作原理和内部结构之前,我们先了解应用打包的核心文件-APK(Android Package)。APK 文件是 Android 操作系统中的应用程序包,它包含了应用所有的资源、代码和必要的配置文件。
APK 的基本结构,它是以 ZIP 格式进行压缩打包的。通过解压后查看 APK 文件的目录结构,我们能更清晰了解每个组成部分的作用:
这是常见结构,主要组成部分。
这里以这道题为例子。我们下载并解压这个 APK 文件。

AndroidManifest.xml

这个文件是 Android 应用最关键的配置文件,可以理解为应用说明书和蓝图,他向系统声明了应用的基本属性、组件以及权限等。
他又包括了:
应用的包名(package):每个 Android 应用都有一个唯一的包名,通过包名来区分不同应用
应用的组件(Activities, Services, Broadcast Receivers, Content Providers):声明应用包含的组件,以及这些组件的属性和功能
权限声明:列出应用所需的权限,如访问网络、读取存储、使用相机等
应用主题和图标:定义应用的 UI 样式、图标等
最小 SDK 版本和目标 SDK 版本:确定应用能在什么版本的 Android 系统上运行。
这里我就以网上图片来举例,因为我这个文件好像是 DEX。
也能看出来是 XML 格式的,也规定了一下版本号在图中。
这里面的字段 是包含了应用的信息及权限定义。
package:定义了应用的包名,通常为反向域名格式,如 com.example.app。
android:versionCode:定义应用版本号
android:versionName:定义应用的版本名称
然后还有就是 字段,包含了应用的核心配置,主题、图标等。
:声明应用的各个界面(Activity),以及这些 Activity 的属性和行为。
:声明应用所需的权限,访问网络、发送短信等。
:定义组件的功能和响应的时间。

classes.dex

该文件包含了应用程序的可执行代码。他是应用的 Dalvik 字节码文件,也是安卓应用在运行时通过 Dalvik虚拟机 或 ART(Android Runtime)解释执行的核心文件。每个 Android 应用中,所有的 Java 源代码都经过编译后形成一个或多个 DEX(Dalvik Executable)文件,这些包含了应用的业务逻辑和代码实现。
这里为了方便理解,又找了几篇文章作为介绍。
首先是整个的逆向流程:输入apk—apktool解析为smali和dex—dex2jar转换为jar—使用jd-gui查看java代码

拓展:JVM、DVM、ART

这里 JVM 基于栈,DVM 基于寄存器。JAVA 虚拟机在运行程序时会频繁的从栈上读取写入数据,会很耗费 CPU 时间,而 Dalvik 虚拟机基于寄存器架构,数据的访问通过寄存器间直接通信,会比基于栈方式要快。
比如说这段代码的 foo 方法为例,编译成 class 后,反编译文件看 JAVA 字节码:
而编译生成 DEX 文件后,查看 Dalvik 字节码:
字节码中,代码指令变少了,执行速度也更快了。
ART 虚拟机,在了解之前,我们需要先了解 JIT(Just In Time,即时编译技术)和 AOT(Ahead Of Time,预编译技术)两种编译模式。
JIT 以 JVM 为例,javac 把程序编译成 JAVA 字节码,JVM 通过逐条解释字节码将其翻译成对应的机器指令,逐条读入,逐条解释翻译,执行速度必然比 C/C++ 编译后的可执行二进制字节码程序慢,为提高效率引入了 JIT 技术。
JIT 会在运行时分析应用程序的代码,识别哪些方法可以归类为热方法,这些方法会被 JIT 编译器编译成相对应的汇编代码,然后存储到代码缓存中,以后调用这些方法时就不用解释执行了,可以直接使用代码缓存中已编译好的汇编代码。
其实就是根据你的习惯,把常用的预编译了,想做预制菜一样,谁买的好,就提前做。
AOT 则是 C/C++ 这类语言,编译器在编译时直接将程序源码编译成目标机器码,运行时直接运行机器码。
Dalvik 虚拟机执行的就是 DEX 字节码,ART 虚拟机执行的时本地机器码。
Dalvik 执行的是 DEX 字节码,依靠 JIT 编译器去解释执行,运行时动态的执行频率很高的 DEX 字节码翻译成本地机器码,然后再执行。
因此安卓运行时从 Dalvik 虚拟机替换成 ART 虚拟机,并不要求开发者重新将自己的应用直接编译成目标机器码。应用程序仍然是一个包含 DEX 字节码的 APK 文件。所以安装应用时,DEX 中的字节码将被编译成本地机器码,之后每次打开应用,执行的都是本地机器码。
其实这里就用一张表即可表达。

resources.arsc

resources.arsc 文件包含了应用程序的所有编译后的资源映射信息。这个文件并不存储实际的资源内容(图片,字符串),而是存储资源与资源 ID 的映射关系。例如,他会保存应用中的字符串、颜色、尺寸、样式等信息以及这些资源的 ID。通过这个文件,Android 能在应用时快速访问和加载所需的资源。
网上的案例图片。

assets/

assets/ 目录包含了应用程序的原始资源文件,这些资源不经过编译,直接以原始形式存储。通常开发者可以在该目录中存放文件、音频、HTML 等,应用通过 API 来读取这些资源。

lib/

lib/ 目录包含了本地文件,通常通过 JNI(Java Native Interface)与 C/C++ 编写的本地代码。这些库文件可以针对不同的硬件架构(ARM/X86),进行编译,因此 lib/ 目录下通常会为每个架构创建相应的子目录。这个目录中存放本地库可以通过 Java 代码调用 JNI 接口实现与系统底层的交互。

res/

res/ 目录包含了 Android 应用所需的所有资源文件。与 assets/ 目录不同,res/ 目录中的资源文件是经过编译的,按照不同类型的资源进行组织,例如:
drawable/:存放图像资源(PNG、JPEG 等格式图片)
layout/:存放 XML 格式的布局文件,定义界面的结构
values/:存放各种配置文件,定义应用的常量、颜色、字符串等资源。
在 values/ 目录下,除了 strings.xml、colors.xml 等常见资源文件,还会有像 dimens.xml(尺寸定义文件)和 attrs.xml(自定义属性)等资源文件。

META-INF/

该目录与 Java 的 JAR 文件类似,用于存放 APK 文件的元数据,如签名文件、校验信息等。此目录主要包括了以下文件:
主要确保 APK 的完整性和安全性。

4、Android 逆向分析基础

Android 逆向分析是一个深入挖掘应用内部工作原理的过程,通常用于漏洞挖掘,恶意软件分析或应用的安全性研究。
我们本章深入 Android APK 的反编译与结构分析,剖析壳分析与绕过技术,以及如何对资源与布局文件进行分析。我们还会涉猎 Java 层的逆向技巧,以及如何在 Native 层逆向。

1、APK 反编译与结构分析:寻找程序入口点

这里讲几个点,app 入口一定在 mainfest 文件中,除非是被加载。
Learn:
MAIN 指定了程序入口地址。
LAUNCHER 是程序在手机桌面的图标。
如果只设置了 MAIN,没有设置 LAUNCHER,则程序可被安装到手机,但是没有图标。
如果只有 LAUNCHER,没有 MAIN,则程序不知道从哪个 Activity 启动,桌面不显示。
给多个 Activity 设置了 MAIN 和 LAUNCHER,则会在桌面显示多个 APP 图标,进入分别设置的 Activity 中。
Modify:
最简单的修改就是修改入口,改为自己的 Activity,从自己的 Activity 调用原来的 Activity。有些有些的开展广告、Logo 都是这么加的。
这里我们做一下这道题,我们下载后是一个 APK 文件。
这里我们要查以下壳,其实和 iot 安全的固件加密类似,个人理解。
如果 APP 加壳了,用 JADX 看到的是一堆没用的壳。
我们用 JADX 打开发现了 flag 相关的信息。
分析一下,这个包名,com.example.flag 是我们主要分析的点,然后入口活动 android:name: com.example.flag.MainActivity,我们需要访问这个 Main Acticity 类,所有的标志校验逻辑、按钮点击时间都在这里。
还发现了一点就是调试开关,他是开启了 debuggable 模式的。
在 Android 中,AndroidManifest.xml 文件是非常重要的,定义了应用的所有组件以及组件之间的关系,包括应用的入口点。打开 AndroidManifest.xml 文件后查找 activity 标签,它定义了各个活动。
这两条分别标记了应用的主入口和表示该 Activity 会出现在应用启动器中(桌面)

android:name=”com.example.flag.MainActivity”

这个就是我们 app 程序的入口点。

成功找到 activity 代码入口处,分析 activity 的生命执行流程
然后我们找到这个入口点开始分析,找到了一个 onCreate 函数。其实这个就是 App 启动时干的第一件事。我们直接看下面代码中的  button.setOnClickListener,这是按钮被点击后的出发逻辑。
我们也能在这里看到 flag 的相关信息。

String xx = editview.getText().toString();

if (xx.length() != 32 || xx.charAt(31) != ‘a’ || xx.charAt(1) != ‘b’ || (xx.charAt(0) + xx.charAt(2)) – 48 != 56) {

flag = 0;

}

我们看着段代码,分析他的需求:
这个字符串长度为 32
第三十二个字符为 a。
第一个字符为 b。
字符串第一个字符和第三个字符和 ASCII 码相加减去 48 等于 56。
就像这样。

2、壳分析与绕过:简单分析梆梆免费加固

许多应用都用加固技术防止反编译和分析。梆梆是一种常见的加固,主要通过修改 APK 结构,注入防护代码提高安全。

这里没找到com.example.how_debug.MainActivity 的代码实现,可以判定 app 加壳。还可以通过观察 APK 文件是否在 AndroidManifest.xml 配置 Application 来判定,该 App 实现了一个自定义操作,讲 Application 类进行自定义,修改成了com.SecShell.SecShell.ApplicationWrapper来实现加壳逻辑。

上文是参考文章所描述,下文是 ai 给出的答案:

这里面  android:name=”com.SecShell.SecShell.ApplicationWrapper”,正常的 App 这里通常是空的,或者指向开发者自己写的  com.example.how_debug.MyApplication,夹克应用程序他会强行把入口改成壳公司的代码。

入口 Activity 与 Application 的包名不符。 com.example.how_debug.MainActivity  这是主入口,但 Shell 入口是  com.SecShell.SecShell.ApplicationWrapper(怀疑是腾讯的代码)。当 App 启动时,系统会先运行 SecShell,这个壳程序会在后台偷偷把加密的 DEX 文件解密到内存中,然后再去调用真的  MainActivity  。

常见的壳。
这道题是梆梆加固:
根据 APK 文件是否存在 AndroidManifest.xml 配置 Application 信息,加固会做不同处理,通过上传 Application 不同配置的 APK 文件:
当 APK 配置有 Application 信息时,梆梆加固重写 Application 类
当 APK 未配置 Application 信息时,梆梆加固新建类,并在 AndroidManifest.xml 中配置自己的 Application。

使用 Frida-DEXDump 工具进行简单脱壳

adb -s 127.0.0.1:5555 shell
/data/local/tmp/fs64 &
frida-dexdump -D 127.0.0.1:5555 -f com.example.how_debug
这里先不演示了,因为关于版本问题,雷电9的版本太新了,根本打不开这个 App。
找的网上的截图。
整个脱壳流程就是这样。
这里再尝试使用其他脱壳工具吧。
这里我们使用BlackDEX,比较常用的

3、如何寻找一个 Acticity 页面的 xml 布局文件的位置

方法一

这里还是拿一到ctf举例
打开是这样的,我们输入东西会提示失败,然后 JDAX 分析一下吧。
找到程序的主入口,没加壳。然后我们找一下报错的位置。
这里是找到了错误点,然后就是分析一下这个位置了。
其实在这里就有点线索了,给我们密码了,这个函数,下面是要求,分析一下看看怎么 go on 吧。
其实可以看最关键的函数就行。
先看这里一个检查,就是检查我们登录框输入的东西,主要检测逻辑就是这个 if (check.checkPassword(str)),密码是啥取决于 Check 类里面的 CheckPassword,老办法,追踪一下。
这个就是主要逻辑了,看看哈,长度是 12 位,并且这个 pass[len] = ((255 – len)-100-原字符),然后最后结果要为 0。
可以换算成一个公式: ((255 – i) – 100) – 输入字符 == ‘0’
也就是说:输入字符 == (255 – i)-100-’0‘
这里’0‘ ASCII 码里面是 48,再推导:输入字符 == 107 – i
这里的 i 是代码中的 len,也就是从 0 开始遍历的。
那也就很简单了,我们输入的字符就是 107-0=107、107-1=106,以此类推转化为 ASCII 中对应的。
kjihgfedcba`
这就是最后我们要输入的密码了。
结果又是一张图片,这里就更清晰了,接下来我们就需要去找这张图片位置了,然后再找这个输入框。这里也就是第二个 Activity 了。
能看到这里有个 MainActivity2 啊,直接看他就行了。
重点关注这里,有一个安卓开发的概念:广播(Broadcast)
这段是隐藏意图广播,可能 Flag 在某个广播器里面。
下一步我们就需要去 JADX 树状图最上面的 Resources -> AndroidManifest.xml 里面去找,找 标签,看看有没有一个什么接收的标签,看看他的 android:name,然后去看 Java 类。
在这里能看到这样一段代码,看这就像是这张图片啊,找找这东西,去 res/drawable 里面看看。
确实找到了这张图片啊。
然后回到 AndroidManifest.xml 里面看到了接收的标签啊,有两个入口,去看看吧。
输入了这东西得到 Flag。
原理应该是这里有提示啊,有两个接收的入口。
在这儿呢,找到了,当接收 android.is.very.fun 的广播就会激活 NextContent。

方法2

这里还有第二种方法,就是使用 drozer,的作用是命令航直接向 App 内部组件(如广播、服务)发送命令。
在 MainActivity2 源码中,能看到这样的广播:
说明 App 内部有接收器在后台监听。如果输入的 str(频道名)对上了,直接触发隐藏逻辑。
或者我们用 ADB,这里我们使用 ADB 演示。
直接广播一下。
adb -s emulator-5554 shell am broadcast -a android.intent.action.MAIN -n com.example.test.ctf02/.GetAndChange –es s “android.is.very.fun”

4、Java 层代码逆向

再次拿到手,依旧没加壳,但这次是 Java 层面的审计。
这里能看到依旧是开启了调试模式,并且开启了备份啊。
这一段,我们能明显看到 MainActivity 中有 intent-filter 并且包含了 LAUNCHER 类,这意味着他是隐式导出的。
似乎看到了可以点,这部分,依旧是需要点击的。
能看到我们输入错误的答案他会报错,
找到了啊,我们应该要满足上面的代码。
这里就是一个变异的 ROT(凯撒密码)。
这里,This Is Flag Home,能看到,这层变换是字符在 [A-H] 或 [a-h]之间+18,在 [I-Z] 和 [i-z] 之间-8,
然后计算 m,这里 T -> L(T 是20个字母,在 I-Z,20-8=12,L)
h -> z(h 在 a-h,8+18=26,z)
依次类推: LpqaQsBpmNtiyPwu m

第二层

一旦上面成立,开始处理数组 b,y[12] = (char) (b[12]+16),超过字母则-26。
pvkq{m164675262033l4m49lnp7p9mnk28k75}每一个字母向后移16位。
flag{c164675262033b4c49fdb7f9cba28a75}

5、Native 层逆向

这里了解到,Native 层逆向需要分析 Android 应用中用的 C/C++ 代码。Native 代码通常通过 JNI 与 Java 层交互。要逆向 Native 需要反编译 APK 中的 .so 库文件。
这里这道题已经没了,所以直接放网上的 WP 了。
打开后是这样一个东西,依旧是点击模块啊。
直接找到这个验证模块,然后追踪这个 CheckString 函数。
上 IDA,搜索刚才的方法名,就是这个然后转伪代码,看一下。
这里就反汇编审计代码了,他就是进行了一个比较能看出来。
这里还要追踪到这个 TestDec 函数。
这样一段代码,主要功能就是两个循环体,第一个循环体将字符串才能够中间砍断,头频道尾,第二个循环体将字符串两两交换。
反正就是这么个流程。

5、安卓系统四大组件

1、活动(Activity):用于用户交互界面,处理屏幕显示和用户输入

2、服务(Service):用于后台服务,执行长期运行的操作而不直接与用户交互

3、广播接收者(Broadcast recive):接受和处理广播信息,能监听系统或其他应用的事件

4、内容提供者(Content provider):支持多个应用中存储和读取数据,数据库

1、Activity 活动

Activity 是 Android 应用中负责用户界面和交互的核心组件。每个应用至少有一个 Activity,它通常作为应用的启动点,用于展示 UI 并处理用户输入。其生命周期方法管理与用户的交互,通过 Intent 在多个 Activity 间跳转。
可以理解为一个程序的入口。
主要功能在于:
用户界面的展示和用户的交互
每个应用至少一个 Activity,通常应用的启动点就是一个 Activity
可以通过 Intent 在不同的 Activity 之间跳转
这些都是 Activity 的范畴。
标签在 AndroidManfest.xml 中用于声明一个 Activity 组件。通过设置各个属性,开发者可以控制 Activity 的行为、主题、启动模式以及是否允许外部访问等。intent-filter 用于声明 Activity 能够响应的 Intent,是启动 Activity 的关键。
关于 Activity 声明周期的概念:
为了在 Activity 声明周期的各个阶段转换,Activity 类提供了六个核心回调集:onCreate(),onStart(),onResume(),onPause(),onStop,onDestroy()。
当 Activity 进入到进状态,会触发每一个回调。
这里只要调用就需要声明 Activity。
在 Android 中创建一个 Activity 类,需要集成 Activity  或其子类。
常见的子类包括:
AppCompatActivity:支持库中的 Activity 子类,提供对 ActionBar 的支持。
FragmentActivity:继承 Activity,用于支持 Fragment。

2、Service 活动

声明案例。
Setvice 是 Android 应用中的一个独立组件,可在后台运行,独立于界面部分进行长时间操作。
简单的 Service 类代码,能看到也是继承的。
onStartCommand:当服务启动时,onStartCommand 方法会被调用,在这里可以编写服务的核心逻辑,例如执行后台任务、处理数据等。返回值 START_STICKY 表示服务被杀死后会自动启动:
START_NOT_STICKY:服务被系统杀死后不重启
START_REDELIVER_INTENT:服务被杀死后,系统会尝试重启并重新传递最后的 Intent
OnBind:当服务通过绑定方式启动,onBind 方法会被调用,通常返回一个 IBinder 接口。服务不支持绑定,直接返回 null。

3、Broadcast Receiver(广播接收器)

主要用于接收和处理广播消息,广播是 Android 系统中用于传递信息的基址,可以监听特定的广播事件并作出响应。例如,接收系统广播(设备开机完成、Wi-Fi 状态变化等),或者应用发送的广播。可以不同页面间通信。
看到这里发现了,Android 的组件都有自己的生命周期的。
非常简短的生命周期,能看到 标签用于 AndroidManifest.xml 中声明一个 BroadcastReveiver 组件。
逆向分析广播案例:
该图就是广播接收者。
用于在应用的组件声明中指定哪些 Intent 可以触发该组件。
用于指定 Intent 过滤器能够处理的操作(Action),他告诉 Android 系统,当有一个 Intent 动作(Action)匹配时,应该触发相应的组件(如 BroadcastRecevier)。
接收器发出时调用的代码。

4、Content Provider(内容提供者)

ContentProvider 用于跨应用共享数据。它提供了一个统一的接口,使得一个应用可以访问另一个应用的数据。ContentProvider 可以封装数据库操作、文件操作或其他数据存储方式,并通过 ContentResolver 提供访问权限。
类似于数据库的借口了,这东西,主要功能估计就是各类数据的接口访问和权限控制了。
就这一部分,猜的大差不差。
这些是标签作用,了解一下即可。
常见的 ContentProvider 是联系人数据库(ContactsProvider),允许应用查询联系人信息。

6、安卓系统体系梳理

1、安卓系统架构

非常大的一张图片,看起来像是六层。

1.1 系统应用(System Apps)

它位于架构的最顶层(应用层),属于系统架构的一部分。
他是安卓操作系统的基础组成部分,包括了电话拨号器、邮箱、日历、相机等。

1.2 Java API 框架(Java API Framework)

开发者最常用的层,提供构建 Android 应用所需的各种管理器和服务
这一层的东西非常多了。

1.3 原生 C/C++ 库层(Native C/C++ Libraries)

这一层提供底层的库,通过 JNI 向上层 Java 框架提供服务。

1.4 Android 运行时层(Android Runtime)

负责执行应用代码,管理内存与线程

1.5 硬件抽象层(HAL,Hardware Abstraction Layer)

1.6 内核层&电源管理

内核是系统最底层,负责硬件驱动和核心系统功能。注意图片中将 Power Management 单独列出,实际它主要依赖内核驱动和框架层的电源管理服务。

2、安卓程序启动

安卓系统启动流程:

1、引导加载阶段(BootROM -> Boot Loader)

BootROM(固化在芯片中的只读代码)
手机断电时,按下电源键后,CPU 首先执行的是 BootROM 中的代码(硬件写死,无法修改)。
BootROM 会检查引导介质(如 eMMC/UFS),找到并加载 Boot Loader 到内存中,然后跳转执行。
Boot Loader(引导加载程序)
初始化硬件(内存、时钟、存储控制器等),建立内存映射,加载 Linux 内核到内存。
Boot Loader 通常还会检查启动分区是否有效、是否进入恢复模式(Recovery)、是否解锁 Bootloader 等。
最终,Boot Loader 将控制权交给 Linux 内核。

2、Linux 内核启动阶段

3、原生守护进程与 Init 阶段

4、Java 框架启动核心:Zygote

5、System Server

5、应用启动

这里简单介绍一下 init.rc 文件,就类似于开机启动项,和 iot 设备类似。
就这样,他们都是基于 Linux 内核,可以这样理解,IOT 设备的核心架构是内核+标准 Linux 驱动,而安卓是内核+硬件抽象层(HAL)。

7、总结

通过静态/动态分析,解构 APK 代码、资源、加固基址,揭示运行逻辑。
核心挑战在于加固技术博弈,应用在于恶意软件分析、漏洞挖掘、隐私保护、数字取证。
参考文章:https://bbs.kanxue.com/thread-285906.htm