├── README.md └── vbfdecode.py /README.md: -------------------------------------------------------------------------------- 1 | # Simple python3 vbf decoder/info displayer 2 | 3 | Requires python 3.3+, tested on 3.6. 4 | VBF files are used for updating firmware on volvo/mazda and ford cars. 5 | Ford decided to include the apperantly broken description as comments so it tries to find that as well. Software is as is (e.g. crappy, quick and dirty, bad find command), ymmv. 6 | 7 | ## Usage 8 | ``` 9 | python3 vbfdecode.py --help 10 | usage: Try me with a filename.vbf 11 | 12 | positional arguments: 13 | file VBF file to show data about 14 | 15 | optional arguments: 16 | -h, --help show this help message and exit 17 | -b, --binary Write binary blobs in vbf to [address].bin 18 | ``` 19 | 20 | ## Result 21 | Example file is a ford ACM (radio) vbf file: 22 | 23 | ``` 24 | python3 vbfdecode.py F1BT-14C044-HJ.vbf 25 | VBF [v2.4]: 26 | Description: Strategy for ACU [Audio Control Unit] 27 | 09.5MY Ford C344E L0CPlus 28 | Created on 2018/05/08 29 | Released by VTAMILSE@visteon.com 30 | Comment: c346e_ahu_ngl0cplus_mid_dab_c_prod 31 | 32 | Software part: F1BT-14C044-HN type: EXE 33 | Network: CAN_HS @ 0x727 34 | Frame_format:CAN_STANDARD 35 | Erase frames: 36 | 0x000BC000 (0x00004000) 37 | Data blobs: 38 | 0x00004000 655800 dd6a 39 | 0x000B8000 27976 38ae 40 | ``` 41 | -------------------------------------------------------------------------------- /vbfdecode.py: -------------------------------------------------------------------------------- 1 | import argparse 2 | import re 3 | from binascii import unhexlify, hexlify 4 | 5 | class VBF: 6 | version = 0 7 | description = "" 8 | sw_part = "" 9 | sw_part_type = "" 10 | network = 0x00 11 | data_format_identifier = 0x00 12 | ecu_address = 0x00 13 | verification_block_start = 0x00 14 | frame_format = "" 15 | erase = [] 16 | checksum = bytes() 17 | raw = bytes() 18 | data = [] 19 | 20 | def __init__(self, data): 21 | if not isinstance(data, bytes): 22 | raise TypeError("Requires binary input") 23 | 24 | try: 25 | self.version = data[data.find(b"vbf_version"):data.find(b"\n")].split(b"=")[1].replace(b" ", b"").replace(b";", b"").decode() 26 | except: 27 | raise ValueError("Version not found") 28 | 29 | header = data[data.find(b"header"):data.find(b"\n}")+2] 30 | 31 | desc = header[header.find(b"description ="):header.find(b"};")].replace(b"//", b"") # remove comment lines if they exist 32 | self.description = "" 33 | for line in desc.split(b"\n"): 34 | self.description = self.description + line[line.find(b"\"")+1:line.find(b"\",")].decode() + "\n" if len(line[line.find(b"\"")+1:line.find(b"\",")]) > 0 else self.description 35 | header = header[header.find(b"sw_part_number"):] 36 | erase = False 37 | for line in header.split(b"\n"): 38 | line = line.replace(b"\t", b"").replace(b"\r", b"") 39 | if line.startswith(b"//"): 40 | continue 41 | try: 42 | if b"sw_part_number" in line: 43 | self.sw_part = line[line.find(b" = ")+3:line.find(b"\";")].replace(b" ", b"").replace(b"\"", b"").decode() 44 | if b"sw_part_type" in line: 45 | self.sw_part_type = line[line.find(b" = ")+3:line.find(b";")].replace(b" ", b"").replace(b"\"", b"").decode() 46 | if b"network" in line: 47 | self.network = line[line.find(b" = ")+3:line.find(b";")].replace(b" ", b"").replace(b"\"", b"").decode() 48 | if b"data_format_identifier" in line: 49 | self.data_format_identifier = int(line[line.find(b" = ")+3:line.find(b";")].replace(b" ", b"").replace(b"\"", b"").decode(), 16) 50 | if b"ecu_address" in line: 51 | self.ecu_address = int(line[line.find(b" = 0x")+5:line.find(b";")].replace(b" ", b"").replace(b"\"", b"").decode(), 16) 52 | if b"verification_block_start" in line: 53 | self.verification_block_start = int(line[line.find(b" = 0x")+5:line.find(b";")].replace(b" ", b"").replace(b"\"", b"").decode(), 16) 54 | if b"frame_format" in line: 55 | self.frame_format = line[line.find(b" = ")+3:line.find(b";")].replace(b" ", b"").replace(b"\"", b"").decode() 56 | if b"file_checksum" in line: 57 | self.file_checksum = unhexlify(line[line.find(b" = 0x")+5:line.find(b";")].replace(b" ", b"").replace(b"\"", b"")) 58 | if b"erase = " in line or erase: 59 | # dirty find 60 | erase = True 61 | r = re.compile(r'{\s*0x([0-9A-Fa-f]+),\s*0x([0-9A-Fa-f]+)\s*}') 62 | m = r.search(line.decode()) 63 | if m is not None: 64 | self.erase.append([m.group(1), m.group(2)]) 65 | if erase: 66 | if b"};" in line: 67 | erase = False 68 | except Exception as e: 69 | print(line) 70 | raise 71 | binary_offset = data.find(b"\n}")+2 72 | binary = data[binary_offset:] 73 | self.data = list() 74 | while len(binary) > 0: 75 | location = int.from_bytes(binary[:4], 'big') 76 | size = int.from_bytes(binary[4:8], 'big') 77 | data = binary[8:8+size] 78 | checksum = binary[8+size:8+size+2] 79 | binary = binary[8+size+2:] 80 | print("Offset: 0x{:X}, Location: 0x{:X}, Size: 0x{:X}, Data Offset: 0x{:X}".format(binary_offset, location, size, binary_offset + 8)) 81 | binary_offset += 8+size+2 82 | if location != self.verification_block_start: 83 | self.data.append((location, data, checksum)) 84 | 85 | 86 | def __str__(self): 87 | string = "VBF v{}\n".format(self.version) 88 | string = string + "Description: {}".format(self.description) 89 | string = string + "Software part: {} type: {}\n".format(self.sw_part, self.sw_part_type) 90 | string = string + "Network: 0x{:08X}\n".format(self.network) 91 | string = string + "Data Format Identifier: 0x{:08X}\n".format(self.data_format_identifier) 92 | string = string + "ECU address: 0x{:08X}\n".format(self.ecu_address) 93 | string = string + "Frame_format:{}\n".format(self.frame_format) 94 | string = string + "Erase frames:\n" 95 | for x in self.erase: 96 | string = string + "0x{} (0x{})\n".format(x[1], x[0]) 97 | string = string + "Data blobs:\n" 98 | for i in self.data: 99 | string = string + "0x{:08X}\t0x{:08X}\t {}\n".format(i[0], len(i[1]), hexlify(i[2]).decode()) 100 | return string 101 | 102 | def dump(self, dst): 103 | for x in self.data: 104 | print("{:08X}.bin ".format(x[0])) 105 | filename = "{:08X}.bin".format(x[0]).replace(" ", "") 106 | with open(dst + "/" + filename, "wb") as f: 107 | f.write(x[1]) 108 | f.close() 109 | 110 | if __name__ == "__main__": 111 | parser = argparse.ArgumentParser(prog="VBF somewhat decoder and blob extracter", usage="Try me with a filename.vbf") 112 | parser.add_argument("file", help="VBF file to show data about", type=str) 113 | parser.add_argument("-b", "--binary", help="Write binary blobs in vbf to [address].bin", action="store_true", default=False) 114 | args = parser.parse_args() 115 | filename = args.file 116 | 117 | with open(filename, "rb") as f: 118 | vbf = VBF(f.read()) 119 | print(vbf) 120 | 121 | if args.binary: 122 | print("Saving: ") 123 | for x in vbf.data: 124 | print("%8X.bin " % (x[0])) 125 | with open(("%8X.bin" % (x[0])).replace(" ", ""), "wb") as f: 126 | f.write(x[1]) 127 | --------------------------------------------------------------------------------