pwn和AES结合的一题,比较有趣记录一下。
源码:
import random
import string
from Crypto.Cipher import AES
def pad(text, block_size):
pad_size = block_size - (len(text) % block_size)
if pad_size == 0:
pad_size = block_size
pad = chr(pad_size) * pad_size
return text + pad
def random_key(num):
return "".join(
[random.choice(string.ascii_letters + string.digits) for n in range(num)]
).upper()
key = random_key(16)
random_string = random_key(random.randint(1, 15))
def ecb_enc(text):
cipher = AES.new(key, AES.MODE_ECB)
return cipher.encrypt(text)
def encryption_oracle(text, unknown):
text = pad(random_string + text + unknown, 16)
return ecb_enc(text)
def string_length_detect():
str1 = encryption_oracle("A" * 16)
def main():
# decoded_flag = "ZmxhZ3t5MHVfa24wd19oMHdfQjEwY2tzX0FyZV9uMFRfcjMxaWFiMTMuLi59".decode(
# "base64"
# )
decoded_flag=open('flag.txt','r').readline()
print(encryption_oracle(decoded_flag, "").encode("hex"))
while True:
userInput = raw_input("Tell me something: ")
print(encryption_oracle(str(userInput), decoded_flag).encode("hex"))
if __name__ == "__main__":
exit(main())
大概分析下加密逻辑
nc到端口后大概是这样一个交互系统,首先会给出flag的密文,然后根据用户输入内容,生成userInput pad flag
后加密的密文。
85829fadf80f134d0097a221ba4f47511b0970034a91e98db1c7c2024ff8327e76b516e79d29db3110aa732ebfcd5fce9970f767ef628600d95ca4d468f201c3
Tell me something: abc
abc
1e087d1961c2f82e0380db9aab7055ef4c36c6b44dc4443bda5ca0063c4cb3fcf41ba1587ca9414add8012a62da0b05674753964c838c7434663c1deac98c62f
加密函数逻辑大概是将明文text
进行一次pad(random_string+text+unknown, 16)
拼接操作,预处理得到长度为16倍数的字符串。其中random_string
是长度1~15的随机字符串。
然后将预处理字符串进行一次AES/ECB加密,注意到加密密钥key
是16位随机字符串。
解题思路
首先要熟悉一下AES/ECB加密,参考这里
可以说ECB模式是AES中最容易理解的模式,本质上就是把明文分组逐个用密钥加密,再把每一组密文拼接作为最终密文,可以看做若干个明文分组独立加密互不影响。
接下来就是构造输入字符了
- 如果先输入head字符串,与开头的random_string刚好组成16位字符串分组,则后续分组加密与random_string无关。
random_string+head | xxxx… | xxxx… |
---|
- 再构造如下明文字符串
random_string+head | x*15+f | flag{… |
---|
- 显然上面字符串与下面字符串的前两个分组密文一定相同
random_string+head | x*15+f | lag{… |
---|
以此类推,通过构造特殊的input字符串,比对对应密文分组,达到按字节爆破flag内容的效果。实际上程序一开始给出的flag单独加密结果不需要使用。
最终exp:
from pwn import *
r=remote('node4.buuoj.cn',29999)
re=r.recvline().decode()
re=bytes.fromhex(re)
set1=[]
print('attack start.......')
for i in range(1,17):
sen_mes='a'*i
try:
r.sendline(sen_mes)
except:
print('something ro')
r.recvline()
re1=r.recvline().strip().decode()
if re1[0:16] in set1:
head=i-1
break
set1.append(re1[:16])
print(head)
#a reference
#attack [0:16]
mes='a'*head
realflag=''
for i in range(1,17):
ju=mes+'b'*(16-i)
r.sendline(ju)
r.recvline()
ju_recv=r.recvline().strip().decode()
for j in range(32,128):
mes_add=mes+'b'*(16-i)+realflag+chr(j)
r.sendline(mes_add)
r.recvline()
try_recv=r.recvline().strip().decode()
if ju_recv[32:64]==try_recv[32:64]:
realflag=realflag+chr(j)
print('realflag:{}'.format(realflag))
break
#attack[16:32]
for i in range(1,17):
ju=mes+'b'*(16-i)
r.sendline(ju)
r.recvline()
ju_recv=r.recvline().strip().decode()
for j in range(32,128):
mes_add=mes+realflag[i:15+i]+chr(j)
r.sendline(mes_add)
r.recvline()
try_recv=r.recvline().strip().decode()
if ju_recv[64:96]==try_recv[32:64]:
realflag=realflag+chr(j)
print('realflag:{}'.format(realflag))
break
#attack all
for i in range(1,17):
ju=mes+'b'*(16-i)
r.sendline(ju)
r.recvline()
ju_recv=r.recvline().strip().decode()
for j in range(32,128):
mes_add=mes+realflag[i+16:31+i]+chr(j)
r.sendline(mes_add)
r.recvline()
try_recv=r.recvline().strip().decode()
if ju_recv[96:128]==try_recv[32:64]:
realflag=realflag+chr(j)
print('realflag:{}'.format(realflag))
if chr(j) == '}':
exit(0)
break