好久不玩 CTF,偶然又看到了中科大的校赛,就又去看了一下。 完全不知道还好,但只要看到了题,就会上瘾,忍不住去做,太花时间了。
虽然我还是一样菜,但题目还是跟几年前一样保持着趣味性,挺有意思的
Xcaptcha
直接写脚本交互,注意 cookie 的设置
检索和社工题
检索题都好找,社工题对我不太友好,我太菜了,试了好久放弃了。版本号那里不知道具体含义只看到0231,就卡很久, 最后航班我没找到可以看历史轨迹的,找最近机场看航线猜可能的结果组合也太多,各种乱试也没弄对。
HeiLang
不觉得这很酷吗?
猜数字
Java 从字符串 Parse 一个 Double 判断是否与目标相等,判断的方法 !isLess && !isMore
有问题,
构造特殊输入 “NaN”
LaTeX 机器人
第一问:直接用 \input
命令插入文件 $$\input{/flag1}$$
第二问:文件中有特殊字符,从这里 了解到一个\newread
命令,
从一个 stackoverflow 回答了解到\catcode
命令,
最终可以读取文件并替换特殊字符,最后构造的输入:
$$\newread\file \catcode`\#=11 \catcode`\_=13 \openin\file=/flag2 \read\file to\line \line \closein\file$$
Flag 的痕迹
从这个开源项目的 GitHub issue 的相关讨论了解到除了 revision
还有一个 diff
可以用,项目的鉴权逻辑有问题,直接利用
安全的在线评测
第一问可以直接读文件,第二问不会,打算先缓存当前次数后读文件,但 RE
线路板
隐写题,根据文件后缀名找软件打开文件,去掉遮挡
Flag 自动机
动态调试,有个反调试函数TlsCallback_0
,跳转到正常的函数之前,通过 MessageBox
和 SetWindowPos
系统 API
的调用找到关键函数,用x64dbg
修改汇编指令去掉没用的跳转和按钮的移动。不太会动态调试,按理说看题目逻辑应该会写入到文件,我是中间有一步从寄存器读出来,没能写入到文件,我的修改:
>flag_machine.exe
0000151A:0F->E9
0000151B:84->EB
0000151C:FC->02
0000151D:03->00
0000151F:00->90
0000180A:81->C7
0000180B:7D->45
00001811:74->EB
000019FF:FF->90
00001A00:D0->90
0000226C:83->FF
0000226D:F8->D0
0000226E:02->90
虽然接触得不多,但我觉得x64dbg
这个项目是个好东西
微积分计算小练习
XSS,但后端有屏蔽关键字,有些提交直接没响应,具体屏蔽规则不知道,用各种绕过方式瞎试,最后用的是 斜杠+unicode编码
<img/src/onerror="alert(document.cookie)">
杯窗鹅影
这个有意思,用到的知识之前还真没了解过。Wine 不提供沙箱,所以函数可以直接用 linux 的系统调用访问 wine 之外的各种东西。
第一问直接用 read
系统调用,第二问感觉要写汇编,没做出来
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
int main(int argc, char **argv) {
// flag1
int fd = open("/flag1", O_RDONLY);
if (fd == -1) {
printf("file not found.\n");
}
char buf[1024];
memset(buf, '\0', 1024);
read(fd, &buf, 1024);
write(1, &buf, 1024);
return 0;
}
蒙特卡罗轮盘赌
随机数种子设置是time(0)+clock()
,时间戳可以一连上就获取本地时间,早一两秒也不要紧,因为 clock()
的返回大概在几百之间,前两次故意输错结果,根据返回爆破种子。
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
double rand01() { return (double)rand() / RAND_MAX; }
void compute(unsigned int seed, char *guess) {
int M = 0;
int N = 400000;
for (int j = 0; j < N; j++) {
double x = rand01();
double y = rand01();
if (x * x + y * y < 1)
M++;
}
double pi = (double)M / N * 4;
sprintf(guess, "%1.5f", pi);
guess[7] = '\0';
}
int main(int argc, const char **argv) {
// disable buffering
setvbuf(stdin, NULL, _IONBF, 0);
setvbuf(stdout, NULL, _IONBF, 0);
setvbuf(stderr, NULL, _IONBF, 0);
if (argc != 4) {
printf("Invalid input");
return 0;
}
const unsigned t = atoi(argv[1]);
const char *target1 = argv[2];
const char *target2 = argv[3];
for (unsigned int s = 0; s < 2000; s++) {
srand(t + s);
char guess1[2000];
char guess2[2000];
compute(t + s, (char *)&guess1);
compute(t + s, (char *)&guess2);
if (strcmp(target1, guess1) == 0 && strcmp(target2, guess2) == 0) {
char ans[2000];
for (int i = 0; i < 3; i++) {
compute(t + s, (char *)&ans);
printf("%s\n", ans);
}
return 0;
}
}
return 1;
}
置换魔群
这个也有意思,学习了一下代数,复习了一下密码学。我数学不好,所以这里写的很不专业,表达个大体意思。
第一问:给出n, e
, 和x**e
,求x
,
可以发现置换有周期性,周期也就是模数是组成置换的每个轮换的最小公倍数,像 RSA 那样用模逆求出d
,乘回去即可
def solve_rsa(enc: permutation_element, n: int, e: int):
import math
std = enc.to_standard(enc.permutation_list)
lens = list(map(lambda x: len(x), std))
lcm = math.lcm(*lens)
d = pow(e, -1, lcm)
ans = enc**d
return ans
第二问:给出n, g, y
,y=g^x
, 求出 x
。还是先用轮换表示,
找出原始和目标的每个轮换之间差几步,存为 vs
,每个轮换的长度存为ms
。
题目就是求同余方程组的解,不过不保证ms
之间是互质的,抄来了一个扩展的中国剩余定理,解出结果
def solve_dh(n: int, g: permutation_element, y: permutation_element):
g_std = g.to_standard(g.permutation_list)
y_std = y.to_standard(y.permutation_list)
y_idx = {}
for y_group in y_std:
for y_ele in y_group:
y_idx[y_ele] = y_group
vs = []
ms = []
for g_group in g_std:
if len(g_group) == 1:
continue
y_group = set()
for v in g_group:
y_group.add(y_idx[v])
y_group = sorted(list(y_group), key=lambda grp: grp[0])
new_y = [i for i in range(1, n + 1)]
for sg in y_group:
sg = list(sg)
sg.append(sg[0])
for v, vn in zip(sg, sg[1:]):
new_y[v - 1] = vn
new_g = [i for i in range(1, n + 1)]
sg = list(g_group)
sg.append(sg[0])
for v, vn in zip(sg, sg[1:]):
new_g[v - 1] = vn
new_g = permutation_element(len(new_g), new_g)
new_y = permutation_element(len(new_y), new_y)
temp = new_g
o = 0
while True:
if temp == new_y:
break
temp = temp * new_g
o += 1
if o == len(g_group):
vs.append(len(g_group))
ms.append(len(g_group))
else:
vs.append(o % len(g_group))
ms.append(len(g_group))
print(ms)
print(vs)
if len(vs) == 1:
return vs[0]
ans, mod = CRT(zip(vs, ms))
return ans + 1
第三问:给出n
,自己输入两个g
,得到对应的y=g^x
,求x
。不会了
光与影
完全不懂图形学,下载到本地,有一段 GLSL 代码,里面有个 SDF5
很可疑,就手动把值改到很大再打开网页,蒙出来了结果。
链上记忆大师
第一问送分,学一下 solidity 的智能合约上的数据存储
后面不会了,不熟悉,懒得学
片上系统
第一问隐写,拿文件提示的软件打开,选 SD 相关的 decoder,选好时钟信号和看起来有数据的信号,就能看到 hex 的 flag
第二问感觉太麻烦,没看
量子藏宝图
第一问学一下 BB84 的密钥交换,用最简单的全相同的位可以水过,但还是不懂实际原理
第二问给了个电路图,用提示里的库 qiskit 手动构建电路图求解,但还是不懂实际原理,懒得学+学不会
企鹅拼盘
只会前两问,提取出逻辑,直接爆破。这里花了挺久,因为想复用原来的异步逻辑, 结果题目用的 TUI 库高速迭代,早期版本根本没有文档了,跟新版本差别巨大,根本学不会了。 还好逻辑比较简单,于是只取了一部分代码加手写一部分爆破。
第三问看起来就很麻烦很数学,不会。
补充
总结
总之题还是很友好,不过我太菜了555。本来想着随便玩玩就好,后来看着进排行榜了而且有些题真的挺有意思就有点认真了, 最后花了挺多时间还是排得巨低,但真的很好玩。