2024Hgame-babyAndroid逆向分析
字数 1139 2025-08-22 12:23:00

Android逆向分析:2024Hgame-babyAndroid解题详解

1. 题目概述

这是一个来自2024年Hgame比赛的Android逆向题目,包含Java层和Native层(so)的验证逻辑。题目要求输入用户名和密码,通过两层验证后会显示"Congratulate!!!^_^"的成功提示。

2. Java层分析

2.1 MainActivity分析

MainActivity的主要逻辑在onClick方法中:

public void onClick(View view) {
    byte[] bytes = this.username.getText().toString().getBytes();
    byte[] bytes2 = this.password.getText().toString().getBytes();
    
    if (new Check1(getResources().getString(R.string.key).getBytes()).check(bytes)) {
        if (check2(bytes, bytes2)) {
            Toast.makeText(this, "Congratulate!!!^_^", 0).show();
            return;
        } else {
            Toast.makeText(this, "password wrong!!!>_<", 0).show();
            return;
        }
    }
    Toast.makeText(this, "username wrong!!!>_<", 0).show();
}

验证流程分为两步:

  1. Check1类验证用户名
  2. check2本地方法验证密码

2.2 Check1类分析

Check1类实现了RC4加密算法:

public class Check1 {
    private byte[] S = new byte[256];
    private int i;
    private int j;

    public Check1(byte[] bArr) {
        // RC4密钥调度算法(KSA)
        for (int i = 0; i < 256; i++) {
            this.S[i] = (byte) i;
        }
        int i2 = 0;
        for (int i3 = 0; i3 < 256; i3++) {
            byte[] bArr2 = this.S;
            i2 = (i2 + bArr2[i3] + bArr[i3 % bArr.length]) & 255;
            swap(bArr2, i3, i2);
        }
        this.i = 0;
        this.j = 0;
    }

    private void swap(byte[] bArr, int i, int i2) {
        byte b = bArr[i];
        bArr[i] = bArr[i2];
        bArr[i2] = b;
    }

    public byte[] encrypt(byte[] bArr) {
        // RC4伪随机生成算法(PRGA)
        byte[] bArr2 = new byte[bArr.length];
        for (int i = 0; i < bArr.length; i++) {
            int i2 = (this.i + 1) & 255;
            this.i = i2;
            int i3 = this.j;
            byte[] bArr3 = this.S;
            int i4 = (i3 + bArr3[i2]) & 255;
            this.j = i4;
            swap(bArr3, i2, i4);
            byte[] bArr4 = this.S;
            bArr2[i] = (byte) (bArr4[(bArr4[this.i] + bArr4[this.j]) & 255] ^ bArr[i]);
        }
        return bArr2;
    }

    public boolean check(byte[] bArr) {
        return Arrays.equals(new byte[]{-75, 80, 80, 48, -88, 75, 103, 45, -91, 89, -60, 91, -54, 5, 6, -72}, encrypt(bArr));
    }
}

2.3 获取RC4密钥

密钥通过getResources().getString(R.string.key).getBytes()获取,可以使用Frida进行Hook:

function hook_key() {
    var targetClass = Java.use("android.content.res.Resources");
    var targetMethod = "getString";
    
    targetClass[targetMethod].overload("int").implementation = function(resourceId) {
        console.log("getResources().getString called with resource ID: " + resourceId);
        var result = this.getString(resourceId);
        console.log("Result of getResources().getString: " + result);
        return result;
    };
}

Hook后获取到密钥为:3e1fel

2.4 解密用户名

使用Python实现RC4算法解密用户名:

class RC4:
    def __init__(self, key):
        self.S = list(range(256))
        self.key = key
        self.key_schedule()
    
    def key_schedule(self):
        j = 0
        for i in range(256):
            j = (j + self.S[i] + self.key[i % len(self.key)]) % 256
            self.S[i], self.S[j] = self.S[j], self.S[i]
    
    def encrypt(self, data):
        i = 0
        j = 0
        output = []
        for byte in data:
            i = (i + 1) % 256
            j = (j + self.S[i]) % 256
            self.S[i], self.S[j] = self.S[j], self.S[i]
            output.append((byte ^ self.S[(self.S[i] + self.S[j]) % 256]) % 256)
        return output
    
    def decrypt(self, data):
        return self.encrypt(data)

if __name__ == "__main__":
    key = b"3e1fel"
    rc4 = RC4(key)
    ciphertext = [-75, 80, 80, 48, -88, 75, 103, 45, -91, 89, -60, 91, -54, 5, 6, -72]
    decrypted_text = rc4.decrypt(ciphertext)
    print("Decrypted Text:", decrypted_text)
    print("As String:", bytes(decrypted_text).decode('utf-8'))

解密得到用户名:G>IkH<aHu5FE3GSV

3. Native层分析

3.1 动态注册函数定位

使用Frida Hook RegisterNatives来定位check2函数:

