ISCC校赛haN的wp

Misc

书法大师

是一张书法作品,题目很明显在强调笔画。

右键查看图片属性得到压缩包的密码:L9k8JhGfDsA

用binwalk分离图片文件,有message*的txt文件若干。

我随便找了一个message25,内容是“ 边个 乐成 少旗 太不 兄一 土上 时个 卫厂 中旗 卫丁 没牛 没乙 乐羊 尘群 比慢 边那 生亮 自船 巧挂 下上 衣西 石一 马罪 上睡”。

将笔画数写出来,是5 3 5 6 4 14 4 4 5 1 3 3 7 3 3 2 4 14 3 2 7 4 7 1 5 6 6 13 4 14 5 6 5 9 6 11 5 9 3 3 6 6 5 1 3 13 3 13

解码无果,将大于10的笔画数转成16进制的表示:53564e44513373324e327471566d4e56596b593366513d3d

16进制转字符串:SVNDQ3s2N2tqVmNVYkY3fQ==

解码,ISCC{67kjVcUbF7}

反方向的钟

打开附件是一个txt文件,图片,音频。

看题目,在深度声音找,将wav文件放进去,有一段加密的文件,需要密钥。

分析txt文件,一眼其实能看出是零宽字符。解的是image-20250508213414383

得到iscc2025Icf4

放到deepsound尝试解密,发现失败了。

后面想到分析图片,搜FF D9 jpg文件尾部,将图片部分删掉

image-20250508213611692

发现这里应该是一个usbpcap流量。但是我尝试对流量包分析但是没有用,不知道是不是没找到方向。

最后没招了,看D‏‎‍‎‍‍‌‎‎‏‍‌‎‎‌‎‍‌‍‍‎‌‌‎‌x8CBEldRG85JSoGLCIlNABN格式猜测是base的一种,然后iscc2025Icf4作为密钥解前面的字符串?

最后试到是将二者xor。

image-20250508214152133

得到ISCC{mvZUFL2EQFW2}

Reverse

SP

首先用die打开,发现是upx壳,脱壳ida分析。

image-20250508214435269

image-20250508215018834

设断点调试,看程序怎么运行。

image-20250508215400641

在右边隐藏fpu看到了flag

000000000079FD20 0000000000C21CA0 "ISCC{V!9#Lp@qX^eT}"

我爱看小品

一样,用die打开分析。

image-20250508215640095

看到打包工具是pyinstaller,就知道要反编译pyc文件。

