mirror of
https://github.com/86Box/bios-tools.git
synced 2026-02-22 01:25:34 -07:00
858 lines
25 KiB
Python
858 lines
25 KiB
Python
#!/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("<IHH8B", raw)
|
|
|
|
GUID_ESCD = "FDE821FD2525954ABB9047EC5763FF9E".decode('hex')
|
|
GUID_SETUP = "D01023C054D73945B0CF9F9F2618D4A9".decode('hex')
|
|
GUID_UEFIV = "112BF272ABCEE242958A0DA1622D94E3".decode('hex')
|
|
GUID_DMIV = "12ED2C42E5AEB94384E0AFB3E416254D".decode('hex')
|
|
GUID_HOLE = "630FAEF68C5F1643A2EA76B9AF762756".decode('hex')
|
|
|
|
def guid2type(guid):
|
|
gmap = {
|
|
GUID_ESCD: "ESCD",
|
|
GUID_SETUP: "SETUP",
|
|
GUID_UEFIV: "UEFIV",
|
|
GUID_DMIV: "DMIV",
|
|
GUID_HOLE: "HOLE",
|
|
}
|
|
if type(guid) != type(""):
|
|
guid = "".join(map(chr, guid))
|
|
if guid in gmap:
|
|
return gmap[guid]
|
|
elif guid[0] == '\xBA':
|
|
return "FFV"
|
|
else:
|
|
return None
|
|
|
|
def guidname2str(s):
|
|
s2 = s.rstrip('\0')
|
|
if s[8] == '\xFF':
|
|
return (s2[:8] + s2[9:]).rstrip('\0')
|
|
elif s2.isalnum():
|
|
return s2.rstrip('\0')
|
|
else:
|
|
return strguid(s)
|
|
|
|
LETTER_FILE_TYPE = {
|
|
'A': 'ACPI',
|
|
'B': 'BIOSCODE',
|
|
'C': 'UPDATE',
|
|
'D': 'DISPLAY',
|
|
'E': 'SETUP',
|
|
'F': 'PIF',
|
|
'F': 'MARKS',
|
|
'G': 'DECOMPCODE',
|
|
'I': 'BOOTBLOCK',
|
|
'L': 'LOGO',
|
|
'M': 'MISER',
|
|
'N': 'ROMPILOTLOAD',
|
|
'O': 'NETWORK',
|
|
'P': 'PSI',
|
|
'P': 'ROMPILOTINT',
|
|
'R': 'OPROM',
|
|
'S': 'STRINGS',
|
|
'T': 'TEMPLATE',
|
|
'U': 'USER',
|
|
'W': 'WAV',
|
|
'X': 'ROMEXEC',
|
|
'*': 'AUTOGEN',
|
|
'$': 'BIOSENTRY',
|
|
}
|
|
|
|
FILE_TYPE_NAMES = {
|
|
0x00 : "ALL",
|
|
0x01 : "BIN",
|
|
0x02 : "SECTION",
|
|
0x03 : "CEIMAIN",
|
|
0x04 : "PEIMAIN",
|
|
0x05 : "DXEMAIN",
|
|
0x06 : "PEI",
|
|
0x07 : "DXE",
|
|
0x08 : "COMBINED_PEIM_DRIVER",
|
|
0x09 : "APP",
|
|
0x0B : "FFV",
|
|
0xC2 : "CEI",
|
|
0xC3 : "XIP",
|
|
0xC4 : "BB",
|
|
0xD0 : "SDXE",
|
|
0xD1 : "DXESDXE",
|
|
0xF0 : "GAP",
|
|
}
|
|
|
|
FILETYPE_BIN = 0x01
|
|
FILETYPE_SECTION = 0x02
|
|
FILETYPE_CEIMAIN = 0x03
|
|
FILETYPE_PEIMAIN = 0x04
|
|
FILETYPE_DXEMAIN = 0x05
|
|
FILETYPE_PEI = 0x06
|
|
FILETYPE_DXE = 0x07
|
|
FILETYPE_COMBINED_PEIM_DRIVER = 0x08
|
|
FILETYPE_APP = 0x09
|
|
FILETYPE_FFV = 0x0B
|
|
FILETYPE_CEI = 0xC2
|
|
FILETYPE_XIP = 0xC3
|
|
FILETYPE_BB = 0xC4
|
|
FILETYPE_SDXE = 0xD0
|
|
FILETYPE_DXESDXE = 0xD1
|
|
FILETYPE_GAP = 0xF0
|
|
|
|
SectionedTypes = [FILETYPE_PEIMAIN, FILETYPE_SECTION, FILETYPE_CEIMAIN,
|
|
FILETYPE_DXEMAIN, FILETYPE_PEI, FILETYPE_DXE, FILETYPE_CEI,
|
|
FILETYPE_BB, FILETYPE_SDXE, FILETYPE_DXESDXE]
|
|
|
|
SECTION_TYPE_NAMES = {
|
|
0x01 : "COMPRESSION",
|
|
0x02 : "GUID_DEFINED",
|
|
0x10 : "PE32",
|
|
0x11 : "PIC",
|
|
0x12 : "TE",
|
|
0x13 : "DXE_DEPEX",
|
|
0x14 : "VERSION",
|
|
0x15 : "USER_INTERFACE",
|
|
0x16 : "COMPATIBILITY16",
|
|
0x17 : "FIRMWARE_VOLUME_IMAGE",
|
|
0x18 : "FREEFORM_SUBTYPE_GUID",
|
|
0x19 : "BIN",
|
|
0x1A : "PE64",
|
|
0x1B : "PEI_DEPEX",
|
|
0xC0 : "SOURCECODE",
|
|
0xC1 : "FFV",
|
|
0xC2 : "RE32",
|
|
0xC3 : "XIP16",
|
|
0xC4 : "XIP32",
|
|
0xC5 : "XIP64",
|
|
0xC6 : "PLACE16",
|
|
0xC7 : "PLACE32",
|
|
0xC8 : "PLACE64",
|
|
0xCF : "PCI_DEVICE",
|
|
0xD0 : "PDB",
|
|
}
|
|
|
|
SECTION_COMPRESSION = 0x01
|
|
SECTION_GUID_DEFINED = 0x02
|
|
SECTION_PE32 = 0x10
|
|
SECTION_PIC = 0x11
|
|
SECTION_TE = 0x12
|
|
SECTION_DXE_DEPEX = 0x13
|
|
SECTION_VERSION = 0x14
|
|
SECTION_USER_INTERFACE = 0x15
|
|
SECTION_COMPATIBILITY16 = 0x16
|
|
SECTION_FIRMWARE_VOLUME_IMAGE = 0x17
|
|
SECTION_FREEFORM_SUBTYPE_GUID = 0x18
|
|
SECTION_BIN = 0x19
|
|
SECTION_PE64 = 0x1A
|
|
SECTION_PEI_DEPEX = 0x1B
|
|
SECTION_SOURCECODE = 0xC0
|
|
SECTION_FFV = 0xC1
|
|
SECTION_RE32 = 0xC2
|
|
SECTION_XIP16 = 0xC3
|
|
SECTION_XIP32 = 0xC4
|
|
SECTION_XIP64 = 0xC5
|
|
SECTION_PLACE16 = 0xC6
|
|
SECTION_PLACE32 = 0xC7
|
|
SECTION_PLACE64 = 0xC8
|
|
SECTION_PCI_DEVICE = 0xCF
|
|
SECTION_PDB = 0xD0
|
|
|
|
|
|
"""
|
|
struct FlashFileHeader
|
|
{
|
|
uint8 FileState;
|
|
uint8 Flags;
|
|
uint8 HeaderChecksum;
|
|
uint8 DataChecksum;
|
|
DWORD SizeAndType;
|
|
UUID GuidName;
|
|
};
|
|
"""
|
|
|
|
class FfsFileHeader(ctypes.LittleEndianStructure):
|
|
_fields_ = [
|
|
("FileState", uint8_t), #
|
|
("Flags", uint8_t), #
|
|
("HeaderChecksum", uint8_t), #
|
|
("DataChecksum", uint8_t), #
|
|
("Size", uint8_t*3), #
|
|
("Type", uint8_t), #
|
|
("Name", uint8_t * 16), #
|
|
]
|
|
|
|
def name2str(self):
|
|
s = "".join(map(chr, self.Name))
|
|
return guidname2str(s)
|
|
|
|
def is_sectioned(self):
|
|
return self.Type in SectionedTypes
|
|
|
|
def size(self):
|
|
s = self.Size[:]
|
|
return ((s[2]<<16) | (s[1] << 8) | s[0]) & 0xFFFFFF
|
|
|
|
def pprint(self):
|
|
nm = self.name2str()
|
|
if nm[0] == '_' and nm[1] in LETTER_FILE_TYPE and nm[2:].isdigit():
|
|
print "File: '%s' (%s)" % (nm, LETTER_FILE_TYPE[nm[1]])
|
|
else:
|
|
print "File: '%s'" % nm
|
|
print "Type: %s (0x%02X)" % (FILE_TYPE_NAMES.get(self.Type, "Unknown"), self.Type)
|
|
print "Flags: 0x%02X" % self.Flags
|
|
print "Size: 0x%06X" % self.size()
|
|
|
|
|
|
class Xip1632(ctypes.LittleEndianStructure):
|
|
_fields_ = [
|
|
("Name", uint8_t * 16), #
|
|
("Offset", uint32_t), #
|
|
("Subsystem", uint16_t), #
|
|
]
|
|
|
|
def name2str(self):
|
|
s = "".join(map(chr, self.Name))
|
|
return guidname2str(s)
|
|
|
|
def pprint(self):
|
|
print "File: '%s'" % self.name2str()
|
|
print "Offset: 0x%08X" % (self.Offset)
|
|
print "Subsystem: 0x%04X" % (self.Subsystem)
|
|
|
|
"""
|
|
|
|
struct FlashSectionHeader
|
|
{
|
|
char Size[3];
|
|
char Type;
|
|
};
|
|
|
|
"""
|
|
|
|
class FfsSectionHeader(ctypes.LittleEndianStructure):
|
|
_fields_ = [
|
|
("Size", uint8_t*3), #
|
|
("Type", uint8_t), #
|
|
]
|
|
def size(self):
|
|
s = self.Size[:]
|
|
return ((s[2]<<16) | (s[1] << 8) | s[0]) & 0xFFFFFF
|
|
|
|
def sectype(self):
|
|
return SECTION_TYPE_NAMES.get(self.Type, "%02X" % self.Type)
|
|
|
|
def pprint(self):
|
|
print "Type: %s (0x%02X)" % (SECTION_TYPE_NAMES.get(self.Type, "Unknown"), self.Type)
|
|
print "Size: 0x%06X" % self.size()
|
|
|
|
def strdepex(data):
|
|
off = 0
|
|
opnames = [
|
|
"BEFORE",
|
|
"AFTER",
|
|
"PUSH",
|
|
"AND",
|
|
"OR",
|
|
"NOT",
|
|
"TRUE",
|
|
"FALSE",
|
|
"END",
|
|
"SOR",
|
|
]
|
|
ops = []
|
|
while off < len(data):
|
|
opc = ord(data[off])
|
|
off += 1
|
|
s = "0x%02X " % opc
|
|
if opc < len(opnames):
|
|
s += opnames[opc]
|
|
if opc in [0,1,2]:
|
|
s += " " + strguid(data[off:off+16])
|
|
off += 16
|
|
else:
|
|
s += " <bad opcode>"
|
|
ops.append(s)
|
|
return "\n".join(ops)
|
|
|
|
lzint_path = os.path.join(os.path.dirname(__file__), "unlzint")
|
|
|
|
def unlzint(data):
|
|
import subprocess
|
|
try:
|
|
clen, ulen = struct.unpack("<II", data[:8])
|
|
if clen + 8 > len(data):
|
|
print "<bad compressed data>"
|
|
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 "<decompression error>"
|
|
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("<I", data)[0]
|
|
seg = addr >> 4
|
|
offset = addr - (seg<<4)
|
|
print " Address: %08X (%04X:%04X)" % (addr, seg, offset)
|
|
elif len(data) == 6:
|
|
offset, segment = struct.unpack("<IH", data)
|
|
print " Address: %04X:%08X" % (segment, offset)
|
|
else:
|
|
print " PLACE16 bad data length(%d)" % len(data)
|
|
elif sh.Type in [SECTION_XIP16, SECTION_XIP32]:
|
|
xip = get_struct(data, 0, Xip1632)
|
|
xip.pprint()
|
|
elif sh.Type == SECTION_COMPRESSION:
|
|
data = unlzint(data)
|
|
pass
|
|
elif sh.Type == SECTION_USER_INTERFACE:
|
|
name = data.decode('utf-16le').rstrip('\0')
|
|
print " Name: '%s'" % name
|
|
elif sh.Type in [SECTION_DXE_DEPEX, SECTION_PEI_DEPEX]:
|
|
print " Depex:\n%s" % strdepex(data)
|
|
sh.Data = data
|
|
sections.append(sh)
|
|
i += 1
|
|
pos1 += sh.size()
|
|
return sections
|
|
|
|
"""
|
|
|
|
struct VolumeDirHeader
|
|
{
|
|
uint8_t field_18;
|
|
uint8_t field_19;
|
|
uint16_t HeaderSize;
|
|
uint32_t VolDirSize;
|
|
};
|
|
|
|
"""
|
|
|
|
class VolumeDirHeader(ctypes.LittleEndianStructure):
|
|
_fields_ = [
|
|
("f0", uint8_t), #
|
|
("f1", uint8_t), #
|
|
("HeaderSize", uint16_t), #
|
|
("VolDirSize", uint32_t), #
|
|
]
|
|
|
|
"""
|
|
struct VolumeDir2Entry
|
|
{
|
|
UUID volumeGuid;
|
|
_DWORD volumeStart;
|
|
_DWORD volumeSize;
|
|
};
|
|
"""
|
|
|
|
class VolumeDir2Entry(ctypes.LittleEndianStructure):
|
|
_fields_ = [
|
|
("Guid", uint8_t * 16), #
|
|
("VolStart", uint32_t), #
|
|
("VolSize", uint32_t), #
|
|
]
|
|
|
|
def pprint(self):
|
|
print "Guid: '%s'" % strguid(self.Guid)
|
|
print "Start: 0x%06X" % self.VolStart
|
|
print "Size: 0x%06X" % self.VolSize
|
|
|
|
def parseVolumeDir2(infile, pos1, endpos, verbose):
|
|
entries = []
|
|
dirhdr = get_struct(infile, pos1, VolumeDirHeader)
|
|
pos1 += dirhdr.HeaderSize
|
|
numVols = dirhdr.VolDirSize // 0x18
|
|
i = 0
|
|
while i < numVols:
|
|
ve = get_struct(infile, pos1, VolumeDir2Entry)
|
|
if verbose:
|
|
print "\nEntry %d" % i
|
|
ve.pprint()
|
|
entries.append(ve)
|
|
i += 1
|
|
pos1 += 0x18
|
|
return entries
|
|
|
|
"""
|
|
struct FlashSectorInfo
|
|
{
|
|
unsigned __int8 iGuid;
|
|
char field_1;
|
|
unsigned __int16 numRepeats;
|
|
int byteSize;
|
|
int flags;
|
|
};
|
|
"""
|
|
|
|
class FlashSectorInfo(ctypes.LittleEndianStructure):
|
|
_pack_ = 1
|
|
_fields_ = [
|
|
("iGuid", uint8_t), #
|
|
("unk1", uint8_t), #
|
|
("numRepeats", uint16_t), #
|
|
("byteSize", uint32_t), #
|
|
("flags", uint32_t), #
|
|
]
|
|
|
|
def pprint(self, verbose):
|
|
if verbose:
|
|
print "iGuid: %d" % self.iGuid
|
|
print "unk1: %d" % self.unk1
|
|
print "repeats: %d" % self.numRepeats
|
|
print "size: 0x%08X" % self.byteSize
|
|
print "flags: 0x%08X" % self.flags
|
|
else:
|
|
print "%d * 0x%08X bytes, flags: %08X" % (self.numRepeats, self.byteSize, self.flags)
|
|
|
|
|
|
"""
|
|
struct VolumeInfoBinHeader
|
|
{
|
|
UUID volname;
|
|
int totalHeaderSize;
|
|
int field_14;
|
|
int field_18;
|
|
__int64 volumeAddress;
|
|
unsigned __int16 numGuids;
|
|
unsigned __int16 numSectorInfos;
|
|
};
|
|
"""
|
|
|
|
class VolumeInfoBinHeader(ctypes.LittleEndianStructure):
|
|
_pack_ = 1
|
|
_fields_ = [
|
|
("Name", uint8_t * 16), #
|
|
("HeaderSize", uint32_t), #
|
|
("unk14", uint32_t), #
|
|
("unk18", uint32_t), #
|
|
("VolumeAddress", uint64_t), #
|
|
("numGuids", uint16_t), #
|
|
("numSectorInfos", uint16_t), #
|
|
]
|
|
|
|
def name2str(self):
|
|
s = "".join(map(chr, self.Name))
|
|
return guidname2str(s)
|
|
|
|
def parse_extras(self, infile, pos):
|
|
pos += ctypes.sizeof(self)
|
|
self.guids = []
|
|
for i in range(self.numGuids):
|
|
self.guids.append(infile[pos:pos+16])
|
|
pos += 16
|
|
self.sectors = []
|
|
for i in range(self.numSectorInfos):
|
|
si = get_struct(infile, pos, FlashSectorInfo)
|
|
self.sectors.append(si)
|
|
pos += 12
|
|
|
|
def pprint(self, verbose):
|
|
if verbose:
|
|
print "Name: '%s'" % self.name2str()
|
|
print "Hdr len: 0x%02X" % self.HeaderSize
|
|
print "Unk14: 0x%02X" % self.unk14
|
|
print "Unk18: 0x%02X" % self.unk18
|
|
print "VolAddr: 0x%08X" % self.VolumeAddress
|
|
print "nGuids: 0x%02X" % self.numGuids
|
|
print "nSectorInfos: 0x%02X" % self.numSectorInfos
|
|
else:
|
|
print " volume '%s' @ 0x%08X" % (self.name2str(), self.VolumeAddress)
|
|
for i in range(len(self.sectors)):
|
|
si = self.sectors[i]
|
|
if si.iGuid == 0:
|
|
sguid = "{no GUID}"
|
|
elif si.iGuid <= len(self.guids):
|
|
sguid = strguid(self.guids[si.iGuid-1])
|
|
else:
|
|
sguid = "{iGuid out of range}"
|
|
if verbose:
|
|
print "[sector info %d %s]" % (i, sguid)
|
|
else:
|
|
print " sector %s:" % (sguid),
|
|
si.pprint(verbose)
|
|
|
|
def replace_bad(value, deletechars):
|
|
for c in deletechars:
|
|
value = value.replace(c,'_')
|
|
return value
|
|
|
|
vollist = []
|
|
filelist = []
|
|
|
|
class FfsFile:
|
|
def __init__(self, infile, pos):
|
|
self.filepos = pos
|
|
self.infile = infile
|
|
self.header = get_struct(infile, pos, FfsFileHeader)
|
|
|
|
def get_endpos(self):
|
|
return self.filepos + self.header.size()
|
|
|
|
def get_romaddr(self, bioslen):
|
|
return (self.filepos - bioslen)&0xFFFFFFFF
|
|
|
|
def parse_sections(self):
|
|
hdrlen = ctypes.sizeof(self.header)
|
|
pos = self.filepos
|
|
pos1 = pos + hdrlen
|
|
endpos = self.get_endpos()
|
|
self.sections = []
|
|
self.secmap = {}
|
|
while pos1 < endpos:
|
|
sh = get_struct(self.infile, pos1, FfsSectionHeader)
|
|
sh.filepos = pos1
|
|
self.sections.append(sh)
|
|
self.secmap[sh.Type] = sh
|
|
pos1 += sh.size()
|
|
|
|
def sect_data(self, stype):
|
|
sh = self.secmap[stype]
|
|
pos1 = sh.filepos
|
|
return self.infile[pos1 + ctypes.sizeof(sh):pos1 + sh.size()]
|
|
|
|
def raw_data(self):
|
|
datapos = self.filepos + ctypes.sizeof(self.header)
|
|
endpos = self.get_endpos()
|
|
return self.infile[datapos:endpos]
|
|
|
|
def pprint(self, bioslen, detailed = False):
|
|
if detailed:
|
|
print "ROM address: %08X, File offset: %08X" % (self.get_romaddr(bioslen), self.filepos)
|
|
print "Header:"
|
|
self.header.pprint()
|
|
addr = self.get_romaddr(bioslen)
|
|
enda = addr + self.header.size() - 1
|
|
dsize = self.header.size() - ctypes.sizeof(self.header)
|
|
namestr = self.header.name2str()
|
|
if namestr[0] == '_' and namestr[1] in LETTER_FILE_TYPE and namestr[2:].isdigit():
|
|
namestr = "%s (%s)" % (namestr, LETTER_FILE_TYPE[namestr[1]])
|
|
|
|
s = "%08X-%08X %08X %-40s %-8s %d(0x%x)" % (self.get_romaddr(bioslen), enda, self.filepos, namestr, FILE_TYPE_NAMES.get(self.header.Type, "Unknown"), dsize, dsize)
|
|
print s
|
|
if self.header.is_sectioned():
|
|
self.parse_sections()
|
|
if detailed:
|
|
for i in range(len(self.sections)):
|
|
print " section %d:" % i
|
|
self.sections[i].pprint()
|
|
# unhandled sections
|
|
us = set(self.secmap.keys())
|
|
if SECTION_COMPRESSION in self.secmap:
|
|
print " compressed payload"
|
|
us.remove(SECTION_COMPRESSION)
|
|
if SECTION_BIN in self.secmap:
|
|
print " raw payload"
|
|
us.remove(SECTION_BIN)
|
|
if SECTION_PE32 in self.secmap:
|
|
print " PE32 payload"
|
|
us.remove(SECTION_PE32)
|
|
if SECTION_DXE_DEPEX in self.secmap:
|
|
print " DXE DEPEX present"
|
|
us.remove(SECTION_DXE_DEPEX)
|
|
if SECTION_PEI_DEPEX in self.secmap:
|
|
print " PEI DEPEX present"
|
|
us.remove(SECTION_PEI_DEPEX)
|
|
if SECTION_USER_INTERFACE in self.secmap:
|
|
data = self.sect_data(SECTION_USER_INTERFACE)
|
|
name = data.decode('utf-16le').rstrip('\0')
|
|
print " UI name: %s" % name
|
|
us.remove(SECTION_USER_INTERFACE)
|
|
if SECTION_PLACE16 in self.secmap:
|
|
data = self.sect_data(SECTION_PLACE16)
|
|
offset = 0xFFFFFFFF
|
|
segment = 0xFFFF
|
|
if len(data) == 4:
|
|
addr = struct.unpack("<I", data)[0]
|
|
seg = addr >> 4
|
|
offset = addr - (seg<<4)
|
|
print " PLACE16: %08X (%04X:%04X)" % (addr, seg, offset)
|
|
elif len(data) == 6:
|
|
offset, segment = struct.unpack("<IH", data)
|
|
print " PLACE16: %04X:%08X" % (segment, offset)
|
|
else:
|
|
print " PLACE16 bad data length(%d)" % len(data)
|
|
us.remove(SECTION_PLACE16)
|
|
if SECTION_XIP16 in self.secmap:
|
|
xip = get_struct(self.sect_data(SECTION_XIP16), 0, Xip1632)
|
|
print " XIP16 file '%s', entry = 0x%08X, subsystem = %02X" % (xip.name2str(), xip.Offset, xip.Subsystem)
|
|
us.remove(SECTION_XIP16)
|
|
if SECTION_XIP32 in self.secmap:
|
|
xip = get_struct(self.sect_data(SECTION_XIP32), 0, Xip1632)
|
|
print " XIP32 file '%s', entry = 0x%08X, subsystem = %02X" % (xip.name2str(), xip.Offset, xip.Subsystem)
|
|
us.remove(SECTION_XIP32)
|
|
if us:
|
|
print " unhandled sections:", map(hex,us)
|
|
|
|
def parseFfsFile(infile, pos, bioslen, volno = None):
|
|
ff = get_struct(infile, pos, FfsFileHeader)
|
|
if ff.size() == 0 or ff.FileState != 0xF8:
|
|
return
|
|
ff.pprint()
|
|
pos0 = pos
|
|
hdrlen = ctypes.sizeof(ff)
|
|
pos1 = pos + hdrlen
|
|
pos += ff.size()
|
|
fname = ff.name2str()
|
|
if ff.Type in [FILETYPE_PEIMAIN, FILETYPE_SECTION, FILETYPE_CEIMAIN,
|
|
FILETYPE_DXEMAIN, FILETYPE_PEI, FILETYPE_DXE, FILETYPE_CEI,
|
|
FILETYPE_BB, FILETYPE_SDXE, FILETYPE_DXESDXE]:
|
|
ss = parseSectionedFile(infile, pos1, pos)
|
|
for i in range(len(ss)):
|
|
fname2 = "%08X_%s_S%d_%s.bin" % (pos0, fname, i, ss[i].sectype())
|
|
if volno != None:
|
|
fname2 = "V%d_" % volno + fname2
|
|
fname2 = replace_bad(fname2, '\/:*?"<>|')
|
|
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)
|