Krypton 闯关详解

本文是对 [OverTheWire][1] 的 Krypton 系列的闯关的笔记,主要包括1-7 关。

简单介绍一下闯关流程,首先使用 Level 0 的用户名和密码使用 ssh 进行访问,端口号均为 2231。本关卡页面首先给了一个 Base64 加密的字符串,于是我们解密之后便可以得到原字符串,即 Level 0 的密码。同时这里给出了通用的闯关方法,不同关卡的内容均在 /krypton/kryptonx (x 代表 Level 的数字)下面,同时会给出 README 文件和一些包含下一关密码的相关文件。

# 输入指令
echo -n "S1JZUFRPTklTR1JFQVQ=" | base64 -d

# 获得输出为
KRYPTONISGREAT

krypton 1

# 连接指令
ssh krypton1@krypton.labs.overthewire.org -p 2231

# 密码
KRYPTONISGREAT

首先我们使用 cat 查看 README 内容,我们的是该关卡使用 ROT13 进行加密,随后我们查看密码文件,内容如下:

# 密文
YRIRY GJB CNFFJBEQ EBGGRA

ROT13 就是在 26 的字母内,以每 13 个为一个字符对应,A–N、B–O等进行相互转换即可,便可获得密码。脚本如下:

import string

dataset = string.ascii_uppercase
rot_N = 13

cipher = "YRIRY GJB CNFFJBEQ EBGGRA"
message = ''
for i in cipher:
res = i
if i != " ":
num = dataset.find(i)
res_num = (num + rot_N)%26
res = dataset[res_num]

message += res
print(message)
# LEVEL TWO PASSWORD ROTTEN

krypton 2

# 连接指令
ssh krypton2@krypton.labs.overthewire.org -p 2231

# 密码
ROTTEN

该关卡使用 Caesar 密码(等效于 ROT-N)进行加密,同时 README 提供了一个样例,可以发现使用了 Leviathan 系列的权限绕过方法使用了 keyfile.dat 文件,随后在该目录下使用指令 ls -l 查看发现果然当前用户没有权限。于是再查看 krypton3 文件可以获取加密后的密文,同时使用提供的方法使用了该加密程序 encrypt(具体原因我暂时没搞懂),加密我们构造的文件 test.txt(内容为 ‘A’),根据返回值我们便可以知道 N 的大小。

1

最后在 N 和密文的基础上,使用之前的脚本便可以获取明文。

import string

dataset = string.ascii_uppercase
rot_N = 14

cipher = "OMQEMDUEQMEK"
message = ''
for i in cipher:
res = i
if i != " ":
num = dataset.find(i)
res_num = (num + rot_N)%26
res = dataset[res_num]

message += res
print(message)
# CAESARISEASY

krypton 3

# 连接指令
ssh krypton3@krypton.labs.overthewire.org -p 2231

# 密码
CAESARISEASY

这一关写到,这三个文件是由同一个密钥加密的,根据前几关的思路,这关想必也是替换密码,关键在于找到替换的表。首先我们查看了三个文件,发现都是一些普通的大写字母,看不出什么异常。于是尝试去查看提示,发现它提示如下:

2

于是果断采用字母频率分析,并将其与英语中字母频率相对应,从而获得字母替换表。我们选用 found1 进行测试,这里使用该脚本统计。

import string

msg = "the text in found1"
letter_freq = {}

for c in string.ascii_uppercase:
letter_freq[c] = 0

for l in msg:
if l in string.ascii_uppercase:
letter_freq[l] +=1

s = [(k, letter_freq[k]) for k in sorted(letter_freq, key=letter_freq.get, reverse=True)]

print(s)

输出我们的统计结果后,与英语常见词频进行匹配替换。

ciphertext = "KSVVWBGSJDSVSISVXBMNYQUUKBNWCUANMJS"
#engligh_freq = "ETAOINSHRDLCUMWFGYPBVKJXQZ"
modified_freq = "EQTSORINHCLDUPMFWGYBKVXQJZ"
ciphert_freq = "SQJUBNCGDZVWMYTXKELAFIOHRP"

cleartext = ''
for l in ciphertext:
i = ciphert_freq.index(l)
cleartext += engligh_freq[i]

print(cleartext)

便会输出明文(但是这个有点玄学,因为字母出现频率大致分布相同,但是个别字母可能排序不同,因此尝试一下才知道)。此外还可以使用此网站(quipqiup)进行词频统计尝试。

krypton 4

# 连接指令
ssh krypton4@krypton.labs.overthewire.org -p 2231

# 密码
BRUTE

该关卡采用的是 Vigenère 密码,使用多表加密(并提示我们密钥长度为 6),与之前的单表加密稍微困难一些(但有限),加密方式如下:

vigenere_square

