-
Notifications
You must be signed in to change notification settings - Fork 158
/
Dell_PFS_Extract.py
1238 lines (868 loc) · 51 KB
/
Dell_PFS_Extract.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
#!/usr/bin/env python3 -B
# coding=utf-8
"""
Dell PFS Extract
Dell PFS Update Extractor
Copyright (C) 2018-2024 Plato Mavropoulos
"""
import contextlib
import ctypes
import io
import lzma
import os
import zlib
from common.checksums import get_chk_8_xor
from common.comp_szip import is_szip_supported, szip_decompress
from common.num_ops import get_ordinal
from common.path_ops import del_dirs, get_path_files, make_dirs, path_name, path_parent, path_stem, safe_name
from common.patterns import PAT_DELL_FTR, PAT_DELL_HDR, PAT_DELL_PKG
from common.struct_ops import Char, get_struct, UInt8, UInt16, UInt32, UInt64
from common.system import printer
from common.templates import BIOSUtility
from common.text_ops import file_to_bytes
from AMI_PFAT_Extract import IntelBiosGuardHeader, parse_bg_script, parse_bg_sign, PFAT_INT_SIG_MAX_LEN
TITLE = 'Dell PFS Update Extractor v7.1'
class DellPfsHeader(ctypes.LittleEndianStructure):
""" Dell PFS Header Structure """
_pack_ = 1
_fields_ = [
('Tag', Char * 8), # 0x00
('HeaderVersion', UInt32), # 0x08
('PayloadSize', UInt32), # 0x0C
# 0x10
]
def struct_print(self, padd):
""" Display structure information """
printer(['Header Tag :', self.Tag.decode('utf-8')], padd, False)
printer(['Header Version:', self.HeaderVersion], padd, False)
printer(['Payload Size :', f'0x{self.PayloadSize:X}'], padd, False)
class DellPfsFooter(ctypes.LittleEndianStructure):
""" Dell PFS Footer Structure """
_pack_ = 1
_fields_ = [
('PayloadSize', UInt32), # 0x00
('Checksum', UInt32), # 0x04 ~CRC32 w/ Vector 0
('Tag', Char * 8), # 0x08
# 0x10
]
def struct_print(self, padd):
""" Display structure information """
printer(['Payload Size :', f'0x{self.PayloadSize:X}'], padd, False)
printer(['Payload Checksum:', f'0x{self.Checksum:08X}'], padd, False)
printer(['Footer Tag :', self.Tag.decode('utf-8')], padd, False)
class DellPfsEntryBase(ctypes.LittleEndianStructure):
""" Dell PFS Entry Base Structure """
_pack_ = 1
_fields_ = [
('GUID', UInt32 * 4), # 0x00 Little Endian
('HeaderVersion', UInt32), # 0x10 1 or 2
('VersionType', UInt8 * 4), # 0x14
('Version', UInt16 * 4), # 0x18
('Reserved', UInt64), # 0x20
('DataSize', UInt32), # 0x28
('DataSigSize', UInt32), # 0x2C
('DataMetSize', UInt32), # 0x30
('DataMetSigSize', UInt32), # 0x34
# 0x38 (parent class, base)
]
def struct_print(self, padd):
""" Display structure information """
guid = f'{int.from_bytes(self.GUID, "little"):0{0x10 * 2}X}'
unknown = f'{int.from_bytes(self.Unknown, "little"):0{len(self.Unknown) * 8}X}'
version = get_entry_ver(self.Version, self.VersionType)
printer(['Entry GUID :', guid], padd, False)
printer(['Entry Version :', self.HeaderVersion], padd, False)
printer(['Payload Version :', version], padd, False)
printer(['Reserved :', f'0x{self.Reserved:X}'], padd, False)
printer(['Payload Data Size :', f'0x{self.DataSize:X}'], padd, False)
printer(['Payload Signature Size :', f'0x{self.DataSigSize:X}'], padd, False)
printer(['Metadata Data Size :', f'0x{self.DataMetSize:X}'], padd, False)
printer(['Metadata Signature Size:', f'0x{self.DataMetSigSize:X}'], padd, False)
printer(['Unknown :', f'0x{unknown}'], padd, False)
class DellPfsEntryR1(DellPfsEntryBase):
""" Dell PFS Entry Revision 1 Structure """
_pack_ = 1
_fields_ = [
('Unknown', UInt32 * 4), # 0x38
# 0x48 (child class, R1)
]
class DellPfsEntryR2(DellPfsEntryBase):
""" Dell PFS Entry Revision 2 Structure """
_pack_ = 1
_fields_ = [
('Unknown', UInt32 * 8), # 0x38
# 0x58 (child class, R2)
]
class DellPfsInfo(ctypes.LittleEndianStructure):
""" Dell PFS Information Header Structure """
_pack_ = 1
_fields_ = [
('HeaderVersion', UInt32), # 0x00
('GUID', UInt32 * 4), # 0x04 Little Endian
# 0x14
]
def struct_print(self, padd):
""" Display structure information """
guid = f'{int.from_bytes(self.GUID, "little"):0{0x10 * 2}X}'
printer(['Info Version:', self.HeaderVersion], padd, False)
printer(['Entry GUID :', guid], padd, False)
class DellPfsName(ctypes.LittleEndianStructure):
""" Dell PFS FileName Header Structure """
_pack_ = 1
_fields_ = [
('Version', UInt16 * 4), # 0x00
('VersionType', UInt8 * 4), # 0x08
('CharacterCount', UInt16), # 0x0C UTF-16 2-byte Characters
# 0x0E
]
def struct_print(self, padd, name):
""" Display structure information """
version = get_entry_ver(self.Version, self.VersionType)
printer(['Payload Version:', version], padd, False)
printer(['Character Count:', self.CharacterCount], padd, False)
printer(['Payload Name :', name], padd, False)
class DellPfsMetadata(ctypes.LittleEndianStructure):
""" Dell PFS Metadata Header Structure """
_pack_ = 1
_fields_ = [
('ModelIDs', Char * 501), # 0x000
('FileName', Char * 100), # 0x1F5
('FileVersion', Char * 33), # 0x259
('Date', Char * 33), # 0x27A
('Brand', Char * 80), # 0x29B
('ModelFile', Char * 80), # 0x2EB
('ModelName', Char * 100), # 0x33B
('ModelVersion', Char * 33), # 0x39F
# 0x3C0
]
def struct_print(self, padd):
""" Display structure information """
printer(['Model IDs :', self.ModelIDs.decode('utf-8').removesuffix(',END')], padd, False)
printer(['File Name :', self.FileName.decode('utf-8')], padd, False)
printer(['File Version :', self.FileVersion.decode('utf-8')], padd, False)
printer(['Date :', self.Date.decode('utf-8')], padd, False)
printer(['Brand :', self.Brand.decode('utf-8')], padd, False)
printer(['Model File :', self.ModelFile.decode('utf-8')], padd, False)
printer(['Model Name :', self.ModelName.decode('utf-8')], padd, False)
printer(['Model Version:', self.ModelVersion.decode('utf-8')], padd, False)
class DellPfsPfatMetadata(ctypes.LittleEndianStructure):
""" Dell PFS BIOS Guard Metadata Structure """
_pack_ = 1
_fields_ = [
('Address', UInt32), # 0x00
('Unknown0', UInt32), # 0x04
('Offset', UInt32), # 0x08 Matches BG Script > I0
('DataSize', UInt32), # 0x0C Matches BG Script > I2 & Header > Data Size
('Unknown1', UInt32), # 0x10
('Unknown2', UInt32), # 0x14
('Unknown3', UInt8), # 0x18
# 0x19
]
def struct_print(self, padd):
""" Display structure information """
printer(['Address :', f'0x{self.Address:X}'], padd, False)
printer(['Unknown 0:', f'0x{self.Unknown0:X}'], padd, False)
printer(['Offset :', f'0x{self.Offset:X}'], padd, False)
printer(['Length :', f'0x{self.DataSize:X}'], padd, False)
printer(['Unknown 1:', f'0x{self.Unknown1:X}'], padd, False)
printer(['Unknown 2:', f'0x{self.Unknown2:X}'], padd, False)
printer(['Unknown 3:', f'0x{self.Unknown3:X}'], padd, False)
def is_pfs_pkg(input_file):
"""
The Dell ThinOS PKG update images usually contain multiple sections.
Each section starts with a 0x30 header, which begins with pattern 72135500.
The section length is found at 0x10-0x14 and its (optional) MD5 hash at 0x20-0x30.
Section data can be raw or LZMA2 (7zXZ) compressed. The latter contains the PFS update image.
"""
input_buffer = file_to_bytes(input_file)
return PAT_DELL_PKG.search(input_buffer)
def is_pfs_hdr(input_file):
"""
The Dell PFS update images usually contain multiple sections.
Each section is zlib-compressed with header pattern ********++EEAA761BECBB20F1E651--789C,
where ******** is the zlib stream size, ++ is the section type and -- the header Checksum XOR 8.
The "Firmware" section has type AA and its files are stored in PFS format.
The "Utility" section has type BB and its files are stored in PFS, BIN or 7z formats.
"""
input_buffer = file_to_bytes(input_file)
return bool(PAT_DELL_HDR.search(input_buffer))
def is_pfs_ftr(input_file):
"""
Each section is followed by the footer pattern ********EEAAEE8F491BE8AE143790--,
where ******** is the zlib stream size and ++ the footer Checksum XOR 8.
"""
input_buffer = file_to_bytes(input_file)
return bool(PAT_DELL_FTR.search(input_buffer))
def is_dell_pfs(input_file):
""" Check if input is Dell PFS/PKG image """
input_buffer = file_to_bytes(input_file)
is_pkg = is_pfs_pkg(input_buffer)
is_hdr = is_pfs_hdr(input_buffer)
is_ftr = is_pfs_ftr(input_buffer)
return bool(is_pkg or is_hdr and is_ftr)
def pfs_pkg_parse(input_file, extract_path, padding=0, structure=True, advanced=True):
""" Parse & Extract Dell PFS Update image """
input_buffer = file_to_bytes(input_file)
make_dirs(extract_path, delete=True)
is_dell_pkg = is_pfs_pkg(input_buffer)
if is_dell_pkg:
pfs_results = thinos_pkg_extract(input_buffer, extract_path)
else:
pfs_results = {path_stem(input_file) if os.path.isfile(input_file) else 'Image': input_buffer}
# Parse each Dell PFS image contained in the input file
for pfs_index, (pfs_name, pfs_buffer) in enumerate(pfs_results.items(), start=1):
# At ThinOS PKG packages, multiple PFS images may be included in separate model-named folders
pfs_path = os.path.join(extract_path, f'{pfs_index} {pfs_name}') if is_dell_pkg else extract_path
# Parse each PFS ZLIB section
for zlib_offset in get_section_offsets(pfs_buffer):
# Call the PFS ZLIB section parser function
pfs_section_parse(pfs_buffer, zlib_offset, pfs_path, pfs_name, pfs_index, 1, False,
padding, structure, advanced)
def thinos_pkg_extract(input_file, extract_path):
""" Extract Dell ThinOS PKG 7zXZ """
input_buffer = file_to_bytes(input_file)
# Initialize PFS results (Name: Buffer)
pfs_results = {}
# Search input image for ThinOS PKG 7zXZ header
thinos_pkg_match = PAT_DELL_PKG.search(input_buffer)
lzma_len_off = thinos_pkg_match.start() + 0x10
lzma_len_int = int.from_bytes(input_buffer[lzma_len_off:lzma_len_off + 0x4], 'little')
lzma_bin_off = thinos_pkg_match.end() - 0x5
lzma_bin_dat = input_buffer[lzma_bin_off:lzma_bin_off + lzma_len_int]
# Check if the compressed 7zXZ stream is complete
if len(lzma_bin_dat) != lzma_len_int:
return pfs_results
working_path = os.path.join(extract_path, 'THINOS_PKG_TEMP')
make_dirs(working_path, delete=True)
pkg_tar_path = os.path.join(working_path, 'THINOS_PKG.TAR')
with open(pkg_tar_path, 'wb') as pkg_payload:
pkg_payload.write(lzma.decompress(lzma_bin_dat))
if is_szip_supported(pkg_tar_path, 0, args=['-tTAR'], check=True, silent=True):
if szip_decompress(pkg_tar_path, working_path, 'TAR', 0, args=['-tTAR'], check=True, silent=True) == 0:
os.remove(pkg_tar_path)
else:
return pfs_results
else:
return pfs_results
for pkg_file in get_path_files(working_path):
if is_pfs_hdr(pkg_file):
pfs_name = path_name(path_parent(pkg_file))
pfs_results.update({pfs_name: file_to_bytes(pkg_file)})
del_dirs(working_path)
return pfs_results
def get_section_offsets(buffer):
""" Get PFS ZLIB Section Offsets """
pfs_zlib_list = [] # Initialize PFS ZLIB offset list
pfs_zlib_init = list(PAT_DELL_HDR.finditer(buffer))
if not pfs_zlib_init:
return pfs_zlib_list # No PFS ZLIB detected
# Remove duplicate/nested PFS ZLIB offsets
for zlib_c in pfs_zlib_init:
is_duplicate = False # Initialize duplicate/nested PFS ZLIB offset
for zlib_o in pfs_zlib_init:
zlib_o_size = int.from_bytes(buffer[zlib_o.start() - 0x5:zlib_o.start() - 0x1], 'little')
# If current PFS ZLIB offset is within another PFS ZLIB range (start-end), set as duplicate
if zlib_o.start() < zlib_c.start() < zlib_o.start() + zlib_o_size:
is_duplicate = True
if not is_duplicate:
pfs_zlib_list.append(zlib_c.start())
return pfs_zlib_list
def pfs_section_parse(zlib_data, zlib_start, extract_path, pfs_name, pfs_index, pfs_count, is_rec,
padding=0, structure=True, advanced=True):
""" Dell PFS ZLIB Section Parser """
is_zlib_error = False # Initialize PFS ZLIB-related error state
section_type = zlib_data[zlib_start - 0x1] # Byte before PFS ZLIB Section pattern is Section Type (e.g. AA, BB)
section_name = {0xAA: 'Firmware', 0xBB: 'Utilities'}.get(section_type, f'Unknown ({section_type:02X})')
# Show extraction complete message for each main PFS ZLIB Section
printer(f'Extracting Dell PFS {pfs_index} > {pfs_name} > {section_name}', padding)
# Set PFS ZLIB Section extraction sub-directory path
section_path = os.path.join(extract_path, safe_name(section_name))
# Create extraction sub-directory and delete old (if present, not in recursions)
make_dirs(section_path, delete=(not is_rec), parents=True, exist_ok=True)
# Store the compressed zlib stream start offset
compressed_start = zlib_start + 0xB
# Store the PFS ZLIB section header start offset
header_start = zlib_start - 0x5
# Store the PFS ZLIB section header contents (16 bytes)
header_data = zlib_data[header_start:compressed_start]
# Check if the PFS ZLIB section header Checksum XOR 8 is valid
if get_chk_8_xor(header_data[:0xF]) != header_data[0xF]:
printer('Error: Invalid Dell PFS ZLIB section Header Checksum!', padding)
is_zlib_error = True
# Store the compressed zlib stream size from the header contents
compressed_size_hdr = int.from_bytes(header_data[:0x4], 'little')
# Store the compressed zlib stream end offset
compressed_end = compressed_start + compressed_size_hdr
# Store the compressed zlib stream contents
compressed_data = zlib_data[compressed_start:compressed_end]
# Check if the compressed zlib stream is complete, based on header
if len(compressed_data) != compressed_size_hdr:
printer('Error: Incomplete Dell PFS ZLIB section data (Header)!', padding)
is_zlib_error = True
# Store the PFS ZLIB section footer contents (16 bytes)
footer_data = zlib_data[compressed_end:compressed_end + 0x10]
# Check if PFS ZLIB section footer was found in the section
if not is_pfs_ftr(footer_data):
printer('Error: This Dell PFS ZLIB section is corrupted!', padding)
is_zlib_error = True
# Check if the PFS ZLIB section footer Checksum XOR 8 is valid
if get_chk_8_xor(footer_data[:0xF]) != footer_data[0xF]:
printer('Error: Invalid Dell PFS ZLIB section Footer Checksum!', padding)
is_zlib_error = True
# Store the compressed zlib stream size from the footer contents
compressed_size_ftr = int.from_bytes(footer_data[:0x4], 'little')
# Check if the compressed zlib stream is complete, based on footer
if compressed_size_ftr != compressed_size_hdr:
printer('Error: Incomplete Dell PFS ZLIB section data (Footer)!', padding)
is_zlib_error = True
# Decompress PFS ZLIB section payload
try:
if is_zlib_error:
raise ValueError('ZLIB_ERROR_OCCURED') # ZLIB errors are critical
section_data = zlib.decompress(compressed_data) # ZLIB decompression
except Exception as error: # pylint: disable=broad-except
printer(f'Error: Failed to decompress PFS ZLIB section: {error}!', padding)
section_data = zlib_data # Fallback to raw ZLIB data upon critical error
# Call the PFS Extract function on the decompressed PFS ZLIB Section
pfs_extract(section_data, pfs_index, pfs_name, pfs_count, section_path, padding, structure, advanced)
def pfs_extract(buffer, pfs_index, pfs_name, pfs_count, extract_path, padding=0, structure=True, advanced=True):
""" Parse & Extract Dell PFS Volume """
# Show PFS Volume indicator
if structure:
printer('PFS Volume:', padding)
# Get PFS Header Structure values
pfs_hdr = get_struct(buffer, 0, DellPfsHeader)
# Validate that a PFS Header was parsed
if pfs_hdr.Tag != b'PFS.HDR.':
printer('Error: PFS Header could not be found!', padding + 4)
return # Critical error, abort
# Show PFS Header Structure info
if structure:
printer('PFS Header:\n', padding + 4)
pfs_hdr.struct_print(padding + 8)
# Validate that a known PFS Header Version was encountered
chk_hdr_ver(pfs_hdr.HeaderVersion, 'PFS', padding + 8)
# Get PFS Payload Data
pfs_payload = buffer[PFS_HEAD_LEN:PFS_HEAD_LEN + pfs_hdr.PayloadSize]
# Parse all PFS Payload Entries/Components
entry_index = 1 # Index number of each PFS Entry
entry_start = 0 # Increasing PFS Entry starting offset
entries_all = [] # Storage for each PFS Entry details
filename_info = [] # Buffer for FileName Information Entry Data
signature_info = [] # Buffer for Signature Information Entry Data
pfs_entry_struct, pfs_entry_size = get_pfs_entry(pfs_payload, entry_start) # Get PFS Entry Info
while len(pfs_payload[entry_start:entry_start + pfs_entry_size]) == pfs_entry_size:
# Analyze PFS Entry Structure and get relevant info
_, entry_version, entry_guid, entry_data, entry_data_sig, entry_met, entry_met_sig, next_entry = \
parse_pfs_entry(pfs_payload, entry_start, pfs_entry_size, pfs_entry_struct, 'PFS Entry', padding, structure)
entry_type = 'OTHER' # Adjusted later if PFS Entry is Zlib, PFAT, PFS Info, Model Info
# Get PFS Information from the relevant (hardcoded) PFS Entry GUIDs
if entry_guid in ['E0717CE3A9BB25824B9F0DC8FD041960', 'B033CB16EC9B45A14055F80E4D583FD3']:
entry_type = 'NAME_INFO'
filename_info = entry_data
# Get Model Information from the relevant (hardcoded) PFS Entry GUID
elif entry_guid == '6F1D619A22A6CB924FD4DA68233AE3FB':
entry_type = 'MODEL_INFO'
# Get Signature Information from the relevant (hardcoded) PFS Entry GUID
elif entry_guid == 'D086AFEE3ADBAEA94D5CED583C880BB7':
entry_type = 'SIG_INFO'
signature_info = entry_data
# Get Nested PFS from the relevant (hardcoded) PFS Entry GUID
elif entry_guid == '900FAE60437F3AB14055F456AC9FDA84':
entry_type = 'NESTED_PFS' # Nested PFS are usually zlib-compressed so it might change to 'ZLIB' later
# Store all relevant PFS Entry details
entries_all.append([entry_index, entry_guid, entry_version, entry_type,
entry_data, entry_data_sig, entry_met, entry_met_sig])
entry_index += 1 # Increase PFS Entry Index number for user-friendly output and name duplicates
entry_start = next_entry # Next PFS Entry starts after PFS Entry Metadata Signature
# Parse all PFS Information Entries/Descriptors
info_start = 0 # Increasing PFS Information Entry starting offset
info_all = [] # Storage for each PFS Information Entry details
while len(filename_info[info_start:info_start + PFS_INFO_LEN]) == PFS_INFO_LEN:
# Get PFS Information Header Structure info
filename_info_hdr = get_struct(filename_info, info_start, DellPfsInfo)
# Show PFS Information Header Structure info
if structure:
printer('PFS Filename Information Header:\n', padding + 4)
filename_info_hdr.struct_print(padding + 8)
# Validate that a known PFS Information Header Version was encountered
if filename_info_hdr.HeaderVersion != 1:
printer(f'Error: Unknown PFS Filename Information Header Version {filename_info_hdr.HeaderVersion}!',
padding + 8)
break # Skip PFS Information Entries/Descriptors in case of unknown PFS Information Header Version
# Get PFS Information Header GUID in Big Endian format, in order
# to match each Info to the equivalent stored PFS Entry details.
entry_guid = f'{int.from_bytes(filename_info_hdr.GUID, "little"):0{0x10 * 2}X}'
# Get PFS FileName Structure values
entry_info_mod = get_struct(filename_info, info_start + PFS_INFO_LEN, DellPfsName)
# The PFS FileName Structure is not complete by itself. The size of the last field (Entry Name) is determined
# from CharacterCount multiplied by 2 due to usage of UTF-16 2-byte Characters. Any Entry Name leading and/or
# trailing space/null characters are stripped and Windows reserved/illegal filename characters are replaced.
name_start = info_start + PFS_INFO_LEN + PFS_NAME_LEN # PFS Entry's FileName start offset
name_size = entry_info_mod.CharacterCount * 2 # PFS Entry's FileName buffer total size
name_data = filename_info[name_start:name_start + name_size] # PFS Entry's FileName buffer
entry_name = safe_name(name_data.decode('utf-16').strip()) # PFS Entry's FileName value
# Show PFS FileName Structure info
if structure:
printer('PFS FileName Entry:\n', padding + 8)
entry_info_mod.struct_print(padding + 12, entry_name)
# Get PFS FileName Version string via "Version" and "VersionType" fields
# PFS FileName Version string must be preferred over PFS Entry's Version
entry_version = get_entry_ver(entry_info_mod.Version, entry_info_mod.VersionType)
# Store all relevant PFS FileName details
info_all.append([entry_guid, entry_name, entry_version])
# The next PFS Information Header starts after the calculated FileName size
# Two space/null characters seem to always exist after each FileName value
info_start += (PFS_INFO_LEN + PFS_NAME_LEN + name_size + 0x2)
# Parse Nested PFS Metadata when its PFS Information Entry is missing
for entry in entries_all:
_, entry_guid, _, entry_type, _, _, entry_metadata, _ = entry
if entry_type == 'NESTED_PFS' and not filename_info:
# When PFS Information Entry exists, Nested PFS Metadata contains only Model IDs
# When it's missing, the Metadata structure is large and contains equivalent info
if len(entry_metadata) >= PFS_META_LEN:
# Get Nested PFS Metadata Structure values
entry_info = get_struct(entry_metadata, 0, DellPfsMetadata)
# Show Nested PFS Metadata Structure info
if structure:
printer('PFS Metadata Information:\n', padding + 4)
entry_info.struct_print(padding + 8)
# As Nested PFS Entry Name, we'll use the actual PFS File Name
# Replace common Windows reserved/illegal filename characters
entry_name = safe_name(entry_info.FileName.decode('utf-8').removesuffix('.exe').removesuffix('.bin'))
# As Nested PFS Entry Version, we'll use the actual PFS File Version
entry_version = entry_info.FileVersion.decode('utf-8')
# Store all relevant Nested PFS Metadata/Information details
info_all.append([entry_guid, entry_name, entry_version])
# Re-set Nested PFS Entry Version from Metadata
entry[2] = entry_version
# Parse all PFS Signature Entries/Descriptors
sign_start = 0 # Increasing PFS Signature Entry starting offset
while len(signature_info[sign_start:sign_start + PFS_INFO_LEN]) == PFS_INFO_LEN:
# Get PFS Information Header Structure info
signature_info_hdr = get_struct(signature_info, sign_start, DellPfsInfo)
# Show PFS Information Header Structure info
if structure:
printer('PFS Signature Information Header:\n', padding + 4)
signature_info_hdr.struct_print(padding + 8)
# Validate that a known PFS Information Header Version was encountered
if signature_info_hdr.HeaderVersion != 1:
printer(f'Error: Unknown PFS Signature Information Header Version {signature_info_hdr.HeaderVersion}!',
padding + 8)
break # Skip PFS Signature Entries/Descriptors in case of unknown Header Version
# PFS Signature Entries/Descriptors have DellPfsInfo + DellPfsEntryR* + Sign Size [0x2] + Sign Data [Sig Size]
pfs_entry_struct, pfs_entry_size = get_pfs_entry(signature_info, sign_start + PFS_INFO_LEN) # PFS Entry Info
# Get PFS Entry Header Structure info
entry_hdr = get_struct(signature_info, sign_start + PFS_INFO_LEN, pfs_entry_struct)
# Show PFS Information Header Structure info
if structure:
printer('PFS Information Entry:\n', padding + 8)
entry_hdr.struct_print(padding + 12)
# Show PFS Signature Size & Data (after DellPfsEntryR*)
sign_info_start = sign_start + PFS_INFO_LEN + pfs_entry_size
sign_size = int.from_bytes(signature_info[sign_info_start:sign_info_start + 0x2], 'little')
sign_data_raw = signature_info[sign_info_start + 0x2:sign_info_start + 0x2 + sign_size]
sign_data_txt = f'{int.from_bytes(sign_data_raw, "little"):0{sign_size * 2}X}'
if structure:
printer('Signature Information:\n', padding + 8)
printer(f'Signature Size: 0x{sign_size:X}', padding + 12, False)
printer(f'Signature Data: {sign_data_txt[:32]} [...]', padding + 12, False)
# The next PFS Signature Entry/Descriptor starts after the previous Signature Data
sign_start += (PFS_INFO_LEN + pfs_entry_size + 0x2 + sign_size)
# Parse each PFS Entry Data for special types (zlib or PFAT)
for entry in entries_all:
_, _, _, entry_type, entry_data, _, _, _ = entry
# Very small PFS Entry Data cannot be of special type
if len(entry_data) < PFS_HEAD_LEN:
continue
# Check if PFS Entry contains zlib-compressed sub-PFS Volume
pfs_zlib_offsets = get_section_offsets(entry_data)
# Check if PFS Entry contains sub-PFS Volume with PFAT Payload
is_pfat = False # Initial PFAT state for sub-PFS Entry
_, pfat_entry_size = get_pfs_entry(entry_data, PFS_HEAD_LEN) # Get possible PFS PFAT Entry Size
pfat_hdr_off = PFS_HEAD_LEN + pfat_entry_size # Possible PFAT Header starts after PFS Header & Entry
pfat_entry_hdr = get_struct(entry_data, 0, DellPfsHeader) # Possible PFS PFAT Entry
if len(entry_data) - pfat_hdr_off >= PFAT_HDR_LEN:
pfat_hdr = get_struct(entry_data, pfat_hdr_off, IntelBiosGuardHeader)
is_pfat = pfat_hdr.get_platform_id().upper().startswith('DELL')
# Parse PFS Entry which contains sub-PFS Volume with PFAT Payload
if pfat_entry_hdr.Tag == b'PFS.HDR.' and is_pfat:
entry_type = 'PFAT' # Re-set PFS Entry Type from OTHER to PFAT, to use such info afterwards
entry_data = parse_pfat_pfs(pfat_entry_hdr, entry_data, padding, structure) # Parse sub-PFS PFAT Volume
# Parse PFS Entry which contains zlib-compressed sub-PFS Volume
elif pfs_zlib_offsets:
entry_type = 'ZLIB' # Re-set PFS Entry Type from OTHER to ZLIB, to use such info afterwards
pfs_count += 1 # Increase the count/index of parsed main PFS structures by one
# Parse each sub-PFS ZLIB Section
for offset in pfs_zlib_offsets:
# Get the Name of the zlib-compressed full PFS structure via the already stored PFS Information
# The zlib-compressed full PFS structure(s) are used to contain multiple FW (CombineBiosNameX)
# When zlib-compressed full PFS structure(s) exist within the main/first full PFS structure,
# its PFS Information should contain their names (CombineBiosNameX). Since the main/first
# full PFS structure has count/index 1, the rest start at 2+ and thus, their PFS Information
# names can be retrieved in order by subtracting 2 from the main/first PFS Information values
sub_pfs_name = f'{info_all[pfs_count - 2][1]} v{info_all[pfs_count - 2][2]}' if info_all else ' UNKNOWN'
# Set the sub-PFS output path (create sub-folders for each sub-PFS and its ZLIB sections)
sub_pfs_path = os.path.join(extract_path, f'{pfs_count} {safe_name(sub_pfs_name)}')
# Recursively call the PFS ZLIB Section Parser function for the sub-PFS Volume (pfs_index = pfs_count)
pfs_section_parse(entry_data, offset, sub_pfs_path, sub_pfs_name, pfs_count, pfs_count,
True, padding + 4, structure, advanced)
# Adjust PFS Entry Data after parsing PFAT (same ZLIB raw data, not stored afterwards)
entry[4] = entry_data
# Adjust PFS Entry Type from OTHER to PFAT or ZLIB (ZLIB is ignored at file extraction)
entry[3] = entry_type
# Name & Store each PFS Entry/Component Data, Data Signature, Metadata, Metadata Signature
for entry in entries_all:
file_index, file_guid, file_version, file_type, file_data, file_data_sig, file_meta, file_meta_sig = entry
# Give Names to special PFS Entries, not covered by PFS Information
if file_type == 'MODEL_INFO':
file_name = 'Model Information'
elif file_type == 'NAME_INFO':
file_name = 'Filename Information'
if not advanced:
continue # Don't store Filename Information in non-advanced user mode
elif file_type == 'SIG_INFO':
file_name = 'Signature Information'
if not advanced:
continue # Don't store Signature Information in non-advanced user mode
else:
file_name = ''
# Most PFS Entry Names & Versions are found at PFS Information via their GUID
# Version can be found at DellPfsEntryR* but prefer PFS Information when possible
for info in info_all:
info_guid, info_name, info_version = info
# Give proper Name & Version info if Entry/Information GUIDs match
if info_guid == file_guid:
file_name = info_name
file_version = info_version
# PFS with zlib-compressed sub-PFS use the same GUID
info[0] = 'USED'
# Break at 1st Name match to not rename again from
# next zlib-compressed sub-PFS with the same GUID.
break
# For both advanced & non-advanced users, the goal is to store final/usable files only
# so empty or intermediate files such as sub-PFS, PFS w/ PFAT or zlib-PFS are skipped
# Main/First PFS CombineBiosNameX Metadata files must be kept for accurate Model Information
# All users should check these files in order to choose the correct CombineBiosNameX modules
write_files = [] # Initialize list of output PFS Entry files to be written/extracted
is_zlib = bool(file_type == 'ZLIB') # Determine if PFS Entry Data was zlib-compressed
if file_data and not is_zlib:
write_files.append([file_data, 'data']) # PFS Entry Data Payload
if file_data_sig and advanced:
write_files.append([file_data_sig, 'sign_data']) # PFS Entry Data Signature
if file_meta and (is_zlib or advanced):
write_files.append([file_meta, 'meta']) # PFS Entry Metadata Payload
if file_meta_sig and advanced:
write_files.append([file_meta_sig, 'sign_meta']) # PFS Entry Metadata Signature
# Write/Extract PFS Entry files
for file in write_files:
full_name = f'{pfs_index} {pfs_name} -- {file_index} {file_name} v{file_version}' # Full PFS Entry Name
pfs_file_write(file[0], file[1], file_type, full_name, extract_path, padding, structure, advanced)
# Get PFS Footer Data after PFS Header Payload
pfs_footer = buffer[PFS_HEAD_LEN + pfs_hdr.PayloadSize:PFS_HEAD_LEN + pfs_hdr.PayloadSize + PFS_FOOT_LEN]
# Analyze PFS Footer Structure
chk_pfs_ftr(pfs_footer, pfs_payload, pfs_hdr.PayloadSize, 'PFS', padding, structure)
def parse_pfs_entry(entry_buffer, entry_start, entry_size, entry_struct, text, padding=0, structure=True):
""" Analyze Dell PFS Entry Structure """
# Get PFS Entry Structure values
pfs_entry = get_struct(entry_buffer, entry_start, entry_struct)
# Show PFS Entry Structure info
if structure:
printer('PFS Entry:\n', padding + 4)
pfs_entry.struct_print(padding + 8)
# Validate that a known PFS Entry Header Version was encountered
chk_hdr_ver(pfs_entry.HeaderVersion, text, padding + 8)
# Validate that the PFS Entry Reserved field is empty
if pfs_entry.Reserved != 0:
printer(f'Error: Detected non-empty {text} Reserved field!', padding + 8)
# Get PFS Entry Version string via "Version" and "VersionType" fields
entry_version = get_entry_ver(pfs_entry.Version, pfs_entry.VersionType)
# Get PFS Entry GUID in Big Endian format
entry_guid = f'{int.from_bytes(pfs_entry.GUID, "little"):0{0x10 * 2}X}'
# PFS Entry Data starts after the PFS Entry Structure
entry_data_start = entry_start + entry_size
entry_data_end = entry_data_start + pfs_entry.DataSize
# PFS Entry Data Signature starts after PFS Entry Data
entry_data_sig_start = entry_data_end
entry_data_sig_end = entry_data_sig_start + pfs_entry.DataSigSize
# PFS Entry Metadata starts after PFS Entry Data Signature
entry_met_start = entry_data_sig_end
entry_met_end = entry_met_start + pfs_entry.DataMetSize
# PFS Entry Metadata Signature starts after PFS Entry Metadata
entry_met_sig_start = entry_met_end
entry_met_sig_end = entry_met_sig_start + pfs_entry.DataMetSigSize
entry_data = entry_buffer[entry_data_start:entry_data_end] # Store PFS Entry Data
entry_data_sig = entry_buffer[entry_data_sig_start:entry_data_sig_end] # Store PFS Entry Data Signature
entry_met = entry_buffer[entry_met_start:entry_met_end] # Store PFS Entry Metadata
entry_met_sig = entry_buffer[entry_met_sig_start:entry_met_sig_end] # Store PFS Entry Metadata Signature
return pfs_entry, entry_version, entry_guid, entry_data, entry_data_sig, entry_met, entry_met_sig, entry_met_sig_end
def parse_pfat_pfs(entry_hdr, entry_data, padding=0, structure=True):
""" Parse Dell PFS Volume with PFAT Payload """
# Show PFS Volume indicator
if structure:
printer('PFS Volume:', padding + 4)
# Show sub-PFS Header Structure Info
if structure:
printer('PFS Header:\n', padding + 8)
entry_hdr.struct_print(padding + 12)
# Validate that a known sub-PFS Header Version was encountered
chk_hdr_ver(entry_hdr.HeaderVersion, 'sub-PFS', padding + 12)
# Get sub-PFS Payload Data
pfat_payload = entry_data[PFS_HEAD_LEN:PFS_HEAD_LEN + entry_hdr.PayloadSize]
# Get sub-PFS Footer Data after sub-PFS Header Payload, which must
# must be retrieved at the initial entry_data, before PFAT parsing.
pfat_footer = entry_data[PFS_HEAD_LEN + entry_hdr.PayloadSize:PFS_HEAD_LEN + entry_hdr.PayloadSize + PFS_FOOT_LEN]
# Parse all sub-PFS Payload PFAT Entries
pfat_entries_all = [] # Storage for all sub-PFS PFAT Entries Order/Offset & Payload/Raw Data
pfat_entry_start = 0 # Increasing sub-PFS PFAT Entry start offset
pfat_entry_index = 1 # Increasing sub-PFS PFAT Entry count index
_, pfs_entry_size = get_pfs_entry(pfat_payload, 0) # Get initial PFS PFAT Entry Size for loop
while len(pfat_payload[pfat_entry_start:pfat_entry_start + pfs_entry_size]) == pfs_entry_size:
# Get sub-PFS PFAT Entry Structure & Size info
pfat_entry_struct, pfat_entry_size = get_pfs_entry(pfat_payload, pfat_entry_start)
# Analyze sub-PFS PFAT Entry Structure and get relevant info
pfat_entry, _, _, pfat_entry_data, _, pfat_entry_met, _, pfat_next_entry = \
parse_pfs_entry(pfat_payload, pfat_entry_start, pfat_entry_size, pfat_entry_struct,
'sub-PFS PFAT Entry', padding + 4, structure)
# Each sub-PFS PFAT Entry includes an AMI BIOS Guard (a.k.a. PFAT) block at the beginning
# We need to parse the PFAT block and remove its contents from the final Payload/Raw Data
pfat_hdr_off = pfat_entry_start + pfat_entry_size # PFAT block starts after PFS Entry
# Get sub-PFS PFAT Header Structure values
pfat_hdr = get_struct(pfat_payload, pfat_hdr_off, IntelBiosGuardHeader)
# Get ordinal value of the sub-PFS PFAT Entry Index
pfat_entry_idx_ord = get_ordinal(pfat_entry_index)
# Show sub-PFS PFAT Header Structure info
if structure:
printer(f'PFAT Block {pfat_entry_idx_ord} - Header:\n', padding + 12)
pfat_hdr.struct_print(padding + 16)
pfat_script_start = pfat_hdr_off + PFAT_HDR_LEN # PFAT Block Script Start
pfat_script_end = pfat_script_start + pfat_hdr.ScriptSize # PFAT Block Script End
pfat_script_data = pfat_payload[pfat_script_start:pfat_script_end] # PFAT Block Script Data
pfat_payload_start = pfat_script_end # PFAT Block Payload Start (at Script end)
pfat_payload_end = pfat_script_end + pfat_hdr.DataSize # PFAT Block Data End
pfat_payload_data = pfat_payload[pfat_payload_start:pfat_payload_end] # PFAT Block Raw Data
pfat_hdr_bgs_size = PFAT_HDR_LEN + pfat_hdr.ScriptSize # PFAT Block Header & Script Size
# The PFAT Script End should match the total Entry Data Size w/o PFAT block
if pfat_hdr_bgs_size != pfat_entry.DataSize - pfat_hdr.DataSize:
printer(f'Error: Detected sub-PFS PFAT Block {pfat_entry_idx_ord} Header & PFAT Size mismatch!',
padding + 16)
# Get PFAT Header Flags (SFAM, ProtectEC, GFXMitDis, FTU, Reserved)
is_sfam, _, _, _, _ = pfat_hdr.get_flags()
# Parse sub-PFS PFAT Signature, if applicable (only when PFAT Header > SFAM flag is set)
if is_sfam:
if structure:
printer(f'PFAT Block {pfat_entry_idx_ord} - Signature:\n', padding + 12)
# Get sub-PFS PFAT Signature length from Header pattern (not needed for Dell PFS)
_pfat_sign_len = pfat_payload.find(pfat_hdr.get_hdr_marker(), pfat_payload_end,
pfat_payload_end + PFAT_INT_SIG_MAX_LEN) - pfat_payload_end
# Get sub-PFS PFAT Signature Structure values
pfat_sign_len = parse_bg_sign(pfat_payload, pfat_payload_end, _pfat_sign_len, structure, padding + 16)
if len(pfat_payload[pfat_payload_end:pfat_payload_end + pfat_sign_len]) != pfat_sign_len:
printer(f'Error: Detected sub-PFS PFAT Block {pfat_entry_idx_ord} Signature Size mismatch!',
padding + 12)
# Show PFAT Script via BIOS Guard Script Tool
if structure:
printer(f'PFAT Block {pfat_entry_idx_ord} - Script:\n', padding + 12)
_ = parse_bg_script(pfat_script_data, padding + 16)
# The payload of sub-PFS PFAT Entries is not in proper order by default
# We can get each payload's order from PFAT Script > OpCode #2 (set I0 imm)
# PFAT Script OpCode #2 > Operand #3 stores the payload Offset in final image
pfat_entry_off = int.from_bytes(pfat_script_data[0xC:0x10], 'little')
# We can get each payload's length from PFAT Script > OpCode #4 (set I2 imm)
# PFAT Script OpCode #4 > Operand #3 stores the payload Length in final image
pfat_entry_len = int.from_bytes(pfat_script_data[0x1C:0x20], 'little')
# Check that the PFAT Entry Length from Header & Script match
if pfat_hdr.DataSize != pfat_entry_len:
printer(f'Error: Detected sub-PFS PFAT Block {pfat_entry_idx_ord} Header & Script Size mismatch!',
padding + 12)
# Initialize sub-PFS PFAT Entry Metadata Address
pfat_entry_adr = pfat_entry_off
# Parse sub-PFS PFAT Entry/Block Metadata
if len(pfat_entry_met) >= PFS_PFAT_LEN:
# Get sub-PFS PFAT Metadata Structure values
pfat_met = get_struct(pfat_entry_met, 0, DellPfsPfatMetadata)
# Store sub-PFS PFAT Entry Metadata Address
pfat_entry_adr = pfat_met.Address
# Show sub-PFS PFAT Metadata Structure info
if structure:
printer(f'PFAT Block {pfat_entry_idx_ord} - Metadata:\n', padding + 12)
pfat_met.struct_print(padding + 16)
# Another way to get each PFAT Entry Offset is from its Metadata, if applicable
# Check that the PFAT Entry Offsets from PFAT Script and PFAT Metadata match
if pfat_entry_off != pfat_met.Offset:
printer(f'Error: Detected sub-PFS PFAT Block {pfat_entry_idx_ord} Metadata & PFAT Offset mismatch!',
padding + 16)
# Prefer Offset from Metadata, in case PFAT Script differs
pfat_entry_off = pfat_met.Offset
# Another way to get each PFAT Entry Length is from its Metadata, if applicable
# Check that the PFAT Entry Length from PFAT Script and PFAT Metadata match
if not pfat_hdr.DataSize == pfat_entry_len == pfat_met.DataSize:
printer(f'Error: Detected sub-PFS PFAT Block {pfat_entry_idx_ord} Metadata & PFAT Length mismatch!',
padding + 16)
# Check that the PFAT Entry payload Size from PFAT Header matches the one from PFAT Metadata