[ISCC 2024]Puzzle_Game
本文最后更新于142 天前,其中的信息可能已经过时,如有错误可以直接在文章下留言

题目描述

打开附件是一个.apk文件,我们直接用手机下载该软件,然后随便输入,看它输出什么,输出了input error.Keep pumping! 如图

然后就用jadx打开.apk,左上角导航->文本搜索->搜Keep pumping,来到它的主逻辑处 ,如图

Java代码,我还不是很熟悉,还没看完Java,大多都问AI,看第55行的函数Jformat,被类onClick调用,当函数返回true时,才会提示”OH YES, ONE STEP AWAY FROM SUCCESS!”,也就是我们希望的结果,所以我们先只用看Jformat函数的返回值。

str.substring ()是 Java 中的一个方法,用于从字符串中提取子字符串。str.substring(int beginIndex, int endIndex)即是从指定的 beginIndex 开始,提取字符串直到 endIndex – 1。

所以代码意思是看你输入的字符串的第1个到第5个字符是否是 ISCC{ 。和最后一个字符是不是 },然后再看a.a接收ISCC{}包着的字符串,就是我们的flag。

我们点进去看,应该是是一个类

看class a里面的a函数

public static boolean a(String str) {
        String substring = str.substring(0, str.length() - 15);
//提取字符串的第1到字符串长度-15个字符,但是我们不知道传入的字符串长度是多少。
        String str2 = Myjni.getstr();
//这里点进去是System.loadLibrary("whathappened"),我们暂时不知道是啥,应该是加载一个lib
        String str3 = substring + str2;
//拼接前面两个字符
        String str4 = substring + str.substring(str.length() - 15, str.length());
//这里的str4就是就是我们的flag
        return b(substring) && getSHA256(str4).equals("437414687cecdd3526281d4bc6492f3931574036943597fddd40adfbe07a9afa") && str4.equals(str3);
//这里第一b函数接收我们的substring,第二对我们的flag进行sha256加密处理,并与一串值对比,第三判断我们的flag是不是等于str3,那str3其实也是我们的flag,Myjni.getstr()函数是个关键函数。
    }

那么我们就有思路,flag应该是被分成了两部分,第一部分应该由b函数可以得到,第而部分由函数Myjni.getstr()得到,先看b函数。

