安卓逆向

apk 文件结构

APK 是 Android PacKage 的缩写,是 Android 源文件打包后的安装包。apk 其实就是一个 zip 格式的压缩包。想要知道 apk 包含了什么,可以修改后缀名为zip,然后用解压缩工具打开 apk 文件。 如果有 代码混淆 和 加密,通过普通解压缩工具打开里面的文件或目录会看到各种乱码。
APK 文件 目录:

  • assets 不经过 aapt 编译的资源文件,

    apk 中不用编译的资源(其他类型的文件)通常放在 /assets 目录和 /res/raw 目录下

  • drawable 图片

  • lib .so 文件 有的题需要逆向lib .so文件,放到IDA64位里查看

  • META-INF 文件摘要,摘要加密和签名证书文件目录

  • CERT.RSA 公钥和加密算法描述

  • CERT.SF 加密文件,它是使用私钥对摘要明文加密后得到的 密文信息,只有使用私钥配对的公钥才能解密该文件

  • MANIFEST.MF 程序清单文件,它包含包中所有文件的摘要明文

  • res 资源文件目录,二进制格式

  • layout 布局

  • menu 菜单

  • resources.arsc 经过 aapt 编译过的资源文件

  • classes.dex 可执行文件

  • AndroidManifest.xml 配置文件,做题时可以先查找 AndroidManifest.xml 文件里的 Activity 标签,一个Activity相当于一个页面,可以快速找到MainActivity并跳转。

  1. assets 文件夹:程序资源目录。assets 文件夹用于保存需要保持原始文件的资源文件夹,开发过程中拖了什么到里面,打包完之后里面还是什么。一般用于存放音频,网页(帮助页面之类的),字体等文件。主要需要知道的点是,它与 res 文件夹的区分以及如何在应用中访问该文件夹的资源,如它可以有多级目录而 res 则只有两级。
  2. res 文件夹:顾名思义,该文件夹是资源文件夹。它里面存放的所有文件都会被映射到 R 文件中,生成对应的资源 ID,便于代码中通过 ID 直接访问。其中的资源文件包括了动画(anim),图像(drwable),布局(layout),常量值(values),颜色值(colors),尺寸值(dimens),字符串(strings),自定义样式(styles)等。在编译时会自动生成索引文件(R.java),在 Java 代码中用 R.xxx.yyy 来引用。而 assets 目录下资源文件不会生成索引,在 Java 代码中需要使用 AssetManager 来访问。一般使用 Java 开发的 Android 工程使用的资源文件都会放在 res下。
  3. lib 文件夹:so 文件存放位置。该目录存放着应用需要的 native 库文件。文件夹下有时会多一个层级,这是根据不同CPU 型号而划分的,如 ARM,ARM-v7a,x86等。
  4. META-INF:签名证书目录
  5. AndroidManifest.xml:这是 Android 应用的全局配置文件,它包含了这个应用的很多配置信息,例如包名、版本号、所需权限、注册的服务等。可以根据这个文件在相当程度上了解这个应用的一些信息。该文件是被编译为二进制的 XML 文件,可以通过一些工具(如 apktool、jadx、jeb、AndroidKiller 等)反编译后进行查看。也可以通过 android studio —> build —> Analyze Apk 来分析 apk
  6. dex 文件:classes.dex 文件是 Android 系统运行于 Dalvik Virtual Machine 上的可执行文件,也是Android 应用程序的核心所在。项目工程中的 Java 源码通过 javac 生成 class 文件,再通过 dx 工具转换为 classes.dex,注意到我们这里有 classes2.dex 和 classes3.dex。这是方法数超过一个 dex 的上限,分 dex 的结果。
  7. resource.arsc 文件:字符串、资源索引文件。它记录了资源文件,资源文件位置(各个维度的路径)和资源 id 的映射关系。并且将所有的 string 都存放在了 string pool 中,节省了在查找资源时,字符串处理的开销。
  8. META-INF 文件夹:该目录的主要作用是用于保证 APK 的完整性以及安全性。该文件夹下,主要有三个文件。
  9. MANIFEST.MF:这个文件保存 整个apk文件 中 所有文件的文件名 + SHA-1后的编码值。这也就意味着,MANIFEST.MF 象征着 apk 包的完整性。

这部分内容只需要了解一下,知道apk文件结构就好。

apk编译打包流程

可以参考这位大佬写的博客(很详细)https://juejin.cn/post/7113713363900694565

