前言
CBC全称为密码分组链接模式。不同于ECB模式,在CBC模式中,每个明文块先与前一个密文块儿进行异或后,在进行加密。这种加密模式很好的隐藏了明文的统计特性,但是同样也暴露出了一个很严重的缺点。本文将针对CBC模式的特点,详细讲解字节反转攻击的原理及应用。一、CBC加密模式
-
Plaintext:明文
-
IV:初始化向量
-
Ciphertext:加密后的数据
-
Key:密钥
加密过程转化成文字来叙述:
- 先将Plaintext分组(常见以16字节为一组)
- 生成初始化向量IV和密钥Key(IV不需要保密,但一定要不可预测)
- 将IV和明文1组异或
- 将3中所得结果用Key进行加密,得到密文1组
- 将密文1组当作新IV与明文2组进行异或处理
- 将5中的结果用Key进行加密,得到密文2组
- 重复上述步骤,最后将IV和所有的密文分组拼接起来,得到最终密文
二、CBC解密模式
解密过程转化成文字来叙述:
- 从密文中提取出IV,并将密文分组
- 用Key将密文1组解密
- 2中解密的结果与IV异或,获得明文1组
- 密文1组成为新一轮的IV
- 重复上述步骤,将所有明文分组拼接后,得到最终明文
三、CBC字节反转攻击原理
字节反转攻击原理文字描述:
我们从上述图片可以看出,如果猜测出明文分组Pn,那么可以通过修改密文分组Ci-1为Ci-1XOR Pn XOR A,那么解密出来的Pn的结果是A。当然我们一般是不可能直接猜出来整个明文分组Pn,我们更多遇到的是上述图片描述的那样,仅知道Pn中部分明文。同样地,我们依旧可以发动字节反转攻击,仅需要针对正确的位置进行修改密文内容即可。
这种攻击的精髓在于:通过CBC的加密特点改变明文内容,但是这种改变并不会引起其它明文块儿对应位的改变。这种攻击常用来绕过过滤器,提权(比如从guest变为admin)等。
四、CBC字节反转攻击例题
from Crypto.Cipher import AES
from Crypto.Random import get_random_bytes
import base64
with open('./','r') as f:
FLAG = f.readline().strip()
#print(FLAG)
BLOCK_SIZE=16
IV = get_random_bytes(BLOCK_SIZE)
key = get_random_bytes(BLOCK_SIZE)
pad = lambda s: s + (BLOCK_SIZE - len(s) % BLOCK_SIZE) * chr(BLOCK_SIZE - len(s) % BLOCK_SIZE).encode()
unpad = lambda s: s[:-ord(s[len(s) - 1:])]
prefix = "flag="+FLAG+"&userdata="
suffix = "&user=guest"
def menu():
print ("1. encrypt")
print ("2. decrypt")
return input("> ")
def encrypt():
data = input("your data: ")
plain = (prefix+data+suffix).encode()
aes = AES.new(key, AES.MODE_CBC, IV)
print (base64.b64encode(aes.encrypt(pad(plain))))
def decrypt():
data = input("input data: ").encode()
aes = AES.new(key, AES.MODE_CBC, IV)
plain = unpad(aes.decrypt(base64.b64decode(data)))
#print (b'DEBUG ====> ' + plain)
if plain[-5:]==b"admin":
print (plain)
else:
print ("you are not admin")
def main():
for i in range(10):
cmd = menu()
if cmd=="1":
encrypt()
elif cmd=="2":
decrypt()
else:
exit()
if __name__=="__main__":
main()
- 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
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
例题分析:
该题目希望我们提供一个加密的字符串,如果将该字符串解密所得最后几位内容为admin,那么将直接输出明文。由题目可得,明文由prefix+data+suffix三部分组成,其中flag藏在prefix中,data由用户输入,suffix隐匿着我们要修改的字段。明文分组长度是16字节,加密算法是AES,加密模式是CBC,填充方式是PKCS。
根据CBC字节反转原理,解密Pn需要Cn-1的参与。那么在已知Pn部分明文是guest时,我们可在Cn-1中寻找确定相对的guest位置,然后对该位置的数值进行修改,譬如改为Ci-1[idx] XOR ord(‘g’) XOR ord(‘a’)。
我们的目标就是将guest修改为admin!
我们可以先随便提供一个明文,然后将密文进行修改,使得解密后的字符串最后内容是admin,我们可以通过枚举flag的长度,从而确定需要在什么位置进行修改。
例题精讲:
步骤一:根据明文的形式,爆破flag的长度
pad = 16
data = 'a'*pad
for x in range(10,100):
BLOCK_SIZE = 16
prefix = 'flag='+'a'*x+"&userdata="
suffix = "&user=guest"
plain = prefix + data + suffix
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
步骤二:确定guest的首字母的index
idx = (22+x+pad) % BLOCK_SIZE + ((22+x+pad)//BLOCK_SIZE-1) * BLOCK_SIZE
- 1
步骤三:将Cn-1对应guest位置的数值进行修改
cipher[idx+0] = chr(ord(cipher[idx+0])^ord('g')^ord('a'))
cipher[idx+1] = chr(ord(cipher[idx+1])^ord('u')^ord('d'))
cipher[idx+2] = chr(ord(cipher[idx+2])^ord('e')^ord('m'))
cipher[idx+3] = chr(ord(cipher[idx+3])^ord('s')^ord('i'))
cipher[idx+4] = chr(ord(cipher[idx+4])^ord('t')^ord('n'))
- 1
- 2
- 3
- 4
- 5
完整exp(Python3版本)如下:
from pwn import *
import base64
import binascii
context.log_level = 'debug'
pad =16
data = b'a' * pad
#r = remote("127.0.0.1",10050)
for x in range(10,100):
r = remote("127.0.0.1",10050)
r.sendlineafter(b'> ', b'1')
r.sendlineafter(b"your data:", data)
#(0.5)
temp = r.recvline().decode()[2:]
#print(temp)
cipher =list(base64.b64decode(temp))
BLOCK_SIZE = 16
prefix = b'flag='+b'a'*x+b'&userdata='
suffix = b'&user=guest'
plain = prefix + data + suffix
idx = (22+x+pad)%BLOCK_SIZE + ((22+x+pad) // BLOCK_SIZE -1 ) * BLOCK_SIZE
#print(idx)
cipher[idx + 0] = cipher[idx + 0] ^ ord('g') ^ ord('a')
cipher[idx + 1] = cipher[idx + 1] ^ ord('u') ^ ord('d')
cipher[idx + 2] = cipher[idx + 2] ^ ord('e') ^ ord('m')
cipher[idx + 3] = cipher[idx + 3] ^ ord('s') ^ ord('i')
cipher[idx + 4] = cipher[idx + 4] ^ ord('t') ^ ord('n')
#print(cipher)
stemp = [binascii.unhexlify(hex(i)[2:].zfill(2).encode()) for i in cipher]
stemp1 = b''
for i in stemp:
stemp1 += i
r.sendlineafter(b'> ',b'2')
r.sendlineafter(b'input data: ', base64.b64encode(stemp1))
msg = r.recv().decode()
#print(msg)
#pause()
if 'you are not admin' not in msg:
print(msg)
break
r.close()
- 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
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
参考资料
/ctf-wiki/crypto/blockcipher/mode/cbc-zh/#_7
/2018/11/19/CBC-reverse/
/post/crypt/cbc-crack/