function hook_jni_7() {
    Java.perform(function() {
        var symbols = Process.getModuleByName("libart.so").enumerateSymbols();
        var RegisterNatives_addr = NULL;
        
        for (var index = 0; index < symbols.length; index++) {
            const symbol = symbols[index];
            if (symbol.name.indexOf("CheckJNI") == -1 && 
                symbol.name.indexOf("RegisterNatives") >= 0) {
                RegisterNatives_addr = symbol.address;
            }
        }
        
        console.log("RegisterNatives_addr :", RegisterNatives_addr);
        
        Interceptor.attach(RegisterNatives_addr, {
            onEnter: function(args) {
                var env = Java.vm.tryGetEnv();
                var class_name = env.getClassName(args[1]);
                console.log("class_name ", class_name)
                
                var method_count = args[3].toInt32();
                for (var index = 0; index < method_count; index++) {
                    var method_name = args[2].add(Process.pointerSize * 3 * index).readPointer().readCString();
                    console.log("method_name ", method_name);
                    
                    var signature = args[2].add(Process.pointerSize * 3 * index).add(Process.pointerSize).readPointer().readCString();
                    console.log("signature ", signature);
                    
                    var fnPtr = args[2].add(Process.pointerSize * 3 * index).add(Process.pointerSize * 2).readPointer();
                    console.log("fnPtr ", fnPtr);
                    
                    var modeule = Process.findModuleByAddress(fnPtr);
                    console.log("modeule ", JSON.stringify(modeule))
                    console.log(" func in IDA addr 32 :", fnPtr.sub(modeule.base).sub(1))
                    console.log(" func in IDA addr 64 :", fnPtr.sub(modeule.base))
                }
            },
            onLeave: function(retval) {}
        });
    });
}

输出结果:

RegisterNatives_addr : 0x7393c3968c
class_name com.feifei.babyandroid.MainActivity
method_name check2
signature ([B[B)Z
fnPtr 0x737bc9ab18
modeule {"name":"libbabyandroid.so","base":"0x737bc9a000","size":16384,"path":"/data/app/com.feifei.babyandroid-7xPEumuUSZXKdqoEsZ28zQ==/base.apk!/lib/arm64-v8a/libbabyandroid.so"}
 func in IDA addr 32 : 0xb17
 func in IDA addr 64 : 0xb18

3.2 IDA逆向分析

在IDA中定位到check2函数(偏移0xB18),分析发现是AES加密算法。

4. 解题步骤总结

  1. 获取RC4密钥

    • 使用Frida Hook getResources().getString()获取密钥3e1fel
  2. 解密用户名

    • 实现RC4算法解密密文[-75, 80, 80, 48, -88, 75, 103, 45, -91, 89, -60, 91, -54, 5, 6, -72]
    • 得到用户名G>IkH<aHu5FE3GSV
  3. 分析密码验证

    • 定位check2 native函数
    • 逆向分析so文件,发现是AES加密
    • 根据AES特征还原密码验证逻辑
  4. 获取完整flag

    • 结合用户名和密码验证通过后,获取完整flag

5. 关键知识点

  1. RC4算法

    • 密钥调度算法(KSA)
    • 伪随机生成算法(PRGA)
    • 加密解密使用相同操作
  2. Frida使用技巧

    • Hook Java方法获取关键数据
    • Hook JNI RegisterNatives定位native函数
  3. Android逆向工具链

    • Jadx: Java层反编译
    • IDA Pro: Native层逆向分析
    • Frida: 动态分析Hook
  4. 加密算法识别

    • RC4特征:256字节S盒,密钥调度,异或操作
    • AES特征:固定块大小,轮密钥扩展,S盒替换

通过以上分析步骤,可以完整还原题目验证逻辑并获取flag。

Android逆向分析:2024Hgame-babyAndroid解题详解 1. 题目概述 这是一个来自2024年Hgame比赛的Android逆向题目,包含Java层和Native层(so)的验证逻辑。题目要求输入用户名和密码,通过两层验证后会显示"Congratulate!!!^_ ^"的成功提示。 2. Java层分析 2.1 MainActivity分析 MainActivity的主要逻辑在 onClick 方法中: 验证流程分为两步: Check1 类验证用户名 check2 本地方法验证密码 2.2 Check1类分析 Check1类实现了RC4加密算法: 2.3 获取RC4密钥 密钥通过 getResources().getString(R.string.key).getBytes() 获取,可以使用Frida进行Hook: Hook后获取到密钥为: 3e1fel 2.4 解密用户名 使用Python实现RC4算法解密用户名: 解密得到用户名: G>IkH<aHu5FE3GSV 3. Native层分析 3.1 动态注册函数定位 使用Frida Hook RegisterNatives 来定位 check2 函数: 输出结果: 3.2 IDA逆向分析 在IDA中定位到 check2 函数(偏移0xB18),分析发现是AES加密算法。 4. 解题步骤总结 获取RC4密钥 : 使用Frida Hook getResources().getString() 获取密钥 3e1fel 解密用户名 : 实现RC4算法解密密文 [-75, 80, 80, 48, -88, 75, 103, 45, -91, 89, -60, 91, -54, 5, 6, -72] 得到用户名 G>IkH<aHu5FE3GSV 分析密码验证 : 定位 check2 native函数 逆向分析so文件,发现是AES加密 根据AES特征还原密码验证逻辑 获取完整flag : 结合用户名和密码验证通过后,获取完整flag 5. 关键知识点 RC4算法 : 密钥调度算法(KSA) 伪随机生成算法(PRGA) 加密解密使用相同操作 Frida使用技巧 : Hook Java方法获取关键数据 Hook JNI RegisterNatives定位native函数 Android逆向工具链 : Jadx: Java层反编译 IDA Pro: Native层逆向分析 Frida: 动态分析Hook 加密算法识别 : RC4特征:256字节S盒,密钥调度,异或操作 AES特征:固定块大小,轮密钥扩展,S盒替换 通过以上分析步骤,可以完整还原题目验证逻辑并获取flag。