class DecryptParser: def __init__(self, stream, find_addr=None, output=None, key=None, hmac_out=False, full=False): self.stream = stream self.find_addr = find_addr self.print_next_payload = False self.decrypt_start = False self.output_file = output self.hmac_out = hmac_out self.full = full if key is not None: #self.cipher = AESCipher(b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xaa\xaa\xbb\xbb\xcc\xcc\xdd\xdd') self.cipher = AESCipher( b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' ) else: print("No Key Supplied, exitting") sys.exit(0) if output is not None: try: if os.path.getsize(output): open(output, "w").close() except: open(output, "a").close() def handle_bit(self): ''' First step in the process is to parse out the header, in the while loop is when handling the bitstream starts. Note that the keys for each field in the header all correspond to the alphabet starting from the second field. Read first 2 bytes, struct lib used for navigating bytes to python objects and converts everything to big endian assumedly for the ReWriter, but only this specific field (#1) is big endian in the file. ''' print("Parsing Header") a, = struct.unpack(">H", self.stream.read(2)) if a != 9: raise ValueError("Missing <0009> header, not a bit file") # reads next 9 bytes, according to Xilinx themselves, this is, quote - "some sort of header" unk = self.stream.read(a) # unknown data # this next read field must evalaute to 1 b, = struct.unpack(">H", self.stream.read(2)) if b != 1: raise ValueError("Missing <0001> header, not a bit file") self.handle_bitstart(a, unk, b) while True: ''' Here is where we can start to loop over each field. At the beginning of the loop the parser takes out the key. Keys are a 1 byte value starting with ascii letter 'a'(0x61) through 'e'(0x65) there may be keys afterwards so the loop handles unknowns as well. ''' key = self.stream.read(1) if not key: # loop closes when the byte read from where a Key should be is 0x00. break self.handle_keystart(key) if key == b"e": length, = struct.unpack(">I", self.stream.read(4)) self.handle_binstart(length) self.handle_bin(end_at=self.stream.tell() + length) elif key in b"abcd": data = self.stream.read( *struct.unpack(">H", self.stream.read(2))) self.handle_meta(key, data) else: print("Unknown key: {}: {}".format(key, d)) def handle_bitstart(self, a, unk, b): pass def handle_keystart(self, key): pass def handle_meta(self, key, data): assert data.endswith(b"\x00") data = data[:-1].decode() name = { b"a": "Design", b"b": "Part name", b"c": "Date", b"d": "Time" }[key] print("{}: {}".format(name, data)) def handle_binstart(self, length): print("Bitstream payload length: {:#x}".format(length)) ''' This function takes care of what goes on after the 'e' key i read. Author assumes no extraneous keys. ''' def handle_bin(self, end_at=None): sync = b"" count = 0 first_block = True # some edits to show behavior while not sync.endswith(b"\xaa\x99\x55\x66"): sync += self.stream.read(1) count = count + 1 # python will print \xaa\x99Uf, Uf in ascii encoding is \x55\x66. print("Number of padded words after Header end: ", count) print("Ended on", sync[-8:]) # end edits OPAD_count = 0 ''' Here begins actual packet parsing ''' while True: ''' end_at is defined as the current length of the stream + the payload length defined in the header. This case passes when the loop parses out the entirity of the remaining bytes ''' if end_at is not None and self.stream.tell() >= end_at: assert self.stream.tell() == end_at break hdr = self.stream.read(4) if len(hdr) != 4: assert end is None assert len(hdr) == 0 break hdr_un, = struct.unpack(">I", hdr) typ = hdr_un >> 29 # first 3 bits are the packet type `001` == Type 1, `010` == Type 2 if typ == 1 and not self.decrypt_start: self.handle_type1(hdr_un) elif typ == 2 and not self.decrypt_start: self.handle_type2(hdr_un) else: ''' Decryption Section ''' # read in more cipher text to 16 bytes ciphertext = hdr + self.stream.read(12) # if this is the very first block being decrypted, it is the HKEY if first_block: plaintext = self.cipher.decrypt_word(ciphertext, iv=self.CBC_IV, xor=True) print("decrypted HMAC Key P1: ", binascii.hexlify(plaintext)) first_block = False second_block = True self.CBC_IV = ciphertext if self.hmac_out: self.write_decrypted_bin(plaintext) continue if second_block: plaintext = self.cipher.decrypt_word(ciphertext, iv=self.CBC_IV, xor=True) print("decrypted HMAC Key P2: ", binascii.hexlify(plaintext)) second_block = False self.CBC_IV = ciphertext if self.hmac_out: self.write_decrypted_bin(plaintext) continue else: try: plaintext = self.cipher.decrypt_word(ciphertext, iv=self.CBC_IV, xor=False, swap=True) #print("decrypted: ", binascii.hexlify(plaintext)) except: print("Unexpected error when decrypting bytes - ", ciphertext) print("End of file may have been reached") sys.exit(0) if binascii.hexlify( plaintext ) == b'36363636363636363636363636363636' or binascii.hexlify( plaintext) == b'6c6c6c6c6c6c6c6c6c6c6c6c6c6c6c6c': if self.hmac_out: self.write_decrypted_bin( self.cipher.decrypt_word(ciphertext, iv=self.CBC_IV, xor=True)) self.CBC_IV = ciphertext continue if self.output_file is not None: if binascii.hexlify( plaintext) == b'5c5c5c5c5c5c5c5c5c5c5c5c5c5c5c5c': OPAD_count = (OPAD_count + 1) #sys.exit(0) elif b'80000000000000000000000000000000' == binascii.hexlify( plaintext ): #or b'010b6400' in binascii.hexlify(plaintext): print("Found END of HMAC Calc") print(plaintext) print(binascii.hexlify(plaintext)) if not self.full: sys.exit(0) #elif b'923d8e2a' in binascii.hexlify(plaintext): # sys.exit(0) if OPAD_count >= 3: print("OPAD Reached, returning") #sys.exit(0) self.write_decrypted_bin(plaintext) self.CBC_IV = ciphertext self.handle_end() def write_decrypted_bin(self, pt): with open(self.output_file, "ab") as f: f.write(pt) def handle_sync(self, sync): pass def handle_end(self): pass ''' Type 1 Packets are for declaring reads and writes. they appear to be used as a 'header' that is defining the operation that is about to happen. Must always be followed by a Type 2 Packet if payload is larger than 11 bit length worth of words. ''' def handle_type1(self, hdr): ''' Type 1 Header(32 bits): 31 30 29 28 27 26 25 24 23 22 21 20 19 18 17 16 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0 T T T O O RA RA RA RA RA RA RA RA RA A A A A A R R W W W W W W W W W W W T = Header Type O = Opcode RA = Reserved Register Address Bit A = Register Address Bit R = Reserved Bit W = Word Count (# of 32 bit words to follow header) ''' packet = Type1() # bit shifting the header bits, the & 0x3 is simply there to condense the remainder to 2 bits op = (hdr >> 27) & 0x3 #print("Before 0x3: ", "{0:b}".format(op)) # Applies mask a to the register field of the hdr. # Only the least significant 5 bits of the field are writable - the others are reserved. self.addr = (hdr >> 13) & 0x7ff assert self.addr == self.addr & 0x1f # this mask gets the first 11 bits which corresponds to length (in bytes) length = hdr & 0x7ff payload = self.stream.read(length * 4) assert len(payload) == length * 4 packet.raw_header = hdr packet.register = self.addr packet.op = op packet.payload = payload packet.p_length = length packet.handle() ''' If the register we just hit was the CBC register, we need to store that value since the payload is the IV for the bitstream ''' if registers[self.addr] == "CBC": self.CBC_IV = packet.CBC_IV print("Found AES", binascii.hexlify(packet.CBC_IV)) ''' Once we hit the DWC register, we know that the next bytes read are the beginning of the encrypted ciphertext. ''' if registers[self.addr] == "DWC": self.decrypt_start = True print((hdr)) print("Decrypted Word Cound: ", binascii.hexlify(packet.payload)) #self.handle_op(op, hdr, payload) ''' Type 2 Packets follow Type 1 packets when the payload is too large. These packets use the address defined by he previous Type 1 packet. Header 32(bits): 31 30 29 28 27 26 25 24 23 22 21 20 19 18 17 16 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0 T T O O W W W W W W W W W W W W W W W W W W W W W W W W W W W W T = Header Type O = Opcode W = Word Count (# of 32 bit words to follow header) ''' def handle_type2(self, hdr): op = (hdr >> 27) & 0x3 length = hdr & 0x7ffffff payload = self.stream.read(length * 4) assert len(payload) == length * 4 print("Handling type 2 OP: ", opcode_list[op]) #self.handle_op(op, hdr, payload) packet = Type2() packet.op = op packet.p_length = length packet.payload = payload packet.current_FAR = self.current_FAR packet.handle() def handle_op(self, op, hdr, payload): # OP Codes - # 00 : NOP # 01 : Read # 10 : Write # 11 : Reserved assert op != 3 if op == 0: self.handle_nop(hdr, payload) elif op == 1: self.handle_read(hdr, payload) elif op == 2: #print("HDR: ", hdr, " payload: ", payload) ok = handle_write(hdr, payload) print("out of handle_write call") print("OK:", ok) return 1 def handle_nop(self, hdr, payload): pass def handle_read(self, hdr, payload): pass