1.编译器将您的源代码转换成 DEX 文件(Dalvik 可执行文件,其中包括在 Android 设备上运行的字节码),并将其他所有内容转换成编译后的资源。

2.打包器将 DEX 文件和编译后的资源组合成 APKAAB(具体取决于所选的 build 目标)。

3.打包器使用调试或发布密钥库为 APKAAB 签名。

4.在生成最终 APK 之前,打包器会使用 zipalign 工具对应用进行优化,以减少其在设备上运行时所占用的内存

这是学习安卓逆向时要了解的知识。

安卓逆向的工具

jadx

jadx是一款开源的DEX到Java的反汇编工具。它支持apk、dex、jar、class、zip、aar等文件。jadx操作方便,反编译后的代码可读性高,同时还拥有较完善的gui界面,除去混淆部分的代码,jadx已经非常接近源代码了。比较方便的是可以搜索。

jadx还支持对Smali代码的调试,单步,设置断点,修改寄存器的值,修改类属性等相关功能。

官方地址:https://github.com/skylot/jadx/

jadx的左侧是项目的目录结构,在左侧展开想要查看的包,右侧就会出现对应的Java代码。上图显示的是MainActivity。

jadx的文本搜索功能比较高效,可以同时从类名,方法名,代码等选择搜索字段,很方便,我是新手,我一般都选上,有的简单的题目可以直接搜索flag,找到flag,或者搜索right,error之类的字眼,可以找到主要的代码。

上图中发现第二个很有可能就是flag2,就选中后点击转到的按钮,可以跳转到对应的代码处。

在jadx中还有很多功能比如将反编译的文件保存为Gradle项目,还可以查找方法的声明,选中一个方法,右键点击方法名,选择跳到声明,这样就能找到该方法的位置。

查找用例,右键点击方法名,就可以找到调用该方法的位置了。

一些简单的功能和使用,后续可以自己了解更多哦

JEB工具

JEB是一款强大的安卓APK逆向分析工具。JEB安装好后,在它的安装目录下分别有jeb_linux.sh , jeb_macos.sh和jeb_wincon.bat这3个文件,它们是不同操作系统的启动程序,分别对应Linux,macOS和 Windows操作系统。运行jeb_wincon.bat文件启动JEB,首次启动时的速度可能稍慢。

常用功能:

  • 选中smali代码,按Tab键就可以进行反编译,然后就可以进行静态分析了。
  • 双击方法,可以跳转到方法的定义
  • 点击方法,按X键可以查看方法的调用
  • 双击Manifest即可查看AndroidManifest.xml
  • 搜索功能,可以搜索字符串,函数等

在安卓逆向动态分析中动态调试有两种方法:

1.JEB调试(两种模式)

  • 普通调试

    1.真机或者模拟器安装要调试的apk文件

    2.jeb打开apk文件,找到要下断点的smali位置用ctrl+B打断点

    3.导航带上点击调试器,开始,找到相关的进程双击进行调试

  • degub调试

    可以学习大佬的这篇文章(写的很好):https://blog.csdn.net/freeking101/article/details/105910877

2.AndroidStudio+smalidea插件进行动态调试

GDA

GDA 不只是一款反编译器,同时也是一款轻便且功能强大的综合性逆向分析利器,不依赖 java 环境。支持 apk, dex, odex, oat, jar, class, aar文件的反编译,支持python及java脚本自动化分析。其包含多个由作者独立研究的高速分析引擎:反编译引擎、漏洞检测引擎、 恶意行为检测引擎、污点传播分析引擎、反混淆引擎、apk壳检测引擎等等

详细的应用操作可以学习这篇博客:https://zhuanlan.zhihu.com/p/28354064

安卓逆向解题思路

刚入门,学的不多,思路待补充

  1. 拿到apk文件,用安卓逆向工具打开(我一般用jadx),然后找到MainActivity
  2. 找到并打开AndroidManifest.xml,然后找到文件里的 Activity 标签,一个Activity相当于一个页面,可以快速找到MainActivity并跳转。
  3. 分析MainActivity的Java源代码,看如何对flag加密,进行静态分析。
  4. 如果有 native 标签说明函数是 C 语言编写的,主体在 so 文件,需要逆向so文件

练习

题一:ezAndroidStudy

用jadx打开apk文件,找到AndroidManifest.xml,找到MainActivity并跳转。

