Cryptohack ECB-Oracle

Oracle在密码学里通常指一个能提供特定信息的黑盒系统

而ECB这一工作模式最大的不安全性来自于这一特性——相同的明文块能够产生相同的密文块

当ECB遇到了Oracle,我们就能够通过爆破,逐步的获取每一位明文的信息。具体解题步骤如下:

分析题目代码

image-20250715091449428

加密程序很简单明了,将plaintext与FLAG拼接在一起,然后进行ECB加密

这里plaintext是我们自己输入的,FLAG是固定的。

从这一点至少可以分析出,我们尝试不同长度的plaintext根据ECB加密生成的密文长度,来判断flag的长度

发现输入plaintext长度为1~6字节时,加密结果为32字节

输入plaintext长度为7字节时,加密结果为48字节

由此可知:

1
2
len(FLAG)+6 = 32
len(FLAG) = 26

真的是这样吗?

翻车记录翻车记录之pad填充 | AiY0u的博客

所以其实我们还要再减去1,真实的FLAG长度为25

我们已知了FLAG的开头为crypto{,结尾为}

那么需要爆破16个字符,直接爆破计算量是很大的

我们需要利用开头所说的ECB的安全问题,假设我们输入的plaintext为15个a,那么第一个明文块将会是‘aaaaaaaaaaaaaaac’

假设我们输入9个a,那么第一个明文块将会是‘aaaaaaaaacrypto{’

假设我们输入8个a,那么第一个明文块将会是‘aaaaaaaacrypto{?’

这里留了一个未知的字符?进来,对应加密得到16字节的密文m

我们只需要将?字符从ASCII码值32遍历到127,最多96次就能得到?字符

其他字符的获取与这一原理相似,不再赘述

写python脚本需要用到request库

我们先用开发者工具查看一下该网站加密时的http请求是怎么写的

发现get请求的请求头为

image-20250715094710365

返回结果

image-20250715094729928

那么我们就可以开始写代码了

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
import requests

def get_url(plaintext): #生成url
plaintext_hex = plaintext.encode('utf-8').hex()
url = 'https://aes.cryptohack.org/ecb_oracle/encrypt/' + plaintext_hex + '/'
return url

def get_c(url): #生成对应密文

c = requests.get(url)
return c.text[15:47] #这里先看一眼c.text是啥,然后再把需要的部分切片出来



#寻找前15字节
flag1 = ''
for i in range(15,0,-1):
url1 = get_url('a'*i)
m1 = get_c(url1)
for j in range(32,128):
t = chr(j)
url2 = get_url('a'*i+flag1+t)
m2 = get_c(url2)
if m1==m2:
flag1 += t
print('find:'+t)
print('now:' + flag1)
break
else:
print("not true the "+t)

对flag的后半部分原理相同,但是代码会有许多差别,可以自行体会

别问我为什么print这么多,我debug de了好久

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
import requests
from Crypto.Util.Padding import pad

def get_url(plaintext):
plaintext_hex = plaintext.encode('utf-8').hex()
url = 'https://aes.cryptohack.org/ecb_oracle/encrypt/' + plaintext_hex + '/'
return url

def get_c_i(url): #前16字节密文
c = requests.get(url)
return c.text[15:47]

def get_c_l(url): #后16字节密文
c = requests.get(url)
return c.text[-35:-3]


flag2 = ''
for i in range(8,18):
url1 = get_url('a'*i)
m1 = get_c_l(url1)
print(m1)
for j in range(32,128):
t = chr(j)
padded = pad(t.encode() + flag2.encode(),16)
padded = padded.decode()
print(padded)
url2 = get_url(padded)
m2 = get_c_i(url2)
print(m2)
if m1==m2:
flag2 = t + flag2
print('find:'+t)
print('now:' + flag2)
break
else:
print('not true the'+t)

将两段代码分别运行,便能获取两段flag