├── huff11.bin ├── README.md ├── LICENSE ├── HuffDec11.py ├── HuffDec12.py ├── Code12.txt ├── Data12.txt └── unME12.py /huff11.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ptresearch/unME12/HEAD/huff11.bin -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Intel ME 12.x Firmware Images Unpacker 2 | ===== 3 | This repository contains Python 2.7 scripts for unpacking firmware regions for ME 12.x 4 | 5 | ## Usage 6 | 7 | unME12.py 8 | 9 | Report would be written to ME_Image_File_Name.txt 10 | 11 | Extracted data (partitions, modules, metadata) would be written to ME_Image_File_Name folder 12 | 13 | Compiled lzma binary is required on PATH for LMZA decompression (see [http://www.7-zip.org/download.html][1]) 14 | 15 | ## Limitations 16 | 17 | No progress output. Don't worry - just wait 18 | 19 | ## Related URLs: 20 | 21 | [Intel ME: The Way of the Static Analysis][2] 22 | 23 | [Intel DCI Secrets][3] 24 | 25 | ## Author 26 | 27 | Dmitry Sklyarov ([@_Dmit][6]) 28 | 29 | ## Research Team 30 | 31 | Mark Ermolov ([@\_markel___][4]) 32 | 33 | Maxim Goryachy ([@h0t_max][5]) 34 | 35 | Dmitry Sklyarov ([@_Dmit][6]) 36 | 37 | ## License 38 | This software is provided under a custom License. See the accompanying LICENSE file for more information. 39 | 40 | [1]: http://www.7-zip.org/download.html 41 | [2]: https://www.troopers.de/troopers17/talks/772-intel-me-the-way-of-the-static-analysis/ 42 | [3]: http://conference.hitb.org/hitbsecconf2017ams/sessions/commsec-intel-dci-secrets/ 43 | [4]: https://twitter.com/_markel___ 44 | [5]: https://twitter.com/h0t_max 45 | [6]: https://twitter.com/_Dmit 46 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2018 Positive Technologies 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 4 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 5 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 6 | 7 | 8 | Copyright (c) 2018 Positive Technologies 9 | 10 | Данная лицензия разрешает лицам, получившим копию данного программного обеспечения и сопутствующей документации (в дальнейшем именуемыми «Программное Обеспечение»), безвозмездно использовать Программное Обеспечение без ограничений, включая неограниченное право на использование, копирование, изменение, слияние, публикацию, распространение, сублицензирование и/или продажу копий Программного Обеспечения, а также лицам, которым предоставляется данное Программное Обеспечение, при соблюдении следующих условий: 11 | 12 | Указанное выше уведомление об авторском праве и данные условия должны быть включены во все копии или значимые части данного Программного Обеспечения. 13 | 14 | ДАННОЕ ПРОГРАММНОЕ ОБЕСПЕЧЕНИЕ ПРЕДОСТАВЛЯЕТСЯ «КАК ЕСТЬ», БЕЗ КАКИХ-ЛИБО ГАРАНТИЙ, ЯВНО ВЫРАЖЕННЫХ ИЛИ ПОДРАЗУМЕВАЕМЫХ, ВКЛЮЧАЯ ГАРАНТИИ ТОВАРНОЙ ПРИГОДНОСТИ, СООТВЕТСТВИЯ ПО ЕГО КОНКРЕТНОМУ НАЗНАЧЕНИЮ И ОТСУТСТВИЯ НАРУШЕНИЙ, НО НЕ ОГРАНИЧИВАЯСЬ ИМИ. НИ В КАКОМ СЛУЧАЕ АВТОРЫ ИЛИ ПРАВООБЛАДАТЕЛИ НЕ НЕСУТ ОТВЕТСТВЕННОСТИ ПО КАКИМ-ЛИБО ИСКАМ, ЗА УЩЕРБ ИЛИ ПО ИНЫМ ТРЕБОВАНИЯМ, В ТОМ ЧИСЛЕ, ПРИ ДЕЙСТВИИ КОНТРАКТА, ДЕЛИКТЕ ИЛИ ИНОЙ СИТУАЦИИ, ВОЗНИКШИМ ИЗ-ЗА ИСПОЛЬЗОВАНИЯ ПРОГРАММНОГО ОБЕСПЕЧЕНИЯ ИЛИ ИНЫХ ДЕЙСТВИЙ С ПРОГРАММНЫМ ОБЕСПЕЧЕНИЕМ. 15 | -------------------------------------------------------------------------------- /HuffDec11.py: -------------------------------------------------------------------------------- 1 | import os, sys, struct, zlib 2 | 3 | class Error(Exception): pass 4 | 5 | def cwDec(w): # Convert 16-bit value to string codeword 6 | return bin(0x10000 | w).rstrip('0')[3:-1] 7 | 8 | def cwEnc(cw): # Convert string codeword to 16-bit value 9 | return int((cw+'1').ljust(16, '0'), 2) 10 | 11 | #*************************************************************************** 12 | #*************************************************************************** 13 | #*************************************************************************** 14 | 15 | def HuffTabReader_bin(ab): 16 | fmtRec = struct.Struct(" nBits # Length must increase 105 | s = int(aCW[o-1], 2) # Start value for current length 106 | for i in xrange(s, e+1): 107 | cw = bin(i)[2:].zfill(nBits) 108 | self.propagateMap(HuffNode(cw, self)) 109 | e = int(aCW[o], 2)|1 # End value for next length 110 | for i in xrange(e/2 + 1, s): # Handle values with unknown codeword length 111 | cw = bin(i)[2:].zfill(nBits) 112 | self.propagateMap(HuffNode(cw, None)) 113 | nBits = nextBits 114 | for v in self.aMap: assert v is not None 115 | 116 | def enumCW(self, ab): 117 | v = int(bin(int("01"+ab.encode("hex"), 16))[3:][::-1], 2) # Reversed bits 118 | cb = 0 119 | while cb < self.BLOCK_SIZE: # Block length 120 | node = self.aMap[v & 0x7FFF] 121 | if node.nBits is None: raise Error("Unknown codeword %s* length" % node.cw) 122 | yield node 123 | v >>= node.nBits 124 | if node.cb is not None: cb += node.cb 125 | 126 | def decompressChunk(self, ab, iTab): 127 | r = [] 128 | cb = 0 129 | for node in self.enumCW(ab): 130 | v = node.av[iTab] 131 | if v is None: raise Error("Unknown sequence for codeword %s in table #%d" % (node.cw, iTab)) 132 | r.append(v) 133 | cb += len(v) 134 | if cb >= self.BLOCK_SIZE: break 135 | return "".join(r) 136 | 137 | def decompress(self, ab, length): 138 | nChunks, left = divmod(length, self.BLOCK_SIZE) 139 | assert 0 == left 140 | aOfs = list(struct.unpack_from("<%dL" % nChunks, ab)) 141 | aOpt = [0]*nChunks 142 | for i in xrange(nChunks): 143 | aOpt[i], aOfs[i] = divmod(aOfs[i], 0x40000000) 144 | 145 | base = nChunks*4 146 | aOfs.append(len(ab) - base) 147 | r = [] 148 | for i, opt in enumerate(aOpt): 149 | iTab, bCompr = divmod(opt, 2) 150 | assert 1 == bCompr 151 | unpacked = self.decompressChunk(ab[base + aOfs[i]: base + aOfs[i+1]], iTab) 152 | assert len(unpacked) == self.BLOCK_SIZE 153 | r.append(unpacked) 154 | return "".join(r) 155 | 156 | -------------------------------------------------------------------------------- /HuffDec12.py: -------------------------------------------------------------------------------- 1 | import os, sys, struct, zlib 2 | 3 | class Error(Exception): pass 4 | 5 | def cwDec(w): # Convert 16-bit value to string codeword 6 | return bin(0x10000 | w).rstrip('0')[3:-1] 7 | 8 | def cwEnc(cw): # Convert string codeword to 16-bit value 9 | return int((cw+'1').ljust(16, '0'), 2) 10 | 11 | #*************************************************************************** 12 | #*************************************************************************** 13 | #*************************************************************************** 14 | 15 | def HuffTabReader_bin(ab): 16 | fmtRec = struct.Struct(" 1 else None # Huffman sequence value 44 | if v is None: # Not defined 45 | cb = None 46 | elif v.startswith("??"): # Sequence length is known 47 | cb = len(v)/2 48 | v = None 49 | else: # Value is known 50 | v = v.decode("hex") 51 | cb = len(v) 52 | yield(cw, cb, v) 53 | 54 | def HuffTabPack_text(dLen, d, mode=None): 55 | if mode is None: mode = HuffDecoder.DUMP_ALL 56 | r = [] 57 | for cw in sorted(dLen.keys())[::-1]: 58 | cb = dLen[cw] 59 | if cb is None and mode in (HuffDecoder.DUMP_KNOWN, HuffDecoder.DUMP_LEN): continue # Ignore if sequence length is not known 60 | v = d.get(cw, None) 61 | if v is None: 62 | if HuffDecoder.DUMP_KNOWN == mode: continue # Ignore if sequence is not known 63 | v = "" if cb is None else "??"*cb 64 | else: v = v.encode("hex").upper() 65 | pad = '\t'*(2 - len(cw)/8) 66 | r.append("%s%s%s" % (cw, pad, v)) 67 | return "\n".join(r) 68 | 69 | #*************************************************************************** 70 | #*************************************************************************** 71 | #*************************************************************************** 72 | 73 | def HuffTab_extendLen(dLen, extShape=False): 74 | shape = [] 75 | aCW = sorted(dLen.keys())[::-1] 76 | minBits, maxBits = len(aCW[0]), len(aCW[-1]) 77 | aCW.append('0'*(maxBits+1)) # Longer than max 78 | 79 | nBits = minBits # Current length 80 | e = int(aCW[0], 2)|1 # End value for current length 81 | for o in xrange(1, len(aCW)): 82 | nextBits = len(aCW[o]) 83 | if nextBits == nBits: continue # Run until length change 84 | assert nextBits > nBits # Length must increase 85 | s = int(aCW[o-1], 2) # Start value for current length 86 | for i in xrange(s, e+1): 87 | cw = bin(i)[2:].zfill(nBits) 88 | if cw not in dLen: dLen[cw] = None 89 | e = int(aCW[o], 2)|1 # End value for next length 90 | shape.append([(x << (16-nBits)) for x in xrange(s, e/2, -1)]) 91 | if extShape: 92 | for i in xrange(s-1, e/2, -1): 93 | cw = bin(i)[2:].zfill(nBits) 94 | if cw not in dLen: dLen[cw] = None 95 | nBits = nextBits 96 | return shape 97 | 98 | #*************************************************************************** 99 | #*************************************************************************** 100 | #*************************************************************************** 101 | 102 | class HuffNode(object): 103 | def __init__(self, cw, hd): 104 | self.cw = cw # String codeword value 105 | # self.w = cwEnc(cw) # Encoded codeword value 106 | if hd: 107 | self.nBits = len(cw) # Length of codeword in bits 108 | self.cb = hd.dLen.get(cw, None) 109 | self.av = [d.get(cw, None) for d in hd.adTab] 110 | else: 111 | self.nBits = None # Actual length of codeword is unknown 112 | 113 | #*************************************************************************** 114 | #*************************************************************************** 115 | #*************************************************************************** 116 | 117 | class HuffDecoder(object): 118 | NAMES = ("Code", "Data") 119 | DUMP_KNOWN = 0 120 | DUMP_LEN = 1 121 | DUMP_ALL = 2 122 | dPrefix = {DUMP_KNOWN:"kno", DUMP_LEN:"len", DUMP_ALL:"all"} 123 | fmtInt = struct.Struct(">fo, HuffTabPack_text(dLen, d, mode) 189 | 190 | def propagateMap(self, node): 191 | cw = node.cw 192 | for idx in xrange(int(cw[::-1], 2), len(self.aMap), 1< nBits # Length must increase 208 | s = int(aCW[o-1], 2) # Start value for current length 209 | for i in xrange(s, e+1): 210 | cw = bin(i)[2:].zfill(nBits) 211 | self.propagateMap(HuffNode(cw, self)) 212 | e = int(aCW[o], 2)|1 # End value for next length 213 | for i in xrange(e/2 + 1, s): # Handle values with unknown codeword length 214 | cw = bin(i)[2:].zfill(nBits) 215 | self.propagateMap(HuffNode(cw, None)) 216 | nBits = nextBits 217 | for v in self.aMap: assert v is not None 218 | 219 | def enumCW(self, ab): 220 | v = int(bin(int("01"+ab.encode("hex"), 16))[3:][::-1], 2) # Reversed bits 221 | cb = 0 222 | while cb < self.BLOCK_SIZE: # Block length 223 | node = self.aMap[v & self.mask] 224 | if node.nBits is None: raise Error("Unknown codeword %s* length" % node.cw) 225 | yield node 226 | v >>= node.nBits 227 | if node.cb is not None: cb += node.cb 228 | 229 | def decompressChunk(self, ab, iTab): 230 | r = [] 231 | cb = 0 232 | for node in self.enumCW(ab): 233 | v = node.av[iTab] 234 | if v is None: raise Error("Unknown sequence for codeword %s in table #%d" % (node.cw, iTab)) 235 | r.append(v) 236 | cb += len(v) 237 | if cb >= self.BLOCK_SIZE: break 238 | return "".join(r) 239 | 240 | def decompress(self, ab, length): 241 | nChunks, left = divmod(length, self.BLOCK_SIZE) 242 | assert 0 == left 243 | aOfs = list(struct.unpack_from("<%dL" % nChunks, ab)) 244 | aOpt = [0]*nChunks 245 | for i in xrange(nChunks): 246 | aOpt[i], aOfs[i] = divmod(aOfs[i], 0x40000000) 247 | 248 | base = nChunks*4 249 | aOfs.append(len(ab) - base) 250 | r = [] 251 | for i, opt in enumerate(aOpt): 252 | iTab, bCompr = divmod(opt, 2) 253 | assert 1 == bCompr 254 | unpacked = self.decompressChunk(ab[base + aOfs[i]: base + aOfs[i+1]], iTab) 255 | assert len(unpacked) == self.BLOCK_SIZE 256 | r.append(unpacked) 257 | return "".join(r) 258 | 259 | #*************************************************************************** 260 | #*************************************************************************** 261 | #*************************************************************************** 262 | 263 | def main(argv): 264 | hd = HuffDecoder() 265 | # with open("huff11.bin", "wb") as fo: fo.write(zlib.compress(hd.packTables(), 9)[2:-4]) 266 | # with open("all.bin", "wb") as fo: fo.write(hd.packTables()) 267 | # for mode in (HuffDecoder.DUMP_KNOWN, HuffDecoder.DUMP_LEN, HuffDecoder.DUMP_ALL): hd.saveTables(mode) 268 | 269 | hd.prepareMap() 270 | for fn in argv[1:]: 271 | with open(fn, "rb") as fi: ab = fi.read() 272 | nChunks, = struct.unpack_from("> lo 29 | for name, lo, hi in bitDef: setattr(obj, name, bitf(val, lo, hi)) 30 | 31 | def ListTrueBools(obj, bitDef): 32 | return [v[0] for v in filter(lambda x: x[1] == x[2] and getattr(obj, x[0]), bitDef)] 33 | 34 | class StructReader(object): 35 | def __init__(self, ab, base=0, isLE=True): 36 | self.ab = ab 37 | self.base = base 38 | self.o = self.base 39 | self.cE = "<" if isLE else ">" 40 | 41 | def sizeLeft(self): 42 | return len(self.ab) - self.o 43 | 44 | def getData(self, o, cb): 45 | o += self.base 46 | if o < len(self.ab) and cb >= 0 and o + cb <= len(self.ab): 47 | return self.ab[o:o+cb] 48 | 49 | def read(self, obj, stDef, o=None): 50 | if o is None: o = self.o 51 | self.o += self.base 52 | for fldDef in stDef: # Walk field definitions 53 | name = fldDef[0] 54 | fmt = self.cE + fldDef[1] 55 | val, = struct.unpack_from(fmt, self.ab, o) 56 | if 3 == len(fldDef): 57 | expected = fldDef[2] 58 | if isinstance(expected, (list, tuple)): 59 | if not val in expected: 60 | print >>sys.stderr, "- %s.%s: not %s in %s" % (obj.__class__.__name__, name, val, expected) 61 | else: 62 | if val != expected: 63 | print >>sys.stderr, "- %s.%s:" % (obj.__class__.__name__, name), 64 | if isinstance(val, str): print >>sys.stderr, "Got %s, expected %s" % (val.encode("hex"), expected.encode("hex")) 65 | else: print >>sys.stderr, "Got [%s], expected [%s]" % (repr(val), repr(expected)) 66 | else: assert val == expected 67 | setattr(obj, name, val) 68 | o += struct.calcsize(fmt) 69 | self.o = o 70 | 71 | def done(self): 72 | assert len(self.ab) == self.o 73 | 74 | aPubKeyHash = [v.decode("hex") for v in ( 75 | "EA6FA86514FA887C9044218EDB4D70BB3BCC7C2D37587EA8F760BAFBE158C587", 76 | "A24E0682EDC8870DCA947C01603D19818AF714BEE9F39D2872D79B8C422F3890", 77 | "EA3E9C34C8FD6BDEA277F0A8C6AC5A37E8E39256469C89D279FA86A7317B21AE", 78 | "C8E7AA2C5F691F63A892BC044CD3935C5E77C6CB71C8E8627BE4987DFB730856", 79 | "3D512A6DB7C855E9F6328DB8B2C259A2C0F291BB6E3EC74A2FB811AD84C5D404", 80 | "04A6F35B14628879050AB0B3459326DDF946AE4E5EFD7BB1930883F57F68D084", 81 | "980F9572AC1B5BDC9A5F3E89F2503A624C9C5BDF97B72D9031DCCDAB11A9F7A8", 82 | "C468E6BA739856797BAF70910861BDE4C3BA95C956B1DCE24B738D614F1211BA", 83 | )] 84 | 85 | #*************************************************************************** 86 | #*************************************************************************** 87 | #*************************************************************************** 88 | 89 | args_lzma = { 90 | "Windows": ["lzma", "d", "-si", "-so"], 91 | "Linux": ["lzma", "-d"], 92 | "Darwin": ["lzma", "-d"], # "brew install xz" or "sudo port install xz" 93 | }[platform.system()] 94 | 95 | def LZMA_decompress(compdata): 96 | process = subprocess.Popen(args_lzma, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE) 97 | output, errout = process.communicate(compdata) 98 | retcode = process.poll() 99 | if retcode: raise Error(errout) 100 | return output 101 | 102 | def decompress(data, compType, length): 103 | if compType is None: 104 | return data 105 | elif "lzma" == compType: 106 | if not data.startswith("36004000".decode("hex")): 107 | print >>sys.stderr, "- Bad LZMA[0x%X] header %s" % (len(data), data[:17].encode("hex")) 108 | return None 109 | assert data.startswith("36004000".decode("hex")) 110 | assert '\0\0\0' == data[14:17] 111 | return LZMA_decompress(data[:14] + data[17:]) 112 | elif "huff" == compType: 113 | return g.HuffDecoder.decompress(data, length) if g.HuffDecoder else None 114 | else: 115 | raise Error("Invalid compType %s" % compType) 116 | 117 | 118 | RESTART_NOT_ALLOWED = 0 119 | RESTART_IMMEDIATLY = 1 120 | RESTART_ON_NEXT_BOOT = 2 121 | 122 | # MODULE_TYPES 123 | PROCESS_TYPE = 0 124 | SHARED_LIBRARY_TYPE = 1 125 | DATA_TYPE = 2 126 | IUNIT_TYPE = 3 # v12 127 | 128 | # PARTITION_TYPES 129 | FPT_AREATYPE_GENERIC = 1 130 | FPT_AREATYPE_CODE = 0 131 | FPT_AREATYPE_ROM = 1 132 | FPT_AREATYPE_DATA = 1 133 | 134 | # COMPRESSION_TYPE 135 | COMP_TYPE_NOT_COMPRESSED = 0 136 | COMP_TYPE_HUFFMAN = 1 137 | COMP_TYPE_LZMA = 2 138 | dCompType = { 139 | COMP_TYPE_NOT_COMPRESSED : None, 140 | COMP_TYPE_HUFFMAN: "huff", 141 | COMP_TYPE_LZMA: "lzma", 142 | } 143 | 144 | #*************************************************************************** 145 | #*************************************************************************** 146 | #*************************************************************************** 147 | 148 | class Extension(object): 149 | NAME = None 150 | TYPE = None 151 | LIST = None 152 | def Banner(self, noSize=False): 153 | nItems = "" if noSize or (self.LIST is None) else "[%d]" % len(getattr(self, self.LIST)) 154 | return ". Ext#%d %s%s:" % (self.TYPE, self.NAME, nItems) 155 | 156 | def PrintItems(self, flog): 157 | for i,e in enumerate(getattr(self, self.LIST)): print >>flog, "%6d: %s" % (i+1, e) 158 | 159 | def LoadItems(self, stR, cls, cnt=None): 160 | if cnt is None: 161 | lst = [] 162 | while stR.o < len(stR.ab): lst.append(cls(stR)) 163 | else: 164 | lst = [cls(stR) for i in xrange(cnt)] 165 | stR.done() 166 | setattr(self, self.LIST, lst) 167 | 168 | #*************************************************************************** 169 | #*************************************************************************** 170 | #*************************************************************************** 171 | 172 | #*************************************************************************** 173 | #*************************************************************************** 174 | #*************************************************************************** 175 | 176 | class System_Info_Ext(Extension): # 0 : used in Mainfist 177 | NAME = "SystemInfo" 178 | TYPE = 0 # for system info extension 179 | LIST = "indParts" 180 | SYSTEM_INFO_EXTENSION = ( 181 | ("uma_size", "L", ), # Minimum UMA size required for this SKU in bytes 182 | ("chipset_version", "L", ), # Chipset version 183 | ("img_default_hash", "32s", ), # SHA2 hash of a 'defaults' file added to the image. (/intel.cfg). The load manager is responsible for verifying the hash of this file and creating the default files at the first system boot. 184 | ("pageable_uma_size", "L", ), # Size of pageable space within UMA in bytes. Must be divisible by 4K. 185 | ("reserved_0", "Q", 0), # 186 | ("reserved_1", "L", 0), # 187 | # INDEPENDENT_PARTITION_ENTRY[] 188 | ) 189 | def __init__(self, ab): 190 | stR = StructReader(ab) 191 | stR.read(self, self.SYSTEM_INFO_EXTENSION) 192 | self.img_default_hash = self.img_default_hash[::-1] # Reverse? 193 | self.LoadItems(stR, Independent_Partition_Entry) 194 | 195 | def dump(self, flog=sys.stdout): 196 | print >>flog, "%s uma_size:0x%X, chipset_version:0x%X, pageable_uma_size:0x%X defaults_h:%s" % (self.Banner(), self.uma_size, self.chipset_version, self.pageable_uma_size, self.img_default_hash.encode("hex")) 197 | self.PrintItems(flog) 198 | 199 | #*************************************************************************** 200 | 201 | class Independent_Partition_Entry: 202 | INDEPENDENT_PARTITION_ENTRY = ( 203 | ("name", "4s", ), # 204 | ("version", "L", ), # 205 | ("user_id", "H", ), # 206 | ("reserved", "H", ), # 207 | ) 208 | def __init__(self, stR): 209 | stR.read(self, self.INDEPENDENT_PARTITION_ENTRY) 210 | self.name = self.name.rstrip('\0') 211 | 212 | def __str__(self): 213 | return "[%-4s] user_id:0x%04X ver:0x%08X %X" % (self.name, self.user_id, self.version, self.reserved) 214 | 215 | #*************************************************************************** 216 | #*************************************************************************** 217 | #*************************************************************************** 218 | 219 | class Init_Script_Ext(Extension): # 1 : used in Mainfist 220 | NAME = "InitScript" 221 | TYPE = 1 # for initialization script extension 222 | LIST = "scripts" 223 | # length: In bytes; equals (16 + 52*n) for this version where n is the number of modules in the initialization script 224 | INIT_SCRIPT = ( 225 | ("reserved", "L", 0), # Reserved for future use. 226 | ("number_of_modules", "L", ), # Number of modules in this initialization script. Cannot be more than MAX_MODULES (this is a configuration parameter defining the maximum number of modules supported by the system set at system build time). 227 | # INIT_SCRIPT_ENTRY[] # initialization script extension entries 228 | ) 229 | def __init__(self, ab): 230 | stR = StructReader(ab) 231 | stR.read(self, self.INIT_SCRIPT) 232 | 233 | clsSize, remainder = divmod(stR.sizeLeft(), self.number_of_modules) 234 | if remainder: raise Error("Init_Script_Ext data size == %d is not miltiple of nItems == %d" % stR.sizeLeft(), self.number_of_modules) 235 | cls = {24: Init_Script_Entry, 28: Init_Script_Entry_v12}[clsSize] 236 | self.LoadItems(stR, cls, self.number_of_modules) 237 | 238 | def dump(self, flog=sys.stdout): 239 | print >>flog, self.Banner() 240 | self.PrintItems(flog) 241 | 242 | #*************************************************************************** 243 | 244 | class Init_Script_Entry: 245 | INIT_SCRIPT_ENTRY = ( 246 | ("partition_name", "4s", ), # Manifest Partition Name. This field identifies the manifest in which this module's hash will be found irregardles of manifest's physical location (i.e. FTP manifest may be physically located in NFTP flash partition during FW update). 247 | ("name", "12s", ), # Module Name 248 | ("bf_init_flags", "L", ), # Flags used govern initialization flow. 249 | ("bf_boot_type", "L", ), # Boot path flag bits to indicate which boot path(s) this module is applicable to. Bit 0 - Normal Bit 1 - HAP Bit 2 - HMRFPO Bit 3 - Temp Disable Bit 4 - Recovery Bit 5 - Safe Mode Bit 6 - FW Update Bits 7:31 - Reserved 250 | ) 251 | def __init__(self, stR): 252 | self.unk = None 253 | stR.read(self, self.INIT_SCRIPT_ENTRY) 254 | self.partition_name = self.partition_name.rstrip('\0') 255 | self.name = self.name.rstrip('\0') 256 | self.init_flags = Init_Script_Flags(self.bf_init_flags) 257 | self.boot_type = Init_Script_Boot_Type(self.bf_boot_type) 258 | 259 | def __str__(self): 260 | return "%4s:%-12s Init: %08X (%s) Boot: %08X (%s)" % (self.partition_name, self.name, self.bf_init_flags, self.init_flags, self.bf_boot_type, self.boot_type) 261 | 262 | #*************************************************************************** 263 | 264 | class Init_Script_Entry_v12: 265 | INIT_SCRIPT_ENTRY = ( 266 | ("partition_name", "4s", ), # Manifest Partition Name. This field identifies the manifest in which this module's hash will be found irregardles of manifest's physical location (i.e. FTP manifest may be physically located in NFTP flash partition during FW update). 267 | ("name", "12s", ), # Module Name 268 | ("bf_init_flags", "L", ), # Flags used govern initialization flow. 269 | ("bf_boot_type", "L", ), # Boot path flag bits to indicate which boot path(s) this module is applicable to. Bit 0 - Normal Bit 1 - HAP Bit 2 - HMRFPO Bit 3 - Temp Disable Bit 4 - Recovery Bit 5 - Safe Mode Bit 6 - FW Update Bits 7:31 - Reserved 270 | ("unk", "L", ), # 271 | ) 272 | def __init__(self, stR): 273 | stR.read(self, self.INIT_SCRIPT_ENTRY) 274 | self.partition_name = self.partition_name.rstrip('\0') 275 | self.name = self.name.rstrip('\0') 276 | self.init_flags = Init_Script_Flags(self.bf_init_flags) 277 | self.boot_type = Init_Script_Boot_Type(self.bf_boot_type) 278 | 279 | def __str__(self): 280 | return "%4s:%-12s Init: %08X (%s) Boot: %08X (%s) Unk: %X" % (self.partition_name, self.name, self.bf_init_flags, self.init_flags, self.bf_boot_type, self.boot_type, self.unk) 281 | 282 | #*************************************************************************** 283 | 284 | class Init_Script_Flags: # !!! Not sure... 285 | dRestart = { 286 | RESTART_NOT_ALLOWED : "Not Allowed", # 0 287 | RESTART_IMMEDIATLY : "Immediatly", # 1 288 | RESTART_ON_NEXT_BOOT: "On Next Boot", # 2 289 | } 290 | INIT_SCRIPT_FLAGS = ( # BitFields 291 | ("Ibl", 0, 0), 292 | ("IsRemovable", 1, 1), 293 | ("InitImmediately", 2, 2), 294 | ("RestartPolicy", 3, 15), 295 | ("Cm0_u", 16, 16), 296 | ("Cm0_nu", 17, 17), 297 | ("Cm3", 18, 18), 298 | # ("reserved", 19, 31), 299 | ) 300 | def __init__(self, dw): 301 | BitFields(self, dw, self.INIT_SCRIPT_FLAGS) 302 | 303 | def __str__(self): 304 | r = ListTrueBools(self, self.INIT_SCRIPT_FLAGS) 305 | if self.RestartPolicy: r.append("Restart %s" % self.dRestart[self.RestartPolicy]) 306 | return ", ".join(r) 307 | 308 | #*************************************************************************** 309 | 310 | class Init_Script_Boot_Type: 311 | INIT_SCRIPT_BOOT_TYPE = ( # BitFields 312 | ("Normal", 0, 0), 313 | ("HAP", 1, 1), 314 | ("HMRFPO", 2, 2), 315 | ("TmpDisable", 3, 3), 316 | ("Recovery", 4, 4), 317 | ("SafeMode", 5, 5), 318 | ("FWUpdate", 6, 6), 319 | # ("reserved", 7, 31), 320 | ) 321 | def __init__(self, dw): 322 | BitFields(self, dw, self.INIT_SCRIPT_BOOT_TYPE) 323 | 324 | def __str__(self): 325 | return ", ".join(ListTrueBools(self, self.INIT_SCRIPT_BOOT_TYPE)) 326 | 327 | #*************************************************************************** 328 | #*************************************************************************** 329 | #*************************************************************************** 330 | 331 | class Feature_Permissions_Ext(Extension): # 2 : used in Mainfist 332 | NAME = "FeaturePermissions" 333 | TYPE = 2 # for feature permission extension 334 | LIST = "permissions" 335 | # length: In bytes; equals (12 + 2*n) for this version where n is the number of features in this extension 336 | FEATURE_PERMISSIONS_EXTENSION = ( 337 | ("num_of_features", "L", ), # Number of features feature numbering always starts from 0. 338 | # FEATURE_PERMISSION_ENTRY[] # feature permission extension entries 339 | ) 340 | def __init__(self, ab): 341 | stR = StructReader(ab) 342 | stR.read(self, self.FEATURE_PERMISSIONS_EXTENSION) 343 | self.LoadItems(stR, Feature_Permission_Entry, self.num_of_features) 344 | 345 | def dump(self, flog=sys.stdout): 346 | print >>flog, "%s [%s]" % (self.Banner(), ", ".join("0x%04X" % e.user_id for e in self.permissions)) 347 | 348 | #*************************************************************************** 349 | 350 | class Feature_Permission_Entry: 351 | FEATURE_PERMISSION_ENTRY = ( 352 | ("user_id", "H", ), # User ID that may change feature state for feature 0. 353 | ("reserved", "H", 0), # 354 | ) 355 | def __init__(self, stR): 356 | stR.read(self, self.FEATURE_PERMISSION_ENTRY) 357 | 358 | #*************************************************************************** 359 | #*************************************************************************** 360 | #*************************************************************************** 361 | 362 | class Partition_Info_Ext(Extension): # 3 : used in Mainfist 363 | NAME = "PartitionInfo" 364 | TYPE = 3 # for partition info extension 365 | LIST = "modules" 366 | # length: In bytes; equals (92 + 52*n) for this version where n is the number of modules in the manifest 367 | MANIFEST_PARTITION_INFO_EXT = ( 368 | ("partition_name", "4s", ), # Name of the partition 369 | ("partition_length", "L", ), # Length of complete partition before any process have been removed by the OEM or the firmware update process 370 | ("partition_hash", "32s", ), # SHA256 hash of the original complete partition covering everything in the partition except for the manifest (directory binaries and LUT) 371 | ("version_control_number", "L", ), # The version control number (VCN) is incremented whenever a change is made to the FW that makes it incompatible from an update perspective with previously released versions of the FW. 372 | ("partition_version", "L", ), # minor number 373 | ("data_format_version", "L", ), # 374 | ("instance_id", "L", ), # 375 | ("flags", "L", ), # Support multiple instances Y/N. Used for independently updated partitions that may have multiple instances (such as WLAN uCode or Localization) 376 | ("reserved", "16s", ('\0'*16, '\xFF'*16,)), # set to 0xff 377 | ("unknown0", "l", (0, 1, 3, -1)), # Was 0xffffffff 378 | # MANIFEST_MODULE_INFO_EXT[] # Module info extension entries 379 | ) 380 | def __init__(self, ab): 381 | stR = StructReader(ab) 382 | stR.read(self, self.MANIFEST_PARTITION_INFO_EXT) 383 | self.partition_name = self.partition_name.rstrip('\0') 384 | self.partition_hash = self.partition_hash[::-1] # Reverse? 385 | self.LoadItems(stR, Module_Info) 386 | 387 | def dump(self, flog=sys.stdout): 388 | print >>flog, self.Banner(True) 389 | print >>flog, " Name: [%s]" % self.partition_name 390 | print >>flog, " Length: %08X" % self.partition_length 391 | print >>flog, " Hash: %s" % self.partition_hash.encode("hex") 392 | print >>flog, " VCN: %d" % self.version_control_number 393 | print >>flog, " Ver: %X, %X" % (self.partition_version, self.data_format_version) 394 | print >>flog, " Instance ID: %d" % self.instance_id 395 | print >>flog, " Flags: %d" % self.flags 396 | print >>flog, " Unknown: %d" % self.unknown0 397 | print >>flog, " Modules[%d]:" % len(self.modules) 398 | self.PrintItems(flog) 399 | 400 | #*************************************************************************** 401 | 402 | class Module_Info: 403 | dModType = { 404 | PROCESS_TYPE: "Proc", # 0 405 | SHARED_LIBRARY_TYPE: "Lib ", # 1 406 | DATA_TYPE: "Data", # 2 407 | IUNIT_TYPE: "iUnt", # 3 v12 408 | } 409 | MANIFEST_MODULE_INFO_EXT = ( 410 | ("name", "12s", ), # Character array. If name length is shorter than field size the name is padded with 0 bytes 411 | ("type", "B", (0,1,2,3)), # 0 - Process; 1 - Shared Library; 2 - Data; 3 - iUnit 412 | ("reserved0", "B", ), # 413 | ("reserved1", "H", (0, 0xFFFF)), # set to 0xffff 414 | ("metadata_size", "L", ), # 415 | ("metadata_hash", "32s" ), # For a process/shared library this is the SHA256 of the module metadata file; for a data module this is the SHA256 hash of the module binary itself 416 | ) 417 | def __init__(self, stR): 418 | stR.read(self, self.MANIFEST_MODULE_INFO_EXT) 419 | self.name = self.name.rstrip('\0') 420 | self.metadata_hash = self.metadata_hash[::-1] # Reverse 421 | 422 | def __str__(self): 423 | return "%-4s, Meta cb:%4X h:%s %s" % (self.dModType[self.type], self.metadata_size, self.metadata_hash.encode("hex"), self.name) 424 | 425 | #*************************************************************************** 426 | #*************************************************************************** 427 | #*************************************************************************** 428 | 429 | class Shared_Lib_Ext(Extension): # 4 : used in Metadata 430 | NAME = "SharedLib" 431 | TYPE = 4 # for shared library extension 432 | # length: In bytes equals 52 for this version 433 | SHARED_LIB_EXTENSION = ( 434 | ("context_size", "L", ), # Size in bytes of the shared library context 435 | ("total_alloc_virtual_space", "L", ), # Including padding pages for library growth. Currently set to a temporary value. This needs to be updated once the SHARED_CONTEXT_SIZE symbol is defined in the build process. 436 | ("code_base_address", "L", ), # Base address for the library private code in VAS. Must be 4KB aligned. 437 | ("tls_size", "L", ), # Size of Thread-Local-Storage used by the shared library. 438 | ("reserved", "L", ), # reserved bytes set to 0xffffffff 439 | ) 440 | def __init__(self, ab): 441 | stR = StructReader(ab) 442 | stR.read(self, self.SHARED_LIB_EXTENSION) 443 | stR.done() 444 | 445 | def dump(self, flog=sys.stdout): 446 | print >>flog, "%s context_size:0x%X, total_alloc_virtual_space:0x%X, code_base_address:0x%X, tls_size:0x%x" % (self.Banner(), self.context_size, self.total_alloc_virtual_space, self.code_base_address, self.tls_size) 447 | 448 | #*************************************************************************** 449 | #*************************************************************************** 450 | #*************************************************************************** 451 | 452 | class Man_Process_Ext(Extension): # 5 : used in Metadata 453 | NAME = "Process" 454 | TYPE = 5 # for process attribute extension 455 | # length: In bytes equals 160 + 2*n for this version where n is the number of group IDs entries in the extension 456 | MAN_PROCESS_EXTENSION = ( 457 | ("bf_flags", "L", ), # Flags 458 | ("main_thread_id", "L", ), # TID for main thread. Optional for IBL processes only. Must be 0 for other processes. 459 | ("priv_code_base_address", "L", ), # Base address for code. Address is in LAS for Bringup/Kernel VAS for other processes. Must be 4KB aligned 460 | ("uncompressed_priv_code_size","L", ), # Size of uncompressed process code. Does not include code for shared library. 461 | ("cm0_heap_size", "L", ), # Size of Thread-Local-Storage for the process 462 | ("bss_size", "L", ), # 463 | ("default_heap_size", "L", ), # 464 | ("main_thread_entry", "L", ), # VAS of entry point function for the process main thread 465 | ("allowed_sys_calls", "12s", ), # Bitmask of allowed system calls by the process 466 | ("user_id", "H", ), # Runtime User ID for process 467 | ("reserved_0", "L", ), # Temporary placeholder for thread base 468 | ("reserved_1", "H", 0), # Must be 0 469 | ("reserved_2", "Q", ), # 470 | # group_ids['H'] # Group ID for process 471 | ) 472 | def __init__(self, ab): 473 | stR = StructReader(ab) 474 | stR.read(self, self.MAN_PROCESS_EXTENSION) 475 | abGIDs = stR.ab[stR.o:] 476 | self.group_ids = list(struct.unpack("<%dH" % (len(abGIDs) / 2), abGIDs)) 477 | self.flags = Man_Process_Flags(self.bf_flags) 478 | 479 | def dump(self, flog=sys.stdout): 480 | print >>flog, self.Banner() 481 | print >>flog, " flags: %s" % self.flags 482 | print >>flog, " main_thread_id: 0x%X" % self.main_thread_id 483 | print >>flog, " priv_code_base_address: 0x%08X" % self.priv_code_base_address 484 | print >>flog, " uncompressed_priv_code_size: 0x%X" % self.uncompressed_priv_code_size 485 | print >>flog, " cm0_heap_size: 0x%X" % self.cm0_heap_size 486 | print >>flog, " bss_size: 0x%X" % self.bss_size 487 | print >>flog, " default_heap_size: 0x%X" % self.default_heap_size 488 | print >>flog, " main_thread_entry: 0x%08X" % self.main_thread_entry 489 | print >>flog, " allowed_sys_calls: %s" % self.allowed_sys_calls.encode("hex") 490 | print >>flog, " user_id: 0x%04X" % self.user_id 491 | print >>flog, " group_ids[%d]: [%s]" % (len(self.group_ids), ", ".join("0x%04X" % gid for gid in self.group_ids)) 492 | 493 | #*************************************************************************** 494 | 495 | class Man_Process_Flags: 496 | MAN_PROCESS_FLAGS = ( # BitFields 497 | ("fault_tolerant", 0, 0), # Kernel exception policy: 0 - Reset System, 1 - Terminate Process 498 | ("permanent_process", 1, 1), # permanent process Y/N. A permanent process' code/rodata sections are not removed from RAM when it terminates normally in order to optimize its reload flow. 499 | ("single_instance", 2, 2), # Single Instance Y/N. When the process is spawned if it is already running in the system the spawn will fail. 500 | ("trusted_snd_rev_sender", 3, 3), # Trusted SendReceive Sender Y/N. If set this process is allowed to send IPC_SendReceive messages to any process (not only public). 501 | ("trusted_notify_sender", 4, 4), # Trusted Notify Sender Y/N. If set this process is allowed to send IPC_Notify notifications to any process (not only public). 502 | ("public_snd_rev_receiver", 5, 5), # Public SendReceive Receiver Y/N. If set any other process is allowed to send IPC_SendReceive messages to it (not only trusted). 503 | ("public_notify_receiver", 6, 6), # Public Notify Receiver Y/N. If set any other process is allowed to IPC_Notify notifications messages to it (not only trusted). 504 | #("reserved", 7, 31), # reserved. Set to 0 505 | ) 506 | def __init__(self, dw): 507 | BitFields(self, dw, self.MAN_PROCESS_FLAGS) 508 | 509 | def __str__(self): 510 | return ", ".join(ListTrueBools(self, self.MAN_PROCESS_FLAGS)) 511 | 512 | #*************************************************************************** 513 | #*************************************************************************** 514 | #*************************************************************************** 515 | 516 | class Threads_Ext(Extension): # 6 : used in Metadata 517 | NAME = "Threads" 518 | TYPE = 6 # for threads extension 519 | LIST = "threads" 520 | def __init__(self, ab): 521 | self.LoadItems(StructReader(ab), Thread_Entry) 522 | 523 | def dump(self, flog=sys.stdout): 524 | print >>flog, self.Banner() 525 | self.PrintItems(flog) 526 | 527 | #*************************************************************************** 528 | 529 | class Thread_Entry: 530 | THREAD_ENTRY = ( 531 | ("stack_size", "L", ), # Size of main thread stack in bytes (not including guard page including space reserved for TLS). Must be divisible by 4K with the following exception: if the default heap size is smaller than 4K the last thread's stack size may have any size. 532 | ("flags", "L", ), # Bit0 - set to 0 for live thread 1 for CM0-U-only thread; Bits 1-31 - reserved must be 0 533 | ("scheduling_policy", "L", ), # Bits 0-7: Scheduling Policy, 0 -> fixed priority; Bits 8-31: Scheduling attributes. For a fixed priority policy this is the scheduling priority of the thread. 534 | ("reserved", "L", ), # 535 | ) 536 | def __init__(self, stR): 537 | stR.read(self, self.THREAD_ENTRY) 538 | 539 | def __str__(self): 540 | return "stack_size:0x%08X, flags:%X, scheduling_policy:%08X" % (self.stack_size, self.flags, self.scheduling_policy) 541 | 542 | #*************************************************************************** 543 | #*************************************************************************** 544 | #*************************************************************************** 545 | 546 | class Device_Ids_Ext(Extension): # 7 : used in Metadata 547 | NAME = "DeviceIds" 548 | TYPE = 7 # for device ids extension 549 | LIST = "device_id_group" 550 | def __init__(self, ab): 551 | self.LoadItems(StructReader(ab), Device_Entry) 552 | 553 | def dump(self, flog=sys.stdout): 554 | print >>flog, "%s [%s]" % (self.Banner(), ", ".join("%08X" % v.device_id for v in self.device_id_group)) 555 | 556 | #*************************************************************************** 557 | 558 | class Device_Entry: 559 | DEVICE_ENTRY = ( 560 | ("device_id", "L", ), # 561 | ("reserved", "L", ), # 562 | ) 563 | def __init__(self, stR): 564 | stR.read(self, self.DEVICE_ENTRY) 565 | 566 | #*************************************************************************** 567 | #*************************************************************************** 568 | #*************************************************************************** 569 | 570 | class Mmio_Ranges_Ext(Extension): # 8 : used in Metadata 571 | NAME = "MmioRanges" 572 | TYPE = 8 # for mmio ranges extension 573 | LIST = "mmio_range_defs" 574 | def __init__(self, ab): 575 | self.LoadItems(StructReader(ab), Mmio_Range_Def) 576 | 577 | def dump(self, flog=sys.stdout): 578 | print >>flog, self.Banner() 579 | # self.PrintItems(flog) 580 | for i,e in enumerate(getattr(self, self.LIST)): print >>flog, " %s" % (e) 581 | 582 | #*************************************************************************** 583 | 584 | class Mmio_Range_Def: 585 | MMIO_RANGE_DEF = ( 586 | ("base", "L", ), # Base address of the MMIO range 587 | ("size", "L", ), # Limit in bytes of the MMIO range 588 | ("flags", "L", ), # Read access Y/N 589 | ) 590 | def __init__(self, stR): 591 | stR.read(self, self.MMIO_RANGE_DEF) 592 | 593 | def __str__(self): 594 | return "base:%08X, size:%08X, flags:%08X" % (self.base, self.size, self.flags) 595 | 596 | #*************************************************************************** 597 | #*************************************************************************** 598 | #*************************************************************************** 599 | 600 | class Special_File_Producer_Ext(Extension): # 9 : used in Metadata 601 | NAME = "SpecialFileProducer" 602 | TYPE = 9 # for special file producer extension 603 | LIST = "files" 604 | SPECIAL_FILE_PRODUCER_EXTENSION = ( 605 | ("major_number", "H", ), # 606 | ("flags", "H", ), # 607 | # SPECIAL_FILE_DEF[] 608 | ) 609 | def __init__(self, ab): 610 | stR = StructReader(ab) 611 | stR.read(self, self.SPECIAL_FILE_PRODUCER_EXTENSION) 612 | self.LoadItems(stR, Special_File_Def) 613 | 614 | def dump(self, flog=sys.stdout): 615 | print >>flog, "%s major_number=0x%04X" % (self.Banner(), self.major_number) 616 | self.PrintItems(flog) 617 | 618 | #*************************************************************************** 619 | 620 | class Special_File_Def: 621 | SPECIAL_FILE_DEF = ( 622 | ("name", "12s", ), # 623 | ("access_mode", "H", ), # 624 | ("user_id", "H", ), # 625 | ("group_id", "H", ), # 626 | ("minor_number", "B", ), # 627 | ("reserved0", "B", ), # 628 | ("reserved1", "L", ), # 629 | ) 630 | def __init__(self, stR): 631 | stR.read(self, self.SPECIAL_FILE_DEF) 632 | self.name = self.name.rstrip('\0') 633 | 634 | def __str__(self): 635 | return "%-12s access_mode:%04o, user_id:0x%04X group_id:0x%04X minor_number:%02X" % (self.name, self.access_mode, self.user_id, self.group_id, self.minor_number) 636 | 637 | #*************************************************************************** 638 | #*************************************************************************** 639 | #*************************************************************************** 640 | 641 | class Mod_Attr_Ext(Extension): # 10 : used in Metadata 642 | NAME = "ModAttr" 643 | TYPE = 10 # for this module attribute extension 644 | # length: In bytes; equals 56 for this version 645 | dCompType = { 646 | COMP_TYPE_NOT_COMPRESSED:" ", 647 | COMP_TYPE_HUFFMAN:"Huff", 648 | COMP_TYPE_LZMA:"LZMA", 649 | } 650 | MOD_ATTR_EXTENSION = ( 651 | ("compression_type", "B", (0,1,2,)), # 0 - Uncompressed; 1 - Huffman Compressed; 2 - LZMA Compressed 652 | ("encrypted", "B", (0,1)), # Used as "encrypted" flag 653 | ("reserved1", "B", 0), # Must be 0 654 | ("reserved2", "B", 0), # Must be 0 655 | ("uncompressed_size", "L", ), # Uncompressed image size must be divisible by 4K 656 | ("compressed_size", "L", ), # Compressed image size. This is applicable for LZMA compressed modules only. For other modules should be the same as uncompressed_size field. 657 | ("module_number", "H", ), # Module number unique in the scope of the vendor. 658 | ("vendor_id", "H", 0x8086), # Vendor ID (PCI style). For Intel modules must be 0x8086. 659 | ("image_hash", "32s", ), # SHA2 Hash of uncompressed image 660 | ) 661 | def __init__(self, ab): 662 | stR = StructReader(ab) 663 | stR.read(self, self.MOD_ATTR_EXTENSION) 664 | self.image_hash = self.image_hash[::-1] # Reverse 665 | stR.done() 666 | 667 | def dump(self, flog=sys.stdout): 668 | print >>flog, "%s %s enc=%d %08X->%08X id:%04X.%04X h:%s" % (self.Banner(), self.dCompType[self.compression_type], self.encrypted, self.compressed_size, self.uncompressed_size, self.module_number, self.vendor_id, self.image_hash.encode("hex")) 669 | 670 | #*************************************************************************** 671 | #*************************************************************************** 672 | #*************************************************************************** 673 | 674 | class Locked_Ranges_Ext(Extension): # 11 : used in Metadata 675 | NAME = "LockedRanges" 676 | TYPE = 11 # for unknown 11 extension 677 | LIST = "ranges" 678 | def __init__(self, ab): 679 | self.LoadItems(StructReader(ab), Locked_Range) 680 | 681 | def dump(self, flog=sys.stdout): 682 | print >>flog, self.Banner() 683 | self.PrintItems(flog) 684 | 685 | #*************************************************************************** 686 | 687 | class Locked_Range: 688 | LOCKED_RANGE = ( 689 | ("base", "L", ), # Base address in VAS of range to be locked. Must be divisible in 4KB. 690 | ("size", "L", ), # Size of range to be locked. Must be divisible in 4KB. 691 | ) 692 | def __init__(self, stR): 693 | stR.read(self, self.LOCKED_RANGE) 694 | 695 | def __str__(self): 696 | return "base:0x%08X, size:%X" % (self.base, self.size) 697 | 698 | #*************************************************************************** 699 | #*************************************************************************** 700 | #*************************************************************************** 701 | 702 | class Client_System_Info_Ext(Extension): # 12 : used in Manifest 703 | NAME = "ClientSystemInfo" 704 | TYPE = 12 # for client system info extension 705 | CLIENT_SYSTEM_INFO_EXTENSION = ( 706 | ("fw_sku_caps", "L", ), # 707 | ("fw_sku_caps_reserved", "28s", '\xFF'*28), # 708 | ("bf_fw_sku_attributes", "Q", ), # Bits 0:3 - CSE region size in multiples of 0.5 MB Bits 4:6 - firmware sku; 0 for 5.0MB 1 for 1.5MB 2 for slim sku. Bit 7 - Patsberg support Y/N Bit 8 - M3 support Y/N Bit 9 - M0 support Y/N Bits 10:11 - reserved Bits 12:15 - Si class (all H M L) Bits 16:63 - reserved 709 | ) 710 | 711 | def __init__(self, ab): 712 | stR = StructReader(ab) 713 | stR.read(self, self.CLIENT_SYSTEM_INFO_EXTENSION) 714 | self.attr = Client_System_Sku_Attributes(self.bf_fw_sku_attributes) 715 | stR.done() 716 | 717 | def dump(self, flog=sys.stdout): 718 | print >>flog, self.Banner() 719 | print >>flog, " fw_sku_caps: %x" % self.fw_sku_caps 720 | print >>flog, " fw_sku_attributes: %s" % self.attr 721 | 722 | #*************************************************************************** 723 | 724 | class Client_System_Sku_Attributes: 725 | dFirmwareSKU = { 726 | 0: "5.0MB", 727 | 1: "1.5MB", 728 | 2: "Slim", 729 | 3: "SPS", 730 | } 731 | CLIENT_SYSTEM_SKU_ATTRIBUTES = ( # BitFields 732 | ("CSE_region_size", 0, 3), # Bits 0:3 - CSE region size in multiples of 0.5 MB 733 | ("firmware_sku", 4, 6), # Bits 4:6 - firmware sku; 0 for 5.0MB 1 for 1.5MB 2 for slim sku. 734 | ("Patsberg", 7, 7), # Bit 7 - Patsberg support Y/N 735 | ("M3", 8, 8), # Bit 8 - M3 support Y/N 736 | ("M0", 9, 9), # Bit 9 - M0 support Y/N 737 | # ("reserved0", 10, 11), # Bits 10:11 - reserved 738 | ("Si_class", 12, 15), # Bits 12:15 - Si class (all H M L) 739 | # ("reserved1", 16, 63), # Bits 16:63 - reserved 740 | ) 741 | def __init__(self, qw): 742 | BitFields(self, qw, self.CLIENT_SYSTEM_SKU_ATTRIBUTES) 743 | 744 | def __str__(self): 745 | return "CSE region size: %.2f, firmware sku: %s, Si class: %X, %s" % (0.5*self.CSE_region_size, self.dFirmwareSKU[self.firmware_sku], self.Si_class, ", ".join(ListTrueBools(self, self.CLIENT_SYSTEM_SKU_ATTRIBUTES))) 746 | 747 | #*************************************************************************** 748 | #*************************************************************************** 749 | #*************************************************************************** 750 | 751 | class User_Info_Ext(Extension): # 13 : used in Manifest 752 | NAME = "UserInfo" 753 | TYPE = 13 # for user info extension 754 | LIST = "users" 755 | def __init__(self, ab): 756 | try: 757 | self.LoadItems(StructReader(ab), User_Info_Entry) 758 | except: 759 | self.LoadItems(StructReader(ab), User_Info_Entry_new) 760 | 761 | def dump(self, flog=sys.stdout): 762 | print >>flog, self.Banner() 763 | self.PrintItems(flog) 764 | 765 | #*************************************************************************** 766 | 767 | class User_Info_Entry: 768 | USER_INFO_ENTRY = ( 769 | ("user_id", "H", ), # User ID. 770 | ("reserved", "H", (0,1)), # Must be 0. 771 | ("non_volatile_storage_quota","L", ), # Maximum size of non-volatile storage area. 772 | ("ram_storage_quota", "L", ), # Maximum size of RAM storage area. 773 | ("wop_quota", "L", ), # Quota to use in wear-out prevention algorithm; in most cases this should match the non-volatile storage quota; however it is possible to virtually add quota to a user to allow it to perform more write operations on expense of another user. At build time the build system will check that the sum of all users WOP quota is not more than the sum of all users non-volatile storage quota. 774 | ("working_dir", "36s", ), # Starting directory for the user. Used when accessing files with a relative path. Character array; if name length is shorter than field size the name is padded with 0 bytes. 775 | ) 776 | def __init__(self, stR): 777 | stR.read(self, self.USER_INFO_ENTRY) 778 | self.working_dir = self.working_dir.rstrip('\0') 779 | assert self.working_dir.find('\0') < 0 780 | 781 | def __str__(self): 782 | return "user id:0x%04X, NV quota:%8X, RAM quota:%8X, WOP quota:%8X, working dir: [%s]" % (self.user_id, self.non_volatile_storage_quota, self.ram_storage_quota, self.wop_quota, self.working_dir) 783 | 784 | #*************************************************************************** 785 | 786 | class User_Info_Entry_new: 787 | USER_INFO_ENTRY = ( 788 | ("user_id", "H", ), # User ID. 789 | ("reserved", "H", 0), # Must be 0. 790 | ("non_volatile_storage_quota","L", ), # Maximum size of non-volatile storage area. 791 | ("ram_storage_quota", "L", ), # Maximum size of RAM storage area. 792 | ("wop_quota", "L", ), # Quota to use in wear-out prevention algorithm; in most cases this should match the non-volatile storage quota; however it is possible to virtually add quota to a user to allow it to perform more write operations on expense of another user. At build time the build system will check that the sum of all users WOP quota is not more than the sum of all users non-volatile storage quota. 793 | ) 794 | def __init__(self, stR): 795 | stR.read(self, self.USER_INFO_ENTRY) 796 | 797 | def __str__(self): 798 | return "user id:0x%04X, NV quota:%8X, RAM quota:%8X, WOP quota:%8X" % (self.user_id, self.non_volatile_storage_quota, self.ram_storage_quota, self.wop_quota) 799 | 800 | #*************************************************************************** 801 | #*************************************************************************** 802 | #*************************************************************************** 803 | 804 | class Package_Info_Ext(Extension): # 15 : used in TXE Mainfist 805 | NAME = "PackageInfo" 806 | TYPE = 15 # for partition info extension 807 | LIST = "modules" 808 | SIGNED_PACKAGE_INFO_EXT = ( 809 | ("package_name", "4s", ), # Name of the partition 810 | ("version_control_number", "L", ), # The version control number (VCN) is incremented whenever a change is made to the FW that makes it incompatible from an update perspective with previously released versions of the FW. 811 | ("usage_bitmap", "16s", ), # Bitmap of usages depicted by this manifest, indicating which key is used to sign the manifest 812 | ("svn", "L", ), # Secure Version Number 813 | ("unknown", "L", ), # 814 | ("reserved", "12s", '\x00'*12), # Must be 0 815 | # SIGNED_PACKAGE_INFO_EXT_ENTRY[] # Module info extension entries 816 | ) 817 | def __init__(self, ab): 818 | stR = StructReader(ab) 819 | stR.read(self, self.SIGNED_PACKAGE_INFO_EXT) 820 | self.package_name = self.package_name.rstrip('\0') 821 | self.LoadItems(stR, Package_Info_Ext_Entry) 822 | 823 | def dump(self, flog=sys.stdout): 824 | print >>flog, self.Banner(True) 825 | print >>flog, " Name: [%s]" % self.package_name 826 | print >>flog, " VCN: %d" % self.version_control_number 827 | print >>flog, " Usage Bitmap: %s" % self.usage_bitmap.encode("hex") 828 | print >>flog, " svn: %d" % self.svn 829 | print >>flog, " unknown: 0x%X" % self.unknown 830 | print >>flog, " Modules[%d]:" % len(self.modules) 831 | self.PrintItems(flog) 832 | 833 | #*************************************************************************** 834 | 835 | class Package_Info_Ext_Entry: 836 | dModType = { 837 | PROCESS_TYPE: "Proc", # 0 838 | SHARED_LIBRARY_TYPE: "Lib ", # 1 839 | DATA_TYPE: "Data", # 2 840 | IUNIT_TYPE: "iUnt", # 3 v12 841 | } 842 | dHashAlgorithm = { 843 | 1: "SHA1", 844 | 2: "SHA256", 845 | } 846 | SIGNED_PACKAGE_INFO_EXT_ENTRY = ( 847 | ("name", "12s", ), # Character array. If name length is shorter than field size the name is padded with 0 bytes 848 | ("type", "B", (0,1,2,3)), # 0 - Process; 1 - Shared Library; 2 - Data; 3 - iUnit 849 | ("hash_algorithm", "B", 2), # 0 - Reserved; 1 - SHA1; 2 - SHA256 850 | ("hash_size", "H", 32), # Size of Hash in bytes = N; BXT to support only SHA256. So N=32. 851 | ("metadata_size", "L", ), # Size of metadata file 852 | ("metadata_hash", "32s" ), # The SHA2 of the module metadata file 853 | ) 854 | def __init__(self, stR): 855 | stR.read(self, self.SIGNED_PACKAGE_INFO_EXT_ENTRY) 856 | self.name = self.name.rstrip('\0') 857 | self.metadata_hash = self.metadata_hash[::-1] # Reverse 858 | 859 | def __str__(self): 860 | return "%-4s, Meta cb:%4X h=%s[%d]:%s %s" % (self.dModType[self.type], self.metadata_size, self.dHashAlgorithm[self.hash_algorithm], self.hash_size, self.metadata_hash.encode("hex"), self.name) 861 | 862 | #*************************************************************************** 863 | #*************************************************************************** 864 | #*************************************************************************** 865 | 866 | class Unk_16_Ext(Extension): # 16 : used in Manifest (for iUnit) 867 | NAME = "Unk_iUnit_16" 868 | TYPE = 16 # for iUnit extension 869 | UNK_IUNIT_16_EXT = ( 870 | ("v0_1", "L", 1), # 871 | ("unk16", "16s", '\0'*16), # 872 | ("v2_3", "L", 3), # 873 | ("v3", "L", ), # 874 | ("v4_1", "L", 1), # 875 | ("h", "32s", ), # 876 | ("reserved", "24s", '\0'*24), # 877 | ) 878 | def __init__(self, ab): 879 | stR = StructReader(ab) 880 | stR.read(self, self.UNK_IUNIT_16_EXT) 881 | # self.h = self.h[::-1] # Reverse? 882 | stR.done() 883 | 884 | def dump(self, flog=sys.stdout): 885 | print >>flog, self.Banner() 886 | print >>flog, " %X %X %X %X h=%s" % (self.v0_1, self.v2_3, self.v3, self.v4_1, self.h.encode("hex")) 887 | 888 | #*************************************************************************** 889 | #*************************************************************************** 890 | #*************************************************************************** 891 | 892 | class Unk_18_Ext(Extension): # 18 : used in Manifest 893 | NAME = "Unk_18" 894 | TYPE = 18 # for user info extension 895 | LIST = "records" 896 | UNK_18_EXT = ( 897 | ("items", "L", ), # 898 | ("unk", "16s", ), # 899 | ) 900 | def __init__(self, ab): 901 | stR = StructReader(ab) 902 | stR.read(self, self.UNK_18_EXT) 903 | self.LoadItems(stR, Unk_18_Ext_Entry) 904 | 905 | def dump(self, flog=sys.stdout): 906 | print >>flog, self.Banner() 907 | print >>flog, " Records[%d] %s:" % (self.items, self.unk.encode("hex")) 908 | self.PrintItems(flog) 909 | 910 | #*************************************************************************** 911 | 912 | class Unk_18_Ext_Entry: 913 | UNK_18_EXT_ENTRY = ( 914 | ("ab", "56s", ), # 915 | ) 916 | def __init__(self, stR): 917 | stR.read(self, self.UNK_18_EXT_ENTRY) 918 | 919 | def __str__(self): 920 | return self.ab.encode("hex") 921 | 922 | #*************************************************************************** 923 | #*************************************************************************** 924 | #*************************************************************************** 925 | 926 | class Unk_22_Ext(Extension): # 22 : used in Manifest (v12) 927 | NAME = "Unk_22" 928 | TYPE = 22 # for v12 extension 929 | UNK_22_EXT = ( 930 | ("name", "4s", ), # 931 | ("unk24", "24s", ), # 932 | ("h", "32s", ), # 933 | ("reserved", "20s", '\0'*20), # 934 | ) 935 | def __init__(self, ab): 936 | stR = StructReader(ab) 937 | stR.read(self, self.UNK_22_EXT) 938 | # self.h = self.h[::-1] # Reverse? 939 | stR.done() 940 | 941 | def dump(self, flog=sys.stdout): 942 | print >>flog, "%s [%s] u=%s h=%s" % (self.Banner(), self.name, self.unk24.encode("hex"), self.h.encode("hex")) 943 | 944 | #*************************************************************************** 945 | #*************************************************************************** 946 | #*************************************************************************** 947 | 948 | class Unk_50_Ext(Extension): # 50 : used in Manifest (HP) 949 | NAME = "Unk_50" 950 | TYPE = 50 # for iUnit extension 951 | UNK_50_EXT = ( 952 | ("name", "4s", ), # 953 | ("dw0", "L", 0), # 954 | ) 955 | def __init__(self, ab): 956 | stR = StructReader(ab) 957 | stR.read(self, self.UNK_50_EXT) 958 | stR.done() 959 | 960 | def dump(self, flog=sys.stdout): 961 | print >>flog, "%s [%s]" % (self.Banner(), self.name) 962 | 963 | #*************************************************************************** 964 | #*************************************************************************** 965 | #*************************************************************************** 966 | 967 | aExtHandlers = ( 968 | System_Info_Ext, # 0 969 | Init_Script_Ext, # 1 970 | Feature_Permissions_Ext, # 2 971 | Partition_Info_Ext, # 3 972 | Shared_Lib_Ext, # 4 973 | Man_Process_Ext, # 5 974 | Threads_Ext, # 6 975 | Device_Ids_Ext, # 7 976 | Mmio_Ranges_Ext, # 8 977 | Special_File_Producer_Ext, # 9 978 | Mod_Attr_Ext, # 10 979 | Locked_Ranges_Ext, # 11 980 | Client_System_Info_Ext, # 12 981 | User_Info_Ext, # 13 982 | # None, # 14 983 | Package_Info_Ext, # 15 984 | Unk_16_Ext, # 16 985 | Unk_18_Ext, # 18 986 | Unk_22_Ext, # 22 987 | Unk_50_Ext, # 50 988 | ) 989 | 990 | dExtHandlers = {ext.TYPE: ext for ext in aExtHandlers} 991 | 992 | def Ext_ParseAll(obj, ab, o=0): 993 | def EnumTags(ab, o=0): 994 | while o < len(ab): 995 | tag, cb = struct.unpack_from("= 8 997 | assert o+cb <= len(ab) 998 | yield tag, ab[o+8:o+cb] 999 | o += cb 1000 | 1001 | obj.extList = [] 1002 | for extType, extData in EnumTags(ab, o): 1003 | ext = dExtHandlers.get(extType, None) 1004 | if ext is not None: 1005 | extObj = ext(extData) 1006 | setattr(obj, ext.NAME, extObj) 1007 | obj.extList.append(extObj) 1008 | # else: raise Error("Extension #%d[%d] not supported" % (extType, len(extData))) 1009 | else: 1010 | parent = obj.name + (".met" if isinstance(obj, CPD_Entry) else "") 1011 | print >>sys.stderr, "- %s: Unknown extType#%d[%d] %s" % (parent, extType, len(extData), extData.encode("hex")) 1012 | 1013 | def Ext_DumpAll(obj, flog=sys.stdout): 1014 | if hasattr(obj, "extList"): 1015 | for extObj in obj.extList: extObj.dump(flog) 1016 | # for ext in aExtHandlers: 1017 | # if hasattr(obj, ext.NAME): getattr(obj, ext.NAME).dump(flog) 1018 | 1019 | #*************************************************************************** 1020 | #*************************************************************************** 1021 | #*************************************************************************** 1022 | 1023 | class CPD_Manifest: 1024 | MARKER = "$MN2" 1025 | MANIFEST_HEADER = ( 1026 | ("type", "L", 4), # Must be 0x4 1027 | ("length", "L", 161), # in Dwords equals 161 for this version 1028 | ("version", "L", 0x10000), # 0x1000 for this version 1029 | ("flags", "L", ), # Debug intel owned 1030 | ("vendor", "L", 0x8086), # 0x8086 for intel 1031 | ("date", "L", ), # yyymmdd in BCD format 1032 | ("size", "L", ), # in Dwords size of the entire manifest. Maximum size is 2K DWORDS (8KB) 1033 | ("header_id", "4s", MARKER), # Magic number. Equals $MN2 for this version 1034 | ("reserved0", "L", ), # Must be 0x4 [not True: it is 0!] 1035 | ("version_major", "H", ), # Major Version [== 11] 1036 | ("version_minor", "H", ), # Minor Version 1037 | ("version_hotfix", "H", ), # Hotfix 1038 | ("version_build", "H", ), # Build number 1039 | ("svn", "L", ), # Secure Version Number 1040 | ("reserved1", "Q", 0), # must be 0 1041 | ("reserved2", "64s", '\0'*64), # will be set to 0 1042 | ("modulus_size", "L", 64), # In DWORDs; 64 for pkcs 1.5-2048 1043 | ("exponent_size", "L", 1), # In DWORDs; 1 for pkcs 1.5-2048 1044 | ) 1045 | CRYPTO_BLOCK = ( 1046 | ("public_key", "256s", ), # Public Key 1047 | ("exponent", "L", ), # Exponent [== 17] 1048 | ("signature", "256s" ), # RSA signature of manifest 1049 | ) 1050 | def __init__(self, ab, name): 1051 | self.name = name 1052 | self.stR = StructReader(ab) 1053 | self.stR.read(self, self.MANIFEST_HEADER) 1054 | self.stR.read(self, self.CRYPTO_BLOCK) 1055 | assert 4*self.size == len(ab) 1056 | Ext_ParseAll(self, ab, 4*self.length) # Parse all Extensions 1057 | 1058 | if 12 == self.version_major: g.HuffDecoder = HuffDecoder12 1059 | 1060 | def dump(self, flog=sys.stdout): 1061 | print >>flog, "CPD Manifest" 1062 | print >>flog, " Date: %08X" % self.date 1063 | print >>flog, " Version: %d.%d.%d.%d" % (self.version_major, self.version_minor, self.version_hotfix, self.version_build) 1064 | print >>flog, " SVN: %08X" % self.svn 1065 | 1066 | # Validate RSA Public Key hash 1067 | h = hashlib.sha256(self.public_key + struct.pack(">flog, " RSA Modulus: known[%d]," % aPubKeyHash.index(h), 1070 | except: 1071 | print >>flog, "- RSA Modulus hash %s is unknown\nmod=0x%s" % (h.encode("hex"), self.public_key[::-1].encode("hex").upper()) 1072 | 1073 | # Validate RSA Signatire 1074 | h = hashlib.sha256(self.stR.ab[:0x80] + self.stR.ab[4*self.length:]).digest() 1075 | modulus = int(self.public_key[::-1].encode("hex"), 16) 1076 | sign = int(self.signature[::-1].encode("hex"), 16) 1077 | decoded = ("%0512X" % pow(sign, self.exponent, modulus)).decode("hex") 1078 | expected = "\x00\x01" + "\xFF"*202 + "003031300D060960864801650304020105000420".decode("hex") + h 1079 | print >>flog, "Exponent:%d, Verification:%s, SigHash: %s" % (self.exponent, "OK" if decoded == expected else "FAILED", hashlib.sha256(self.signature).hexdigest().upper()) 1080 | Ext_DumpAll(self, flog) # Dump all extensions 1081 | 1082 | #*************************************************************************** 1083 | #*************************************************************************** 1084 | #*************************************************************************** 1085 | 1086 | class CPD_Entry: 1087 | CPD_ENTRY_OFFSET = ( # BitFields 1088 | ("address", 0, 24), 1089 | ("compress_flag", 25, 25), 1090 | # ("offset_reserved", 26, 31), 1091 | ) 1092 | CPD_ENTRY = ( 1093 | ("name", "12s", ), 1094 | ("bf_offset", "L", ), 1095 | ("length", "L", ), 1096 | ("reserved", "L", ), 1097 | ) 1098 | def __init__(self, parent): 1099 | self.cpd = parent 1100 | self.cpd.stR.read(self, self.CPD_ENTRY) 1101 | self.name = self.name.rstrip('\0') 1102 | BitFields(self, self.bf_offset, self.CPD_ENTRY_OFFSET) 1103 | self.mod = None 1104 | self.metadata = None 1105 | 1106 | def __str__(self): 1107 | return "%-12s %s %8X %-8X" % (self.name, "HUFF" if self.compress_flag else " ", self.address, self.length) 1108 | 1109 | def getData(self): 1110 | return self.cpd.stR.getData(self.address, self.ModAttr.compressed_size if self.compress_flag else self.length) 1111 | 1112 | def saveRaw(self, baseDir, name=None): 1113 | if name is None: name = self.name 1114 | with open (os.path.join(baseDir, name), "wb") as fo: fo.write(self.getData()) 1115 | 1116 | #*************************************************************************** 1117 | #*************************************************************************** 1118 | #*************************************************************************** 1119 | 1120 | class CPD: # Code Partition Directory 1121 | MARKER = "$CPD" 1122 | CPD_HEADER = ( 1123 | ("marker", "4s", MARKER), 1124 | ("entries", "L", ), 1125 | ("header_version", "B", 0x01), 1126 | ("entry_version", "B", 0x01), 1127 | ("header_length", "B", 0x10), 1128 | ("checksum", "B", ), 1129 | ("partition_name", "4s", ), 1130 | ) 1131 | def __init__(self, ab, base): 1132 | self.stR = StructReader(ab, base) 1133 | self.stR.read(self, self.CPD_HEADER) # Read header 1134 | self.partition_name = self.partition_name.rstrip('\0') 1135 | self.files = [CPD_Entry(self) for i in xrange(self.entries)] # Read directory entries 1136 | self.d = {e.name:e for e in self.files} # Dict maps name CPD_Entry 1137 | 1138 | e = self.files[0] # Access Manifest (very first entry in lookup table) 1139 | self.Manifest = CPD_Manifest(e.getData(), e.name) if e.name.endswith(".man") else None 1140 | # assert self.partition_name + ".man" == e.name 1141 | 1142 | self.modules = None 1143 | if self.Manifest: 1144 | if hasattr(self.Manifest, "PackageInfo"): 1145 | self.modules = self.Manifest.PackageInfo.modules 1146 | elif hasattr(self.Manifest, "PartitionInfo"): 1147 | self.modules = self.Manifest.PartitionInfo.modules 1148 | if len(self.files) != 1 + 2*len(self.modules): # Manfest + nFiles * (Data + Metadata) 1149 | print >>sys.stderr, "- Partition holds %d files but only %d module[s] (%d expected)" % (len(self.files), len(self.modules), (len(self.files)-1)/2) 1150 | 1151 | if self.modules: # Try to attach Module Info and Metadata to Entry 1152 | for i,mod in enumerate(self.modules): # Walk through modules listed in partion manifest 1153 | e = self.d[mod.name] # Access CPD_Entry by module name 1154 | e.mod = mod # Attach Module Info to entry 1155 | metaName = e.name if e.name.endswith(".met") else e.name + ".met" 1156 | e.metadata = self.d[metaName].getData() # Get Metadata content 1157 | assert len(e.metadata) == e.mod.metadata_size # Check Metadata length 1158 | # assert hashlib.sha256(e.metadata).digest() == mod.metadata_hash # Check Metadata hash 1159 | if hashlib.sha256(e.metadata).digest() != e.mod.metadata_hash: # Check Metadata hash 1160 | print >>sys.stderr, "MetaHash %s[%d]: %s != %s" % (e.name, e.mod.metadata_size, hashlib.sha256(e.metadata).hexdigest(), e.mod.metadata_hash.encode("hex")) 1161 | Ext_ParseAll(e, e.metadata) # Parse all Metadata Extensions and store them in CPD_Entry 1162 | 1163 | def dump(self, flog=sys.stdout, baseDir=None): 1164 | print >>flog, "%08X: CPD %-4s.%02X [%d]" % (self.stR.base, self.partition_name, self.checksum, self.entries) 1165 | if baseDir is not None: 1166 | baseDir = os.path.join(baseDir, "%08X.%s" % (self.stR.base, self.partition_name)) 1167 | if not os.path.exists(baseDir): os.makedirs(baseDir) 1168 | if self.Manifest: 1169 | with open(os.path.join(baseDir, "Manifest.txt"), "wt") as fo: self.Manifest.dump(fo) 1170 | if self.Manifest: self.Manifest.dump(flog) 1171 | 1172 | print >>flog, "\nCPD Files[%d]:" % len(self.files) 1173 | for i,e in enumerate(self.files): 1174 | print >>flog, "=================================\n%4d: %s" % (i+1, e) 1175 | Ext_DumpAll(e, flog) # Dump all Metadata Extensions 1176 | if baseDir is None: continue 1177 | 1178 | fileName = e.name 1179 | fileExt = os.path.splitext(fileName)[1] 1180 | bSaveRaw = False 1181 | if ".man" == fileExt: # CPD Manifest 1182 | if g.dumpManifest: bSaveRaw = True 1183 | elif ".met" == fileExt: # Module Metadata 1184 | if g.dumpMeta: bSaveRaw = True 1185 | else: # Module 1186 | if e.metadata: # Module (with metadata) 1187 | if g.dumpRaw: 1188 | bSaveRaw = True 1189 | fileName += ".raw" 1190 | else: bSaveRaw = True # Not a module (without metadata) - always dump "as is" 1191 | if bSaveRaw: e.saveRaw(baseDir, fileName) # Save raw file data (compressed/encrypted) 1192 | 1193 | if e.metadata: # Only for modules with metadata 1194 | with open(os.path.join(baseDir, "%s.txt" % e.name), "wt") as fo: # Dump module info 1195 | print >>fo, "%4d: %s" % (i+1, e) 1196 | Ext_DumpAll(e, fo) # Dump all Metadata Extensions 1197 | 1198 | if not hasattr(e, "ModAttr"): 1199 | e.saveRaw(baseDir) 1200 | continue 1201 | 1202 | compType = dCompType[e.ModAttr.compression_type] 1203 | data = e.getData() 1204 | if e.ModAttr.encrypted: 1205 | print >>sys.stderr, "- Module %s is encrypted" % e.name 1206 | 1207 | if "huff" == compType: 1208 | assert e.length == e.ModAttr.uncompressed_size 1209 | nChunks, left = divmod(e.length, 0x1000) 1210 | assert 0 == left 1211 | 1212 | plain = decompress(data, compType, e.length) 1213 | 1214 | hashChecked = False 1215 | if "huff" != compType and hashlib.sha256(data).digest() == e.ModAttr.image_hash: 1216 | # print "%8s: data" % e.name 1217 | hashChecked = True 1218 | 1219 | if plain: 1220 | if not hashChecked: 1221 | # assert hashlib.sha256(plain).digest() == e.ModAttr.image_hash 1222 | if hashlib.sha256(plain).digest() == e.ModAttr.image_hash: 1223 | hashChecked = True 1224 | # print "%8s: plain" % e.name 1225 | 1226 | with open(os.path.join(baseDir, "%s.mod" % e.name), "wb") as fo: fo.write(plain) 1227 | else: 1228 | if "huff" == compType: 1229 | chunks = HUFF_chunks(nChunks, data) 1230 | chunks.save(os.path.join(baseDir, "%s.%s" % (e.name, compType))) 1231 | if g.dumpChunks: chunks.dump(os.path.join(baseDir, "%s" % e.name)) 1232 | else: 1233 | with open(os.path.join(baseDir, "%s.%s" % (e.name, compType)), "wb") as fo: 1234 | fo.write(data) 1235 | if not hashChecked: print >>sys.stderr, "- hash %s.%s[%s]: %s" % (self.partition_name, e.name, compType, e.ModAttr.image_hash.encode("hex")) 1236 | # print 1237 | 1238 | #*************************************************************************** 1239 | #*************************************************************************** 1240 | #*************************************************************************** 1241 | 1242 | class HUFF_chunks: 1243 | def __init__(self, nChunks, data=None): 1244 | if isinstance(nChunks, str): 1245 | fn = nChunks 1246 | with open(fn, "rb") as f: 1247 | nChunks = struct.unpack(">flog, "NumFptEntries: %d" % self.NumFptEntries 1367 | print >>flog, "HeaderVersion: %d.%d" % divmod(self.HeaderVersion, 16) 1368 | print >>flog, "EntryVersion: %d.%d" % divmod(self.EntryVersion, 16) 1369 | print >>flog, "HeaderLength: 0x%02X" % self.HeaderLength 1370 | print >>flog, "HeaderChecksum: 0x%02X" % self.HeaderChecksum 1371 | print >>flog, "TicksToAdd: 0x%04X" % self.TicksToAdd 1372 | print >>flog, "TokensToAdd: 0x%04X" % self.TokensToAdd 1373 | print >>flog, "FlashLayout: 0x%X" % self.FlashLayout 1374 | print >>flog, "Fitc: %d.%d.%d.%d" % (self.FitcMajor, self.FitcMinor, self.FitcHotfix, self.FitcBuild) 1375 | print >>flog, "ROM Bypass instruction: %s" % (self.RomBypass.encode("hex") if self.RomBypass.rstrip('\0') else "") 1376 | for i,e in enumerate(self.partitions): 1377 | print >>flog, "%4d: %s" % (i+1, e) 1378 | if baseDir is None: continue # Do not write files 1379 | data = e.getData() 1380 | if data: 1381 | with open(os.path.join(baseDir, "%08X.%d.%s.part" % (e.offset, e.attributes.type, e.name)), "wb") as fo: 1382 | fo.write(data) 1383 | 1384 | #*************************************************************************** 1385 | #*************************************************************************** 1386 | #*************************************************************************** 1387 | 1388 | class ME11: 1389 | def __init__(self, fn): 1390 | self.fn = fn 1391 | with open (self.fn, "rb") as f: self.ab = f.read() 1392 | 1393 | for o in xrange(0, len(self.ab), 0x1000): # Search for FPT 1394 | if not self.ab[o+16:o+16+4] == FPT.MARKER: continue 1395 | self.fpt = FPT(self.ab, o) 1396 | break 1397 | else: 1398 | print "FPT not found" 1399 | self.fpt = None 1400 | # raise Error("FPT not found") 1401 | 1402 | o = 0 1403 | self.CPDs = [] 1404 | while True: # Search for CPDs 1405 | o = self.ab.find(CPD.MARKER, o) 1406 | if o < 0: break 1407 | if "\x01\x01\x10" == self.ab[o+8:o+11]: 1408 | # print "%s at %08X" % (CPD.MARKER, o) 1409 | print ". Processing CPD at 0x%X" % o 1410 | self.CPDs.append(CPD(self.ab, o)) 1411 | # try: except: pass 1412 | o += 4 1413 | 1414 | def dump(self): 1415 | baseDir = os.path.splitext(self.fn)[0] 1416 | # baseDir = None 1417 | if baseDir: 1418 | if not os.path.exists(baseDir): os.makedirs(baseDir) 1419 | flog = open(baseDir + ".txt", "wt") 1420 | else: flog = sys.stdout 1421 | 1422 | if self.fpt: self.fpt.dump(flog, baseDir) 1423 | 1424 | for cpd in self.CPDs: 1425 | print >>flog 1426 | cpd.dump(flog, baseDir) 1427 | 1428 | if flog != sys.stdout: flog.close() 1429 | 1430 | #*************************************************************************** 1431 | #*************************************************************************** 1432 | #*************************************************************************** 1433 | 1434 | def main(argv): 1435 | for fn in argv[1:]: 1436 | me = ME11(fn) 1437 | me.dump() 1438 | 1439 | if __name__=="__main__": main(sys.argv) 1440 | --------------------------------------------------------------------------------