攻防世界刷题遇到了一道AES-CBC 字节翻转攻击的题目,CISCN-2018-Quals-intersting。
0x02 题目CISCN-2018-Quals-intersting下载附件得到了两个文档,一个python脚本和一个heheda.txt的文档,python脚本如下:
import flag
import hashlib
from Crypto.Util.number import getPrime, long_to_bytes, bytes_to_long
from Crypto.Cipher import AES
import random
def gen_iv(seed):
s=random.Random()
s.seed(seed)
while True:
iv=long_to_bytes(s.randint(0xfffffffffffffffffffffffffffffff,0xffffffffffffffffffffffffffffffff))
if hashlib.sha256(iv).hexdigest()[0:4]==hashlib.sha256(long_to_bytes(seed)).hexdigest()[0:4]:
return iv
def gen_password(seed):
s=random.Random()
s.seed(seed)
while True:
password=long_to_bytes(s.randint(0xfffffffffffffffffffffffffffffff,0xffffffffffffffffffffffffffffffff))
if hashlib.sha256(password).hexdigest()[4:8]==hashlib.sha256(long_to_bytes(seed)).hexdigest()[4:8]:
return password
def gen_seed():
iv=flag.iv
key=flag.key
evil=flag.evil
m="token=5t43g5g2j1;admin=0;group=0"
c="bMPWOsg+YH0eSwchPY6HTEvf3ESETSrEQ3/M1d0lUm0=".decode("base64")
cipher = AES.new(key, AES.MODE_CBC, iv)
testc = cipher.encrypt(m)
assert testc==c
assert "admin=1" in evil
assert "group=1" in evil
cipher = AES.new(key, AES.MODE_CBC, iv)
monster = cipher.encrypt(evil)
counter=0
for i in range(len(monster)):
if monster[i]!=c[i]:
counter+=1
assert counter==2
return int(hashlib.sha256(monster).hexdigest(),16)
def main():
msgtemp=str(int(flag.encode(flag.flag),16))
msg=msgtemp+"A"*(16-len(msgtemp)%16)
seed=gen_seed()
iv=gen_iv(seed)
password=gen_password(seed)
cipher = AES.new(password, AES.MODE_CBC, iv)
c = cipher.encrypt(msg)
open("heheda.txt","w").write(c.encode("hex"))
if __name__ == '__main__':
main()
代码分析
初看感觉代码很长,分析发现定义了三个函数,gen_iv()、gen_password()和gen_seed(),main函数中首先调用gen_seed()得到seed,再以seed为参数调用另外两个函数生成了iv和password,再利用iv和password对msg进行加密生成heheda.txt的密文。
如此就需要主要去看gen_seed函数中seed是如何生成的了。
返回的seed是int(hashlib.sha256(monster).hexdigest(),16),monster是对evil进行aes-cbc加密得到,而evil中包含admin=1和group=1。
m="token=5t43g5g2j1;admin=0;group=0"
c="bMPWOsg+YH0eSwchPY6HTEvf3ESETSrEQ3/M1d0lUm0=".decode("base64")
cipher = AES.new(key, AES.MODE_CBC, iv)
testc = cipher.encrypt(m)
assert testc==c
assert "admin=1" in evil
assert "group=1" in evil
cipher = AES.new(key, AES.MODE_CBC, iv)
monster = cipher.encrypt(evil)
counter=0
for i in range(len(monster)):
if monster[i]!=c[i]:
counter+=1
assert counter==2
CBC 字节翻转攻击
网上针对AES-CBC加密模式的介绍很多,CBC:一种循环模式,前一个分组的密文与当前分组的明文进行异或操作后再加密。
在CBC模式下,明文分组并与前一组密文进行异或操作的模式造成了CBC模式的bit翻转攻击。在不知道密钥的情况下,如果我们有一组明密文,就可以通过修改密文,来使密文解密出来的特定位置的字符变成我们想要的字符。
CBC bit 翻转攻击实现
题目中 m="token=5t43g5g2j1;admin=0;group=0" ,明文长度共32个字节,16个字节为一组,其实将明文分为了两组:
| token=5t43g5g2j1; | admin=0;group=0 |
在CBC模式中,首先对第一组进行加密,并对初始向量iv进行异或操作,再对第二组进行加密,并与第一组的密文进行异或操作。因为第二组加密后与第一组进行了异或操作,所以可以利用异或进行明文的修改:
这里,我们想要将admin=0和group=0改成admin=1和group=1,也就是将第二组的第7个字符和第15个字符由0变成1:
chr(ord(c[7])^ord('0')^ord('1'))
chr(ord(c[15])^ord('0')^ord('1'))
已知分组密码的明文和密文长度都是相同的,都是两组,而第二组的第7个字符异或过第一组的第7个字符的密文,第二组的第15个字符异或过第一组的第15个字符的密文,所以可以得到新的密文:
new_c=c[:7]+chr(ord(c[7])^ord('0')^ord('1'))+c[8:15]+chr(ord(c[15])^ord('0')^ord('1'))+c[16:]
该题整个seed的生成代码如下:
m="token=5t43g5g2j1;admin=0;group=0"
c="bMPWOsg+YH0eSwchPY6HTEvf3ESETSrEQ3/M1d0lUm0=".decode("base64")
new_c=c[:7]+chr(ord(c[7])^ord('0')^ord('1'))+c[8:15]+chr(ord(c[15])^ord('0')^ord('1'))+c[16:]
seed=int(hashlib.sha256(new_c).hexdigest(),16)
print(seed)
# 94006254731428276046668737204765414644085700289999454456188227654864539227609
完整exp
获取到了seed后就是常规的AES解密了,解密后的密文还经过了多重加密,可以利用CyberChef进行解密,完整代码如下:
import hashlib
from Crypto.Util.number import getPrime, long_to_bytes, bytes_to_long
from Crypto.Cipher import AES
import random
def gen_iv(seed):
s=random.Random()
s.seed(seed)
while True:
iv=long_to_bytes(s.randint(0xfffffffffffffffffffffffffffffff,0xffffffffffffffffffffffffffffffff))
if hashlib.sha256(iv).hexdigest()[0:4]==hashlib.sha256(long_to_bytes(seed)).hexdigest()[0:4]:
return iv
def gen_password(seed):
s=random.Random()
s.seed(seed)
while True:
password=long_to_bytes(s.randint(0xfffffffffffffffffffffffffffffff,0xffffffffffffffffffffffffffffffff))
if hashlib.sha256(password).hexdigest()[4:8]==hashlib.sha256(long_to_bytes(seed)).hexdigest()[4:8]:
return password
m="token=5t43g5g2j1;admin=0;group=0"
c="bMPWOsg+YH0eSwchPY6HTEvf3ESETSrEQ3/M1d0lUm0=".decode("base64")
new_c=c[:7]+chr(ord(c[7])^ord('0')^ord('1'))+c[8:15]+chr(ord(c[15])^ord('0')^ord('1'))+c[16:]
seed=int(hashlib.sha256(new_c).hexdigest(),16)
iv=gen_iv(seed)
password=gen_password(seed)
cipher = AES.new(password, AES.MODE_CBC, iv)
f=open('heheda.txt','rb').read().decode('hex')
c = cipher.decrypt(f)
while c[-1]=='A':
c=c[:-1]
c=int(c,10)
print(hex(c)[2:-1].decode('hex'))
得到flag:flag{ri_bu_luo_de_xia_tian_ai_de_ming_xing+pian}
0x03 总结根据题目对ASE-CBC bit翻转攻击有了进一步的了解。在CTF题目中,ASE-CBC bit翻转攻击出现在web中的机率更大一些,利用该攻击可以修改访问权限提升至管理员,从而获取flag。



