大家好,这里是求道轩,我是求道仙人。 给我5分钟,带你看世界。
基本看完古典密码部分,虽然有些新的脑洞,但是暂时不值得写,比如JS混淆,曼彻斯特编码,ook,敲击码,base64隐写等,栅栏,凯撒,猪圈,夏多,培根,一次异或等,但是都是直接大概接触过的。
下面是两天的成果
古典密码-编码
古典密码-加密
今天的主角是之前没怎么接触的AES,跟着答案做了几题,收获颇多。
[来源]
集团信安新平台-Crypto训练专题
[工具]
python
[知识点]
AES
高级加密标准(AES,Advanced Encryption Standard)为最常见的对称加密算法,对称加密算法也就是加密和解密用相同的密钥。
AES为分组密码,分组密码也就是把明文分成一组一组的,每组长度相等,每次加密一组数据,直到加密完整个明文。
在AES标准规范中,分组长度只能是128位,也就是说,每个分组为16个字节(每个字节8位)。
密钥的长度可以使用128位、192位或256位。
密钥的长度不同,推荐加密轮数也不同。
AES有多种加密模式,今天先了解最简单的-电码本模式(Electronic Codebook Book (ECB)
将整个明文分成若干段相同的小段,然后对每一小段进行加密。
每128位(16字节)独立 独立 相互独立。
ECB模式
具体加密函数就不管了,反正每128位通过给定密钥key加密得到密文。
下来看两道题。
[解析]
AES04 ECB
下载后两个文件ecb
和ecb.py
,分别是可执行程序以获取flag,以及源代码供分析用。
关注下ecb.py
核心代码如下:
if __name__ == '__main__':
print('flag', aes_ecb_encrypt(pad16(flag)))
while True:
print("Input a string to encrypt (input 'q' to quit):")
user_input = raw_input()
if user_input == 'q':
break
output = aes_ecb_encrypt(pad16(user_input+flag))
print("Here is your encrypted string, have a nice day :)")
print(output)
首先打印了flag的AES-ECB模式加密结果,然后永真循环,接受用户输入user_input
,然后对user_input+flag
字符串AES加密输出。由于ECB模式每16字节相互独立,那么显然这里可以爆破,原理如下:
- 输入15个A,记录其作为前缀加flag的加密结果
- 爆破flag的第一个字符x,
15*A+x
加密结果和前面加密结果的前16字节相同。 - 其他字符同理,比如爆破第17个字符,那么继续输入15个A
- 首先需要知道flag的长度,通过逐渐增加输入长度进行测试。当密文变长时,说明此时的输入+flag长度正好被16整除,且比密文少16个字符,以此求出flag长度。
那么最终脚本就出来了
# -*- coding: utf-8 -*-
from pwn import *
r = process('./ecb')
def sendmessage(text):
s = r.recvline()
r.sendline(text)
s = r.recvline()
s = r.recvline()
return s.strip()
s = r.recvline()
encflag = s.split(' ')[1][1:-2]
tmp = 'A'
flag_length = 0
l = len(sendmessage(tmp))
while(True):
tmp += 'A'
ll = len(sendmessage(tmp))
if(ll>l):
flag_length = (ll - 32)/2 - len(tmp)
break
print flag_length
# flag_length = 21
# 开始爆破每位
dic = 'abcdefghijklmnopqrstuvwxyz0123456789_{}'
known = ''
for i in range(flag_length):
print 'ROUND %d' %(i+1)
target = 'A'*(15+(i//16)*16-len(known))
print 'TARGET text: %s'%target
enctext = sendmessage(target)
print 'TARGET: %s'%enctext[(i//16)*32:(i//16)*32+32]
for j in dic: #爆破合法字符集
tmptext = 'A'*(15+(i//16)*16-len(known))+known+j
print 'TRY %s' % tmptext
enctmp = sendmessage(tmptext)
print 'TRY enctmp %s' % enctmp[(i//16)*32:(i//16)*32+32]
if(enctmp[(i//16)*32:(i//16)*32+32]==enctext[(i//16)*32:(i//16)*32+32]):
known += j
break
print known
运行结果如下
ECB模式爆破每位
找到flag{g3t_17_by_d1g17}
AES05 ECB+
和上面类似,加强版
看看核心代码
if __name__ == '__main__':
print('flag', aes_ecb_encrypt(pad16(flag)))
while True:
print("Input a string to encrypt (input 'q' to quit):")
user_input = raw_input()
if user_input == 'q':
break
b = getrandbits(1)
output = aes_ecb_encrypt(pad16(('A'*b)+user_input+flag))
print("Here is your encrypted string, have a nice day :)")
print(output)
通过分析补充16字节函数,如果text正好是16的倍数,也会追加16个字符。
def pad16(text):
return text + chr(16-(len(text)%16))*(16-(len(text)%16))
在用户输入前面随机加入0或1位A,和上面类似,但是需要变种。
判断flag长度
AES在加密前需要padding,可以通过大量相同内容测试,判断出flag的长度,比如不停的输入1,
ECB模式长度测试
发现密文长度可能是32或48,说明随机加的A导致变化,那么原始flag长度就是30。
更进一步爆破长度脚本如下:
# Step 1: find length of flag
count = 1
flaglength = 0
s = r.recvline()
while(True):
tmpstr = 'A' * count
legal = len(sendmessage(tmpstr))
for i in range(10): #每种前缀尝试10次,看看是否有变化
tmpl = len(sendmessage(tmpstr))
if(tmpl != legal):
ll = legal
if(tmpl>ll):
ll = tmpl
flaglength = ll/2 - count - 17
if(flaglength>0):
break
count += 1
print '*** flag length is %s ***' % str(flaglength)
进一步分析服务器前缀加A影响,如果输入一个A,返回长度可能会不一样,那么输入17个A也不会不一样。较长的结果是18A+flag,较短的是17A+flag结果。
然后我们输入16个A,如果返回结果和上面17A+flag相同,说明服务器帮忙加A了,丢弃,如果和上面不同,说就是16A加flag的结果,依次类推,我们知道了A+flag的正确结果。
flaglength = 30
# Step2: get result of different padding A
paddingA = []
for i in range(20):
paddingA.append('')
while(True):
s = sendmessage('A'*17)
if(len(s)==128): # 64个密文字符 18A+30flag+16个pading
paddingA[18] = s
break
for i in range(17,0,-1):
print "padding {}*A".format(i)
for j in range(10):
tmpstr = sendmessage('A'*i)
if not tmpstr == paddingA[i+1]:
paddingA[i] = tmpstr
break
print paddingA
不同长度前缀A+flag的加密结果
接下来就是要爆破字符了,如何爆破第1个字符呢,通过构造15个A+任意可见字符的前缀,尝试让服务器替我们加密,看哪个结果和刚才的答案paddingA[15]一样。
如果服务器替我们增加了一个A也无所谓,因为16个A的加密结果我知道,扔掉就可以了。以此可以推出flag的第一位,一直到15位类似
完整脚本如下
# Step 3: begin to burp 1~15 digit
known = ''
dic = 'abcdefghijklmnopqrstuvwxyz0123456789_{}'
for i in range(15):
print '****** ROUND %d *****' % (i+1)
last32 = paddingA[16-i][0:32]
new32 = paddingA[15-i][0:32]
print 'LAST: %s' % last32
print 'NEW: %s' % new32
for j in dic:
tmpstr = 'A'*(15-i)+known+j
tmps = ''
while(True): #重复发送,如果发现服务器没给我们添加A 跳出循环
print 'SEND: %s'%tmpstr
tmps = sendmessage(tmpstr)
if not (tmps[0:32]==last32):
break
if(tmps[0:32]==new32):
known += j
print known
break
开始爆破flag
可以发现上面f服务器就没加A,一次就直接判断结果了。
由于ECB模式下各段互不干扰,因此接下来类似,最终结果如下
最终flag
找到flag{d0_n0t_4fr4id_jus7_7r1ck}
[答案]
flag{g3t_17_by_d1g17}
flag{d0_n0t_4fr4id_jus7_7r1ck}