首先选择一个密钥,例如:SECURE 和明文:DONT WORRY BE HAPPY。如果消息比密钥长,您只需重复密钥。然后,明文的第一个字母D 与密钥的第一个字母 S配对。因此,使用该表的 D 行和 S 列,即 V。直到加密了整个消息。

而本关同样我们要先找出密钥,我们查看提示,发现它提示我们观察词频来找到对应匹配。因此我们可以分析一下我们现有的线索。

  • 两个密文文件(相同密钥)
  • 密钥长度为 6

因此,我们如何利用词频来解决呢?我们可以思考一下,因为多表的每行每列都是 26 个字母,因此如果我们使用的是同一密钥,对第 n 位(其中 n = x(mod 6),x = 1,2,3,4,5,6 )的加密结果应该同样满足词频规律。于是我们只需要完成以下工作,代码如下:

  • 收集每 6 个字符串中的 1,2,3,4,5,6 位分别构建子串
  • 对子串都进行加密操作
  • 对加密后的结果进行词频分析
import string

def split(key_length, ciphertext):
res = []
for x in range(key_length):
tmp_str = ''
for c in range(x, len(ciphertext), key_length):
tmp_str += ciphertext[c]
res.append(tmp_str)
return res

def caesar(ciphertext, shift):
charset = string.ascii_uppercase
dec = ""
for c in ciphertext:
if c in charset:
idx = charset.find(c)
idx += shift
if idx >= len(charset):
idx -= len(charset)
elif idx < 0:
idx += len(charset)
dec += charset[idx]
else:
dec = dec + c
return dec

def frequency(text):
letter_freq = {}
for c in string.ascii_uppercase:
letter_freq[c] = 0
for l in text:
if l in string.ascii_uppercase:
letter_freq[l] +=1

s = [(k, letter_freq[k]) for k in sorted(letter_freq, key=letter_freq.get, reverse=True)]
return s

charset = string.ascii_uppercase
engligh_freq = "ETAOINSHRDLUCMWFYGPBVKXJQZ"
ciphertext = "the text in found1"
ciphertext = ciphertext.replace(" ", "")
key_length = 6

data = split(key_length, ciphertext)
key = ''
for line in data:
for shift in range(26):
t = caesar(line, shift)
if frequency(t)[0][0] == 'E':
c = charset.find(line[0])
c -= charset.find(t[0])
c %= len(charset)
key += charset[c]

print(key)

于是我们获得密钥为 FREKEY,并且 krypton5 文件解密后,得到密码 CLEARTEXT。

krypton 5

# 连接指令
ssh krypton5@krypton.labs.overthewire.org -p 2231

# 密码
CLEARTEXT

该关卡同上一关是一样的,只是没有了密钥长度的提示,因此我们只需要对上一关代码中的密钥长度进行尝试即可。得到密钥后对密文进行解密,观察明文可读性,便可知道密钥及明文。当尝试到密钥长度为 8 时,明文输出为 RANDOM,此时对应的密钥为 XEYLENCTH。

krypton 6

# 连接指令
ssh krypton6@krypton.labs.overthewire.org -p 2231

# 密码
RANDOM

该关卡使用的是流密码加密,流密码尝试创建一个即时随机密钥流来加密传入的明文一次一个字节。通常,随机密钥字节与明文异或以产生密文如果可以在接收端复制随机密钥流,则进一步的异或将再次产生明文。这里我们的目录中有一个密钥文件,还有一个加密程序,它将读取密钥文件并使用密钥和随机数加密我们想要的任何消息。我们可以通过引入我们选择的明文来执行已知的密文攻击。这里的挑战并不简单,但随机数生成器很弱。

我们查看提示后发现,他说这个随机数生成是长度有限的、周期性的,因此我们尝试让其加密同一个字符,从而查看其周期长度。

3

可以发现周期长度为 30,我们再尝试另一个明文,发现同样周期为 30,同时相较于之前的密文确实每个字母会 +1。

vigenere_square

于是我们便可以尝试解密操作。利用 A 在每个加密中的差值,举例来说,第一次加密是 A-E 差值为 4,于是我们就对明文的第一个字符的 ascii 码 + 4;第二次加密为 A-I 差值为 8,于是进行 + 8 操作,以此类推。

crypt = 'EICTDGYIYZKTHNSIRFXYCPFUEOCKRN'
ciphertext = "PNUKLYLWRQKGKBE"

for i in range(len(ciphertext)):
k = ord(ciphertext[i]) - ord(crypt[i])
if k < 0: k += 26
k += ord('A')
print(chr(k), end='')

最后获得明文 LFSRISNOTRANDOM

总结

该系列就是一个入门的 MISC 类型的题目,主要就是古典密码学的题目都很简单,确实没必要写笔记,但是写这篇文章能够帮助我把整个思路顺清楚,也是不错的,同时该系列的题目大多用 python 编程实现,也是提升一下自己的编程水平吧。(主要这篇文章也是想让整个 wargame 系列有延续,不要空缺哈哈哈)