-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathpypoa.py
189 lines (137 loc) · 6.12 KB
/
pypoa.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
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
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
from Crypto.Cipher import AES
from Crypto.Util.Padding import unpad
from base64 import b64encode
from Crypto.Util.Padding import pad
from Crypto.Random import get_random_bytes
import traceback
import logging
import binascii
# We assume that the key was securely shared beforehand
debugDecrypt = False
class OracleAttack:
defaultOracleKey = b"Sixteen byte key"
def __init__(self):
self.oracle = localOracle
def setOracle(oracle):
self.oracle = oracle
def execute(self, cipherTextBytes: bytes):
# Default to local oracle if no oracle is provided
if self.oracle is None:
self.oracle = localOracle
cipherText = bytearray(cipherTextBytes)
# Split ciphertext into block pairs
pairs = splitCipherTextIntoBlockPairs(cipherText)
decrypted = bytearray(0)
for pair in pairs:
# Key is only used for padding oracle.
decryptedBlock = decryptPair(pair[0], pair[1], self.oracle)
decrypted.extend(decryptedBlock)
return bytes(decrypted)
def toHex(byteArray: bytearray):
return ''.join('0x{:02x} '.format(x) for x in byteArray)
def encrypt(data, key):
cipher = AES.new(key, AES.MODE_CBC)
ct_bytes = cipher.encrypt(pad(data, AES.block_size))
return cipher.iv, ct_bytes
def decrypt(ct, iv, key):
cipher = AES.new(key, AES.MODE_CBC, iv)
padded = cipher.decrypt(ct)
pt = unpad(padded, AES.block_size)
return pt, padded
def localOracle(cipherText: bytearray):
key = b"Sixteen byte key"
iv = cipherText[:AES.block_size]
ct = cipherText[AES.block_size:]
try:
decrypt(ct, iv, key)
return True
except:
return False
# Leaks the pos'th byte of the ciphertext
def leakByte(pos: int, targetCiphertextBlock: bytearray, precendingBlock: bytearray, fakePadding: int, originalByteAtPos: int, oracle):
# try modifying the last byte of the block until we no longer get a padding error.
for i in range(0, 256):
# Check for padding error
modified = precendingBlock[:]
try:
payload = i
if debugDecrypt:
print("Trying to inject 0x{:02X} as the {}th byte".format(
payload, pos+1))
# Ensure that we test with modified byte
if payload == originalByteAtPos and pos == AES.block_size-1:
if debugDecrypt:
print("Skipping byte: 0x{:02X}".format(payload))
continue
modified[pos] = payload
#print("after", modified)
modifiedBytes = bytes(modified)
ctBytes = bytes(targetCiphertextBlock)
newCipherText = modifiedBytes+ctBytes
if not oracle(newCipherText):
continue
# if debugDecrypt:
# print("Found a modified byte that did not cause padding error: 0x{:02X}".format(payload))
# print("Padded {}".format(padded))
# print("Unpadded {}".format(decrypted))
# Obtain plaintext byte
plaintextByte = payload ^ originalByteAtPos ^ fakePadding
# In the case of the last byte (padding), ensure we actually got 0x01 instead of the actual padding byte
# Since actual padding byte is plaintextbyte, then plaintextByte ^ i
if debugDecrypt:
print("Decrypted byte 0x{:02X}".format(plaintextByte))
return plaintextByte
#print("Plaintext (padding) byte is 0x{:02X} ^ 0x{:02X} ^ 0x01 = 0x{:02X}".format(i, oldByte, 1))
except Exception as err:
if str(err.args[0]) == 'PKCS#7 padding is incorrect.' or str(err.args[0]) == 'Padding is incorrect.':
continue
else:
raise err
raise Exception({error: "Could not find value to xor with"})
# Modifies the nth byte in block such that when the block is used in CBC decryption it will yield the wanted byte in the next block
def modifyByte(n, block, originalCipherTextByte, plainTextByte, wantedByte):
#print("Block is {}".format(block))
# Save old byte
oldByte = originalCipherTextByte
nextModifiedByte = plainTextByte ^ oldByte ^ wantedByte
if debugDecrypt:
print("Calculated that byte 0x{:02X} should be injected to obtain 0x{:02X} in as the {}th byte".format(
nextModifiedByte, wantedByte, n+1))
modified = block[:]
modified[n] = nextModifiedByte
if debugDecrypt:
print("Modified block is [{}]".format(toHex(modified)))
return modified
def splitCipherTextIntoBlockPairs(cipherTextBytes: bytearray):
blocks = [(cipherTextBytes[i:i+AES.block_size])
for i in range(0, len(cipherTextBytes), AES.block_size)]
pairs = [(blocks[i-1], blocks[i]) for i in range(1, len(blocks), 1)]
return pairs
def decryptPair(block1: bytearray, block2: bytearray, oracle):
# Goal is to decryt block2 using block1
targetCiphertextBlock = block2[:]
decryptedBlock = bytearray(len(targetCiphertextBlock))
precendingBlockCipherText = block1[:]
fakePadding = 0x01
for pos in range(len(block2)-1, -1, -1):
if debugDecrypt:
print("--------------------------")
if debugDecrypt:
print("Trying to decipher {}th byte".format(pos+1))
try:
# 1. Leak the plaintext byte pos at pos - key is just used to emulate padding oracle
plaintextByte = leakByte(
pos, targetCiphertextBlock, precendingBlockCipherText, fakePadding, block1[pos], oracle)
except Exception as err:
raise err
# 2. Save the leaked plaintext byte
decryptedBlock[pos] = plaintextByte
if debugDecrypt:
print("Decrypted: {}".format(decryptedBlock))
# 3. Use leaked plaintext bytes to insert fake padding into the ciphertext to obtain next modified ciphertext
fakePadding = fakePadding + 0x01
for j in range(len(block2)-1, pos-1, -1):
ptbyte = decryptedBlock[j]
precendingBlockCipherText = modifyByte(
j, precendingBlockCipherText, block1[j], ptbyte, fakePadding)
return bytes(decryptedBlock)