├── README.md ├── mfa_extract.py └── fs2_update_ini.py /README.md: -------------------------------------------------------------------------------- 1 | # mft-scripts 2 | 3 | Just a small collection of scripts I wrote while working with Mellanox firmware images. 4 | 5 | ## **mfa_extract.py** 6 | 7 | Allows to extract .bin firmware images for a specific PSID from a .mfa bundle. 8 | 9 | 10 | ## **fs2_update_ini.py** 11 | 12 | Allows to replace the "Firmware Configuration" section in a .bin FS2 firmware file. 13 | -------------------------------------------------------------------------------- /mfa_extract.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | 3 | # tested with 4 | # mstflint -v 5 | # mstflint, mstflint 4.6.0. Git SHA Hash: 375120d 6 | 7 | import sys 8 | import os 9 | import subprocess 10 | import struct 11 | import zlib 12 | import lzma 13 | import re 14 | import configparser 15 | 16 | FLINT = "mstflint" 17 | 18 | def read_buff(fn, n=-1): 19 | with open(fn, 'rb') as f: 20 | return f.read(n) 21 | 22 | def lzma_decompress(buf): 23 | decomp = lzma.LZMADecompressor(memlimit=0x10000000) 24 | try: 25 | return decomp.decompress(buf) 26 | except lzma.LZMAError: 27 | pass 28 | return b'' 29 | 30 | def save_bin(fn, buff, bin_off, bin_len): 31 | decomp = lzma.LZMADecompressor(memlimit=0x10000000) 32 | with open(fn, 'wb') as f: 33 | try: 34 | f.write(decomp.decompress(buff)[bin_off:bin_off + bin_len]) 35 | except lzma.LZMAError: 36 | pass 37 | return decomp.eof 38 | 39 | def parse_mtoc(buff, compressed, offset, size): 40 | mtoc = {} 41 | if compressed: 42 | off = 0 43 | while off < len(buff): 44 | a, b, c = struct.unpack_from('>32sB1xH', buff, off) 45 | psid = a.decode('ascii').strip('\0') 46 | _, pn, _, desc = buff[off+40:off+180].decode('latin1').strip('\0').split('\0')[0:4] 47 | mtoc[psid] = { 'pn': pn, 'desc': desc, 'off': [struct.unpack_from('>IHH', buff, 36+off+c+40*i) for i in range(b)]} 48 | off += 36 + 40 * b + c 49 | return mtoc 50 | 51 | def mfa_extract(mfaname, psid): 52 | SECTIONS = {} 53 | BUFFER = bytearray(read_buff(mfaname)) 54 | if b'MFAR' != BUFFER[0:4]: 55 | return 1 56 | off = 16 57 | for i in range(3): 58 | a,b,c,d = struct.unpack_from('>B2xBI4s', BUFFER, off) 59 | off += 8 60 | SECTIONS[a] = {'offset': off, 'size': c, 'compressed': b and (d == b'\xFD7zX'), 'buff': memoryview(BUFFER[off:off+c]) } 61 | if SECTIONS[a]['compressed'] and i < 2: 62 | SECTIONS[a]['buff'] = lzma_decompress(SECTIONS[a]['buff']) 63 | off += c 64 | 65 | MTOC = parse_mtoc(**SECTIONS[1]) 66 | if MTOC.get(psid): 67 | fn = "{}.bin".format(psid) 68 | for moff in MTOC[psid]['off']: 69 | off, size = struct.unpack_from('>ii', SECTIONS[2]['buff'], moff[0]) 70 | if size > 0: 71 | break 72 | if save_bin(fn, SECTIONS[3]['buff'], off, size): 73 | print(subprocess.check_output([FLINT, '-i', fn, 'v']).decode('ascii')) 74 | print(subprocess.check_output([FLINT, '-i', fn, 'q']).decode('ascii')) 75 | return 0 76 | 77 | else: 78 | for i, psid in enumerate(sorted(MTOC.keys()), 1): 79 | print('{i:>3}. {psid:15s}{pn:33s}{desc}'.format(i=i, psid=psid, **MTOC[psid])) 80 | return 0 81 | return 1 82 | 83 | if __name__ == "__main__": 84 | if len(sys.argv) != 3: 85 | print("Usage:\n\t{0} firmware.mfa \t - to extract\n\t{0} firmware.mfa l|list\t - to list".format(*sys.argv)) 86 | sys.exit(2) 87 | sys.exit(mfa_extract(*sys.argv[1:])) 88 | 89 | -------------------------------------------------------------------------------- /fs2_update_ini.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | 3 | import sys 4 | import struct 5 | import zlib 6 | import configparser 7 | import inspect 8 | import subprocess 9 | 10 | DEBUG = 0 11 | 12 | def dbg(*a, **b): 13 | if DEBUG: 14 | print(inspect.stack()[1].function, *a, **b) 15 | 16 | class FS2Image(): 17 | ZLIB = b'\x78\xDA' 18 | GPH_STRUCT = '>IIII' 19 | GPH_SIZE = struct.calcsize(GPH_STRUCT) 20 | GPH_FIELDS = 'type size param next'.split() 21 | GPH_TYPES = 'UNKNOWN DDR CNF JMP EMT ROM GUID BOARD_ID USER_DATA FW_CONF IMG_INFO DDRZ HASH_FILE LAST'.split() 22 | IIT_STRUCT = '>B1xH' 23 | IIT_SIZE = struct.calcsize(IIT_STRUCT) 24 | IIT_FIELDS = 'tag size'.split() 25 | IIT_TAGS = """IiFormatRevision 26 | FwVersion 27 | FwBuildTime 28 | DeviceType 29 | PSID 30 | VSD 31 | SupportedPsids 32 | ProductVer 33 | VsdVendorId 34 | IsGa 35 | HwDevsId 36 | MicVersion 37 | MinFitVersion 38 | HwAccessKey 39 | PROFILES_LIST 40 | SUPPORTED_PROFS 41 | CONFIG_INFO 42 | TLVS_FORMAT 43 | TRACER_HASH 44 | ConfigArea 45 | PSInfo""".splitlines() 46 | FS2_CRC_OFF = 0x22 47 | LASTNEXT = 0xff000000 48 | FS2_BOOT_START = 0x38 49 | 50 | def __init__(self, fn=None): 51 | self.filename = fn 52 | self.buffer = bytearray() 53 | self.sections = [] 54 | self.image_info = [] 55 | if fn: 56 | self.readbin(fn) 57 | 58 | def readbin(self, fn): 59 | self.filename = fn 60 | self.buffer = bytearray(self.read(fn)) 61 | self.sections = self.parse_sections() 62 | self.image_info = self.parse_imginfo() 63 | 64 | def read(self, fn, n=-1): 65 | with open(fn, 'rb') as f: 66 | return f.read(n) 67 | 68 | def save(self, fn=None, update_crc=False): 69 | if update_crc: 70 | self.crc16(update=True) 71 | if not fn: 72 | fn = self.filename 73 | with open(fn, 'wb') as f: 74 | f.write(self.buffer) 75 | 76 | def parse_sections(self): 77 | sections = [] 78 | off = self.FS2_BOOT_START 79 | while 0 < off < len(self.buffer): 80 | gph = dict(zip(self.GPH_FIELDS, struct.unpack_from(self.GPH_STRUCT, self.buffer, off))) 81 | gph['offset'] = off 82 | gph['size'] = gph['size'] * 4 + self.GPH_SIZE 83 | gph['typename'] = 'BOOT2' 84 | if 0 < gph['type'] < len(self.GPH_TYPES): 85 | gph['typename'] = self.GPH_TYPES[gph['type']] 86 | gph['size'] += 4 87 | gph['crc_offset'] = off + gph['size'] - 4 88 | gph['crc'] = struct.unpack_from('>I', self.buffer, gph['crc_offset'])[0] 89 | sections.append(gph) 90 | off = gph['crc_offset'] + 4 91 | dbg('offset={offset:08X} size={size:08X} next={next:08X} crc={crc:04X} type={typename}'.format(**gph)) 92 | if gph['next'] == self.LASTNEXT: 93 | break 94 | return sections 95 | 96 | def parse_imginfo(self): 97 | info = [] 98 | ii = [s for s in self.sections if s['typename'] == 'IMG_INFO'][0] 99 | off = ii['offset'] + self.GPH_SIZE 100 | while off < ii['next']: 101 | iit = dict(zip(self.IIT_FIELDS, struct.unpack_from(self.IIT_STRUCT, self.buffer, off))) 102 | if iit['tag'] == 255: 103 | break 104 | iit['offset'] = off 105 | off += 4 106 | if iit['tag'] < len(self.IIT_TAGS): 107 | iit['tagname'] = self.IIT_TAGS[iit['tag']] 108 | iit['data'] = self.buffer[off:off+iit['size']] 109 | dbg('offset={offset:08x} size={size:08x} tag={tagname}'.format(**iit)) 110 | info.append(iit) 111 | if iit['tagname'] == 'PSID': 112 | self.psid_offset = off 113 | self.psid_size = iit['size'] 114 | self.PSID = iit['data'].decode('ascii').strip() 115 | off += iit['size'] 116 | dbg('PSID={PSID} offset={psid_offset:08x} size={psid_size:08x}'.format(**self.__dict__)) 117 | return info 118 | 119 | def crc16(self, start=0, end=None, update=False): 120 | crc = 0xffff 121 | if start == 0: 122 | orig = struct.unpack_from('>H',self.buffer, self.FS2_CRC_OFF) 123 | struct.pack_into('>H',self.buffer, self.FS2_CRC_OFF, crc) 124 | 125 | for dw, in struct.iter_unpack('>I', self.buffer[start:end]): 126 | for i in range(32): 127 | if crc & 0x8000: 128 | crc = (((crc << 1) | (dw >> 31)) ^ 0x100b) & 0xffff 129 | else: 130 | crc = ((crc << 1) | (dw >> 31)) & 0xffff 131 | dw = (dw << 1) & 0xffffffff 132 | for i in range(16): 133 | if crc & 0x8000: 134 | crc = ((crc << 1) ^ 0x100b) & 0xffff 135 | else: 136 | crc = (crc << 1) & 0xffff 137 | crc ^= 0xffff 138 | if start == 0: 139 | if update: 140 | struct.pack_into('>H',self.buffer, self.FS2_CRC_OFF, crc) 141 | else: 142 | struct.pack_into('>H',self.buffer, self.FS2_CRC_OFF, orig) 143 | return crc 144 | 145 | def set_psid(self, psid): 146 | ii = [s for s in self.sections if s['typename'] == 'IMG_INFO'][0] 147 | struct.pack_into('{}s'.format(self.psid_size),self.buffer, self.psid_offset, bytes('{:\0<{}}'.format(psid, self.psid_size), 'ascii')) 148 | struct.pack_into('>I',self.buffer, ii['crc_offset'], self.crc16(ii['offset'], ii['offset'] + ii['size'] - 4)) 149 | self.image_info = self.parse_imginfo() 150 | 151 | def update_ini(self, fn): 152 | def aligned(buf, n=4, f=b'\0'): 153 | return buf + f * (n - len(buf) % n) 154 | ini = self.read(fn) 155 | cfg = configparser.ConfigParser() 156 | cfg.read_string(ini.decode('ascii')) 157 | dbg('{} - PSID from {}'.format(self.PSID, self.filename)) 158 | dbg('{} - PSID from {}'.format(cfg['ADAPTER']['PSID'], fn)) 159 | zipped = aligned(zlib.compress(ini, 9)) 160 | off = [s for s in self.sections if s['typename'] == 'FW_CONF'][0]['offset'] 161 | self.buffer = bytearray(self.buffer[0:off]) 162 | self.buffer.extend(struct.pack('>IIII', 9, len(zipped)//4, 0, 0xff000000) + zipped) 163 | self.buffer.extend(struct.pack('>I', self.crc16(off))) 164 | self.set_psid(cfg['ADAPTER']['PSID']) 165 | 166 | if __name__ == "__main__": 167 | if len(sys.argv) != 3: 168 | print("Usage:\n\t{0} firmware.bin config.ini".format(*sys.argv)) 169 | sys.exit(1) 170 | fs = FS2Image(sys.argv[1]) 171 | fs.update_ini(sys.argv[2]) 172 | fs.save(sys.argv[1], update_crc=True) 173 | sys.exit(0) 174 | 175 | --------------------------------------------------------------------------------