首页
社区
课程
招聘
[原创] 52破解 2024春节解题{Android 中级题} 解题思路
发表于: 2024-2-27 14:41 3197

[原创] 52破解 2024春节解题{Android 中级题} 解题思路

2024-2-27 14:41
3197

一: 闲言

52论坛又开启一年一度的解题得红包的活动,大年初七,闲着没事,就把题下载下来解题了一波,现在活动结束了,可以分享解题思路,我就分享一下我的思路,俗话说的好,条条大路通罗马,解题思路也不止一条,欢迎大佬们把其他解题思路讨论分享一波。

二:开始

下载好apk后,装入手机运行了一波
图片描述
引入眼帘的就是一个解锁,
那么直接拖入 jadx-gui 看看代码
图片描述

发现收到密码后,进入了 **checkPassword ** 方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
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 计算出正确的值即可

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
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 生成出来看看代码逻辑。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
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 方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
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 方法
图片描述

1
2
3
4
5
public class B {
    public static String d(String str) {
        return "机缘是{" + Utils.md5(Utils.getSha1("password+你的uid".getBytes())) + "}";
    }
}

那么这个算法生成出来的结果就是flag

五:结束

自此这道题就算解题成功,200cb到手。 本地难度适中,掌握dex的结构和代码分析能力就能很快解题。


[注意]传递专业知识、拓宽行业人脉——看雪讲师团队等你加入!

最后于 2024-2-27 14:43 被ty1937编辑 ,原因:
收藏
免费 2
支持
分享
最新回复 (7)
雪    币: 2685
活跃值: (2308)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
2
正己果然是牛人啊
2024-2-27 16:18
0
雪    币: 116
活跃值: (1012)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
3
正己大佬牛逼
2024-2-27 22:03
0
雪    币: 3004
活跃值: (30866)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
4
感谢分享
2024-2-28 09:45
1
雪    币: 499
活跃值: (659)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
5
有几个节省时间的步骤
1.如果不会改checksum可以在jadx的配置里关掉plugin:verify dex file checksum就能查看原始的1.dex
2.取巧版本,把修复dex的代码拷出来直接运行生成2.dex修复那两个类
楼主的方法是标准的解题步骤
2024-2-28 20:14
0
雪    币: 921
活跃值: (1712)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
6
前来打酱油的 有几个节省时间的步骤 1.如果不会改checksum可以在jadx的配置里关掉plugin:verify dex file checksum就能查看原始的1.dex 2.取巧版本,把修复dex的代 ...
一步到位,直接用第二个数组还原3.dex就行了,哈哈
2024-2-29 09:32
0
雪    币: 2608
活跃值: (1918)
能力值: ( LV5,RANK:60 )
在线值:
发帖
回帖
粉丝
7
直接010editor修改一下code_offset最快
2024-2-29 12:45
0
雪    币: 110
活跃值: (1555)
能力值: ( LV3,RANK:20 )
在线值:
发帖
回帖
粉丝
8
学习了,没有太复杂的技术,但是基础必须要牢靠才能算得出来,厉害
2024-2-29 14:01
0
游客
登录 | 注册 方可回帖
返回
//