#!/usr/bin/env python2 # Phoenix FFV BIOS dumper/extractor by roxfan # 2012-09-12 version 0.1 # 3-clause BSD license import sys, struct, ctypes import os.path uint8_t = ctypes.c_ubyte char = ctypes.c_char uint32_t = ctypes.c_uint uint64_t = ctypes.c_uint64 uint16_t = ctypes.c_ushort def read_struct(li, struct): s = struct() slen = ctypes.sizeof(s) bytes = li.read(slen) fit = min(len(bytes), slen) ctypes.memmove(ctypes.addressof(s), bytes, fit) return s def get_struct(str_, off, struct): s = struct() slen = ctypes.sizeof(s) bytes = str_[off:off+slen] fit = min(len(bytes), slen) ctypes.memmove(ctypes.addressof(s), bytes, fit) return s def strguid(raw): return "{%08x-%04x-%04x-%02x%02x-%02x%02x%02x%02x%02x%02x}" % struct.unpack(" len(data): print "" return data if clen + 8 < len(data): data = data[:clen + 8] p = subprocess.Popen([lzint_path, "-", "-"], stdout=subprocess.PIPE, stdin=subprocess.PIPE) outd, errd = p.communicate(input=data) return outd except: print "" return data def parseSectionedFile(infile, pos1, endpos): sections = [] i = 0 while pos1 < endpos: sh = get_struct(infile, pos1, FfsSectionHeader) print "\nSection %d" % i sh.pprint() dlen = sh.size() - ctypes.sizeof(sh) data = infile[pos1 + ctypes.sizeof(sh):pos1 + ctypes.sizeof(sh) + dlen] if sh.Type == SECTION_PLACE16: offset = 0xFFFFFFFF segment = 0xFFFF if len(data) == 4: addr = struct.unpack("> 4 offset = addr - (seg<<4) print " Address: %08X (%04X:%04X)" % (addr, seg, offset) elif len(data) == 6: offset, segment = struct.unpack("> 4 offset = addr - (seg<<4) print " PLACE16: %08X (%04X:%04X)" % (addr, seg, offset) elif len(data) == 6: offset, segment = struct.unpack("|') print " ==> %s" % fname2 open(fname2,"wb").write(ss[i].Data) if ff.Type == FILETYPE_BIN: if ff.name2str() == "volumedir.bin2" and volno == None: vols = parseVolumeDir2(infile, pos1, pos, False) for i in range(len(vols)): v = vols[i] s = (v.VolStart + bioslen) & 0xFFFFFFFF # v.VolStart - 0xFF800000 print "\n***\nVolume %d: address %08X, file offset %08X, size %08X\n***\n" % (i, v.VolStart, s, v.VolSize) parse_range(infile, s, s + v.VolSize, bioslen, i) vollist.extend(vols) fname2 = "%08X_%s.bin" % (pos0, fname) if volno != None: fname2 = "V%d_" % volno + fname2 fname2 = replace_bad(fname2, '\/:*?"<>|') print " ==> %s" % fname2 dlen = ff.size() - hdrlen data = infile[pos1:pos1 + dlen] open(fname2,"wb").write(data) return pos class FfsVolume: def __init__(self, infile, pos, size, guid, ord): self.filepos = pos self.infile = infile self.size = size self.guid = guid self.volInfo = None self.ord = ord self.files = [] def parse_volinfo(self, data): self.volInfo = get_struct(data, 0, VolumeInfoBinHeader) self.volInfo.parse_extras(data, 0) def parse_files(self): pos = self.filepos maxpos = pos + self.size while True: while pos < maxpos and self.infile[pos] in ['\xFF', '\x00']: pos += 1 if pos >= maxpos: break if self.infile[pos] != '\xF8': return ff = FfsFile(self.infile, pos) pos = ff.get_endpos() self.files.append(ff) if ff.header.Type == FILETYPE_BIN and ff.header.name2str() == "volumeinfo.bin": self.parse_volinfo(ff.raw_data()) # skip padding after file while pos < maxpos and infile[pos] in ['\xFF', '\x00']: pos += 1 def pprint(self, bioslen, verbose): if self.guid[0] == 0xBA: self.parse_files() addr = (self.filepos - bioslen)&0xFFFFFFFF namestr = strguid(self.guid) gt = guid2type(self.guid) if gt == None: gt = "VOL" s = "%08X-%08X %08X %-40s %-8s %d(0x%x)" % (addr, addr + self.size-1, self.filepos, namestr, "<%s %d>" % (gt, self.ord), self.size, self.size) # print "\n***\nVolume address %08X, file offset %08X, size %08X\n***\n" % (addr, self.filepos, self.size) print s if self.volInfo: self.volInfo.pprint(verbose) for f in self.files: f.pprint(bioslen, verbose) def parse_range(infile, pos, maxpos, bioslen, volno = None): start = pos while pos < maxpos: nextpos = parseFfsFile(infile, pos, bioslen, volno) if nextpos == None: break print "\nfile offset: %08X" % pos pos = nextpos while pos < maxpos and infile[pos] in ['\xFF', '\x00']: pos += 1 if volno != None and start == pos and pos < maxpos: fname = "V%d_%08X.bin" % (volno, pos) print " ==> %s" % fname open(fname,"wb").write(infile[pos:maxpos]) class PhoenixModuleHeader(ctypes.LittleEndianStructure): _pack_ = 1 _fields_ = [ ("Signature", uint32_t), # ("Signature2", uint8_t*3), # ("Id", uint8_t), # ("Type", uint8_t), # ("HeadLen", uint8_t), # ("Compression", uint8_t), # ("Address", uint32_t), ("ExpLen", uint32_t), ("FragLen", uint32_t), ("NextFrag", uint32_t), ] def pprint(self): print "Signature: 0x%08X" % self.Signature print "Id: %d" % self.Id print "Type: %02X" % self.Type print "HeadLen: %02X" % self.HeadLen print "Compression: %d" % self.Compression addr = self.Address seg = addr >> 4 offset = addr - (seg<<4) print "Address: %08X (%04X:%04X)" % (addr, seg, offset) print "ExpLen: %X" % (self.ExpLen) print "FragLen: %X" % (self.FragLen) print "NextFrag: %X" % (self.NextFrag) """ struct PhoenixModule { uint32_t Signature; uint8_t Signature2[3]; uint8_t Id; uint8_t Type; uint8_t HeadLen; uint8_t Compression; uint16_t Offset; uint16_t Segment; uint32_t ExpLen; uint32_t FragLength; uint32_t NextFrag; } *Module; """ def parse_trailer(infile, pos, maxpos): start = pos i = 0 while pos < maxpos: if infile[pos:pos+4] == "BC\xD6\xF1": modhdr = get_struct(infile, pos, PhoenixModuleHeader) modhdr.pprint() pos += modhdr.HeadLen addr = modhdr.Address seg = addr >> 4 offset = addr - (seg<<4) fname = "%08X_%04X_%04X.bin" % (pos, seg, offset) print " ==> %s" % fname open(fname,"wb").write(infile[pos:pos+modhdr.ExpLen]) pos += modhdr.ExpLen elif infile[pos:pos+8] == "FLASHDXE": pos += 8 while pos < maxpos: nextpos = parseFfsFile(infile, pos, maxpos) if nextpos == None: break print "\nfile offset: %08X" % pos pos = nextpos else: print "\nunknown header at %08X" % pos break if len(sys.argv) < 1: print "Usage: phoenix_scan.py BIOS.BIN [-d] [-t] [-v]" print "-d: extract modules into files" print "-t: extract the trailer parts" print "-v: verbose info about flash layout" sys.exit(1) inf = open(sys.argv[1],"rb") infile = inf.read() pos = infile.find("volumedi\xFFr.bin2") if pos != -1: pos -= 8 print "Found Volume Directory v2 at %08X\n" % (pos) else: print "Volume dir not found; FFS dump won't be available" #sys.exit(1) dump_all = False dump_trailer = False print_verbose = False for a in sys.argv[2:]: if a == '-d': dump_all = True elif a == '-t': dump_trailer = True elif a == '-v': print_verbose = True alllen = len(infile) bioslen = alllen & 0xFFFF0000 if dump_all and pos != -1: parse_range(infile, pos, alllen, bioslen) if dump_trailer: parse_trailer(infile, bioslen, alllen) if pos != -1: voldir = FfsFile(infile, pos) vols = parseVolumeDir2(voldir.raw_data(), 0, 0, print_verbose) ffvols = [] for i in range(len(vols)): v = vols[i] pos = (v.VolStart + bioslen) & 0xFFFFFFFF ffv = FfsVolume(infile, pos, v.VolSize, v.Guid, i) ffvols.append(ffv) print "FFS contents:" for fv in ffvols: print "" fv.pprint(bioslen, print_verbose)