[FlareOn2]Android

PixPin_2025-03-29_19-32-14

native层逆向,将apk后缀改为zip解压,在lib中找到对应so文件,或者jadx选中so文件右键导出

这个函数参数IDA识别错了,选中对应参数按Y修改,前两个是固定的,又vaildata()输入参数类型是string,所以第三个参数类型为jstring,修改好后是这样的

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
jstring __fastcall Java_com_flareon_flare_ValidateActivity_validate(JNIEnv *env, jobject obj, jstring input)
{
const char *v4; // r0
const char *input_str; // r6
unsigned int v7; // r4
int j; // r7
const char *v9; // r1
size_t i; // [sp+0h] [bp-1BB8h]
int v11; // [sp+4h] [bp-1BB4h]
int v13; // [sp+Ch] [bp-1BACh]
unsigned int v14; // [sp+10h] [bp-1BA8h]
int v15[23]; // [sp+1Ch] [bp-1B9Ch] BYREF
char v16[6976]; // [sp+78h] [bp-1B40h] BYREF

j_memset(v16, 0, 6952u);
j_memcpy(v15, off_5004, sizeof(v15)); // 密文
v4 = (*env)->GetStringUTFChars(env, input, 0);// 将 Java 字符串转换为 C 字符串(UTF-8 编码)。
input_str = v4;
if ( v4 && j_strlen(v4) <= 46 ) // 输入长度为46
{
v11 = 0;
v13 = 1;
for ( i = 0; i < j_strlen(input_str); i += 2 )
{
j_memset(v16, 0, 6952u); // v16 = 0
v7 = 0;
if ( input_str[i] )
{
v7 = (unsigned __int8)input_str[i];
if ( input_str[i + 1] )
v7 = (unsigned int)byte_7E7E >= (((unsigned __int8)input_str[i] << 8) | (unsigned int)(unsigned __int8)input_str[i + 1]) ? ((unsigned __int8)input_str[i] << 8) | (unsigned __int8)input_str[i + 1] : 0;
}
for ( j = 0; j != 6952; j += 2 )
{
v14 = *(unsigned __int16 *)&byte_2214[j];// 质数表,len=6952,6952/2 = 3476
while ( !(unsigned __int16)(v7 % v14) )
{
++*(_WORD *)&v16[j];
v7 = (unsigned __int16)(v7 / v14);
if ( v7 <= 1 )
goto LABEL_10;
}
}
LABEL_10:
if ( !j_memcmp((const void *)v15[v11], v16, 3476u) )
++v11;
else
v13 = 0;
}
(*env)->ReleaseStringUTFChars(env, input, input_str);// 释放之前通过 GetStringUTFChars 获取的字符串资源,
// env:指向 JNI 环境的指针,用于与 JVM 交互。
// input:一个 jstring 类型的 Java 字符串对象。
// input_str:通过 GetStringUTFChars 获取的 C 风格的字符串(const char*)。
if ( v11 == 23 && v13 )
v9 = "That's it!";
else
v9 = (const char *)&unk_3D3C; // No
return (*env)->NewStringUTF(env, v9); // 将 C 字符串(UTF-8 编码)转换为 Java 字符串(jstring)。
}
else
{
(*env)->ReleaseStringUTFChars(env, input, input_str);
return (*env)->NewStringUTF(env, &unk_3D3C);// 创建返回的 jstring
}
}

脚本:

  • 读取地址表 t1

​ 从文件偏移 0x5004 - 0x1000 = 0x4004 处读取 23 个地址,组成地址表 t1

调整偏移的原因:ELF 文件在内存中的加载基址通常为 0x1000,文件偏移需减去基址得到实际物理偏移。

  • 读取质数表 word_2214

数据内容word_2214 是一个长度为 3476 的数组,存储质数(如 [2, 3, 5, 7, 11, ...])。