public static boolean b(String str) {
        try {
            if (c(str)) {
//这里又有个c函数,加在上面了
                int parseInt = Integer.parseInt(str);
//将数字字符串转化为整数
                if (get1(parseInt) && d(parseInt)) {
//这里又有两个函数,加在下面了,两个函数都是对数字处理,我们猜测数字的前8位是纯数字
                    int i = parseInt + 11;
                    if (!get1(i)) {
//get1函数的要求是第一位数字是4,才会返回true,上面数字加了11到了这里的if判断,第一位是4的话就不会往后执行了,所以我一开始猜测数字范围是49999989到49999999。
                        if (!d(i)) {
                            return true;
                        }
                    }
                }
            }
            return false;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }
public static boolean c(String str) {
        return str.length() == 8;
//要求flag.length() - 15 = 8,那我们求的flag长度就是23了,而传入b函数的长度只有8
    }
public static boolean get1(int i) {
        String num = Integer.toString(i);
        for (int i2 = 0; i2 < num.length() - 1; i2++) {
            i /= 10;
        }
        return i == 4;
//这个函数的要求就是数字的第一位要是4
    }
public static boolean d(int i) {
        int i2 = 2;
        while (i2 < i && i % i2 != 0) {
            i2++;
        }
        return i == i2;
    }

因为要分析d和get1这两个函数直接得到结果有点不现实,我就直接爆破,一开始爆破的结果是49999991,但是最后发现不对,原因是因为我忽略了如果八位数字的第一位为0,第二位为4,也可以满足get1函数的要求,那就有很多种可能了,爆破的范围就没那么小了,最后爆破出很多结果,只有04999999是正确的。爆破脚本如下

public class Main {
    public static boolean d(int i) {
        int i2 = 2;
        while (i2 < i && i % i2 != 0) {
            i2++;
        }
        return i == i2;
    }
    public static boolean get1(int i) {
        String num = Integer.toString(i);
        for (int i2 = 0; i2 < num.length() - 1; i2++) {
            i /= 10;
        }
        return i == 4;
    }
    public static boolean c(String str) {
        return str.length() == 8;
    }

    public static boolean h(int parseInt) {

                if (get1(parseInt) && d(parseInt)) {
                    int i = parseInt + 11;
                    if (!get1(i)) {
                        if (!d(i)) {
                            return true;
                        }
                    }
                }
              return false;
    }
    public static void main(String[] args) {

        for(int j=0;j<50000000;j++){
//范围太大爆破很慢,自己操作可以灵活一点
           if(h(j)){
               System.out.println(j);
           }
        }

    }
}

我们就得到了前八位纯数字,还差15位,再看函数Myjni.getstr(),点进去代码如下

package com.example.whathappened.MyJNI;

/* loaded from: classes.dex */
public class Myjni {
    public static native String getstr();

    static {
        System.loadLibrary("whathappened");
    }
}

AI问了一下System.loadLibrary是Java中用于加载本地库(如动态链接库DLL)的方法。这里应该是有个文件叫作whathappened,但是没有在附件里面,我就问了同级的大佬,告诉我将.apk后缀改成zip,在lib/下有四个文件夹,里面是不同CPU架构下的libwhathappened.so文件,这里代码加载的应该就是这个文件。然后提取x86_64文件夹里面的,用IDA进行分析,找到了我们要的函数

这里面不知道为啥还传入了一个a1参数,但是在jadx里面并没有传入参数到getstr()函数里面,还返回了应该像是一个函数,对a1和getend()函数返回的v1进行了处理,头要爆炸了。后来问了大佬,跟我解释了一堆巴拉巴拉,跟Java jni有关,但是我没听懂,暑假要开始学安卓逆向了。直接看getend(),点进去看是一坨代码,也不是什么加密,分析了一下,把它运行一下,应该就能得到它的result了,不用进行什么逆向。代码如下

#include<Windows.h>
#include<stdio.h>
#include<math.h>
_BYTE* getend(void)
{
    _BYTE* result; // rax
    __int64 v1; // rcx
    int v2; // edi
    int v3; // r8d
    char v4; // r10
    int v5; // r9d
    int v6; // r11d
    int v7; // r11d
    int v8; // r11d
    char aAbcdefghijklmn[] = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
    result = malloc(0x10uLL);
    result[15] = 0;
    v1 = 0LL;
    v2 = 0;
    do
    {
        v3 = v2
            + 5 * v1
            - 62
            * (((v2 + 5 * v1 + ((-2078209981LL * (v2 + 5 * v1 + 7)) >> 32) + 7) >> 31)
                + ((v2 + 5 * v1 + ((-2078209981LL * (v2 + 5 * v1 + 7)) >> 32) + 7) >> 5))
            + 7;
        v4 = aAbcdefghijklmn[v3];
        v5 = v2 + 10;
        v6 = 100;
        do
        {
            v3 = (v5 + v3) % 62;
            v4 = aAbcdefghijklmn[(v3 + v2 + v4) % 62];
            --v6;
        } while (v6);
        v7 = 100;
        do
        {
            v3 = (v5 + v3) % 62;
            v4 = aAbcdefghijklmn[(v3 + v2 + v4) % 62];
            --v7;
        } while (v7);
        v8 = 100;
        do
        {
            v3 = (v5 + v3) % 62;
            v4 = aAbcdefghijklmn[(v3 + v2 + v4) % 62];
            --v8;
        } while (v8);
        result[v1] = v4;
        if (v2-- == 0)
            v2 = 15;
        ++v1;
    } while (v1 != 15);
    return result;
}
int main()
{   
    _BYTE* ji = getend();
    printf("%s", ji);
}
//输出gwC9nOCNUhsHqZm,长度刚好为15位

注意在Visual Studio中运行以上代码,要在开头放入IDA的defs.h里面的宏定义。

所以得到的flag就是04999999gwC9nOCNUhsHqZm

Sha256加密后,和要对比的Sha256值相同,所以到这就对了,再输入手机里面,提示我们”OH YES, ONE STEP AWAY FROM SUCCESS!”,到这还有一步在哪我也卡住了,又去问了大佬,说安卓有Broadcast和Receiver机制,没学过,没办法。来到最后一步处

这里在与那串Sha256加密后的值对比之后,对其进行了加密,我们当然直接猜测这里的PART1就是纯数字,PART2是后面IDA分析后得出来的结果。经过分析,直接复制粘贴代码执行一次就可以得到flag了,要删除一些无关紧要的代码,脚本如下

package firstlearn;
import java.nio.charset.StandardCharsets;
import java.util.Base64;
import java.util.Random;
public class testtwo {
    private static String combineStrings(String str, String str2) {
        return str + str2;
    }
    private static byte[] generateSalt(int i) {
        byte[] bArr = new byte[i];
        new Random(7906L).nextBytes(bArr);
        return bArr;
    }
    private static byte[] customEncrypt(byte[] bArr, byte[] bArr2) {
        byte[] bArr3 = new byte[bArr.length];
        for (int i = 0; i < bArr.length; i++) {
            bArr3[i] = (byte) (bArr[i] ^ bArr2[i % bArr2.length]);
        }
        return bArr3;
    }
    public static String encrypt(String str, String str2) {
        byte[] generateSalt = generateSalt(16);
        byte[] customEncrypt = customEncrypt(combineStrings(str, str2).getBytes(StandardCharsets.UTF_8), generateSalt);
        byte[] bArr = new byte[generateSalt.length + customEncrypt.length];
        System.arraycopy(generateSalt, 0, bArr, 0, generateSalt.length);
        System.arraycopy(customEncrypt, 0, bArr, generateSalt.length, customEncrypt.length);
        return Base64.getEncoder().encodeToString(bArr);
    }
    public final class ByteCompanionObject {

        public static final byte MAX_VALUE = Byte.MAX_VALUE;
        public static final byte MIN_VALUE = Byte.MIN_VALUE;
        public static final int SIZE_BITS = 8;
        public static final int SIZE_BYTES = 1;
        private ByteCompanionObject() {
        }
    }
    public static String encrypt2(String str) {
        byte[] bytes = str.getBytes(StandardCharsets.UTF_8);
        for (int i = 0; i < bytes.length; i++) {
            bytes[i] = (byte) ((bytes[i] + ByteCompanionObject.MAX_VALUE) % 256);
        }
        byte[] bArr = new byte[bytes.length];
        for (int i2 = 0; i2 < bytes.length; i2++) {
            bArr[i2] = (byte) (i2 % 2 == 0 ? bytes[i2] ^ 123 : bytes[i2] ^ 234);
        }
        return Base64.getEncoder().encodeToString(bArr);
    }
    public static void main(String[] args) {
            String a1="04999999";
            String a2="gwC9nOCNUhsHqZm";
            String s=encrypt2(encrypt(a1,a2)).substring(0, 32);
        System.out.println(s);
    }
}
//输出uR+TPrILryzIJY9SiyiZWK8GtAzNKYom

所以flag就是ISCC{uR+TPrILryzIJY9SiyiZWK8GtAzNKYom}

做完这题,更想学安卓逆向了。

文末附加内容
暂无评论

发送评论 编辑评论


				
|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇
下一篇