一: 闲言
52论坛又开启一年一度的解题得红包的活动,大年初七,闲着没事,就把题下载下来解题了一波,现在活动结束了,可以分享解题思路,我就分享一下我的思路,俗话说的好,条条大路通罗马,解题思路也不止一条,欢迎大佬们把其他解题思路讨论分享一波。
二:开始
下载好apk后,装入手机运行了一波
引入眼帘的就是一个解锁,
那么直接拖入 jadx-gui 看看代码
发现收到密码后,进入了 checkPassword 方法
public boolean checkPassword(String str) {
try {
InputStream open = getAssets().open("classes.dex");
byte[] bArr = new byte[open.available()];
open.read(bArr);
File file = new File(getDir("data", 0), "1.dex");
FileOutputStream fileOutputStream = new FileOutputStream(file);
fileOutputStream.write(bArr);
fileOutputStream.close();
open.close();
String str2 = (String) new DexClassLoader(file.getAbsolutePath(), getDir("dex", 0).getAbsolutePath(), null, getClass().getClassLoader()).loadClass("com.zj.wuaipojie2024_2.C").getDeclaredMethod("isValidate", Context.class, String.class, int[].class).invoke(null, this, str, getResources().getIntArray(R.array.A_offset));
if (str2 == null || !str2.startsWith("唉!")) {
return false;
}
this.tvText.setText(str2);
this.myunlock.setVisibility(8);
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
代码逻辑也很清晰,将assets目录下的classes.dex 打开,读取写入到软件data目录app_data下名字1.dex 文件,然后加载该dex,再使用反射的调用的方式,调用 com.zj.wuaipojie2024_2.C 类 ,方法 isValidate ,并传入了int数组。
三:发现问题
在随便输入一组密码后,在日志中发现了dex加载有问题,发现了报错
说dex 的checksum 有问题,将classes.dex 拖入jadx-gui中提示报错,打不开。
那就上 010Editor ,拖入后
发现 checksum 和sha1 signature 的值确实被篡改过,那么我们使用代码计算出来,重新填回就行了。
在这片文章有对dex文件的介绍
dex文件的介绍
dex 头部中 checksum 和signature的介绍
那直接使用python 计算出正确的值即可
def repairChecksum(dex_file):
self = open(dex_file, "rb")
self.seek(8)
sourceData = self.read(4)
self.seek(12)
checkdata = self.read()
checksum = format(zlib.adler32(checkdata), 'x')
print ("头部原checksum:",sourceData )
print ("计算checksum:",checksum)
def repairSignature(dex_file):
self = open(dex_file, "rb")
self.seek(12)
sourceData = self.read(20)
self.seek(32)
sigdata = self.read()
sha1 = hashlib.sha1()
sha1.update(sigdata)
sha0 = sha1.hexdigest()
print ("计算signature:",sha0)
print ("现在signature:",sourceData.hex())
重新填入后:
重新使用jadx-gtui 打开
没问题,将修好的dex放回apk中,重启应用,重新输入密码,发现了新的报错:
这个报错,说明有1.dex已经被成功加载进去。
经过排查代码发现报错点在这里
也就是读取这个decode.dex 转为ByteBuffer时出的问题,我们进到这个目录查看文件。
那么现在将1.dex 也移动到这个目录。
重新启动APP,发现还是有问题,那是因为移动的decode.dex 的用户组没有修改,所以不能被加载,我们使用命令修改用户组。
再次启动APP,输入密码,发现没有报错,那证明代码中。
四:分析代码逻辑
那么现在我们就来仔细读读代码逻辑
apk 首次加载1.dex调用 类C\ isValidate 方法,传入int数组
问:isValidate 方法里面又做了什么?
答:简述就是根据decode.dex 生成了2.dex,然后加载2.dex,然后反射调用
类 com.zj.wuaipojie2024_2.A 方法 d ,并传入字符串,显而易见这个str 就是 九宫格密码
那么我们现在使用代码,把这个 2.dex 生成出来看看代码逻辑。
private static ByteBuffer read() {
try {
File file = new File("C:\\Users\\Administrator\\Desktop\\1.dex");
if (!file.exists()) {
return null;
}
FileInputStream fileInputStream = new FileInputStream(file);
byte[] bArr = new byte[fileInputStream.available()];
fileInputStream.read(bArr);
ByteBuffer wrap = ByteBuffer.wrap(bArr);
fileInputStream.close();
return wrap;
} catch (Exception unused) {
unused.printStackTrace();
return null;
}
}
private static File fix(ByteBuffer byteBuffer, int i, int i2, int i3) throws Exception {
try {
File dir = new File("C:\\Users\\Administrator\\Desktop");
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(dir, "2.dex");
FileOutputStream fileOutputStream = new FileOutputStream(file);
fileOutputStream.write(array);
fileOutputStream.close();
return file;
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
ByteBuffer read = read();
File fix = fix(read, 0, 3, 7908);
fix方法3个int值就是一开始传入的int数组
生成的2.dex直接拖入jadx-gui,直接到A-d 方法
public static String d(Context context, String str) {
MainActivity.sSS(str);
String signInfo = Utils.getSignInfo(context);
if (signInfo == null || !signInfo.equals(C.SIGNATURE)) {
return "";
}
StringBuffer stringBuffer = new StringBuffer();
int i = 0;
while (stringBuffer.length() < 9 && i < 40) {
int i2 = i + 1;
String substring = "0485312670fb07047ebd2f19b91e1c5f".substring(i, i2);
if (!stringBuffer.toString().contains(substring)) {
stringBuffer.append(substring);
}
i = i2;
}
return !str.equals(stringBuffer.toString().toUpperCase()) ? "" : "唉!哪有什么亿载沉睡的玄天帝,不过是一位被诅咒束缚的旧日之尊,在灯枯之际挣扎的南柯一梦罢了。有缘人,这份机缘就赠予你了。坐标在B.d";
}
可以看到,把密码进行了对比,那我们把代码运行一次,看看密码时多少。
可以看到正确的九宫格密码是:048531267
密码正常,成功跳转
这个是发现 2.dex 类B 方法d
这显示不是正确的flag。
这时细心的小伙伴就发现,上面的数组还有一组数组D- 1,1,8108,那
我们再用这组数组生成一个新的 3.dex
将3.dex 拖入jadx-gui查看代码:
打开B.d 方法
public class B {
public static String d(String str) {
return "机缘是{" + Utils.md5(Utils.getSha1("password+你的uid".getBytes())) + "}";
}
}
那么这个算法生成出来的结果就是flag
五:结束
自此这道题就算解题成功,200cb到手。 本地难度适中,掌握dex的结构和代码分析能力就能很快解题。