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();
}
验证流程分为两步:
Check1类验证用户名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. 解题步骤总结
-
获取RC4密钥:
- 使用Frida Hook
getResources().getString()获取密钥3e1fel
- 使用Frida Hook
-
解密用户名:
- 实现RC4算法解密密文
[-75, 80, 80, 48, -88, 75, 103, 45, -91, 89, -60, 91, -54, 5, 6, -72] - 得到用户名
G>IkH<aHu5FE3GSV
- 实现RC4算法解密密文
-
分析密码验证:
- 定位
check2native函数 - 逆向分析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。