使用pyinstxtractor的在线网页(https://pyinstxtractor-web.netlify.app/)上传exe文件,点击Prcocess执行反编译。

image-20250508215908508

反编译一下something.pyc

# Visit https://www.lddgo.net/string/pyc-compile-decompile for more information
# Version : Python 3.8

import mypy
import yourpy

def something():
    print('  打工奇遇')
    print('宫室长悬陇水声')
    print('廷陵刻此侈宠光')
    print('玉池生肥咽不彻')
    print('液枯自断仙无分')
    print('酒醒玉山来映人')


def check():
    your_input = input()
    if your_input[:5] == 'ISCC{' and your_input[-1] == '}':
        print("Come along, you'll find the answer!")
    else:
        print('Flag is wrong!')

if __name__ == '__main__':
    mypy.myfun()
    something()
    print('Please enter flag:')
    check()

这说明我们还要找到mypy和yourpy文件

image-20250508220145467

但是我发现他们加密了。

在网上搜索了一下,找到了这篇博客:python逆向之pyc反编译-CSDN博客

image-20250508220311375

我的是55 0d 0d 0a,是python3.8版本,下载一下。然后反编译密码得到密钥:

# Visit https://www.lddgo.net/string/pyc-compile-decompile for more information
# Version : Python 3.8

key = 'yibaibayibei1801'

所以将文件夹的东西解密

# For pyinstaller >=4.0
import glob
import zlib
import tinyaes
from pathlib import Path

CRYPT_BLOCK_SIZE = 16

# key obtained from pyimod00_crypto_key
key = bytes('yibaibayibei1801', 'utf-8')

for p in Path("PYZ-00.pyz_extracted").glob("**/*.pyc.encrypted"):
    inf = open(p, 'rb')  # encrypted file input
    outf = open(p.with_name(p.stem), 'wb')  # output file

    # Initialization vector
    iv = inf.read(CRYPT_BLOCK_SIZE)

    cipher = tinyaes.AES(key, iv)

    # Decrypt and decompress
    plaintext = zlib.decompress(cipher.CTR_xcrypt_buffer(inf.read()))

    # Write pyc header
    # The header below is for Python 3.8
    outf.write(b'\x55\x0d\x0d\x0a\0\0\0\0\0\0\0\0\0\0\0\0')

    # Write decrypted data
    outf.write(plaintext)

    inf.close()
    outf.close()

    # Delete .pyc.encrypted file
    p.unlink()



就解密了这些文件。找到mypy和yourpy解密

  1. flag格式为 ISCC{xxxxxxxxxxxxxxxxxxxxxxxxxxxx}
  2. part2被分成4部分处理:
    • 前11个字符:每个字符ASCII值+1
    • 接下来的4个字符:字母则异或32(大小写转换),其他不变
    • 接下来的5个字符:每个字符ASCII值-1
    • 最后10个字符:每个字符ASCII值+i%2(即奇数位+1,偶数位+0)
  3. 处理后得到密文:qzjotubmmfs_IS_udqx^iotfrfsuiog
cipher = "qzjotubmmfs_IS_udqx^iotfrfsuiog"
# Split the cipher into parts based on how it was encrypted
part1 = cipher[:11]    # These characters were +1
part2 = cipher[11:15]  # These were XOR 32 if alphabetic
part3 = cipher[15:20]  # These were -1
part4 = cipher[20:]    # These were +i%2
# Decrypt part1 (reverse the +1 operation)
dec_part1 = "".join([chr(ord(c) - 1) for c in part1])
# Decrypt part2 (reverse the XOR 32 operation)
dec_part2 = "".join([chr(ord(c) ^ 32) if c.isalpha() else c for c in part2])
# Decrypt part3 (reverse the -1 operation)
dec_part3 = "".join([chr(ord(c) + 1) for c in part3])
# Decrypt part4 (reverse the +i%2 operation)
dec_part4 = "".join([chr(ord(c) - (i % 2)) for i, c in enumerate(part4)])
# Combine all parts to get the original part2
original_part2 = dec_part1 + dec_part2 + dec_part3 + dec_part4
flag = f"ISCC{{{original_part2}}}"
print(flag)
#ISCC{pyinstaller_is_very_interesting}

Web

战胜变相一

看robots.txt,得到 f10g.txt,提示细节决定成败。

f12给了一个棋谱,我和ai下了半天的棋,没结果。

提示下棋要两个人下,访问f12g,拿到flag。

提交发现不对,想到f10g.txt-->f12g,故将答案里的0改成2提交成功。

纸家衣外传

用dirsearch.py看了一下目录,有upload和include,应该是文件包含+文件上传。

访问/includes,提示提示flag is here,尝试访问/flag.php

web环境现在没了,我记得是get一把锤子,那就是?chuizi=XXXXXXX格式的

文件上传部分尝试传shell,但是后缀名过滤了,试了几种绕过不成功,只能上传txt文件。

txt文件:<php highlight_file("/var/www/html/includes/flag. php");eval($_POST[' cmd']);phpinfo();?>

然后?chuizi=upload/114514.txt,访问,代码自动执行,答案再base64解码即可。

(由于是公共靶机,访问1.txt可以偷看别人的🐎

Mobile

encode

用jadx分析发现打不开。

image-20250508223823901

而且雷电模拟器安装不了。

在iscc群里看见顾师兄说了一句提dex分析(bushi

反编译classes.dex

因为apk的本质是zip,我们改一下后缀zip,解压。

package com.example.encode;

import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import android.widget.TextView;
import android.widget.Toast;
import androidx.appcompat.app.AppCompatActivity;

/* loaded from: C:\Users\28421\Desktop\classes.dex */
public class MainActivity extends AppCompatActivity {
    Button but_1;
    EditText edt_1;
    TextView tv_1;

    private native boolean nativeCheckFormat(String str);

    private native boolean nativeCheckLast(String str);

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

    @Override // androidx.fragment.app.FragmentActivity, androidx.activity.ComponentActivity, androidx.core.app.ComponentActivity, android.app.Activity
    protected void onCreate(Bundle bundle) {
        super.onCreate(bundle);
        setContentView(R.layout.activity_main);
        this.but_1 = (Button) findViewById(R.id.Push_Yes);
        this.edt_1 = (EditText) findViewById(R.id.Flag_Edit);
        this.tv_1 = (TextView) findViewById(R.id.Tip);
        this.but_1.setOnClickListener(new View.OnClickListener() { // from class: com.example.encode.MainActivity.1
            @Override // android.view.View.OnClickListener
            public void onClick(View view) {
                if (MainActivity.this.Jformat(MainActivity.this.edt_1.getText().toString())) {
                    Toast.makeText(MainActivity.this, "闯关成功!!!", 0).show();
                } else {
                    Toast.makeText(MainActivity.this, "输入错误,继续加油!", 0).show();
                }
            }
        });
    }

    /* JADX INFO: Access modifiers changed from: private */
    public boolean Jformat(String str) {
        int lastIndexOf = str.lastIndexOf("_");
        if (lastIndexOf == -1 || str.length() < 10 || !str.startsWith("ISCC{") || !str.endsWith("}")) {
            return false;
        }
        return nativeCheckLast(str.substring(lastIndexOf + 1, str.length() - 1)) && nativeCheckFormat(str);
    }
}

image-20250508224049416

Java_com_example_encode_MainActivity_nativeCheckLast .text 00000000000224C4 000001C8 00000090 R . . . . B . .

Java_com_example_encode_MainActivity_nativeCheckFormat .text 000000000002268C 00000318 000000A0 R . . . . B . .

搜索main有这两个主函数。

还有一个encode_last_part加密函数。

LOBYTE(v3) = (*(_QWORD *)v9 ^ 0x43655C756C71776BLL | (unsigned __int8)v9[8] ^ 0x4FLL) == 0;

从encode_last_part函数可以看出加密步骤,对输入字符串的每个字符的ASCII值加3

ds说是小端序,所以应该是0x4f43655C756C71776B

  • 0x4F-3 = 0x4C → 'L'

  • 0x43-3 = 0x40 → '@'

  • 0x65-3 = 0x62 → 'b'

  • 0x5C-3 = 0x59 → 'Y'

  • 0x75-3 = 0x72 → 'r'

  • 0x6C-3 = 0x69 → 'i'

  • 0x71-3 = 0x6E → 'n'

  • 0x77-3 = 0x74 → 't'

  • 0x6B-3 = 0x68 → 'h'

    得到L@bYrinth

第二个函数的关键点在

v18 = _mm_xor_si128(_mm_loadu_si128(v17), *(__m128i *)aFenww0ccxq5bca);
LOBYTE(v11) = _mm_testz_si128(v18, v18);

image-20250508230434270

aFenww0ccxq5bca的16字节值为:fENWW0ccXQ5BcAAA

encode_front_part中对每个字符执行了^ 0x2F,所以我们需要再次对解码结果执行^ 0x2F

image-20250508230903480

后面还有一个simplebase64encode函数,解密的时候加上。

image-20250508230947558

得到Slyth3r!n_//

答案是ISCC{Slyth3r!n_L@bYrinth}

三进制战争

jadx反编译,看主函数

 public final class CHECK0 implements View.OnClickListener {
        public CHECK0() {
        }

        @Override // android.view.View.OnClickListener
        public void onClick(View view) {
            Intrinsics.checkNotNullParameter(view, "view");
            EditText edit1 = MainActivity.this.getEdit1();
            Intrinsics.checkNotNull(edit1);
            if (MainActivity.this.CHECK1(edit1.getText().toString())) {
                TextView tv1 = MainActivity.this.getTv1();
                Intrinsics.checkNotNull(tv1);
                tv1.setText("Right");
            } else {
                TextView tv12 = MainActivity.this.getTv1();
                Intrinsics.checkNotNull(tv12);
                tv12.setText("Wrong");
            }
        }
    }

    /* JADX INFO: Access modifiers changed from: private */
    public final boolean CHECK1(String str1) {
        if (str1.length() <= 13) {
            return false;
        }
        String substring = str1.substring(0, 5);
        Intrinsics.checkNotNullExpressionValue(substring, "substring(...)");
        if (Intrinsics.areEqual(substring, "ISCC{") && str1.charAt(str1.length() - 1) == '}') {
            String stringFromJN1 = stringFromJN1();
            String substring2 = str1.substring(11, str1.length() - 1);
            Intrinsics.checkNotNullExpressionValue(substring2, "substring(...)");
            String stringFromJNl = stringFromJNl(stringFromJN1, substring2);
            String substring3 = str1.substring(5, 11);
            Intrinsics.checkNotNullExpressionValue(substring3, "substring(...)");
            if (Intrinsics.areEqual(substring3, stringFromJNI("4zA(19")) && CHECK2(stringFromJNl)) {
                return true;
            }
        }
        return false;
    }

    private final boolean CHECK2(String str1) {
        return Intrinsics.areEqual(str1, "010201010222020102010002001220021221");
    }

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

确定 Flag 结构

MainActivity 代码可知,Flag 格式为:

ISCC{XXXXXXYYYYYYYYYY}
  • XXXXXX = stringFromJNI("4zA(19") 的返回值(6 个字符)
  • YYYYYYYYYY = 让 stringFromJNl(key, YYYYYYYYYY) 返回 "010201010222020102010002001220021221" 的字符串

本来是尝试逆向stringFromJN1(),stringFromJNI(String),stringFromJNl(String, String)三个函数的,但是发现太难了,函数太多。

if (Intrinsics.areEqual(substring3, stringFromJNI("4zA(19")) && CHECK2(stringFromJNl)) {
                return true;

用frida来hook函数,输入4zA(19,获取stringFromJNI("4zA(19")的值

p1.txt

// 步骤1: 环境初始化
Java.perform(function() {
  console.log("[*] 开始Hook第一部分: 获取XXXXXX和key");

  const MainActivity = Java.use("com.example.mobile02.MainActivity");
  const JavaString = Java.use("java.lang.String");

  // Hook stringFromJNI("4zA(19")
  MainActivity.stringFromJNI.implementation = function(input) {
    console.log("[*] stringFromJNI 输入:", input);
    const result = this.stringFromJNI(input);
    console.log("[√] XXXXXX =", result); // 输出6字符的XXXXXX
    return result;
  };

  // Hook stringFromJN1()
  MainActivity.stringFromJN1.implementation = function() {
    const result = this.stringFromJN1();
    console.log("[√] key =", result); // 输出key
    return result;
  };

  // 触发调用(自动点击按钮)
  Java.choose("com.example.mobile02.MainActivity", {
    onMatch: function(instance) {
      instance.stringFromJNI(JavaString.$new("4zA(19")); // 触发XXXXXX计算
      instance.stringFromJN1(); // 触发key计算
    },
    onComplete: function() {}
  });
});

image-20250508232558222

[*] 开始Hook第一部分: 获取XXXXXX和key
[*] stringFromJNI 输入: 4zA(19
[√] XXXXXX = 'cg&Bf'bU
[√] key = ac35b0fc1a63e0657ed4bc5da3cabef6
[Android Emulator 5554::PID::2783 ]->

p2.txt

Java.perform(function() {
    // 1. 配置参数
    const TARGET_ENCODING = "010201010222020102010002001220021221";
    const MAX_LENGTH = TARGET_ENCODING.length / 6; // 6位三进制对应1字符
    const CHAR_SET = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ!@#$%^&*()_+-=[]{}|;':\",./<>?`~ ";
    
    // 2. 破解主函数
    function fullBruteForce(instance, key) {
        console.log("[*] 开始全量暴力破解...");
        console.log("[*] 目标编码:", TARGET_ENCODING);
        console.log("[*] 最大长度:", MAX_LENGTH);
        console.log("[*] 字符集长度:", CHAR_SET.length);
        
        // 使用队列进行广度优先搜索
        let queue = [""];
        let found = null;
        
        while (queue.length > 0 && !found) {
            const current = queue.shift();
            
            // 遍历字符集
            for (const char of CHAR_SET) {
                const nextStr = current + char;
                try {
                    const result = instance.stringFromJNl(key, nextStr);
                    const expected = TARGET_ENCODING.substring(0, nextStr.length * 6);
                    
                    // 打印进度(每100次尝试)
                    if (Math.random() < 0.01) {
                        console.log(`[.] 测试: ${nextStr} -> ${result.substr(0, 20)}...`);
                    }
                    
                    if (result === TARGET_ENCODING) {
                        console.log("\n[√] 完全匹配找到!");
                        console.log(`[√] 原始字符串: ${nextStr}`);
                        console.log(`[√] 编码结果: ${result}`);
                        found = nextStr;
                        break;
                    }
                    
                    if (result === expected) {
                        if (nextStr.length < MAX_LENGTH) {
                            queue.push(nextStr);
                        }
                    }
                } catch (e) {
                    console.log(`[!] 测试 ${nextStr} 时出错: ${e}`);
                }
            }
        }
        
        if (!found) {
            console.log("[X] 未找到匹配的字符串");
        }
        return found;
    }

    // 3. 主执行流程
    Java.choose("com.example.mobile02.MainActivity", {
        onMatch: function(instance) {
            try {
                const key = instance.stringFromJN1();
                console.log("[*] 获取到的key:", key);
                
                const startTime = Date.now();
                const result = fullBruteForce(instance, key);
                const elapsed = (Date.now() - startTime) / 1000;
                
                if (result) {
                    console.log(`\n[√] 破解成功! 耗时: ${elapsed.toFixed(2)}秒`);
                    console.log(`[√] 最终结果: ${result}`);
                    
                    // 自动填写结果
                    Java.scheduleOnMainThread(function() {
                        try {
                            const EditText = Java.use("android.widget.EditText");
                            const edit = Java.cast(instance.getEdit1(), EditText);
                            edit.setText(result);
                            console.log("[√] 已自动填写结果");
                        } catch (e) {
                            console.log("[!] 自动填写失败:", e);
                        }
                    });
                }
            } catch (e) {
                console.log("[!] 主流程出错:", e);
            }
        },
        onComplete: function() {
            console.log("[*] 扫描完成");
        }
    });
});

image-20250508233827049

[*] 获取到的key: ac35b0fc1a63e0657ed4bc5da3cabef6
[*] 开始全量暴力破解...
[*] 目标编码: 010201010222020102010002001220021221
[*] 最大长度: 6
[*] 字符集长度: 94
[.] 测试: o -> 011202...
[.] 测试: V6J -> 010201010222011202...
[.] 测试: V6w)$ -> 01020101022202010201...
[.] 测试: V6w)7z -> 01020101022202010201...

[√] 完全匹配找到!
[√] 原始字符串: V6w)7X
[√] 编码结果: 010201010222020102010002001220021221

[√] 破解成功! 耗时: 0.02秒
[√] 最终结果: V6w)7X
[*] 扫描完成
[!] 自动填写失败: Error: setText(): argument types do not match any of:
        .overload('java.lang.CharSequence', 'android.widget.TextView$BufferType')
[Android Emulator 5554::PID::2783 ]->

最后我答案是ISCC{'cg&Bf'bUV6w)7X}

posted @ 2025-05-29 22:30  Mirai_haN  阅读(30)  评论(0)    收藏  举报