高级题会不了一点儿
初级一
小猫游戏,改一下判断
将t.LOSE
的值改为win,然后将case i.LOSE
的代码段删掉,重新签名安装即可<br />游戏结束会播放原神启动,播完会输出flag
结果为flag{happy_new_year_2024}
初级二
flag是跟着签名走的,所以没法重新编译
看代码可以看到是出金启动FlagActivity
所以直接上objection强制启动
中级
实际是网信柏鹭杯2021不重要的图形
的变种(不能说一模一样,只能说完全一致),不过这题可以很简单的得到密码,而原题的密码需要爆破16长度数组的md5,不过正如题目名一样,重点不在解锁,不解锁也可以
拿flag
反编译能得知assets目录下还有一个dex,这个dex的checksum校验不通过,需要在jadx里忽略校验
将assets目录下的dex拷贝的应用数据目录的app_data
文件夹下,加载并且反射调用com.zj.wuaipojie2024_2.C.isValidate
再次反射,调用com.zj.wuaipojie2024_2.A.d
明显的有问题
回来发现做了修复
由于1.dex
的checksum校验不通过,所以系统不会加载这个dex,也就是说不会触发这个修复方法,因此可以将这个修复的代码摘出来,单独进行修复,涉及到3个类
Main类,将fix和read从com.zj.wuaipojie2024_2.C
中拿出来
package org.example;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.lang.String;
import java.lang.Integer;
import java.nio.ByteBuffer;
import java.util.HashMap;
public class Main {
private static File fix(ByteBuffer byteBuffer, int i, int i2, int i3) throws Exception {
try {
int intValue = D.getClassDefData(byteBuffer, i).get("class_data_off").intValue();
HashMap<String, int[][]> classData = D.getClassData(byteBuffer, intValue);
classData.get("direct_methods")[i2][2] = i3;
byte[] encodeClassData = D.encodeClassData(classData);
byteBuffer.position(intValue);
byteBuffer.put(encodeClassData);
byteBuffer.position(32);
byte[] bArr = new byte[byteBuffer.capacity() - 32];
byteBuffer.get(bArr);
byte[] sha1 = Utils.getSha1(bArr);
byteBuffer.position(12);
byteBuffer.put(sha1);
int checksum = Utils.checksum(byteBuffer);
byteBuffer.position(8);
byteBuffer.putInt(Integer.reverseBytes(checksum));
byte[] array = byteBuffer.array();
File file = new File("D:\\Tools\\workspace\\ctf\\52\\", "decode2.dex");
FileOutputStream fileOutputStream = new FileOutputStream(file);
fileOutputStream.write(array);
fileOutputStream.close();
return file;
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
private static ByteBuffer read() {
try {
File file = new File("D:\\Tools\\workspace\\ctf\\52", "classes.dex");
if (file.exists()) {
FileInputStream fileInputStream = new FileInputStream(file);
byte[] bArr = new byte[fileInputStream.available()];
fileInputStream.read(bArr);
ByteBuffer wrap = ByteBuffer.wrap(bArr);
fileInputStream.close();
return wrap;
}
return null;
} catch (Exception unused) {
return null;
}
}
public static void main(String[] args) throws Exception{
fix(read(),0,3,7908);
// fix(read(),1,1,8108);
}
}
com.zj.wuaipojie2024_2.D
类
package org.example;
import java.lang.reflect.Array;
import java.nio.ByteBuffer;
import java.util.HashMap;
/* loaded from: assets/classes.dex */
public class D {
public static HashMap<String, Integer> getClassDefData(ByteBuffer byteBuffer, int i) {
if (byteBuffer == null) {
throw new IllegalArgumentException("Buffer cannot be null");
}
byteBuffer.position(100);
byteBuffer.position((i * 32) + Integer.reverseBytes(byteBuffer.getInt()));
HashMap<String, Integer> hashMap = new HashMap<>();
int reverseBytes = Integer.reverseBytes(byteBuffer.getInt());
int reverseBytes2 = Integer.reverseBytes(byteBuffer.getInt());
int reverseBytes3 = Integer.reverseBytes(byteBuffer.getInt());
int reverseBytes4 = Integer.reverseBytes(byteBuffer.getInt());
int reverseBytes5 = Integer.reverseBytes(byteBuffer.getInt());
int reverseBytes6 = Integer.reverseBytes(byteBuffer.getInt());
int reverseBytes7 = Integer.reverseBytes(byteBuffer.getInt());
int reverseBytes8 = Integer.reverseBytes(byteBuffer.getInt());
hashMap.put("class_idx", Integer.valueOf(reverseBytes));
hashMap.put("access_flag", Integer.valueOf(reverseBytes2));
hashMap.put("superclass_idx", Integer.valueOf(reverseBytes3));
hashMap.put("interfaces_off", Integer.valueOf(reverseBytes4));
hashMap.put("source_file_idx", Integer.valueOf(reverseBytes5));
hashMap.put("annotation_off", Integer.valueOf(reverseBytes6));
hashMap.put("class_data_off", Integer.valueOf(reverseBytes7));
hashMap.put("static_values_off", Integer.valueOf(reverseBytes8));
return hashMap;
}
public static HashMap<String, int[][]> getClassData(ByteBuffer byteBuffer, int i) {
byteBuffer.position(i);
int i2 = Utils.fromULEB128(byteBuffer)[0];
int i3 = Utils.fromULEB128(byteBuffer)[0];
int i4 = Utils.fromULEB128(byteBuffer)[0];
int i5 = Utils.fromULEB128(byteBuffer)[0];
int[][] iArr = (int[][]) Array.newInstance(Integer.TYPE, i2, 2);
if (i2 > 0) {
int i6 = 0;
for (int i7 = 0; i7 < i2; i7++) {
int[] encode_field = encode_field(byteBuffer);
if (i7 == 0) {
i6 = encode_field[0];
} else {
i6 += encode_field[0];
}
int[] iArr2 = new int[2];
iArr2[0] = i6;
iArr2[1] = encode_field[1];
iArr[i7] = iArr2;
}
}
int[][] iArr3 = (int[][]) Array.newInstance(Integer.TYPE, i3, 2);
if (i3 > 0) {
int i8 = 0;
for (int i9 = 0; i9 < i3; i9++) {
int[] encode_field2 = encode_field(byteBuffer);
if (i9 == 0) {
i8 = encode_field2[0];
} else {
i8 += encode_field2[0];
}
int[] iArr4 = new int[2];
iArr4[0] = i8;
iArr4[1] = encode_field2[1];
iArr3[i9] = iArr4;
}
}
int[][] iArr5 = (int[][]) Array.newInstance(Integer.TYPE, i4, 3);
if (i4 > 0) {
int i10 = 0;
for (int i11 = 0; i11 < i4; i11++) {
int[] encode_method = encode_method(byteBuffer);
if (i11 == 0) {
i10 = encode_method[0];
} else {
i10 += encode_method[0];
}
int[] iArr6 = new int[3];
iArr6[0] = i10;
iArr6[1] = encode_method[1];
iArr6[2] = encode_method[2];
iArr5[i11] = iArr6;
}
}
int[][] iArr7 = (int[][]) Array.newInstance(Integer.TYPE, i5, 3);
if (i5 > 0) {
int i12 = 0;
for (int i13 = 0; i13 < i3; i13++) {
int[] encode_method2 = encode_method(byteBuffer);
if (i13 == 0) {
i12 = encode_method2[0];
} else {
i12 += encode_method2[0];
}
int[] iArr8 = new int[3];
iArr8[0] = i12;
iArr8[1] = encode_method2[1];
iArr8[2] = encode_method2[2];
iArr7[i13] = iArr8;
}
}
HashMap<String, int[][]> hashMap = new HashMap<>();
hashMap.put("static_fields", iArr);
hashMap.put("instance_fields", iArr3);
hashMap.put("direct_methods", iArr5);
hashMap.put("virtual_methods", iArr7);
return hashMap;
}
private static int[] encode_field(ByteBuffer byteBuffer) {
return new int[]{Utils.fromULEB128(byteBuffer)[0], Utils.fromULEB128(byteBuffer)[0]};
}
private static int[] encode_method(ByteBuffer byteBuffer) {
return new int[]{Utils.fromULEB128(byteBuffer)[0], Utils.fromULEB128(byteBuffer)[0], Utils.fromULEB128(byteBuffer)[0]};
}
public static byte[] encodeClassData(HashMap<String, int[][]> hashMap) {
int[][] iArr = hashMap.get("static_fields");
int[][] iArr2 = hashMap.get("instance_fields");
int[][] iArr3 = hashMap.get("direct_methods");
int[][] iArr4 = hashMap.get("virtual_methods");
byte[] merge = merge(Utils.toULEB128(iArr.length), Utils.toULEB128(iArr2.length), Utils.toULEB128(iArr3.length), Utils.toULEB128(iArr4.length));
if (iArr.length > 0) {
int i = 0;
for (int[] iArr5 : iArr) {
merge = merge(merge, decode_field(iArr5, i));
i = iArr5[0];
}
}
if (iArr2.length > 0) {
int i2 = 0;
for (int[] iArr6 : iArr2) {
merge = merge(merge, decode_field(iArr6, i2));
i2 = iArr6[0];
}
}
if (iArr3.length > 0) {
int i3 = 0;
for (int[] iArr7 : iArr3) {
merge = merge(merge, decode_method(iArr7, i3));
i3 = iArr7[0];
}
}
if (iArr4.length > 0) {
int i4 = 0;
for (int[] iArr8 : iArr4) {
merge = merge(merge, decode_method(iArr8, i4));
i4 = iArr8[0];
}
}
return merge;
}
private static byte[] merge(byte[]... bArr) {
int i = 0;
for (byte[] bArr2 : bArr) {
i += bArr2.length;
}
ByteBuffer allocate = ByteBuffer.allocate(i);
for (byte[] bArr3 : bArr) {
allocate.put(bArr3);
}
byte[] bArr4 = new byte[allocate.position()];
allocate.position(0);
allocate.get(bArr4);
return bArr4;
}
private static byte[] decode_field(int[] iArr, int i) {
return merge(Utils.toULEB128(iArr[0] - i), Utils.toULEB128(iArr[1]));
}
private static byte[] decode_method(int[] iArr, int i) {
return merge(Utils.toULEB128(iArr[0] - i), Utils.toULEB128(iArr[1]), Utils.toULEB128(iArr[2]));
}
}
com.zj.wuaipojie2024_2.Utils
类
package org.example;
import java.math.BigInteger;
import java.nio.ByteBuffer;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.ArrayList;
/* loaded from: assets/classes.dex */
public class Utils {
public static final String SHA1 = "SHA1";
public static byte[] toULEB128(int i) {
int i2 = i >> 28;
if (i2 > 0) {
return new byte[]{(byte) ((i & 127) | 128), (byte) (((i >> 7) & 127) | 128), (byte) (((i >> 14) & 127) | 128), (byte) (((i >> 21) & 127) | 128), (byte) (i2 & 15)};
}
int i3 = i >> 21;
if (i3 > 0) {
return new byte[]{(byte) ((i & 127) | 128), (byte) (((i >> 7) & 127) | 128), (byte) (((i >> 14) & 127) | 128), (byte) (i3 & 127)};
}
int i4 = i >> 14;
if (i4 > 0) {
return new byte[]{(byte) ((i & 127) | 128), (byte) (((i >> 7) & 127) | 128), (byte) (i4 & 127)};
}
int i5 = i >> 7;
return i5 > 0 ? new byte[]{(byte) ((i & 127) | 128), (byte) (i5 & 127)} : new byte[]{(byte) (i & 127)};
}
public static byte[] getSha1(byte[] bArr) {
try {
return MessageDigest.getInstance("SHA").digest(bArr);
} catch (Exception unused) {
return null;
}
}
public static String md5(byte[] bArr) {
try {
String bigInteger = new BigInteger(1, MessageDigest.getInstance("md5").digest(bArr)).toString(16);
for (int i = 0; i < 32 - bigInteger.length(); i++) {
bigInteger = "0" + bigInteger;
}
return bigInteger;
} catch (NoSuchAlgorithmException unused) {
throw new RuntimeException("ops!!");
}
}
public static int checksum(ByteBuffer byteBuffer) {
byteBuffer.position(12);
int capacity = byteBuffer.capacity();
int i = 1;
int i2 = 0;
boolean z = false;
while (byteBuffer.position() < capacity) {
ArrayList arrayList = new ArrayList();
int i3 = 0;
while (true) {
if (i3 < 1024) {
arrayList.add(Integer.valueOf(byteBuffer.get() & 255));
if (byteBuffer.position() == byteBuffer.limit()) {
z = true;
break;
}
i3++;
} else {
break;
}
}
int[] calculateVar = calculateVar(arrayList, i, i2);
int i4 = calculateVar[0];
int i5 = calculateVar[1];
if (z) {
return (i5 << 16) + i4;
}
i2 = i5;
i = i4;
}
return 0;
}
private static int[] calculateVar(ArrayList<Integer> arrayList, int i, int i2) {
int i3 = 0;
while (i3 < arrayList.size()) {
int intValue = (arrayList.get(i3).intValue() + i) % 65521;
i2 = (i2 + intValue) % 65521;
i3++;
i = intValue;
}
return new int[]{i, i2};
}
public static int[] fromULEB128(ByteBuffer byteBuffer) {
int i;
int i2 = byteBuffer.get() & 255;
if (i2 > 127) {
int i3 = byteBuffer.get() & 255;
i2 = (i2 & 127) | ((i3 & 127) << 7);
if (i3 > 127) {
int i4 = byteBuffer.get() & 255;
i2 |= (i4 & 127) << 14;
if (i4 > 127) {
int i5 = byteBuffer.get() & 255;
i2 |= (i5 & 127) << 21;
if (i5 > 127) {
i2 |= (byteBuffer.get() & 255) << 28;
i = 5;
} else {
i = 4;
}
} else {
i = 3;
}
} else {
i = 2;
}
} else {
i = 1;
}
return new int[]{i2, i};
}
}
上面这些都是摘除了android相关类的,可以正常用
其中fix需要3个int参数,是从资源中读取的
资源里有两个,说明要修复两次,但是这个程序里只会修复一次,所以我在main里修复两次,得到修复后的dex,校验通过
这时候可以看到方法的全貌了,根据这个逻辑,显然密码是048531267
看到B.d
,也就是说密码是md5(getSha1("048531267uid".getBytes()))
,我的uid是2148984
从代码里将md5和getSha1拿出来运行,得到结果
解锁
现在已经拿到了flag,但是解锁还过不去,有什么办法可以成功解锁呢?我的做法是使用frida,虽然这个cm在so里做了frida检测, 但我发现只要用attach模式就可以避免被杀,spawn模式一杀一个准,虽然可以hook检测函数,但是代码量就上来了,没有必要<br />首先用logcat来看日志,需要1.dex能通过校验,那么办法就是先将修复好的dex放到对应目录下
但是每次解锁,应用都会重新复制1.dex,所以这里是第一个hook点,hookFileOutputStream
的构造方法,将参数改为其他文件路径,同时还可以hookcheckPassword
来查看输入的密码和返回值
Java.perform(function () {
let MainActivity = Java.use("com.zj.wuaipojie2024_2.MainActivity");
MainActivity["checkPassword"].implementation = function (str) {
console.log(`MainActivity.checkPassword is called: str=${str}`);
let result = this["checkPassword"](str);
var outStream = Java.use('java.io.FileOutputStream')
outStream["$init"].overload('java.io.File').implementation = function (arg) {
console.log(arg)
var ret = this.$init('/data/user/0/com.zj.wuaipojie2024_2/app_data/0.dex') }
}
console.log(`MainActivity.checkPassword result=${result}`);
return result;
};
});
这样就不会重复写入1.dex
,将修复好的dex复制过去并重命名, 权限开成777
第二次解锁的时候触发了
这时候可以用修复好的dex替换掉1.dex
后面在应用中修复时,需要加载decode.dex
,而且应用只修复1次,用的偏移是A_offset
,还有个B_offset
没用,所以我们需要单独修复一下这个情况,然后将修复完的dex放到目录下,改名decode.dex
,配置权限777
,让应用完成剩下的修复
同时需要注意,之前hook了FileOutputStream
的构造函数,这里修复的时候同样用到了,所以要对2.dex
做一次判断
Java.perform(function () {
let MainActivity = Java.use("com.zj.wuaipojie2024_2.MainActivity");
MainActivity["checkPassword"].implementation = function (str) {
console.log(`MainActivity.checkPassword is called: str=${str}`);
let result = this["checkPassword"](str);
var outStream = Java.use('java.io.FileOutputStream')
outStream["$init"].overload('java.io.File').implementation = function(arg){
console.log(arg)
if(arg == '/data/user/0/com.zj.wuaipojie2024_2/app_data/2.dex'){
console.log('2.dex')
var ret = this.$init(arg)
}
else{var ret = this.$init('/data/user/0/com.zj.wuaipojie2024_2/app_data/0.dex')}
}
console.log(`MainActivity.checkPassword result=${result}`);
return result;
};
});
使用密码成功解锁(如果不成功,可能是在重新hook时,首次没有触发FileOutputStrem
,导致1.dex
被重写,这时候重新对1.dex
做一次覆盖就可以了)