blob: f1adbe1df96abe29345d9e98bc4fa1ea9a0e3a68 [file] [log] [blame]
Denis 'GNUtoo' Carikli40a2a9c2017-03-01 14:56:25 +01001#!/usr/bin/env python2
roxfan754a7d72012-09-13 01:11:35 +04002# Phoenix FFV BIOS dumper/extractor by roxfan
3# 2012-09-12 version 0.1
4# 3-clause BSD license
5
6import sys, struct, ctypes
7import os.path
8
9uint8_t = ctypes.c_ubyte
10char = ctypes.c_char
11uint32_t = ctypes.c_uint
12uint64_t = ctypes.c_uint64
13uint16_t = ctypes.c_ushort
14
15def read_struct(li, struct):
16 s = struct()
17 slen = ctypes.sizeof(s)
18 bytes = li.read(slen)
19 fit = min(len(bytes), slen)
20 ctypes.memmove(ctypes.addressof(s), bytes, fit)
21 return s
22
23def get_struct(str_, off, struct):
24 s = struct()
25 slen = ctypes.sizeof(s)
26 bytes = str_[off:off+slen]
27 fit = min(len(bytes), slen)
28 ctypes.memmove(ctypes.addressof(s), bytes, fit)
29 return s
30
31def strguid(raw):
32 return "{%08x-%04x-%04x-%02x%02x-%02x%02x%02x%02x%02x%02x}" % struct.unpack("<IHH8B", raw)
33
34GUID_ESCD = "FDE821FD2525954ABB9047EC5763FF9E".decode('hex')
35GUID_SETUP = "D01023C054D73945B0CF9F9F2618D4A9".decode('hex')
36GUID_UEFIV = "112BF272ABCEE242958A0DA1622D94E3".decode('hex')
37GUID_DMIV = "12ED2C42E5AEB94384E0AFB3E416254D".decode('hex')
38GUID_HOLE = "630FAEF68C5F1643A2EA76B9AF762756".decode('hex')
39
40def guid2type(guid):
41 gmap = {
42 GUID_ESCD: "ESCD",
43 GUID_SETUP: "SETUP",
44 GUID_UEFIV: "UEFIV",
45 GUID_DMIV: "DMIV",
46 GUID_HOLE: "HOLE",
47 }
48 if type(guid) != type(""):
49 guid = "".join(map(chr, guid))
50 if guid in gmap:
51 return gmap[guid]
52 elif guid[0] == '\xBA':
53 return "FFV"
54 else:
55 return None
56
57def guidname2str(s):
58 s2 = s.rstrip('\0')
59 if s[8] == '\xFF':
60 return (s2[:8] + s2[9:]).rstrip('\0')
61 elif s2.isalnum():
62 return s2.rstrip('\0')
63 else:
64 return strguid(s)
65
66LETTER_FILE_TYPE = {
67 'A': 'ACPI',
68 'B': 'BIOSCODE',
69 'C': 'UPDATE',
70 'D': 'DISPLAY',
71 'E': 'SETUP',
72 'F': 'PIF',
73 'F': 'MARKS',
74 'G': 'DECOMPCODE',
75 'I': 'BOOTBLOCK',
76 'L': 'LOGO',
77 'M': 'MISER',
78 'N': 'ROMPILOTLOAD',
79 'O': 'NETWORK',
80 'P': 'PSI',
81 'P': 'ROMPILOTINT',
82 'R': 'OPROM',
83 'S': 'STRINGS',
84 'T': 'TEMPLATE',
85 'U': 'USER',
86 'W': 'WAV',
87 'X': 'ROMEXEC',
88 '*': 'AUTOGEN',
89 '$': 'BIOSENTRY',
90}
91
92FILE_TYPE_NAMES = {
93 0x00 : "ALL",
94 0x01 : "BIN",
95 0x02 : "SECTION",
96 0x03 : "CEIMAIN",
97 0x04 : "PEIMAIN",
98 0x05 : "DXEMAIN",
99 0x06 : "PEI",
100 0x07 : "DXE",
101 0x08 : "COMBINED_PEIM_DRIVER",
102 0x09 : "APP",
103 0x0B : "FFV",
104 0xC2 : "CEI",
105 0xC3 : "XIP",
106 0xC4 : "BB",
107 0xD0 : "SDXE",
108 0xD1 : "DXESDXE",
109 0xF0 : "GAP",
110}
111
112FILETYPE_BIN = 0x01
113FILETYPE_SECTION = 0x02
114FILETYPE_CEIMAIN = 0x03
115FILETYPE_PEIMAIN = 0x04
116FILETYPE_DXEMAIN = 0x05
117FILETYPE_PEI = 0x06
118FILETYPE_DXE = 0x07
119FILETYPE_COMBINED_PEIM_DRIVER = 0x08
120FILETYPE_APP = 0x09
121FILETYPE_FFV = 0x0B
122FILETYPE_CEI = 0xC2
123FILETYPE_XIP = 0xC3
124FILETYPE_BB = 0xC4
125FILETYPE_SDXE = 0xD0
126FILETYPE_DXESDXE = 0xD1
127FILETYPE_GAP = 0xF0
128
129SectionedTypes = [FILETYPE_PEIMAIN, FILETYPE_SECTION, FILETYPE_CEIMAIN,
130 FILETYPE_DXEMAIN, FILETYPE_PEI, FILETYPE_DXE, FILETYPE_CEI,
131 FILETYPE_BB, FILETYPE_SDXE, FILETYPE_DXESDXE]
132
133SECTION_TYPE_NAMES = {
134 0x01 : "COMPRESSION",
135 0x02 : "GUID_DEFINED",
136 0x10 : "PE32",
137 0x11 : "PIC",
138 0x12 : "TE",
139 0x13 : "DXE_DEPEX",
140 0x14 : "VERSION",
141 0x15 : "USER_INTERFACE",
142 0x16 : "COMPATIBILITY16",
143 0x17 : "FIRMWARE_VOLUME_IMAGE",
144 0x18 : "FREEFORM_SUBTYPE_GUID",
145 0x19 : "BIN",
146 0x1A : "PE64",
147 0x1B : "PEI_DEPEX",
148 0xC0 : "SOURCECODE",
149 0xC1 : "FFV",
150 0xC2 : "RE32",
151 0xC3 : "XIP16",
152 0xC4 : "XIP32",
153 0xC5 : "XIP64",
154 0xC6 : "PLACE16",
155 0xC7 : "PLACE32",
156 0xC8 : "PLACE64",
157 0xCF : "PCI_DEVICE",
158 0xD0 : "PDB",
159}
160
161SECTION_COMPRESSION = 0x01
162SECTION_GUID_DEFINED = 0x02
163SECTION_PE32 = 0x10
164SECTION_PIC = 0x11
165SECTION_TE = 0x12
166SECTION_DXE_DEPEX = 0x13
167SECTION_VERSION = 0x14
168SECTION_USER_INTERFACE = 0x15
169SECTION_COMPATIBILITY16 = 0x16
170SECTION_FIRMWARE_VOLUME_IMAGE = 0x17
171SECTION_FREEFORM_SUBTYPE_GUID = 0x18
172SECTION_BIN = 0x19
173SECTION_PE64 = 0x1A
174SECTION_PEI_DEPEX = 0x1B
175SECTION_SOURCECODE = 0xC0
176SECTION_FFV = 0xC1
177SECTION_RE32 = 0xC2
178SECTION_XIP16 = 0xC3
179SECTION_XIP32 = 0xC4
180SECTION_XIP64 = 0xC5
181SECTION_PLACE16 = 0xC6
182SECTION_PLACE32 = 0xC7
183SECTION_PLACE64 = 0xC8
184SECTION_PCI_DEVICE = 0xCF
185SECTION_PDB = 0xD0
186
187
188"""
189struct FlashFileHeader
190{
191 uint8 FileState;
192 uint8 Flags;
193 uint8 HeaderChecksum;
194 uint8 DataChecksum;
195 DWORD SizeAndType;
196 UUID GuidName;
197};
198"""
199
200class FfsFileHeader(ctypes.LittleEndianStructure):
201 _fields_ = [
202 ("FileState", uint8_t), #
203 ("Flags", uint8_t), #
204 ("HeaderChecksum", uint8_t), #
205 ("DataChecksum", uint8_t), #
206 ("Size", uint8_t*3), #
207 ("Type", uint8_t), #
208 ("Name", uint8_t * 16), #
209 ]
210
211 def name2str(self):
212 s = "".join(map(chr, self.Name))
213 return guidname2str(s)
214
215 def is_sectioned(self):
216 return self.Type in SectionedTypes
217
218 def size(self):
219 s = self.Size[:]
220 return ((s[2]<<16) | (s[1] << 8) | s[0]) & 0xFFFFFF
221
222 def pprint(self):
223 nm = self.name2str()
224 if nm[0] == '_' and nm[1] in LETTER_FILE_TYPE and nm[2:].isdigit():
225 print "File: '%s' (%s)" % (nm, LETTER_FILE_TYPE[nm[1]])
226 else:
227 print "File: '%s'" % nm
228 print "Type: %s (0x%02X)" % (FILE_TYPE_NAMES.get(self.Type, "Unknown"), self.Type)
229 print "Flags: 0x%02X" % self.Flags
230 print "Size: 0x%06X" % self.size()
231
232
233class Xip1632(ctypes.LittleEndianStructure):
234 _fields_ = [
235 ("Name", uint8_t * 16), #
236 ("Offset", uint32_t), #
237 ("Subsystem", uint16_t), #
238 ]
239
240 def name2str(self):
241 s = "".join(map(chr, self.Name))
242 return guidname2str(s)
243
244 def pprint(self):
245 print "File: '%s'" % self.name2str()
246 print "Offset: 0x%08X" % (self.Offset)
247 print "Subsystem: 0x%04X" % (self.Subsystem)
248
249"""
250
251struct FlashSectionHeader
252{
253 char Size[3];
254 char Type;
255};
256
257"""
258
259class FfsSectionHeader(ctypes.LittleEndianStructure):
260 _fields_ = [
261 ("Size", uint8_t*3), #
262 ("Type", uint8_t), #
263 ]
264 def size(self):
265 s = self.Size[:]
266 return ((s[2]<<16) | (s[1] << 8) | s[0]) & 0xFFFFFF
267
268 def sectype(self):
269 return SECTION_TYPE_NAMES.get(self.Type, "%02X" % self.Type)
270
271 def pprint(self):
272 print "Type: %s (0x%02X)" % (SECTION_TYPE_NAMES.get(self.Type, "Unknown"), self.Type)
273 print "Size: 0x%06X" % self.size()
274
275def strdepex(data):
276 off = 0
277 opnames = [
278 "BEFORE",
279 "AFTER",
280 "PUSH",
281 "AND",
282 "OR",
283 "NOT",
284 "TRUE",
285 "FALSE",
286 "END",
287 "SOR",
288 ]
289 ops = []
290 while off < len(data):
291 opc = ord(data[off])
292 off += 1
293 s = "0x%02X " % opc
294 if opc < len(opnames):
295 s += opnames[opc]
296 if opc in [0,1,2]:
297 s += " " + strguid(data[off:off+16])
298 off += 16
299 else:
300 s += " <bad opcode>"
301 ops.append(s)
302 return "\n".join(ops)
303
304lzint_path = os.path.join(os.path.dirname(__file__), "unlzint")
305
306def unlzint(data):
307 import subprocess
308 try:
309 clen, ulen = struct.unpack("<II", data[:8])
310 if clen + 8 > len(data):
311 print "<bad compressed data>"
312 return data
313 if clen + 8 < len(data):
314 data = data[:clen + 8]
315 p = subprocess.Popen([lzint_path, "-", "-"], stdout=subprocess.PIPE, stdin=subprocess.PIPE)
316 outd, errd = p.communicate(input=data)
317 return outd
318 except:
319 print "<decompression error>"
320 return data
321
322def parseSectionedFile(infile, pos1, endpos):
323 sections = []
324 i = 0
325 while pos1 < endpos:
326 sh = get_struct(infile, pos1, FfsSectionHeader)
327 print "\nSection %d" % i
328 sh.pprint()
329 dlen = sh.size() - ctypes.sizeof(sh)
330 data = infile[pos1 + ctypes.sizeof(sh):pos1 + ctypes.sizeof(sh) + dlen]
331 if sh.Type == SECTION_PLACE16:
332 offset = 0xFFFFFFFF
333 segment = 0xFFFF
334 if len(data) == 4:
335 addr = struct.unpack("<I", data)[0]
336 seg = addr >> 4
337 offset = addr - (seg<<4)
338 print " Address: %08X (%04X:%04X)" % (addr, seg, offset)
339 elif len(data) == 6:
340 offset, segment = struct.unpack("<IH", data)
341 print " Address: %04X:%08X" % (segment, offset)
342 else:
343 print " PLACE16 bad data length(%d)" % len(data)
344 elif sh.Type in [SECTION_XIP16, SECTION_XIP32]:
345 xip = get_struct(data, 0, Xip1632)
346 xip.pprint()
347 elif sh.Type == SECTION_COMPRESSION:
348 data = unlzint(data)
349 pass
350 elif sh.Type == SECTION_USER_INTERFACE:
351 name = data.decode('utf-16le').rstrip('\0')
352 print " Name: '%s'" % name
353 elif sh.Type in [SECTION_DXE_DEPEX, SECTION_PEI_DEPEX]:
354 print " Depex:\n%s" % strdepex(data)
355 sh.Data = data
356 sections.append(sh)
357 i += 1
358 pos1 += sh.size()
359 return sections
360
361"""
362
363struct VolumeDirHeader
364{
365 uint8_t field_18;
366 uint8_t field_19;
367 uint16_t HeaderSize;
368 uint32_t VolDirSize;
369};
370
371"""
372
373class VolumeDirHeader(ctypes.LittleEndianStructure):
374 _fields_ = [
375 ("f0", uint8_t), #
376 ("f1", uint8_t), #
377 ("HeaderSize", uint16_t), #
378 ("VolDirSize", uint32_t), #
379 ]
380
381"""
382struct VolumeDir2Entry
383{
384 UUID volumeGuid;
385 _DWORD volumeStart;
386 _DWORD volumeSize;
387};
388"""
389
390class VolumeDir2Entry(ctypes.LittleEndianStructure):
391 _fields_ = [
392 ("Guid", uint8_t * 16), #
393 ("VolStart", uint32_t), #
394 ("VolSize", uint32_t), #
395 ]
396
397 def pprint(self):
398 print "Guid: '%s'" % strguid(self.Guid)
399 print "Start: 0x%06X" % self.VolStart
400 print "Size: 0x%06X" % self.VolSize
401
402def parseVolumeDir2(infile, pos1, endpos, verbose):
403 entries = []
404 dirhdr = get_struct(infile, pos1, VolumeDirHeader)
405 pos1 += dirhdr.HeaderSize
406 numVols = dirhdr.VolDirSize // 0x18
407 i = 0
408 while i < numVols:
409 ve = get_struct(infile, pos1, VolumeDir2Entry)
410 if verbose:
411 print "\nEntry %d" % i
412 ve.pprint()
413 entries.append(ve)
414 i += 1
415 pos1 += 0x18
416 return entries
417
418"""
419struct FlashSectorInfo
420{
421 unsigned __int8 iGuid;
422 char field_1;
423 unsigned __int16 numRepeats;
424 int byteSize;
425 int flags;
426};
427"""
428
429class FlashSectorInfo(ctypes.LittleEndianStructure):
430 _pack_ = 1
431 _fields_ = [
432 ("iGuid", uint8_t), #
433 ("unk1", uint8_t), #
434 ("numRepeats", uint16_t), #
435 ("byteSize", uint32_t), #
436 ("flags", uint32_t), #
437 ]
438
439 def pprint(self, verbose):
440 if verbose:
441 print "iGuid: %d" % self.iGuid
442 print "unk1: %d" % self.unk1
443 print "repeats: %d" % self.numRepeats
444 print "size: 0x%08X" % self.byteSize
445 print "flags: 0x%08X" % self.flags
446 else:
447 print "%d * 0x%08X bytes, flags: %08X" % (self.numRepeats, self.byteSize, self.flags)
448
449
450"""
451struct VolumeInfoBinHeader
452{
453 UUID volname;
454 int totalHeaderSize;
455 int field_14;
456 int field_18;
457 __int64 volumeAddress;
458 unsigned __int16 numGuids;
459 unsigned __int16 numSectorInfos;
460};
461"""
462
463class VolumeInfoBinHeader(ctypes.LittleEndianStructure):
464 _pack_ = 1
465 _fields_ = [
466 ("Name", uint8_t * 16), #
467 ("HeaderSize", uint32_t), #
468 ("unk14", uint32_t), #
469 ("unk18", uint32_t), #
470 ("VolumeAddress", uint64_t), #
471 ("numGuids", uint16_t), #
472 ("numSectorInfos", uint16_t), #
473 ]
474
475 def name2str(self):
476 s = "".join(map(chr, self.Name))
477 return guidname2str(s)
478
479 def parse_extras(self, infile, pos):
480 pos += ctypes.sizeof(self)
481 self.guids = []
482 for i in range(self.numGuids):
483 self.guids.append(infile[pos:pos+16])
484 pos += 16
485 self.sectors = []
486 for i in range(self.numSectorInfos):
487 si = get_struct(infile, pos, FlashSectorInfo)
488 self.sectors.append(si)
489 pos += 12
490
491 def pprint(self, verbose):
492 if verbose:
493 print "Name: '%s'" % self.name2str()
494 print "Hdr len: 0x%02X" % self.HeaderSize
495 print "Unk14: 0x%02X" % self.unk14
496 print "Unk18: 0x%02X" % self.unk18
497 print "VolAddr: 0x%08X" % self.VolumeAddress
498 print "nGuids: 0x%02X" % self.numGuids
499 print "nSectorInfos: 0x%02X" % self.numSectorInfos
500 else:
501 print " volume '%s' @ 0x%08X" % (self.name2str(), self.VolumeAddress)
502 for i in range(len(self.sectors)):
503 si = self.sectors[i]
504 if si.iGuid == 0:
505 sguid = "{no GUID}"
506 elif si.iGuid <= len(self.guids):
507 sguid = strguid(self.guids[si.iGuid-1])
508 else:
509 sguid = "{iGuid out of range}"
510 if verbose:
511 print "[sector info %d %s]" % (i, sguid)
512 else:
513 print " sector %s:" % (sguid),
514 si.pprint(verbose)
515
516def replace_bad(value, deletechars):
517 for c in deletechars:
518 value = value.replace(c,'_')
519 return value
520
521vollist = []
522filelist = []
523
524class FfsFile:
525 def __init__(self, infile, pos):
526 self.filepos = pos
527 self.infile = infile
528 self.header = get_struct(infile, pos, FfsFileHeader)
529
530 def get_endpos(self):
531 return self.filepos + self.header.size()
532
533 def get_romaddr(self, bioslen):
534 return (self.filepos - bioslen)&0xFFFFFFFF
535
536 def parse_sections(self):
537 hdrlen = ctypes.sizeof(self.header)
538 pos = self.filepos
539 pos1 = pos + hdrlen
540 endpos = self.get_endpos()
541 self.sections = []
542 self.secmap = {}
543 while pos1 < endpos:
544 sh = get_struct(self.infile, pos1, FfsSectionHeader)
545 sh.filepos = pos1
546 self.sections.append(sh)
547 self.secmap[sh.Type] = sh
548 pos1 += sh.size()
549
550 def sect_data(self, stype):
551 sh = self.secmap[stype]
552 pos1 = sh.filepos
553 return self.infile[pos1 + ctypes.sizeof(sh):pos1 + sh.size()]
554
555 def raw_data(self):
556 datapos = self.filepos + ctypes.sizeof(self.header)
557 endpos = self.get_endpos()
558 return self.infile[datapos:endpos]
559
560 def pprint(self, bioslen, detailed = False):
561 if detailed:
562 print "ROM address: %08X, File offset: %08X" % (self.get_romaddr(bioslen), self.filepos)
563 print "Header:"
564 self.header.pprint()
565 addr = self.get_romaddr(bioslen)
566 enda = addr + self.header.size() - 1
567 dsize = self.header.size() - ctypes.sizeof(self.header)
568 namestr = self.header.name2str()
569 if namestr[0] == '_' and namestr[1] in LETTER_FILE_TYPE and namestr[2:].isdigit():
570 namestr = "%s (%s)" % (namestr, LETTER_FILE_TYPE[namestr[1]])
571
572 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)
573 print s
574 if self.header.is_sectioned():
575 self.parse_sections()
576 if detailed:
577 for i in range(len(self.sections)):
578 print " section %d:" % i
579 self.sections[i].pprint()
580 # unhandled sections
581 us = set(self.secmap.keys())
582 if SECTION_COMPRESSION in self.secmap:
583 print " compressed payload"
584 us.remove(SECTION_COMPRESSION)
585 if SECTION_BIN in self.secmap:
586 print " raw payload"
587 us.remove(SECTION_BIN)
588 if SECTION_PE32 in self.secmap:
589 print " PE32 payload"
590 us.remove(SECTION_PE32)
591 if SECTION_DXE_DEPEX in self.secmap:
592 print " DXE DEPEX present"
593 us.remove(SECTION_DXE_DEPEX)
594 if SECTION_PEI_DEPEX in self.secmap:
595 print " PEI DEPEX present"
596 us.remove(SECTION_PEI_DEPEX)
597 if SECTION_USER_INTERFACE in self.secmap:
598 data = self.sect_data(SECTION_USER_INTERFACE)
599 name = data.decode('utf-16le').rstrip('\0')
600 print " UI name: %s" % name
601 us.remove(SECTION_USER_INTERFACE)
602 if SECTION_PLACE16 in self.secmap:
603 data = self.sect_data(SECTION_PLACE16)
604 offset = 0xFFFFFFFF
605 segment = 0xFFFF
606 if len(data) == 4:
607 addr = struct.unpack("<I", data)[0]
608 seg = addr >> 4
609 offset = addr - (seg<<4)
610 print " PLACE16: %08X (%04X:%04X)" % (addr, seg, offset)
611 elif len(data) == 6:
612 offset, segment = struct.unpack("<IH", data)
613 print " PLACE16: %04X:%08X" % (segment, offset)
614 else:
615 print " PLACE16 bad data length(%d)" % len(data)
616 us.remove(SECTION_PLACE16)
617 if SECTION_XIP16 in self.secmap:
618 xip = get_struct(self.sect_data(SECTION_XIP16), 0, Xip1632)
619 print " XIP16 file '%s', entry = 0x%08X, subsystem = %02X" % (xip.name2str(), xip.Offset, xip.Subsystem)
620 us.remove(SECTION_XIP16)
621 if SECTION_XIP32 in self.secmap:
622 xip = get_struct(self.sect_data(SECTION_XIP32), 0, Xip1632)
623 print " XIP32 file '%s', entry = 0x%08X, subsystem = %02X" % (xip.name2str(), xip.Offset, xip.Subsystem)
624 us.remove(SECTION_XIP32)
625 if us:
626 print " unhandled sections:", map(hex,us)
627
628def parseFfsFile(infile, pos, bioslen, volno = None):
629 ff = get_struct(infile, pos, FfsFileHeader)
630 if ff.size() == 0 or ff.FileState != 0xF8:
631 return
632 ff.pprint()
633 pos0 = pos
634 hdrlen = ctypes.sizeof(ff)
635 pos1 = pos + hdrlen
636 pos += ff.size()
637 fname = ff.name2str()
638 if ff.Type in [FILETYPE_PEIMAIN, FILETYPE_SECTION, FILETYPE_CEIMAIN,
639 FILETYPE_DXEMAIN, FILETYPE_PEI, FILETYPE_DXE, FILETYPE_CEI,
640 FILETYPE_BB, FILETYPE_SDXE, FILETYPE_DXESDXE]:
641 ss = parseSectionedFile(infile, pos1, pos)
642 for i in range(len(ss)):
643 fname2 = "%08X_%s_S%d_%s.bin" % (pos0, fname, i, ss[i].sectype())
644 if volno != None:
645 fname2 = "V%d_" % volno + fname2
646 fname2 = replace_bad(fname2, '\/:*?"<>|')
647 print " ==> %s" % fname2
648 open(fname2,"wb").write(ss[i].Data)
649
650 if ff.Type == FILETYPE_BIN:
651 if ff.name2str() == "volumedir.bin2" and volno == None:
652 vols = parseVolumeDir2(infile, pos1, pos, False)
653 for i in range(len(vols)):
654 v = vols[i]
655 s = (v.VolStart + bioslen) & 0xFFFFFFFF # v.VolStart - 0xFF800000
656 print "\n***\nVolume %d: address %08X, file offset %08X, size %08X\n***\n" % (i, v.VolStart, s, v.VolSize)
657 parse_range(infile, s, s + v.VolSize, bioslen, i)
658 vollist.extend(vols)
659
660 fname2 = "%08X_%s.bin" % (pos0, fname)
661 if volno != None:
662 fname2 = "V%d_" % volno + fname2
663
664 fname2 = replace_bad(fname2, '\/:*?"<>|')
665
666 print " ==> %s" % fname2
667 dlen = ff.size() - hdrlen
668 data = infile[pos1:pos1 + dlen]
669 open(fname2,"wb").write(data)
670 return pos
671
672class FfsVolume:
673 def __init__(self, infile, pos, size, guid, ord):
674 self.filepos = pos
675 self.infile = infile
676 self.size = size
677 self.guid = guid
678 self.volInfo = None
679 self.ord = ord
680 self.files = []
681
682 def parse_volinfo(self, data):
683 self.volInfo = get_struct(data, 0, VolumeInfoBinHeader)
684 self.volInfo.parse_extras(data, 0)
685
686 def parse_files(self):
687 pos = self.filepos
688 maxpos = pos + self.size
689 while True:
690 while pos < maxpos and self.infile[pos] in ['\xFF', '\x00']:
691 pos += 1
roxfan754a7d72012-09-13 01:11:35 +0400692 if pos >= maxpos:
693 break
Alexander Couzensb66fd5b2016-03-25 14:21:35 +0700694 if self.infile[pos] != '\xF8':
695 return
roxfan754a7d72012-09-13 01:11:35 +0400696 ff = FfsFile(self.infile, pos)
697 pos = ff.get_endpos()
698 self.files.append(ff)
699 if ff.header.Type == FILETYPE_BIN and ff.header.name2str() == "volumeinfo.bin":
700 self.parse_volinfo(ff.raw_data())
701 # skip padding after file
702 while pos < maxpos and infile[pos] in ['\xFF', '\x00']:
703 pos += 1
704
705 def pprint(self, bioslen, verbose):
706 if self.guid[0] == 0xBA:
707 self.parse_files()
708 addr = (self.filepos - bioslen)&0xFFFFFFFF
709 namestr = strguid(self.guid)
710 gt = guid2type(self.guid)
711 if gt == None:
712 gt = "VOL"
713 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)
714 # print "\n***\nVolume address %08X, file offset %08X, size %08X\n***\n" % (addr, self.filepos, self.size)
715 print s
716 if self.volInfo:
717 self.volInfo.pprint(verbose)
718 for f in self.files:
719 f.pprint(bioslen, verbose)
720
721def parse_range(infile, pos, maxpos, bioslen, volno = None):
722 start = pos
723 while pos < maxpos:
724 nextpos = parseFfsFile(infile, pos, bioslen, volno)
725 if nextpos == None:
726 break
727 print "\nfile offset: %08X" % pos
728 pos = nextpos
729 while pos < maxpos and infile[pos] in ['\xFF', '\x00']:
730 pos += 1
731 if volno != None and start == pos and pos < maxpos:
732 fname = "V%d_%08X.bin" % (volno, pos)
733 print " ==> %s" % fname
734 open(fname,"wb").write(infile[pos:maxpos])
735
736class PhoenixModuleHeader(ctypes.LittleEndianStructure):
737 _pack_ = 1
738 _fields_ = [
739 ("Signature", uint32_t), #
740 ("Signature2", uint8_t*3), #
741 ("Id", uint8_t), #
742 ("Type", uint8_t), #
743 ("HeadLen", uint8_t), #
744 ("Compression", uint8_t), #
745 ("Address", uint32_t),
746 ("ExpLen", uint32_t),
747 ("FragLen", uint32_t),
748 ("NextFrag", uint32_t),
749 ]
750
751 def pprint(self):
752 print "Signature: 0x%08X" % self.Signature
753 print "Id: %d" % self.Id
754 print "Type: %02X" % self.Type
755 print "HeadLen: %02X" % self.HeadLen
756 print "Compression: %d" % self.Compression
757 addr = self.Address
758 seg = addr >> 4
759 offset = addr - (seg<<4)
760 print "Address: %08X (%04X:%04X)" % (addr, seg, offset)
761 print "ExpLen: %X" % (self.ExpLen)
762 print "FragLen: %X" % (self.FragLen)
763 print "NextFrag: %X" % (self.NextFrag)
764
765"""
766 struct PhoenixModule {
767 uint32_t Signature;
768 uint8_t Signature2[3];
769 uint8_t Id;
770 uint8_t Type;
771 uint8_t HeadLen;
772 uint8_t Compression;
773 uint16_t Offset;
774 uint16_t Segment;
775 uint32_t ExpLen;
776 uint32_t FragLength;
777 uint32_t NextFrag;
778 } *Module;
779"""
780
781def parse_trailer(infile, pos, maxpos):
782 start = pos
783 i = 0
784 while pos < maxpos:
785 if infile[pos:pos+4] == "BC\xD6\xF1":
786 modhdr = get_struct(infile, pos, PhoenixModuleHeader)
787 modhdr.pprint()
788 pos += modhdr.HeadLen
789 addr = modhdr.Address
790 seg = addr >> 4
791 offset = addr - (seg<<4)
792 fname = "%08X_%04X_%04X.bin" % (pos, seg, offset)
793 print " ==> %s" % fname
794 open(fname,"wb").write(infile[pos:pos+modhdr.ExpLen])
795 pos += modhdr.ExpLen
796 elif infile[pos:pos+8] == "FLASHDXE":
797 pos += 8
798 while pos < maxpos:
799 nextpos = parseFfsFile(infile, pos, maxpos)
800 if nextpos == None:
801 break
802 print "\nfile offset: %08X" % pos
803 pos = nextpos
804 else:
805 print "\nunknown header at %08X" % pos
806 break
807
808
809if len(sys.argv) < 1:
810 print "Usage: phoenix_scan.py BIOS.BIN [-d] [-t] [-v]"
811 print "-d: extract modules into files"
812 print "-t: extract the trailer parts"
813 print "-v: verbose info about flash layout"
814 sys.exit(1)
815
816inf = open(sys.argv[1],"rb")
817infile = inf.read()
818pos = infile.find("volumedi\xFFr.bin2")
819if pos != -1:
820 pos -= 8
821 print "Found Volume Directory v2 at %08X\n" % (pos)
822else:
823 print "Volume dir not found; FFS dump won't be available"
824 #sys.exit(1)
825
826dump_all = False
827dump_trailer = False
828print_verbose = False
829for a in sys.argv[2:]:
830 if a == '-d':
831 dump_all = True
832 elif a == '-t':
833 dump_trailer = True
834 elif a == '-v':
835 print_verbose = True
836
837alllen = len(infile)
838bioslen = alllen & 0xFFFF0000
839if dump_all and pos != -1:
840 parse_range(infile, pos, alllen, bioslen)
841if dump_trailer:
842 parse_trailer(infile, bioslen, alllen)
843
844if pos != -1:
845 voldir = FfsFile(infile, pos)
846 vols = parseVolumeDir2(voldir.raw_data(), 0, 0, print_verbose)
847 ffvols = []
848 for i in range(len(vols)):
849 v = vols[i]
850 pos = (v.VolStart + bioslen) & 0xFFFFFFFF
851 ffv = FfsVolume(infile, pos, v.VolSize, v.Guid, i)
852 ffvols.append(ffv)
853
854 print "FFS contents:"
855 for fv in ffvols:
856 print ""
857 fv.pprint(bioslen, print_verbose)