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));
}

校验逻辑:

  1. 长度必须为38
  2. 以"flag{"开头
  3. 以"}"结尾
  4. 括号内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函数分析

  1. 返回值分析:

    • 返回的Java字符串由v14转换而来
    • v14来自v18经过处理
  2. 关键字符串:

    • "YourRC4Key" - 可能是RC4加密的密钥
    • "TFSecret_Key" - 可能用于其他处理
  3. 处理流程:

    • 调用jiejie函数(实际是RC4加密)
    • 对结果前8位进行异或操作

4.2 jiejie函数(RC4实现)

  1. S盒初始化:

    • 创建256字节的vector
    • 填充0-255的初始值
  2. S盒置换:

    • 使用密钥进行置换操作
  3. 加密流程:

    • 从S盒中取值进行计算

4.3 最终处理

对RC4加密结果的前8位进行异或操作,得到最终的Key:
A8UdWaeq

5. 解密流程

  1. 加密算法: DES
  2. 参数:
    • Key: A8UdWaeq
    • IV: Wf3DLups
  3. 密文: JqslHrdvtgJrRs2QAp+FEVdwRPNLswrnykD/sZMivmjGRKUMVIC/rw==

使用DES解密工具,输入以上参数即可得到flag的中间32位内容。

6. 总结

  1. 解题步骤:

    • 分析Java层校验逻辑
    • 识别native层加密方式(DES)
    • 获取DES的IV和Key
    • 解密得到flag
  2. 技术要点:

    • Frida Hook技巧
    • Unidbg模拟执行
    • SO文件逆向分析
    • RC4算法识别
    • DES加密参数获取
  3. 难点:

    • getkey() Hook崩溃问题
    • Native层字符串编码问题
    • RC4算法的识别与分析

7. 参考工具

  1. 反编译工具:

    • jadx
    • IDA Pro
  2. 动态分析工具:

    • Frida
    • Objection
  3. 模拟执行:

    • Unidbg
  4. 加密解密工具:

    • CyberChef
    • 在线DES解密工具
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 : 校验逻辑: 长度必须为38 以"flag{"开头 以"}"结尾 括号内32位内容通过 inspect 校验 2.3 inspect函数分析 inspect 函数调用原生库进行DES加密,然后与固定字符串比较: 3. 关键点:获取DES的IV和Key 题目通过 jni.getiv() 和 jni.getkey() 获取DES加密的IV和Key。 3.1 Frida Hook尝试 初始Hook代码: 发现问题: 单独Hook getiv() 成功 同时Hook getkey() 会导致应用崩溃 3.2 崩溃原因分析 通过Logcat发现崩溃原因是: getkey() 返回了非UTF-8编码的字符串 Native层使用 NewStringUTF() 创建Java字符串时失败 尝试Hook NewStringUTF : 3.3 替代方案:Unidbg模拟执行 使用Unidbg模拟执行获取IV和Key: 成功获取: IV: Wf3DLups Key: A8UdWaeq 4. SO文件逆向分析 4.1 getkey函数分析 返回值分析: 返回的Java字符串由 v14 转换而来 v14 来自 v18 经过处理 关键字符串: "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 密文: 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解密工具