Android逆向工程学习笔记(二)
Android逆向工程学习笔记(二)
Native层逆向
Native 层逆向工程涉及到分析 Android 应用中使用的 C 或 C++ 代码。Native 代码通常通过 JNI(Java Native Interface)与 Java 层交互。要逆向 Native 代码,首先需要反编译 APK 中的 .so 库文件。
逆向分析步骤:
- 提取 .so文件:通过解压 APK,可以获取到
lib目录下的.so文件,这些文件通常存储了应用的本地库。 - 反编译 .so 文件:使用工具如 IDA Pro、Ghidra 或 Radare2 来反编译这些二进制文件。通过反汇编,我们可以获得 Native 代码的控制流和逻辑。
- 分析 JNI 接口:查找与 Java 层交互的 JNI 函数。JNI 函数通常以
Java_开头,例如Java_com_example_app_NativeMethod。
用一道例题来介绍逆向流程
题目来源:NSSCTF
题目名称:[鹏城杯 2022]baby_re
开头还是标准的逆向流程,先看一遍AndroidManifest.xml
我这里已经分析得出程序的主入口点在com.example.createso.MainActivity了

public final native int[] baby_xor(int[] iArr);
其中这一行比较让人注意,它只存在声明,不存在定义,往下分析

public static final void m164onCreate$lambda0(MainActivity this$0, int[] c, View it) {
Intrinsics.checkNotNullParameter(this$0, "this$0");
Intrinsics.checkNotNullParameter(c, "$c");
IntStream flag = ((EditText) this$0._$_findCachedViewById(R.id.input)).getText().toString().chars();
int[] array = flag.toArray();
Intrinsics.checkNotNullExpressionValue(array, "flag.toArray()");
int[] cipher = this$0.baby_xor(array);
if (Arrays.equals(cipher, c)) {
Toast.makeText(this$0, "Success", 1).show();
} else {
Toast.makeText(this$0, "Failed", 0).show();
}
}
这里可以看出是有一段校验逻辑的
具体的校验逻辑是,让输入的字符串进行baby_xor处理,然后与数组c进行比对,如果相同则输出success,错误则输出failed
其实具体让我看每一句分析我是看不懂的,我是根据大致的语法来猜测意思,不过好在我猜对了(好孩子不要模仿哦!),还是要学习下Java语法,不然的话看不懂反编译结果哦
回到题目上,这时候就有点让人犯难了,毕竟不知道baby_xor的定义,鬼知道做了什么,这个猜不对吧
这时候就要到Native层逆向了
baby_xor这个函数本身没有 Java 方法体,它的真正实现大概率在 APK 的 .so 原生库里。
.so原生库在哪里呢?在/lib目录下
还记得/lib目录吗?
lib/
lib/ 目录包含了本地库文件,通常是通过 JNI(Java Native Interface) 与C/C++编写的本地代码。这些库文件可以针对不同的硬件架构(如arm、x86等)进行编译,因此lib/目录下通常会为每个架构创建相应的子目录。这个目录中存放的本地库可以通过Java代码调用JNI接口实现与系统底层的交互。
通常你可以在jadx中看到这个文件(如果它有的话),但是jadx无法反编译它,你需要使用工具如 IDA Pro、Ghidra 或 Radare2 来反编译这些二进制文件
高版本的jadx是能导出.so文件的,当然你也可以通过更改apk的后缀名,直接解压其压缩包,获得/lib目录下的文件
然后拖进IDA分析,这里分析的就是apk的JNI代码

Get,找到了baby_xor的逻辑
*(_DWORD *)(IntArrayElements + 4 * i) ^= key[i % 4];
这里需要注意,IDA的反编译结果把IntArrayElements + 4 * i看成了是一个指针,而且是对每四个字符进行处理
但是其实不是这样的
Java层
public final native int[] baby_xor(int[] iArr);
对应JNI:
jintArray iArr
JNI 是 Java 和 C/C++ 之间的桥梁。
JNI 为 Java 的各种类型定义了对应的 C 类型。jintArray就是其中之一
这是API就定义好的
对应的C++类型就是
int *
所以这里的+4*i就很奇怪
IDA把它当成了一个_DWORD,一个32位值
我们可以使用IDA修改其类型
对需要修改的数据按Y即可实现

改成int *之后就变得正常了,对每个字符进行处理,可以开始写解密脚本了
不过这里还有一个对key的初始化函数,hide_key

最终写个python解密脚本
key=[0x56,0x57,0x58,0x59]
key[0]^=0x47
key[1]^=0x32
key[2]^=0x11
key[3]^=0x12
enc = [119, 9, 40, 44, 106, 83, 126, 123, 33, 87, 113, 123, 112, 93, 125, 127, 41, 82, 44, 127, 39, 3, 126, 125, 119, 87, 47, 125, 33, 6, 44, 127, 112, 0, 126, 123, 115, 24]
for i in range(len(enc)):
enc[i]^=key[i%4]
print(bytes(enc))
所以Native层逆向需要注意的是,要搞清楚JNI的类型对应
基础的一张表
| Java | JNI |
|---|---|
int |
jint |
int[] |
jintArray |
byte |
jbyte |
byte[] |
jbyteArray |
long |
jlong |
String |
jstring |
Object |
jobject |
boolean |
jboolean |
这是比较基础的地方了,由于我的学习也不是那么深入,基于这道题目,只获得了这些经验
所以要多进行逆向分析,才能获得经验,多看文章,经验十分重要,经验是记忆,人和CPU一样,没有记忆的话,做事,生活也会变得比较虚无的,人也能创造记忆,以至于我们活在世界上不那么空虚,而创造经验,会让你在技术上不那么空虚
如果你觉得本篇文章对你有帮助,请留下一个宝贵的赞,如果有错误的地方欢迎评论指正

浙公网安备 33010602011771号