CBC字节翻转攻击

CBC字节翻转攻击

CBC工作模式加密,解密流程

  • 加密

  • 解密

攻击原理

的解密过程存在如下特性:

  • 第一段明文受到初始向量的影响
  • 段明文受到第段密文影响

转化为数学公式

如果我们想要改变一组明文块为,那么我们就需要控制前一组密文块,即,然后,也就是说我们需要把前一组密文块修改为

例题

[HNCTF 2022 WEEK3]babyCBC

题目

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
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
import socketserver
import os, sys, signal
import string, random, time, json
from hashlib import sha256
from secret import flag
from Crypto.Cipher import AES
from Crypto.Util.Padding import pad,unpad
from ast import literal_eval as eval

key = os.urandom(32)

def decrypt(ciphertext:str,iv):
try:
iv = bytes.fromhex(iv)
ciphertext = bytes.fromhex(ciphertext)
cipher = AES.new(key, AES.MODE_CBC, iv)
decrypted = unpad(cipher.decrypt(ciphertext),16)
if not b'Admin' in decrypted:
return 'Permissino denied !'

except ValueError as e:
return str(e)

return decrypted


def encrypt(plaintext:str,admin=False):
try:
iv = os.urandom(16)
payload = {'permission':'Guest','Time':f'{time.time()}','data':plaintext}
print(payload)
payload = pad(json.dumps(payload).encode(),16)
cipher = AES.new(key, AES.MODE_CBC, iv)
encrypted = cipher.encrypt(payload)
except ValueError as e:
return str(e)

return iv.hex() + encrypted.hex()


class Task(socketserver.BaseRequestHandler):
def _recvall(self):
BUFF_SIZE = 2048
data = b''
while True:
part = self.request.recv(BUFF_SIZE)
data += part
if len(part) < BUFF_SIZE:
break
return data.strip()

def send(self, msg, newline=True):
if type(msg) is str:
msg = msg.encode()
try:
if newline:
msg += b'\n'
self.request.sendall(msg)
except:
pass

def recv(self, prompt=b'> '):
self.send(prompt, newline=False)
return self._recvall()

def close(self):
self.send(b"Bye~")
self.request.close()

def proof_of_work(self):
random.seed(os.urandom(8))
proof = ''.join([random.choice(string.ascii_letters+string.digits) for _ in range(20)])
_hexdigest = sha256(proof.encode()).hexdigest()
self.send(f"sha256(XXXX+{proof[4:]}) == {_hexdigest}".encode())
x = self.recv(prompt=b'Give me XXXX: ')
if len(x) != 4 or sha256(x+proof[4:].encode()).hexdigest() != _hexdigest:
return False
return True

def handle(self):
if 0 and not self.proof_of_work():
return
menu = '''1. Encrypt data\n2. Decrypt data\n3. Get encrypted flag\n'''

while 1:
self.send('\n' + menu)
try:
r = int(self.recv())
except:
continue
if r == 3:
self.send(b'Encrypted flag:\n')
self.send(encrypt(flag.hex()))
elif r == 2:
iv,data = self.recv(prompt=b'Data to decrypt:').strip().split(b'||')
self.send(decrypt(data.decode(),iv.decode()))
elif r == 1:
data = self.recv(prompt=b'Data to encrypt:').strip()
self.send(encrypt(data.decode()))


self.close()

class ThreadedServer(socketserver.ThreadingMixIn, socketserver.TCPServer):
pass

class ForkedServer(socketserver.ThreadingMixIn, socketserver.TCPServer):
pass

if __name__ == "__main__":

HOST, PORT = '0.0.0.0', 10000
server = ForkedServer((HOST, PORT), Task)
server.allow_reuse_address = True
server.serve_forever()

题解

十六个字节一组进行加密,我们通过获得加密后的以及

然后就需要通过字节翻转,将变为

构造后的密文发送给服务器解密,就能获得对应的明文,就是对应的

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
from Crypto.Util.number import *
import binascii
from pwn import xor