看到了提示语 “你好哟,我是 PangBai \n接下来啊,我将带你了解 apk 的结构\nflag 被分割为5份藏在了 apk 里,想获得 flag 的话一定要跟紧我的脚步呢”,可以知道这个是个简单的找flag拼接的题目。

既然知道了flag被分成了5份,可以用搜索直接搜flag1

再搜索flag2并转到

在这里他又给出了提示,说apk 中不用编译的资源(一下其他类型的文件)通常放在 /assets 目录和 /res/raw 目录下,很显然flag4就在这两个之中,还有提示说逆向so文件,那估计就是flag5了

再次搜索flag3

找flag4,没找到assets,但在 /res/raw 目录下发现了flag4

搜索flag5可以看到:有 native 标签,说明函数是 C 语言编写的,主体在 so 文件,需要逆向so文件。并且一开始也提示了要逆向so文件

在lib里找到x86_64点开找到so文件后导出,然后放到IDA64位里查看

flag5就找到了,最后合到一起

flag{Y0u_@r4_900d_andr01d_r4V4rs4r}

题二:ezencrypt

走一下流程,找到MainActivity并跳转,主要看onClick里面的代码,可以看到Enc enc = new Enc(tx),加密逻辑在 Enc中

这个类有一个native方法doEncCheck,静态块里加载了libezencrypt.so,所以这个库应该包含doEncCheck的实现

分析:Enc(String v),里面调用了encrypt方法,参数是v和从MainActivity.title生成的密钥。然后加密后的结果保存在enc变量里。check方法调用doEncCheck,传入这个enc字符串,返回结果。

使用MainActivity.title作为密钥来源,转换为AES密钥。

采用AES/ECB/PKCS5Padding模式加密输入字符串,结果经Base64编码后存储。

check()调用本地方法doEncCheck()验证加密结果。

逆向so文件放到IDA64里面运行

mm[i]要等于s[i],那加密后的数据在mm里储存

第一部分加密是异或加密

第二部分是典型的RC4加密,密钥是xork

将加密后的数据进行RC4解密,得出来的数据与xork进行异或。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
#include "stdio.h"
#include "string.h"

char xork[] = "meow";
#define size 256

unsigned char sbox[257] = {0};

// 初始化 s 盒
void init_sbox(char *key) {
unsigned int i, j, k;
int tmp;

for (i = 0; i < size; i++) {
sbox[i] = i;
}

j = k = 0;
for (i = 0; i < size; i++) {
tmp = sbox[i];
j = (j + tmp + key[k]) % size;
sbox[i] = sbox[j];
sbox[j] = tmp;
if (++k >= strlen((char *)key)) k = 0;
}
}

// 加解密函数
void encc(char *key, char *data) {
int i, j, k, R, tmp;

init_sbox(key);

j = k = 0;
for (i = 0; i < strlen((char *)data); i++) {
j = (j + 1) % size;
k = (k + sbox[j]) % size;

tmp = sbox[j];
sbox[j] = sbox[k];
sbox[k] = tmp;

R = sbox[(sbox[j] + sbox[k]) % size];

data[i] ^= R;
}
}

void enc(char *in) {
int len = strlen(in);
for (int i = 0; i < len; ++i) {
in[i] ^= xork[i % 4];
}
encc(xork, in);
}

int main() {
unsigned char mm[] = {0xc2, 0x6c, 0x73, 0xf4, 0x3a, 0x45, 0x0e, 0xba, 0x47, 0x81, 0x2a,
0x26, 0xf6, 0x79, 0x60, 0x78, 0xb3, 0x64, 0x6d, 0xdc, 0xc9, 0x04,
0x32, 0x3b, 0x9f, 0x32, 0x95, 0x60, 0xee, 0x82, 0x97, 0xe7, 0xca,
0x3d, 0xaa, 0x95, 0x76, 0xc5, 0x9b, 0x1d, 0x89, 0xdb, 0x98, 0x5d};
enc(mm);
for (size_t i = 0; i < 44; i++) {
putchar(mm[i]);
}
puts("");
}

2BB+GQampKmsrfDG85+0A7n18M+kT2zBDiZSO28Ich4=

这是运行出来的结果,再进行AES解密,密钥是NewStar2024

结语

这仅仅是刚刚入门,了解了安卓逆向,还需要更深入的学习这部分内容。比如安卓逆向的动态调试,修改smali代码,脱壳等

毕竟万事开头难,迎难而上(bushi)