dJulkalender 2021 CTF 挑战全解析:从密码破解到逆向工程

本文详细解析了dJulkalender 2021 CTF挑战的24个谜题,涵盖二进制分析、密码学、游戏解谜和网络渗透等多种计算机技术,展示了如何通过编程和逻辑思维解决复杂问题。

dJulkalender 2021: Write-up

简介

我的母校KTH的计算机科学系组织了一个名为“dJulkalendern”的降临日历活动。这是一个类似CTF的谜题挑战,每天(几乎)都有一个新挑战,同时也是一个竞赛。这些谜题并不像常规CTF那样专注于安全,而是更广泛的IT相关谜题。今年,每个工作日都有一个谜题,我将逐一讲解。为防止文章过长,我会尽量简洁。如有疑问或以不同方式解决,请在下方评论。

第-1天:准备好了吗?

我们收到描述:

这个窗口,Window -1,是一个练习窗口。它不计入最终得分。解决方案是Santa发送的第二封邮件中的第31个单词,如Lore Page所示。祝你好运!

这样做得到密码: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
11
12
13
The main light in the room turns on, and you can hear the sound of 
machinery and electricity stuff turning on all over the basement.
All the monitors in the room has turned on, and you see the word
"parched" slowly scrolling across all of them. Feeling proud of
your work, you go back up the now working elevator back to the office.

                                    _                 _ 
                                   | |               | |
         ____   _____   ____  ____ | |__   _____   __| |
        |  _ \ (____ | / ___)/ ___)|  _ \ | ___ | / _  |
        | |_| |/ ___ || |   ( (___ | | | || ____|( (_| |
        |  __/ \_____||_|    \____)|_| |_||_____) \____|
        |_|         

密码是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分,然后弹出一个消息:“The word is: 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 Zelda游戏。一旦你解决了所有房间,你会收到以下消息:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
It seems like you got thorugh all of the security systems.

You enter a large archive with rows upon rows of shelves filled 
with books and other old looking stuff. You start searching, 
looking around the files. You notice a small table upon which a
singular book is laid out. Your interest is piqued, and you 
approach it. You pick it up and start reading on the backside. 
Among the contents, the word 'fastidious' sticks out to you.

 ____  __    ___  ____  ____  ____  ____  _____  __  __  ___ 
( ___)/__\  / __)(_  _)(_  _)(  _ \(_  _)(  _  )(  )(  )/ __)
 )__)/(__)\ \__ \  )(   _)(_  )(_) )_)(_  )(_)(  )(__)( \__ \
(__)(__)(__)(___/ (__) (____)(____/(____)(_____)(______)(___/

所以密码是fastidious。

第13天:尝试破解

此挑战提供一个带有登录表单的网站。第一个用户密码的哈希是a01fe2d5aea3c1360eb5b8eb64b668af。谷歌此哈希显示密码是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 关于单词不在字典中的声明,并为他们使用的 leetspeek 风味准备一个变异规则。

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等中会有一些最终步骤,最终引导你到一个图像,在左下角包含一些紫色文本:The word is 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
24
25
A known error occured:
Array index in bounds
Buffer not flowing, dam
Expected integer received, received received integer
NotUnimplementedException caught in .NET
Insufficient social credit score received
Float value sank
Could not reach 'google.com', trying 'bing.chilling'
Loop repeated code several times
Conversion to Double only support Integer * 2
Could not add value '+'
Undefined defined as Undefined, should not be defined
Expected struct, got building
43 6f 75 6c 64 20 6e 6f 74 20 63 6f 6e 76 65 72 74 20 74 6f 20 68 65 78
Segmentation fault was excused
Deleted file missing
Math.random() did not return 0.13374200962499438
Math.random() did return 0.9579221445882034
expected unexpected received expected

Additional info:
Some Unexpected State was reached. Since this was unintended, and could 
inhibit the ability to complete the test and therefore not satisfy the 
project requirements, here is a link to the next part of the test: 
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天:极地特快,或感谢火车

在此挑战中,我们收到一个LogiSim原理图,带有一个锁。锁接受五个7位值作为输入。通过研究原理图,我们可以将其翻译成以下Z3代码并解决。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
#!/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]) ^
comments powered by Disqus
使用 Hugo 构建
主题 StackJimmy 设计