ciscn2024 androidso_re分析
字数 1452 2025-08-22 12:23:30
Android SO逆向分析教学:CISCN2024 androidso_re题目解析
1. 题目概述
这是一个来自CISCN2024比赛的Android逆向题目,主要考察对Android原生库(SO文件)的分析能力。题目要求输入一个flag,格式为flag{32位字符},并通过native层的DES加密校验。
2. 初始分析
2.1 APK界面分析
- 入口Activity:
com.example.re11113.MainActivity - 功能: 要求用户输入flag进行校验
2.2 Java层分析
使用jadx反编译APK,发现关键校验函数legal:
private boolean legal(String paramString) {
return paramString.length() == 38
&& paramString.startsWith("flag{")
&& paramString.charAt(paramString.length() - 1) == '}'
&& !inspect.inspect(paramString.substring(5, paramString.length() - 1));
}
校验逻辑:
- 长度必须为38
- 以"flag{"开头
- 以"}"结尾
- 括号内32位内容通过
inspect校验
2.3 inspect函数分析
inspect函数调用原生库进行DES加密,然后与固定字符串比较:
"JqslHrdvtgJrRs2QAp+FEVdwRPNLswrnykD/sZMivmjGRKUMVIC/rw=="
3. 关键点:获取DES的IV和Key
题目通过jni.getiv()和jni.getkey()获取DES加密的IV和Key。
3.1 Frida Hook尝试
初始Hook代码:
function hook_jni() {
Java.perform(function() {
var jniClass = Java.use("com.example.re11113.jni");
jniClass.getiv.implementation = function() {
var ret = this.getiv();
console.log("getiv: " + ret);
return ret;
}
jniClass.getkey.implementation = function() {
var ret = this.getkey();
console.log("getkey: " + ret);
return ret;
}
})
}
setImmediate(hook_jni);
发现问题:
- 单独Hook
getiv()成功 - 同时Hook
getkey()会导致应用崩溃
3.2 崩溃原因分析
通过Logcat发现崩溃原因是:
getkey()返回了非UTF-8编码的字符串- Native层使用
NewStringUTF()创建Java字符串时失败
尝试Hook NewStringUTF:
function hook_newStringUTF() {
Java.perform(function() {
var modules = Process.enumerateModules();
var newStringUTFAddr = null;
for (var i = 0; i < modules.length; i++) {
var module = modules[i];
try {
var symbols = module.enumerateSymbols();
for (var j = 0; j < symbols.length; j++) {
if (symbols[j].name.indexOf("NewStringUTF") >= 0) {
newStringUTFAddr = symbols[j].address;
console.log("Found NewStringUTF in module: " + module.name + " at: " + newStringUTFAddr + " with name: " + symbols[j].name);
break;
}
}
} catch (e) {
console.warn("Failed to enumerate symbols for module: " + module.name);
}
if (newStringUTFAddr) break;
}
if (newStringUTFAddr) {
Interceptor.attach(newStringUTFAddr, {
onEnter: function(args) {
var inputStr = args[1].readCString();
console.log("NewStringUTF called with: " + inputStr);
},
onLeave: function(retval) {
console.log("NewStringUTF returning: " + retval);
}
});
} else {
console.log("NewStringUTF address not found!");
}
});
}
3.3 替代方案:Unidbg模拟执行
使用Unidbg模拟执行获取IV和Key:
package com.re11113;
import com.github.unidbg.AndroidEmulator;
import com.github.unidbg.LibraryResolver;
import com.github.unidbg.arm.backend.DynarmicFactory;
import com.github.unidbg.linux.android.AndroidEmulatorBuilder;
import com.github.unidbg.linux.android.AndroidResolver;
import com.github.unidbg.linux.android.dvm.DalvikModule;
import com.github.unidbg.linux.android.dvm.DvmClass;
import com.github.unidbg.linux.android.dvm.DvmObject;
import com.github.unidbg.linux.android.dvm.VM;
import com.github.unidbg.memory.Memory;
import java.io.File;
public class MainActivity {
public static void main(String[] args) {
long start = System.currentTimeMillis();
com.re11113.MainActivity mainActivity = new com.re11113.MainActivity();
System.out.println("load offset=" + (System.currentTimeMillis() - start) + "ms");
mainActivity.getIvAndKey();
}
private final AndroidEmulator emulator;
private final DvmClass dvmClass;
private final VM vm;
private MainActivity() {
emulator = AndroidEmulatorBuilder.for64Bit()
.addBackendFactory(new DynarmicFactory(true))
.build();
Memory memory = emulator.getMemory();
LibraryResolver resolver = new AndroidResolver(23); // 自带 sdk 23 版本
memory.setLibraryResolver(resolver);
vm = emulator.createDalvikVM(new File("C:\\Users\\33739\\Downloads\\app-debug (1).apk"));
vm.setVerbose(true);
DalvikModule dm = vm.loadLibrary(new File("E:\\unidbg-0.9.7\\so\\re11113\\libSecret_entrance.so"), true);
dm.callJNI_OnLoad(emulator);
dvmClass = vm.resolveClass("com/example/re11113/jni"); // jni 函数所在的类
}
private void getIvAndKey() {
DvmObject<?> result = dvmClass.callStaticJniMethodObject(emulator, "getkey()Ljava/lang/String;");
System.out.println("result is => " + result.getValue());
result = dvmClass.callStaticJniMethodObject(emulator, "getiv()Ljava/lang/String;");
System.out.println("result is => " + result.getValue());
}
}
成功获取:
- IV:
Wf3DLups - Key:
A8UdWaeq
4. SO文件逆向分析
4.1 getkey函数分析
-
返回值分析:
- 返回的Java字符串由
v14转换而来 v14来自v18经过处理
- 返回的Java字符串由
-
关键字符串:
"YourRC4Key"- 可能是RC4加密的密钥"TFSecret_Key"- 可能用于其他处理
-
处理流程:
- 调用
jiejie函数(实际是RC4加密) - 对结果前8位进行异或操作
- 调用
4.2 jiejie函数(RC4实现)
-
S盒初始化:
- 创建256字节的vector
- 填充0-255的初始值
-
S盒置换:
- 使用密钥进行置换操作
-
加密流程:
- 从S盒中取值进行计算
4.3 最终处理
对RC4加密结果的前8位进行异或操作,得到最终的Key:
A8UdWaeq
5. 解密流程
- 加密算法: DES
- 参数:
- Key:
A8UdWaeq - IV:
Wf3DLups
- Key:
- 密文:
JqslHrdvtgJrRs2QAp+FEVdwRPNLswrnykD/sZMivmjGRKUMVIC/rw==
使用DES解密工具,输入以上参数即可得到flag的中间32位内容。
6. 总结
-
解题步骤:
- 分析Java层校验逻辑
- 识别native层加密方式(DES)
- 获取DES的IV和Key
- 解密得到flag
-
技术要点:
- Frida Hook技巧
- Unidbg模拟执行
- SO文件逆向分析
- RC4算法识别
- DES加密参数获取
-
难点:
getkey()Hook崩溃问题- Native层字符串编码问题
- RC4算法的识别与分析
7. 参考工具
-
反编译工具:
- jadx
- IDA Pro
-
动态分析工具:
- Frida
- Objection
-
模拟执行:
- Unidbg
-
加密解密工具:
- CyberChef
- 在线DES解密工具