Automne's Shadow.

CBC Bit-Flipping Attack Conclusion Part2

2019/07/14 Share

Crypto

之前对CBC的字节翻转攻击做过总结,但是理解不够深刻,这篇文章将结合实例在脚本层面进行分析总结。

关键事情说三遍:修改前一组密文,修改前一组密文,修改前一组密文

测试发现,当服务器使用的python版本不同,对应的利用代码也有所不同。

python2

实例参考https://dr3dd.gitlab.io/cryptography/2019/01/10/simple-AES-CBC-bit-flipping-attack/并做了一点修改

代码特点:

1
.encode('hex'),	.decode('hex'),

运行前先安装代码库

1
python -m pip install pycryptodome

服务端代码,运行脚本

1
socat -T60 TCP-LISTEN:10088,reuseaddr,fork EXEC:"python -u c3.py"
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
from Crypto.Cipher import AES
from Crypto.Util.Padding import pad,unpad
from Crypto.Random import get_random_bytes

greeting = """

___ ___ _ ___ _____ ___ _ ___
_ __ / _ \ / _ \ | |__ / __\/__ \ / __\ / | / _ \
| '_ \ | | | || | | || '_ \ / / / /\// _\_____ | || (_) |
| | | || |_| || |_| || |_) |/ /___ / / / / |_____|| | \__, |
|_| |_| \___/ \___/ |_.__/ \____/ \/ \/ |_| /_/


"""

key = get_random_bytes(16)
iv = get_random_bytes(16)

flag = open('flag','rb').read().strip()

def encrypt_data(data):
cipher = AES.new(key, AES.MODE_CBC,iv)
enc = cipher.encrypt(pad(data,16,style='pkcs7'))
return enc.encode('hex')

def decrypt_data(encryptedParams):
cipher = AES.new(key, AES.MODE_CBC,iv)
paddedParams = cipher.decrypt(encryptedParams.decode('hex'))
return unpad(paddedParams,16,style='pkcs7')

print(greeting)
print('hey n00b!! you know how CBC bit flipping works?\nIf you flip the bit correctly i will reward you fl4g!')
msg = "adm1n=0"
print("Current Auth Message is : " + msg)
print("Encryption of auth Message in hex : " + iv.encode('hex') + encrypt_data(msg))
enc_msg = raw_input("Give me Encrypted msg in hex : ")
try:
final_dec_msg = decrypt_data(enc_msg)

if "admin=1" in final_dec_msg:
print('Whoa!! you got it!! Now its reward time!!')
print(flag)
else:
print('Try again you can do it!!')
exit()
except:
print('bye bye!!')

注意下面的代码和判断条件admin=1有两处不同

1
msg = "adm1n=0"

根据 CBC Bit-Flipping Attack的特点,只需翻转IV的对应位和adm1n=0的密文进行拼接

利用脚本如下

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
##python2.7

from pwn import remote

host = '127.0.0.1'
port = 10088

bs = 16

l = remote(host, port)

cipher = l.recvline_contains("Encryption of auth Message")[-64:]

print("cipher: %s" % cipher)
original_data = cipher.decode('hex')

print("original_data: %s" % original_data)

theBlock = original_data[:16]

print("flipping cipher block: %s" % theBlock)

print(theBlock[3])
print(theBlock[6])

##bit flipping point
theBit0 = chr(ord(theBlock[3])^ord("1")^ord("i"))
theBit = chr(ord(theBlock[6])^0^1)

print(ord(theBlock[3])^ord("1")^ord("i"))
print(ord(theBlock[6])^0^1)

print theBit0
print theBit

new_data = original_data[:3] +theBit0 + original_data[4:6] + theBit + original_data[7:]
print(new_data)

new_cookie = new_data.encode('hex')
print(new_cookie)
l.send(new_cookie + '\n')
l.recv()
l.interactive()

注意下面字符和数字异或时的不同

1
2
theBit0 = chr(ord(theBlock[3])^ord("1")^ord("i"))
theBit = chr(ord(theBlock[6])^0^1)

运行效果如下

automne

同理,当服务端的msg为

1
msg = "abc1111111111111adm1n=0"

只需修改第一块密文block的对应位