用途:后续计算中,每个位置 j 的质数 word_2214[j] 将作为基底。

  • 计算生成字节流 res

    1. 遍历 t1 中的每个地址 i,转换为文件偏移 offset = i - 0x1000
    2. 读取该偏移处的数据块,解包为数组 a
    3. 对每个元素 a[j],若不为零,则计算 word_2214[j] ** a[j],并将所有结果累乘到 v
    4. v 拆分为高 8 位和低 8 位,存入 res 列表。
  • 示例

    假设 word_2214 = [2, 3, 5],且某次循环中 a = [1, 2, 0]

    1. 计算 v = 2^1 * 3^2 * 5^0 = 2 * 9 * 1 = 18
    2. 拆分字节:18 = 0x12 → 高 8 位 0x00,低 8 位 0x12res += [0x00, 0x12]
    3. 最终字符:chr(0x00)(不可见)和 chr(0x12)(ASCII 控制字符)。
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
import struct

t1_addr = 0x5004
with open('libvalidate.so', 'rb') as f:
f.seek(t1_addr - 0x1000) # 调整文件指针到地址 0x5004 对应的文件偏移
t1_data = f.read(23 * 4) # 读取 23 个 4 字节整数(地址表)
t1 = struct.unpack("<" + "I" * 23, t1_data) # 解包为小端序的无符号整数列表
for i in t1:
print(hex(i)) # 打印每个地址(调试用)

f.seek(0x2214) # 定位到质数表起始地址
word_2214_data = f.read(6952) # 读取 6952 字节(3476 个 2 字节整数)
word_2214 = struct.unpack("<" + "H" * 3476, word_2214_data) # 解包为小端序的无符号短整型列表
print(word_2214[:5]) # 打印前 5 个质数(调试用)

res = []
for i in t1:
offset = i - 0x1000 # 将地址转换为文件偏移
f.seek(offset)
data = f.read(6952) # 读取与质数表长度相同的数据块
a = struct.unpack("<" + "H" * 3476, data) # 解包为 3476 个无符号短整型
v = 1
for j in range(len(a)):
if a[j] != 0:
v *= word_2214[j] ** a[j] # 质数幂次累乘
res.append((v >> 8)) # 取高 8 位
res.append((v & 0xff)) # 取低 8 位

print(res)
for i in res:
print(chr(i), end='') # 将字节流转换为 ASCII 字符

# 0x2a5d0
# 0x28aa8
# 0x26f80
# 0x25458
# 0x23930
# 0x21e08
# 0x202e0
# 0x1e7b8
# 0x1cc90
# 0x1b168
# 0x19640
# 0x17b18
# 0x15ff0
# 0x144c8
# 0x129a0
# 0x10e78
# 0xf350
# 0xd828
# 0xbd00
# 0xa1d8
# 0x86b0
# 0x6b88
# 0x5060
# (2, 3, 5, 7, 11)
# [83, 104, 111, 117, 108, 100, 95, 104, 97, 118, 101, 95, 103, 48, 110, 101, 95, 116, 111, 95, 116, 97, 115, 104, 105, 95, 36, 116, 97, 116, 105, 111, 110, 64, 102, 108, 97, 114, 101, 45, 111, 110, 46, 99, 111, 109]
# Should_have_g0ne_to_tashi_$tation@flare-on.com

[前置知识]java静态注册参数类型

在 JNI(Java Native Interface)中,Java_com_example_MyClass_myMethod 这类通过静态注册生成的 Native 函数的前两个参数是固定的,它们的含义和类型由 JNI 规范定义。以下是详细解释:


前两个参数的固定含义

参数顺序 参数类型 参数名称 说明
第1个参数 JNIEnv* env JNI 环境指针,提供访问 JNI 函数表的接口(如操作字符串、数组、调用方法等)。
第2个参数 jobjectjclass obj/clazz 调用上下文对象,具体类型取决于 Java 方法是 实例方法 还是 静态方法

第二个参数的具体类型

1. 实例方法(非静态方法)

  • Java 方法定义
1
public native void myInstanceMethod(); // 实例方法
  • Native 函数签名
