dJulkalender 2021: Write-up
简介
我的母校KTH计算机科学系组织了一个名为“dJulkalendern”的圣诞日历活动。这是一个类似CTF的谜题挑战,每天(几乎)都有一个新挑战,同时也是一个比赛。这些谜题并不像常规CTF那样专注于安全,而是更广泛的IT相关谜题。今年,每个工作日都有一个谜题,我将逐一讲解。
为了防止文章过长,我会尽量简洁。如果您有任何问题或以不同方式解决了某个谜题,请在下方评论。
第-1天:准备好了吗?
我们收到描述:
这个窗口,窗口-1,是一个练习窗口。它不计入最终得分。解决方案是Santa发送的第二封邮件中的第31个单词,如Lore页面所示。祝你好运!
这样做得到密码:coming
第1天:Ho-ho-ho
我们收到加密消息:
1
|
hoO-HOo-hoo hoO-Hoo-HoO hoO-HOo-hOo hoO-HoO-HoO hoO-Hoo-hoO hoO-HoO-HOo hoO-Hoo-HoO hoO-HoO-HOo hoO-HOo-Hoo
|
每个块有3x3=9个字符,将小写字符视为0,大写字符视为1,并解码为ASCII,我们得到答案。
1
2
3
4
5
6
7
8
9
|
#!/usr/bin/env python3
with open('encrypted_msg.txt', 'r') as fin:
encrypted_msg = fin.read()
chunks = encrypted_msg.strip().split(' ')
bits = [int(x.replace('-','').replace('h','0').replace('H','1').replace('o','0').replace('O','1'), 2) for x in chunks]
password = bytes(bits).decode('ascii')
print(f'Password: {password}')
|
运行此代码得到答案:permanent
第2天:指令不明确,咖啡卡在机器里
我们收到几个指令步骤,包括一大块数字。将每个数字用不同颜色着色,揭示密码。
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
|
#!/usr/bin/env python3
from PIL import Image
data = """
73212115111204527822772325277236632244464422574922799963999282420050010212
29971115341111366692772211377233102447657442271987499132119921100021200002
13241116857111896523778776277725314411321444290132499879689936000675839123
85743111574224328972779081277228844990543254400912399963999908100065748219
90132417116859205821767767767721441132145691448716399810247089120000869102
88769457111118956421772312227711447342421255449801291657481092847320010044
71536247903111122262777564327790844990132114476413299571029384916517400107
75849301324111224521777896427764714488756444890712396574839102938475810001
86734511142311189764776578237787512447223144754132496809182138400023300010
"""
data = data.strip().split('\n')
width, height = len(data[0]), len(data)
im = Image.new('RGB', (width, height))
COLOR_FACTOR = 25
colors = [(x*COLOR_FACTOR, x*COLOR_FACTOR, x*COLOR_FACTOR) for x in range(10)]
for y in range(height):
for x in range(width):
im.putpixel((x,y), colors[int(data[y][x])])
im = im.resize((8*width, 8*height))
im.show()
|
运行此代码得到以下图像:
在图像中,您可以勉强辨认出单词:shops
第3天:Ho Ho Ho-uston,我们有个问题
挑战给出如何使用netcat连接到MUD的说明。连接到服务器后,您会看到一个经典的文本冒险游戏。游戏涉及四处走动,拾取一些物品,在各种地方使用它们,最终打开电灯开关,显示以下消息:
1
2
3
4
5
6
7
8
9
10
|
房间的主灯亮了,您可以听到地下室各处机器和电器的声音。
房间里的所有显示器都亮了,您看到单词"parched"在所有屏幕上缓慢滚动。为您的工作感到自豪,您乘坐现在正常工作的电梯回到办公室。
_ _
| | | |
____ _____ ____ ____ | |__ _____ __| |
| _ \ (____ | / ___)/ ___)| _ \ | ___ | / _ |
| |_| |/ ___ || | ( (___ | | | || ____|( (_| |
| __/ \_____||_| \____)|_| |_||_____) \____|
|_|
|
密码是:parched
第6天:记录答案
您收到许多日志文件,所有文件都命名为x,y.log,每个文件包含一个字符。通过将日志的文件名作为坐标,并将文件内容放置在该位置的网格中,我们可以渲染图像:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
#!/usr/bin/env python3
import os
WIDTH, HEIGHT = 17, 17
grid = [[' ' for _ in range(WIDTH)] for _ in range(HEIGHT)]
for y in range(HEIGHT):
for x in range(WIDTH):
path = os.path.join('logs', f'{x+1},{y+1}.log')
if os.path.isfile(path):
with open(path, 'r') as fin:
grid[y][x] = fin.read().strip()
for row in grid:
print(''.join(row))
|
运行此代码打印以下内容:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
K
T Z
B P
N U
J J
C W
F N
S T
V GARDEN KQBUW A
G BFFH B B C W
H W Y LRWOM M
V T Y B M Y R
E R BC BCWWV J
B T G K
|
我们可以读出密码:garden
第7天:问题与答案
这个挑战将您发送到一个非常类似于Kahoot!的测验页面。对于每个问题,都会向以下URL发出请求:http://djul-2021-kahootish.medusa.datasektionen.se/getStatus/,包含如下响应:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
{
"id": "0baa928521c1014abf7aa7017d2aba56",
"currentQ": "If God exists and he (or she) revealed themselves, would people who believe in God actually accept God as God?",
"currentA": 1,
"timerOut": 1641505526983,
"currentTime": 1641505497284,
"points": 0,
"alts": [
"What?",
"Answer 1",
"Blandsaft",
"I don't know"
]
}
|
通过查看currentA中的1索引值,我们知道哪个是问题的正确答案,我们可以一直回答正确,直到累积10分,然后会弹出一个提示说“单词是:bumfuzzle”。
第8天:销售、表格、太空堡垒卡拉狄加
我们收到一个zip文件,包含一个.csv和一个.xlsx文件,以及一个说明文本,说内容相同,我们可以使用任何一个。
csv包含一行牛奶和饼干表情符号。我们可以去掉逗号,用0和1替换它们,而不丢失任何信息。我们注意到数据有441个字符长,正好是21x21。将数据组织成正方形揭示了一个QR码模式。用“#”替换0,用“ ”替换1,使其更清晰。我们可以使用Pillow从此创建图像,然后用zbarimg工具解码。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
|
#!/usr/bin/env python3
import csv
from PIL import Image
with open('sales-spreadsheet.csv', 'r') as fin:
csvreader = csv.reader(fin)
data = next(csvreader)
#data2 = ''.join(data).replace('ðª', '0').replace('ð¥', '1')
data2 = ''.join(data).replace('ðª', ' ').replace('ð¥', '#')
for i in range(21):
print(data2[i*21:(i+1)*21])
im = Image.new('RGB', (21, 21))
for y in range(21):
for x in range(21):
col = 255 if data2[21*y+x] == '#' else 0
im.putpixel((x,y), (col,col,col))
im = im.resize((10*21,10*21))
im.show()
im.save('qr.png')
|
1
2
3
|
$ zbarimg --raw qr.png
magnetic
scanned 1 barcode symbols from 1 images in 0,02 seconds
|
这给我们密码:magnetic
第9天:思考与否
我们收到数字38399, 12803, 33497, 22847, 12383。如果我们将每个数字分解因数,求和,并将和解码为ASCII字符,我们得到答案:
1
2
3
4
5
6
7
|
#!/usr/bin/env sage
numbers = [38399, 12803, 33497, 22847, 12383]
factors = [factor(x) for x in numbers]
factor_sums = [sum([factor**exponent for factor, exponent in entry]) for entry in factors]
answer = bytes(factor_sums).decode()
print(f'Password: {answer}')
|
运行此代码得到答案:magma
第10天:MUD业务
这个挑战是另一个MUD。当您连接到服务器时,您会看到一系列房间。在每个房间中,目标是恰好访问房间的每个方格一次。这被称为“自避行走”,出现在许多变体中,包括一些2D塞尔达游戏。一旦您解决了所有房间,您会收到以下消息:
1
2
3
4
5
6
7
8
|
看起来您通过了所有安全系统。
您进入一个大型档案室,一排排架子上摆满了书籍和其他看起来古老的东西。您开始搜索,四处查看文件。您注意到一张小桌子上放着一本书。您的兴趣被激起,您走近它。您拿起它,开始阅读背面。在内容中,单词'fastidious'引起了您的注意。
____ __ ___ ____ ____ ____ ____ _____ __ __ ___
( ___)/__\ / __)(_ _)(_ _)( _ \(_ _)( _ )( )( )/ __)
)__)/(__)\ \__ \ )( _)(_ )(_) )_)(_ )(_)( )(__)( \__ \
(__)(__)(__)(___/ (__) (____)(____/(____)(_____)(______)(___/
|
所以密码是:fastidious
第13天:尝试破解
这个挑战为您提供一个带有登录表单的网站。第一个用户的密码哈希是a01fe2d5aea3c1360eb5b8eb64b668af。在Google上搜索这个哈希显示密码是hyperventilation。
使用此密码登录后,给出提示:
1
2
|
8u7 n07 45 4dm1n! 70 f1nd 7h3 4dm1n5 53c2375, y0u w111 h4v3 70 f1nd 7h3 4dm1n p455w02d. 1m v32y 5u23 7h47 y0u c4n n07 f1nd 17, 17 15 1n n0n3 0f y0u2 d1c710n42135. my h1n7 f02 y0u:
7h3 p455w02d 15 1n 7h3 71713 0f 7h3 8357 50n9 1n 7h3 w021d 4cc02d1n9 70 4dm1n! 87w, w2171n9 11k3 7h15 15 n02m41 f02 4dm1n
|
我们 disregard 关于单词不在字典中的声称,并为他们使用的leetspeak风格准备一个突变规则。
1
2
3
4
5
6
7
|
#!/usr/bin/env python3
a='abcdefghijklmnopqrstuvwxyz'
b='48cd3f9h1jk1mn0pq257uvwxy2'
rule = ''.join('s'+a+b for a,b in zip(a,b) if a!=b)
with open('leet2.rule', 'w') as fout:
fout.write(rule)
|
然后我们尝试使用rockyou.txt密码列表破解密码,应用我们创建的leetspeak规则:
1
2
|
$ echo "070caf799e194e00bfdb29376ccc5395" > djul.hash
$ .\hashcat64.exe -m 0 -a 0 -r leet2.rule djul.hash rockyou.txt
|
这给我们密码:80h3m14n2h4p50dy (Bohemian Rhapsody)。用它登录后,给我们密码:parameterize
第14天:混乱的端点
这个挑战围绕巴别图书馆,也在网站上实现:http://libraryofbabel.info
您应该搜索各种文本,并回答它们可以在哪本书、哪个书架等找到。最终您会收到消息“A DEAD END. YOU WERE DECEIVED.”,但这是假的,通过查看html源代码,在CSS等中会有一些最终步骤,最终引导您到一个图像,在左下角有一些紫色文本:单词是angler。
这给我们密码:angler
第15天:检查负载
在这个挑战中,我们收到一个视频文件,有一个圣诞老人在一些方块上跳舞。这些方块代表一个生命游戏棋盘。通过从视频设置状态并模拟一步生命游戏,细胞拼出单词hammer。
第16天:Omega
挑战包括一个ELF二进制文件。反汇编显示有一个名为christmas_present的函数,代码从未调用。仔细查看函数中的指令,它们没有真正意义,但取其中一些的首字母拼出密码dollar。
第17天:Ho ld Your Horses
这里我们得到另一个MUD,可以连接。我们看到一个巨大的网格,但在采取16个动作后,MUD崩溃,并给我们一个日志文件的链接:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
|
发生已知错误:
数组索引在范围内
缓冲区不流动,水坝
期望收到整数,收到收到整数
在.NET中捕获NotUnimplementedException
收到的社会信用评分不足
浮点值下沉
无法访问'google.com',尝试'bing.chilling'
循环重复代码多次
转换为Double仅支持Integer * 2
无法添加值'+'
未定义定义为未定义,不应定义
期望结构,得到建筑
43 6f 75 6c 64 20 6e 6f 74 20 63 6f 6e 76 65 72 74 20 74 6f 20 68 65 78
分段错误被原谅
删除的文件丢失
Math.random()没有返回0.13374200962499438
Math.random()返回了0.9579221445882034
期望意外收到期望
附加信息:
达到了一些意外状态。由于这是无意的,并且可能抑制完成测试的能力,因此不满足项目要求,这里是测试下一部分的链接:
https://djul.datasektionen.se/public/restricted/17/tactics_exercise.pgn
|
链接的文件是一个PGN文件,是一种存储国际象棋游戏的格式。在PGN查看器中查看游戏,我们可以看到有56步,正好是7x8。通过查看每一步,看它是否移动到黑格或白格,并将其视为1或0,我们可以将移动解码为八个7位数字,代表ASCII字符。我们不知道白格代表1还是0,所以我们尝试两种可能性。
1
2
3
4
5
6
7
8
|
#!/usr/bin/env python3
bits = '00011110011010001111000111000011010001100100010100010011'
letters = [int(bits[i:i+7], 2) for i in range(0, len(bits), 7)]
print(bytes(letters))
letters = [int(bits[i:i+7], 2)^0x7F for i in range(0, len(bits), 7)]
print(bytes(letters))
|
运行此代码得到密码:peaceful
第20天:极地特快,或T. Hanks for the Train
在这个挑战中,我们收到一个LogiSim示意图,带有一个锁。锁接受五个7位值作为输入。通过研究示意图,我们可以将其翻译成以下Z3代码并求解。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
|
#!/usr/bin/env python3
from z3 import *
s = Solver()
code = [BitVec(f'digit_{i}', 7) for i in range(5)]
s.add(And(*[1==Extract(6, 6, d) for d in code]))
s.add(Not(Or(*[1==Extract(5, 5, d) for d in code])))
code_low = [Extract(4,0, d) for d in code]
s.add(~code_low[1] ^ code_low[2] == 0b00000)
s.add((code_low[2] & ~code_low[3]) ^ code_low[4] == 0b00000)
s.add((code_low[2] ^ code_low[3] ^ code_low[0]) == 0b00000)
s.add(~(code_low[1] | code_low[3]) ^ code_low[4] == 0b00000)
s.add((code_low[0] ^ ~(code_low[1] ^ code_low[3])) == 0b00000)
s.add(((code_low[0] ^ (code_low[2]<<1) ^ code_low[4]) ^ code_low[1]) == 0b00000)
s.add((LShR(code_low[1
|