解密脚本如下:

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
##python2.7

from pwn import remote

host = '127.0.0.1'
port = 10088

bs = 16

l = remote(host, port)

cipher = l.recvline_contains("Encryption of auth Message")[-96:]

print("cipher: %s" % cipher)
original_data = cipher.decode('hex')

print("original_data: %s" % original_data)

theBlock = original_data[16:32]

print("flipping cipher block: %s" % theBlock)

print(theBlock[3])
print(theBlock[6])

theBit0 = chr(ord(theBlock[3])^ord("1")^ord("i"))
theBit = chr(ord(theBlock[6])^0^1)

print(ord(theBlock[3])^ord("1")^ord("i"))
print(ord(theBlock[6])^0^1)

print theBit0
print theBit

new_data = original_data[:19] + theBit0 + original_data[20:22] + theBit + original_data[23:]
print(new_data)

new_cookie = new_data.encode('hex')
print(new_cookie)
l.send(new_cookie + '\n')
l.recv()
l.interactive()

python3

以一道xman的题目为例,将其中的部分代码做了修改,省去了mac的部分,只关注字节翻转。

代码特点:

1
bytes.fromhex(),	.hex()

修改后的服务端代码如下(python3运行)

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
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
#coding=utf-8

import random
import sys
import string
from hashlib import sha256
import socketserver
from Crypto.Cipher import AES
import os
import hashlib
import time

secret = os.urandom(16)
admin_key = os.urandom(16).hex()
aes_key = os.urandom(16)
iv = os.urandom(AES.block_size)

wel = b'Welcome, somebody\nyou can:\n1. login\n2. registe\n'


class process(socketserver.BaseRequestHandler):
def justWaite(self):
time.sleep(3)

def _pad(self, s):
s = bytes(s, 'utf-8')
return s + (16 - len(s) % 16) * bytes((16 - len(s) % 16, ))

def _unpad(self, s):
return s[0:-s[-1]].decode('utf-8', 'ignore')

def encrypt(self, plain):
plain = self._pad(plain)
cipher = AES.new(aes_key, AES.MODE_CBC, iv)
ciphertext = iv + cipher.encrypt(plain)
return ciphertext

def decrypt(self, ciphertext):
cipher = AES.new(aes_key, AES.MODE_CBC, iv)
plain = cipher.decrypt(ciphertext[AES.block_size:])
return self._unpad(plain)

def adminLogin(self, username):
self.request.send(b'1. pic1\n2. pic2\n3. flag\n')
c = self.request.recv(2).decode('utf-8')[0]
if c == '1':
self.request.send(bytes('your gun:\n▄︻┻═┳一\n', 'UTF-8'))
elif c == '2':
self.request.send(bytes('your Tu Long Knife:\n━╋▇▇▇◤\n', 'UTF-8'))
elif c == '3':
with open('flag') as f:
flag = f.readline()
self.request.send(flag.encode('utf-8') + b'\n')
else:
self.request.send(b'wrong num')

def userLogin(self, username):
self.request.send(b'1. pic1\n2. pic2\n')
c = self.request.recv(2).decode('utf-8')[0]
if c == '1':
self.request.send(bytes('your gun:\n▄︻┻═┳一', 'UTF-8'))
elif c == '2':
self.request.send(bytes('your Tu Long Knife:\n━╋▇▇▇◤', 'UTF-8'))
else:
self.request.send(b'wrong num')

def handle(self):
# self.justWaite()

while True:
self.isAdmin = False
self.request.send(wel)
wel_choice = self.request.recv(2).decode('utf-8')[0]
if wel_choice == '1':
self.request.send(b'input your cookie:>>')
cookie = self.request.recv(513).decode('utf-8')[:-1]
msg = bytes.fromhex(cookie)
msg = self.decrypt(msg)
print(msg)
print(len(msg))
try:
for i in range(len(msg) - 3):
if msg[i:i + 3] == 'un=':
username = msg[i + 3:]
username = username.split(';')[0]
print(username)

for i in range(len(msg) - 3):
if msg[i:i + 3] == 'pw=':
passwd = msg[i + 3:]
passwd = passwd.split(';')[0]
print(passwd)