1
JNIEXPORT void JNICALL Java_com_example_MyClass_myInstanceMethod(JNIEnv* env, jobject obj);
  • 第二个参数 jobject obj:表示调用该 Native 方法的 Java 对象实例(即 this)。

2. 静态方法

  • Java 方法定义
1
public static native void myStaticMethod(); // 静态方法
  • Native 函数签名
1
JNIEXPORT void JNICALL Java_com_example_MyClass_myStaticMethod(JNIEnv* env, jclass clazz);
  • 第二个参数 jclass clazz:表示调用该 Native 方法的 Java 类对象(即 MyClass.class)。

参数的作用和用法

1. JNIEnv\* env

  • 核心功能
    通过 env 指针可以调用 JNI 提供的 所有功能函数,例如:
    • 字符串操作:env->NewStringUTF(), env->GetStringUTFChars()
    • 数组操作:env->GetIntArrayElements()
    • 方法调用:env->CallVoidMethod(), env->CallStaticObjectMethod()
    • 异常处理:env->ExceptionCheck()
  • 线程安全
    JNIEnv* 是线程相关的,不可跨线程使用(每个线程需通过 JavaVM 获取自己的 JNIEnv)。

2. jobject objjclass clazz

  • 操作对象实例jobject obj):
    通过 obj 可以访问实例的字段或方法:

    cpp

    复制

    1
    2
    3
    4
    // 获取实例字段的 fieldID
    jfieldID fieldId = env->GetFieldID(env->GetObjectClass(obj), "fieldName", "I");
    // 修改字段值
    env->SetIntField(obj, fieldId, 100);
  • 操作静态成员jclass clazz):
    通过 clazz 可以访问类的静态字段或方法:

    cpp

    复制

    1
    2
    3
    4
    // 获取静态字段的 fieldID
    jfieldID staticFieldId = env->GetStaticFieldID(clazz, "staticFieldName", "I");
    // 修改静态字段值
    env->SetStaticIntField(clazz, staticFieldId, 200);

示例代码

1. 实例方法示例

1
2
3
4
5
6
7
8
9
10
11
// Java 实例方法:public native String getMessage();
JNIEXPORT jstring JNICALL Java_com_example_MyClass_getMessage(JNIEnv* env, jobject obj) {
// 通过 obj 操作实例字段
jclass clazz = env->GetObjectClass(obj);
jfieldID fieldId = env->GetFieldID(clazz, "name", "Ljava/lang/String;");
jstring name = (jstring)env->GetObjectField(obj, fieldId);
const char* nameStr = env->GetStringUTFChars(name, nullptr);

// 返回拼接的字符串
return env->NewStringUTF("Hello, ");
}

2. 静态方法示例

1
2
3
4
// Java 静态方法:public static native int add(int a, int b);
JNIEXPORT jint JNICALL Java_com_example_MyClass_add(JNIEnv* env, jclass clazz, jint a, jint b) {
return a + b;
}

常见问题

1. 为什么第二个参数类型不同?

  • 设计逻辑
    实例方法需要操作具体对象(this),而静态方法属于类本身,无需对象实例。

2. 能否在静态方法中访问实例字段?

  • 不能:静态方法没有 jobject obj 参数(无对象实例),只能通过 FindClassGetStaticFieldID 操作静态成员。

3. 如何获取 jclass

  • 方法 1:通过 env->GetObjectClass(obj)(实例方法中可用)。
  • 方法 2:通过 env->FindClass("com/example/MyClass")(需全限定类名,用 / 代替 .)。

总结

  • 前两个参数固定JNIEnv* env + jobject(实例方法)或 jclass(静态方法)。
  • 核心用途
    • env:调用 JNI 函数,操作 Java 对象和资源。
    • obj/clazz:访问调用上下文(实例或类)。
  • 注意事项:线程安全、参数类型匹配、资源释放(如 ReleaseStringUTFChars)。

参考链接

FlareOn2]Android - Mz1 - 博客园