Android逆向工程学习笔记(二)

Android逆向工程学习笔记(二)

Native层逆向

Native 层逆向工程涉及到分析 Android 应用中使用的 C 或 C++ 代码。Native 代码通常通过 JNI(Java Native Interface)与 Java 层交互。要逆向 Native 代码,首先需要反编译 APK 中的 .so 库文件。

逆向分析步骤:

  1. 提取 .so文件:通过解压 APK,可以获取到 lib 目录下的 .so 文件,这些文件通常存储了应用的本地库。
  2. 反编译 .so 文件:使用工具如 IDA Pro、Ghidra 或 Radare2 来反编译这些二进制文件。通过反汇编,我们可以获得 Native 代码的控制流和逻辑。
  3. 分析 JNI 接口:查找与 Java 层交互的 JNI 函数。JNI 函数通常以 Java_ 开头,例如 Java_com_example_app_NativeMethod

用一道例题来介绍逆向流程

题目来源:NSSCTF

题目名称:[鹏城杯 2022]baby_re

开头还是标准的逆向流程,先看一遍AndroidManifest.xml

我这里已经分析得出程序的主入口点在com.example.createso.MainActivity了

image-20260812093746641

  public final native int[] baby_xor(int[] iArr);

其中这一行比较让人注意,它只存在声明,不存在定义,往下分析

image-20260812093943909

  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代码

image-20260812095454662

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即可实现

image-20260812100843165

改成int *之后就变得正常了,对每个字符进行处理,可以开始写解密脚本了

不过这里还有一个对key的初始化函数,hide_key

image-20260812095807104

最终写个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一样,没有记忆的话,做事,生活也会变得比较虚无的,人也能创造记忆,以至于我们活在世界上不那么空虚,而创造经验,会让你在技术上不那么空虚

如果你觉得本篇文章对你有帮助,请留下一个宝贵的赞,如果有错误的地方欢迎评论指正

posted @ 2026-08-12 10:19  DrD1Di  阅读(33)  评论(0)    收藏  举报