for i in range(len(msg) - 8):
if msg[i:i + 8] == 'isAdmin=':
if msg[i + 8:i + 13] == 'True;':
self.isAdmin = True
except Exception as e:
print('error detected\n')
print(e)

self.request.send(b'input your password:>>')
pw_t = self.request.recv(37).decode('utf-8')[:-1]
if pw_t != passwd:
self.request.send(b'wrong passwd!')
break
self.request.send(b'welcome! %s\n' %
(username.encode('utf-8')))
if self.isAdmin:
self.adminLogin(username)
else:
self.userLogin(username)

elif wel_choice == '2':
self.request.send(b'welcome! are you an admin?(Y/N)\n')
reg_c = self.request.recv(2).decode('utf-8')[0]
if reg_c == 'Y':
self.request.send(b'input admin key:>>')
u_key = self.request.recv(33).decode('utf-8')[:-1]
if u_key != admin_key:
break
else:
self.isAdmin = True
self.request.send(b'input your name:>>\n')
username = self.request.recv(37).decode('utf-8')[:-1]
if 'admin' in username.lower():
self.request.send(b'no admin in username\n')
continue
self.request.send(b'input your pw:>>\n')
passwd = self.request.recv(37).decode('utf-8')[:-1]
if 'admin' in passwd.lower():
self.request.send(b'no admin in password\n')
continue
cookie = self.encrypt(
'isAdmin=False' + ';pw=' + passwd + ';un=' + username).hex()
self.request.send(b'your cookie:>>\n' +
cookie.encode('utf-8') + b'\n')
else:
break

self.request.close()


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


if __name__ == "__main__":
HOST, PORT = '0.0.0.0', 10087
server = ThreadedServer((HOST, PORT), process)
server.allow_reuse_address = True
server.serve_forever()

代码中要求isAdmin=True;,但是加密的明文里默认值是isAdmin=False;,并且输入部分不允许有admin的大小写形式,所以需要做翻转。

直接使用上面的方式去翻转是无效的,利用脚本如下,注意其中的xor自定义函数:

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
##python3

from pwn import remote

host = '127.0.0.1'
port = 10087


def xor(var, key):
return bytes(a ^ b for a, b in zip(var, key))


bs = 16


def pad(s):
return s + (bs - len(s) % bs) * bytes((bs - len(s) % bs, ))

l = remote(host, port)

l.recv()
l.send('2\n')
l.recv()
l.send('N\n')
l.recv()
username1 = '9999999990000000000000000'
l.send(username1 + '\n')
l.recv()
passwd = 'pw'
l.send(passwd + '\n')
l.recvline()
cipher1 = l.recvline()[:-1].decode('utf-8')
print("cipher1: %s" % cipher1)
original_data = bytes.fromhex(cipher1)
print("cipher1 length: %s" % len(cipher1))
print("original_data: %s" % original_data)

theBlock = original_data[-32:-16] #modify this block

print("flipping cipher block: %s" % theBlock)

'''
# 'isAdmin=False;pw =pw;un=999999999 ;isrdmin=True;'
# 'isAdmin=False;pw =pw;un=aaaaaaisr dmin=True;'

#print(theBlock[3])

theBit = chr(theBlock[3]^ord("r")^ord("A"))
print(theBit)
print(theBlock[3]^ord("r")^ord("A"))
print(len(theBit))

new_data = original_data[:51] + bytes(theBit,encoding='utf-8') + original_data[52:]
print(new_data)
'''

print(xor(theBlock, 16*b'\x10'))

#bit flipping point
newblock = xor(xor(theBlock, 16*b'\x10'), b'isAdmin=True;'+3*b'\x03')
print(newblock)

new_data = original_data[:-32] + newblock + original_data[-16:]

new_cookie = new_data.hex()
print(new_cookie)
l.interactive()
l.recv()
l.send('1\n')
l.recv()
l.send(new_cookie + '\n')
l.recv()
l.send('pw\n')

l.interactive()

pwntools本身对python3支持不友好,直接运行可能会报错,在python3里安装下面的适配版本即可

1
python3 -m pip install git+https://github.com/arthaud/python3-pwntools.git

运行

automne

CATALOG
  1. 1. python2
  2. 2. python3