admin = b'Admin\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
guest = b'Guest\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
c_hex = '8a23b0f7dcd426b5b4091c50796f3dc22f062c1cc81445b7906b59883389dbe74487a673ac90feceac77a2883195bbf94143f33f5672dc20f46f024a5c5a1d961b2115d9c162c9f2ee0529999878c359579deea23795eb7d78c787ad62373c6c9071b5df738f48178dc1273531ec78e6488cda6f3595f1dda4616870d36b7e127dac52f07854279fb1d7e3d2e26100e51507d61253e84a41384dded5b7b3f7f843202ddf7bd896bfb4668fdde9311fdc'

iv = c_hex[:32]
c_hex = c_hex[32:]
c = binascii.a2b_hex(c_hex)
fake_c = xor(admin, c[:16])
fake_c = xor(fake_c, guest)
fake_hex = str(binascii.b2a_hex(fake_c))[2:-1] + c_hex[32:]
print(iv + "||" + fake_hex)

flag = "4e53534354467b42724f6e59615f5a61596348694b5f42726f6e79615f42726f6e79615f42726f6e79617d"
print(long_to_bytes(int(flag, 16)))

[SWPU 2020]cbc1

题目

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
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
from Crypto.Cipher import AES
import os
flag = os.environ['FLAG']
BLOCKSIZE = 16



def pad(data):
pad_len = BLOCKSIZE - (len(data) % BLOCKSIZE) if len(data) % BLOCKSIZE != 0 else 0
return data + "=" * pad_len

def unpad(data):
return data.replace("=","")


def enc(data,key,iv):
cipher = AES.new(key,AES.MODE_CBC,iv)
encrypt = cipher.encrypt(pad(data))
return encrypt


def dec(data,key,iv):
try:
cipher = AES.new(key,AES.MODE_CBC,iv)
encrypt = cipher.decrypt(data)
return unpad(encrypt)
except:
exit()


def task():
try:
key = os.urandom(16)
iv = os.urandom(16)
pre = "yusa"*4
for _ in range(3):
choice = raw_input(menu)
if choice == '1':
name = raw_input("What's your name?")
if name == 'admin':
exit()
token = enc(pre+name,key,iv)
print "Here is your token(in hex): "+iv.encode('hex')+token.encode('hex')
continue
elif choice == '2':
token = raw_input("Your token(in hex): ").decode('hex')
iv = token[:16]
name = dec(token[16:],key,iv)
print iv.encode('hex')+name.encode('hex')
if name[:16] == "yusa"*4:
print "Hello, "+name[16:]
if name[16:] == 'admin':
print flag
exit()
else:
continue
except:
exit()
menu='''
1. register
2. login
3. exit
'''
if __name__ == "__main__":
task()

题解

同样的,我们需要注册一个用户和相差一个字符,假设为

然后我们根据服务端返回的信息,进行翻转,使得我们密文翻转后,解密得到

此时,前面的会因为我们的修改发生改变,也就是不满足的输出格式

因此我们需要再次修改的值,使得解密后的明文前16字节为

1
2
3
4
5
6
7
8
9
10
11
12
13
from Crypto.Util.number import *

m = 'd9500c00a814bf0d61134174ed9ab1d7819261e12b438ff4f7cca62800d1e94b085aa0e81eae8cfd2eca2a171d93e0a8'
iv = m[:32]
m1 = m[:32] + hex(int(m[32:34], 16) ^ ord("A") ^ ord("a"))[2:] + m[34:]
print(m1)
m2 = 'd9500c00a814bf0d61134174ed9ab1d7c40e4f13658043bc7d28e6ea1e822d7861646d696e'
print(long_to_bytes(int(m2, 16)))
a = b"yusa" * 4
m2 = hex(int(m2[:32], 16) ^ bytes_to_long(a) ^ int(m2[32:64], 16))[2:] + m1[32:]
print(m2)
m3 = '642b3072b4e18fd0654ed4ff8a6defce7975736179757361797573617975736161646d696e'
print(long_to_bytes(int(m3, 16)))