UniCTF_2026
UniCTF_2026
[TOC]
Summary
新神赛,我真不知道设置新生赛道何意味
全是老登,幸好队友带飞,不然寄了
成绩还没出,能不能吃KFC看天意了
目前团队总榜第26,新生赛道第12,个人榜第49


Misc
Welcome
UniCTF{He110_Uni}
工厂应急流量分析
任务 1:谁把阀门打开了?
2.00 分
找到 Modbus 打开阀门指令的相关信息。
提交格式:flag{0xtransaction_id_0xfunction_code_0xcoil_address}
1 | tshark -r 简单协议学习.pcap -Y "modbus.func_code == 5" -T pdml | head -200 |
正确的字段名:
mbtcp.trans_id- Transaction Identifiermodbus.func_code- Function Codemodbus.reference_num- Reference Number (线圈地址)modbus.data- Data (ff00表示开启)
1 | tshark -r 简单协议学习.pcap -Y "modbus.func_code == 5 and modbus.data == ff00" -T fields \ |
转16进制即flag{0x3c4d_0x05_0x0015}
任务 2:被读取的 NodeId
2.00 分
找到通过 OPC UA 协议读取的 NodeId。
提交格式:flag{ns=X;s=Path/To/Node}
1 | tshark -r 简单协议学习.pcap -T fields -e tcp.payload | xxd -r -p | strings | grep -i "readrequest" | head -10 |
任务 3:控制站域名解析结果
2.00 分
找出控制站域名 ctrlws.factory.local 的解析 IP。
提交格式:flag{IP地址}
1 | tshark -r 简单协议学习.pcap -Y "dns.qry.name == ctrlws.factory.local" -T fields \ |
其实也可以从下面题干猜出来
1 | flag{192.168.1.10} |
任务 4:连接建立时间
2.00 分
确定SCADA(源:192.168.1.5)到控制站(目的:192.168.1.10)上首个成功发起的时间点(UTC)。
提交格式:flag{YYYY-MM-DDTHH:MM:SSZ}
1 | tshark -r 简单协议学习.pcap -Y "ip.src == 192.168.1.5 and ip.dst == 192.168.1.10" -T fields \ |
第三个包:2025-03-15T09:30:01.079999000Z,标志0x0002 (这是SYN包!)
格式化可得flag
1 | flag{2025-03-15T09:30:01Z} |
任务 5:HTTP 请求痕迹
2.00 分
提取 SCADA 对控制站发起的 HTTP 请求的 Host 与 URI。
提交格式:flag{Host_URI}
1 | tshark -r 简单协议学习.pcap -Y "http.request and ip.src == 192.168.1.5 and ip.dst == 192.168.1.10" -T fields \ |
任务 6:ICMP Echo Request 序列号
2.00 分
攻击者(192.168.1.100)对控制站发起了 ICMP Echo Request(ping)。找出该 ICMP 请求的序列号(Sequence Number)。
提交格式:flag{0x序列号}
1 | tshark -r 简单协议学习.pcap -Y "icmp.type == 8 and ip.src == 192.168.1.100 and ip.dst == 192.168.1.10" -T fields \-e icmp.seq |
这一问感觉出的有点小问题,卡了一下
得到291(10),16进制是123,但是需要补0。这点一开始想不到,问AI
1 | flag{0x0123} |
任务 7:SNMP Get 请求的 OID
2.00 分
SCADA 对控制站发起了 SNMP Get 请求。找出该请求查询的 OID(Object Identifier)。
提交格式:flag{OID}
1 | tshark -r 简单协议学习.pcap -Y 'ip.src == 192.168.1.5 and ip.dst == 192.168.1.10 and frame contains "GetRequest"' -T fields \ |
得到flag:
UniCTF{base64_misc_ctf_Ahiz_1955f495-c57f-4403-9147-0322281e40d6}
Cube God
Method Explanation
- State Representation: The cube state is modeled as a tuple of 24 characters (stickers ‘U’, ‘D’, ‘F’, ‘B’, ‘L’, ‘R’). This handles the indistinguishable nature of identical stickers automatically.
- Move Simulation: We pre-calculate permutation tables (
TRANS_TABLE) using integer indices (0-23) to represent how positions move. We then use these tables to shuffle the sticker colors efficiently. - Bidirectional Search:
- Precomputation: We generate a lookup table (
SOLVED_BALL) of all states within 6 moves of the solved state. - Online Search: For each round, we search up to 5 moves from the scrambled state. If we hit a state in
SOLVED_BALL, we combine the paths to form the solution.
- Precomputation: We generate a lookup table (
- Hidden Face Reconstruction: The challenge hides one face. We calculate which stickers are missing (since there are exactly 4 of each color). We try all permutations of these missing stickers to fill the hidden face, checking against valid corner constraints (e.g., a corner cannot have both ‘U’ and ‘D’) to find the valid physical state.
1 | import socket |
UniCTF{G0dZzzz_NuM63r_1s_3lEv3N_But_uR_C0d3_i5_D1v1n3_GG1981288629137313792}
总裁四比特,这能玩?
过于阴险了
可以发现jpg有点大,结束后跟了一大坨数据,binwalk和foremost可以搞出两张一模一样的png
这俩纯混淆视线,真正关键的是,不难发现,jpg里面有2个89504E47,但是有3个png文件尾
经过计算手动提取一下
1 | dd if=比特.jpg of=128.bin bs=1 skip=1725696 count=1494653 |
提取出来的128.bin啥也不是,但是俗话说的好,attention is all you need,不难发现每两个字节分别取高4位再拼起来可以得到zip
1 | def pack_high_nibbles(input_file, output_file): |
得到zip解压,发现是png,结尾还有一个zip
解压可得flag
UniCTF{Y0u_4r3_4_6r347_h4ck3r_!}
Silent Resolver
直接提取字符串
1 | strings traffic.pcapng |
可以发现
1 | 00012wgczzoor4hic6nzn2a44nloyy4qk4jb4utekjorf37cc4ob4wd.a1b2c3d4.exfil.unictf.local |
提取一下base32
1 | kbfqgbauaaaaacaajbsskxfwamzbcoaaaaadmaaaaaeaaaaamzwgczzoor4hic6nzn2a44nloyy4qk4jb4utekjorf37cc4ob4wdklrsjqwy4n6pgcxiz5zvjthsrcpxgbgdddqpgzhc4mrofgxakacqjmaqefadcqaaaaaiabegkjk4wybteejyaaaaanqaaaaaqaaaaaaaaaaaaaaaaaeaaeaaaaaamzwgczzoor4hiuclaudaaaaaaaaqaaiagyaaaac6aaaaaaaa |
解码发现是PK开头,转hex
1 | 50 4b 03 04 14 00 00 00 08 00 48 65 25 5c b6 03 32 11 38 00 00 00 36 00 00 00 08 00 00 00 66 6c 61 67 2e 74 78 74 0b cd cb 74 0e 71 ab 76 31 c8 2b 89 0f 29 32 29 2e 89 77 f1 0b 8e 0f 2c 35 2e 32 4c 2d 8e 37 cf 30 ae 8c f7 35 4c cf 28 89 f7 30 4c 31 8e 0f 36 4e 2e 32 2e 29 ae 05 00 50 4b 01 02 14 03 14 00 00 00 08 00 48 65 25 5c b6 03 32 11 38 00 00 00 36 00 00 00 08 00 00 00 00 00 00 00 00 00 00 00 80 01 00 00 00 00 66 6c 61 67 2e 74 78 74 50 4b 05 06 00 00 00 00 01 00 01 00 36 00 00 00 5e 00 00 00 00 00 |
保存为zip解压即可
UniCTF{D0nt_Tr4st_DNS_Qu3r1es_7h3y_M1ght_H1d3_S3cr3ts}
Sign in
打开附件发现是Serpent.dat,问AI发现是一种算法
显然dat的hex值是密文:
1 | 32E393BB9463840159017E9912FCD4D06138D9EEFF5153A42837C4565C4297D4 |
找了一下没看到密钥,想起来回去看看压缩包,结尾发现U2VjcmV0S2V5,base64解码得到SecretKey
http://serpent.online-domain-tools.com/
解密即可
UniCTF{Serpentine_Secrets}
截取的线索
图片是摩斯密码
1 | _Great_to01} |
RinDSA|W6dlkbXsob是异或结果,密钥其实就是文件名7
UniCTF{P1ckle_the_Great_to01}
Crypto
Subgroup-Weaver
1 | from pwn import * |
subgroup_dlp
借助CRT解决
1 | # solve.sage |
UniCTF{Th1s_DLP_probl3m_i5_v3ry_s1mpl3_f0r_y0u!!!}
NTRU
1 | import itertools |
UniCTF{pa3sw0rd_1s_ch2rmin3}
Subgroup-Choreographer
1.从 T 构造阿贝尔群加法 +
对拟群 *(即 T)定义左右除法:
- 左除:a\c=ba\backslash c = ba\c=b 使得 a∗b=ca*b=ca∗b=c
- 右除:c/b=ac / b = ac/b=a 使得 a∗b=ca*b=ca∗b=c
然后取任意固定元 eee(这里取 e=0),定义:
x+y=(x/e)∗(e\y)x + y = (x/e) * (e\backslash y)x+y=(x/e)∗(e\y)
可以验证这是一个 交换结合 的阿贝尔群(本题里同构于 Z4×Z4\mathbb{Z}_4 \times \mathbb{Z}_4Z4×Z4)。
2.将 * 写成仿射形式
在该 + 下枚举常数并约束同态条件,可唯一确定:
x∗y=φ(x)+ψ(y)+kx * y = \varphi(x) + \psi(y) + kx∗y=φ(x)+ψ(y)+k
(本题里常数项对应的群元素是固定的。)
3.证明并利用 D 的可分解性
因为 * 是“φ(x)+ψ(y)+k\varphi(x)+\psi(y)+kφ(x)+ψ(y)+k”这种对两边可分离相加的结构,而 D 的构造只是在不同维度上做 reduce/roll 和 g(u,v)=(((u*v)*u)*v) 这种组合,所以最终仍满足:
D(A,B)=F(A)+G(B)+CD(A,B)=F(A)+G(B)+CD(A,B)=F(A)+G(B)+C
其中 C=D(0,0)C=D(0,0)C=D(0,0),并且 F(A)=D(A,0)−CF(A)=D(A,0)-CF(A)=D(A,0)−C,G(B)=D(0,B)−CG(B)=D(0,B)-CG(B)=D(0,B)−C。
于是公开的
- p1=D(c,k)p1 = D(c,k)p1=D(c,k)
- p2=D(k,q)p2 = D(k,q)p2=D(k,q)
就变成线性方程:
G(k)=p1−F(c)−C,G(q)=p2−F(k)−CG(k)=p1 - F(c) - C,\quad G(q)=p2 - F(k) - CG(k)=p1−F(c)−C,G(q)=p2−F(k)−C
4.求逆得到 k、q
G 是群自同态(可逆线性变换),在 Z4\mathbb{Z}_4Z4 模上可通过构造其矩阵表示并做模 4 高斯消元求解。恢复 k,q 后即可按原脚本算:
key=SHA256(str(sk))key = \text{SHA256}(\text{str}(sk))key=SHA256(str(sk))
再用 AES-CTR 解密得到 flag。
1 | import hashlib |
UniCTF{H1st0ry_f_N0n_Ass0c1at1v3_Waltz_d7a5c5cf9563ef1c}
Subgroup-Spirit
1 | import z3 |
UniCTF{19e4235fc574ba94f4822c4b3bf03741ecfc0940}
Reverse
r_png
在strings定位到”[!] key 必须是 4 位数字,比如 0123\n”。交叉引用溯源到sub_16A40函数,发现是RC4。
虽然魔改但是仍然是对称密码,直接再次加密即可。
爆破密码:
1 | import subprocess |
破解出密码是7999。输出文件改后缀即可。
unictf{325799799302}
Strange_py
先解包
1 | python3 -m pyinstxtractor_ng encrypt.exe |
反正是线上赛,pylingual反编译算了,得到encrypt.py
1 | # Decompiled with PyLingual (https://pylingual.io) |
由于from tea import encoded,在E:\Strange_Py\Py\Strange_Py\encrypt.exe_extracted\PYZ.pyz_extracted下找到了tea.pyc,反编译
1 | # Decompiled with PyLingual (https://pylingual.io) |
从from Eencrypt import *发现还得继续找,结果E:\Strange_Py\Strange_Py\encrypt.exe_extracted只找到了Eencrypt.cp39-win_amd64.pyd
ida打开看看,反汇编窗口发现是upx段,需要脱壳,是标准壳
脱壳之后继续反编译,在strings窗口按照里面使用的各种函数一个个搜就行了
1 | import argparse |
跑一跑脚本
1 | (base) ┌──(rekjo㉿LAPTOP-BMERJF8L)-[/mnt/e/strange_py/strange_py] |
Unictf{W0OL!!!_Y0uh@Ve_fOuNd_mE}
d4yDAY_UP
解压apk,在/assets目录找到game.arcd和game.arci
flag是3,需要解密才能得到ljbc
搜索可以发现密钥aQj8CScgNP4VsfXK
1 | find -name "*.so" -print0 | xargs -0 -n1 sh -c 'echo "== $0 =="; strings -a "$0" | grep -E "^.{16}$" ' |
得到flag_validator.ljbc.chunk0.trim和main_gui_script.ljbc.chunk0.trim
去https://github.com/marsinator358/luajit-decompiler-v2/releases/tag/Mar_24_2024下载特定的反编译器
反编译可得flag_validator.ljbc.chunk0.trim.lua和main_gui_script.ljbc.chunk0.trim.lua
1 | -- chunkname: @main/flag_validator.lua |
可得flag:UniCtf{0@Y_D4y_UuPppp}
