2018-01-21 03:54:16 +00:00
|
|
|
#!/usr/bin/perl
|
|
|
|
# Parse GUIDs, generate EFI structs, etc
|
|
|
|
|
|
|
|
package EFI;
|
|
|
|
use warnings;
|
|
|
|
use strict;
|
|
|
|
use File::Temp 'tempfile';
|
|
|
|
use Digest::SHA 'sha1';
|
2018-01-22 19:58:57 +00:00
|
|
|
#use IO::Compress::Lzma 'lzma'; # apt install libio-compress-lzma-perl
|
|
|
|
#use Compress::Raw::Lzma; # apt install libcompress-raw-lzma-perl
|
2018-01-21 03:54:16 +00:00
|
|
|
|
|
|
|
|
|
|
|
# Address Size Designation
|
|
|
|
# ------- ---- -----------
|
|
|
|
#
|
|
|
|
# EFI_FFS_FILE_HEADER:
|
|
|
|
# 0x0000 16 Name (EFI_GUID)
|
|
|
|
# 0x0010 1 IntegrityCheck.Header (Header Checksum)
|
|
|
|
# 0x0011 1 IntegrityCheck.File -> set to 0xAA (FFS_FIXED_CHECKSUM) and clear bit 0x40 of Attributes
|
|
|
|
# 0x0012 1 FileType -> 0x07 = EFI_FV_FILETYPE_DRIVER
|
|
|
|
# 0x0013 1 Attributes -> 0x00
|
|
|
|
# 0x0014 3 Size, including header and all other sections
|
|
|
|
# 0x0017 1 State (unused) -> 0X00
|
|
|
|
#
|
|
|
|
# EFI_COMMON_SECTION_HEADER:
|
|
|
|
# 0x0000 3 Size, including this header
|
|
|
|
# 0x0003 1 Type -> 0x10 (EFI_SECTION_PE32)
|
|
|
|
# 0x0004 #### <PE data>
|
|
|
|
#
|
|
|
|
# EFI_COMMON_SECTION_HEADER:
|
|
|
|
# 0x0000 3 Size, including this header
|
|
|
|
# 0x0003 1 Type -> 0x15 (EFI_SECTION_USER_INTERFACE)
|
|
|
|
# 0x0004 #### NUL terminated UTF-16 string (eg "FAT\0")
|
|
|
|
#
|
|
|
|
# EFI_COMMON_SECTION_HEADER:
|
|
|
|
# 0x0000 3 Size, including this header
|
|
|
|
# 0x0003 1 Type -> 0x14 (EFI_SECTION_VERSION)
|
|
|
|
# 0x0004 #### NUL terminated UTF-16 string (eg "1.0\0")
|
|
|
|
|
2018-01-22 19:58:57 +00:00
|
|
|
my $sec_hdr_len = 0x04; # FFSv2 sections
|
|
|
|
my $ffs_hdr_len = 0x18; # FFSv2
|
|
|
|
#my $sec_hdr_len = 0x08; # FFSv3 sections include a 32-bit length
|
|
|
|
#my $ffs_hdr_len = 0x20; # FFSv3 files include a 64-bit length
|
2018-01-21 03:54:16 +00:00
|
|
|
|
|
|
|
my $fv_hdr_len = 0x48;
|
2018-02-21 23:52:00 +00:00
|
|
|
my $fv_block_size = 0x1000; # force alignment of files to this spacing
|
2018-01-21 03:54:16 +00:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
our %file_types = qw/
|
|
|
|
RAW 0x01
|
|
|
|
FREEFORM 0x02
|
|
|
|
SECURITY_CORE 0x03
|
|
|
|
PEI_CORE 0x04
|
|
|
|
DXE_CORE 0x05
|
|
|
|
PEIM 0x06
|
|
|
|
DRIVER 0x07
|
|
|
|
COMBINED_PEIM_DRIVER 0x08
|
|
|
|
APPLICATION 0x09
|
|
|
|
SMM 0x0A
|
|
|
|
FIRMWARE_VOLUME_IMAGE 0x0B
|
|
|
|
COMBINED_SMM_DXE 0x0C
|
|
|
|
SMM_CORE 0x0D
|
|
|
|
DEBUG_MIN 0xe0
|
|
|
|
DEBUG_MAX 0xef
|
|
|
|
FFS_PAD 0xf0
|
|
|
|
/;
|
|
|
|
|
2018-01-26 18:51:17 +00:00
|
|
|
|
2018-01-21 03:54:16 +00:00
|
|
|
our %section_types = qw/
|
2018-01-27 04:09:57 +00:00
|
|
|
TIANO_COMPRESSED 0x01
|
2018-01-21 03:54:16 +00:00
|
|
|
GUID_DEFINED 0x02
|
|
|
|
PE32 0x10
|
|
|
|
PIC 0x11
|
|
|
|
TE 0x12
|
|
|
|
DXE_DEPEX 0x13
|
|
|
|
VERSION 0x14
|
|
|
|
USER_INTERFACE 0x15
|
|
|
|
COMPATIBILITY16 0x16
|
|
|
|
FIRMWARE_VOLUME_IMAGE 0x17
|
|
|
|
FREEFORM_SUBTYPE_GUID 0x18
|
|
|
|
RAW 0x19
|
|
|
|
PEI_DEPEX 0x1B
|
|
|
|
SMM_DEPEX 0x1C
|
|
|
|
/;
|
|
|
|
|
2018-01-26 18:51:17 +00:00
|
|
|
|
2018-01-21 03:54:16 +00:00
|
|
|
# Some special cases for non-PE32 sections
|
|
|
|
our %section_type_map = qw/
|
|
|
|
FREEFORM RAW
|
|
|
|
FIRMWARE_VOLUME_IMAGE FIRMWARE_VOLUME_IMAGE
|
|
|
|
/;
|
|
|
|
|
|
|
|
# Special cases for DEPEX sections
|
|
|
|
our %depex_type_map = qw/
|
2018-04-10 19:04:05 +00:00
|
|
|
PEIM PEI_DEPEX
|
2018-01-21 03:54:16 +00:00
|
|
|
DRIVER DXE_DEPEX
|
|
|
|
SMM SMM_DEPEX
|
|
|
|
/;
|
|
|
|
|
2018-01-26 18:51:17 +00:00
|
|
|
# Invert the file type and section type maps
|
|
|
|
our %file_types_lookup = map { hex $file_types{$_} => $_ } keys %file_types;
|
|
|
|
our %section_types_lookup = map { hex $section_types{$_} => $_ } keys %section_types;
|
2018-01-21 03:54:16 +00:00
|
|
|
|
2018-01-26 20:08:48 +00:00
|
|
|
|
|
|
|
sub section_type_lookup
|
|
|
|
{
|
|
|
|
my $type = shift;
|
|
|
|
my $name = $section_types_lookup{$type};
|
|
|
|
$name ||= sprintf "0x%02x", $type;
|
|
|
|
|
|
|
|
return $name;
|
|
|
|
}
|
|
|
|
|
|
|
|
sub file_type_lookup
|
|
|
|
{
|
|
|
|
my $type = shift;
|
|
|
|
my $name = $file_types_lookup{$type};
|
|
|
|
$name ||= sprintf "0x%02x", $type;
|
|
|
|
|
|
|
|
return $name;
|
|
|
|
}
|
|
|
|
|
2018-01-21 03:54:16 +00:00
|
|
|
# convert text GUID to hex
|
|
|
|
sub guid
|
|
|
|
{
|
|
|
|
my $guid = shift;
|
|
|
|
my ($g1,$g2,$g3,$g4,$g5) =
|
|
|
|
$guid =~ /
|
|
|
|
([0-9a-fA-F]{8})
|
|
|
|
-([0-9a-fA-F]{4})
|
|
|
|
-([0-9a-fA-F]{4})
|
|
|
|
-([0-9a-fA-F]{4})
|
|
|
|
-([0-9a-fA-F]{12})
|
|
|
|
/x
|
|
|
|
or die "$guid: Unable to parse guid\n";
|
|
|
|
|
|
|
|
return pack("VvvnCCCCCC",
|
|
|
|
hex $g1,
|
|
|
|
hex $g2,
|
|
|
|
hex $g3,
|
|
|
|
hex $g4,
|
|
|
|
hex substr($g5, 0, 2),
|
|
|
|
hex substr($g5, 2, 2),
|
|
|
|
hex substr($g5, 4, 2),
|
|
|
|
hex substr($g5, 6, 2),
|
|
|
|
hex substr($g5, 8, 2),
|
|
|
|
hex substr($g5,10, 2),
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2018-01-26 20:08:48 +00:00
|
|
|
# Some common GUIDs (these should be in a data file)
|
|
|
|
our $lzma_guid = 'ee4e5898-3914-4259-9d6e-dc7bd79403cf';
|
|
|
|
|
|
|
|
|
2018-01-21 03:54:16 +00:00
|
|
|
# Convert a string to UCS-16 and add a nul terminator
|
|
|
|
sub ucs16
|
|
|
|
{
|
|
|
|
my $val = shift;
|
|
|
|
|
|
|
|
my $rc = '';
|
|
|
|
for(my $i = 0 ; $i < length $val ; $i++)
|
|
|
|
{
|
|
|
|
$rc .= substr($val, $i, 1) . chr(0x0);
|
|
|
|
}
|
|
|
|
|
|
|
|
# nul terminate the string
|
|
|
|
$rc .= chr(0x0) . chr(0x0);
|
|
|
|
|
|
|
|
return $rc;
|
|
|
|
}
|
|
|
|
|
2018-01-26 18:51:17 +00:00
|
|
|
# Convert from UCS-16 back to a normal string
|
|
|
|
sub read_ucs16
|
|
|
|
{
|
|
|
|
my $val = shift;
|
|
|
|
my $offset = shift;
|
|
|
|
my $len = length($val);
|
|
|
|
my $rc = '';
|
|
|
|
|
|
|
|
while($offset < $len-1)
|
|
|
|
{
|
|
|
|
my $word = unpack("n", substr($val, $offset, 2));
|
|
|
|
last if $word == 0x0000;
|
|
|
|
|
|
|
|
$rc .= chr(($word >> 8) & 0xFF);
|
|
|
|
$offset += 2;
|
|
|
|
}
|
|
|
|
|
|
|
|
return $rc;
|
|
|
|
}
|
|
|
|
|
2018-01-21 03:54:16 +00:00
|
|
|
|
|
|
|
# output an EFI Common Section Header
|
|
|
|
# Since we might be dealing with ones larger than 16 MB, we should use extended
|
|
|
|
# section type that gives us a 4-byte length.
|
|
|
|
sub section
|
|
|
|
{
|
|
|
|
my $type = shift;
|
|
|
|
my $data = shift;
|
|
|
|
|
|
|
|
die "$type: Unknown section type\n"
|
|
|
|
unless exists $section_types{$type};
|
|
|
|
|
|
|
|
my $len = length($data) + $sec_hdr_len;
|
2018-01-22 19:58:57 +00:00
|
|
|
die "Section length $len > 0xFFFFFF\n" if $len > 0xFFFFFF;
|
2018-01-21 03:54:16 +00:00
|
|
|
|
|
|
|
my $sec = ''
|
2018-01-25 17:20:42 +00:00
|
|
|
. write24($len)
|
2018-01-21 03:54:16 +00:00
|
|
|
. chr(hex $section_types{$type})
|
|
|
|
. $data;
|
|
|
|
|
|
|
|
return $sec;
|
|
|
|
}
|
|
|
|
|
2018-01-27 03:44:26 +00:00
|
|
|
|
|
|
|
sub section_pad
|
|
|
|
{
|
|
|
|
my $len = shift;
|
|
|
|
return '' if $len < $sec_hdr_len;
|
|
|
|
|
|
|
|
return section(RAW => chr(0x00) x ($len - $sec_hdr_len));
|
|
|
|
}
|
|
|
|
|
2018-01-22 20:43:40 +00:00
|
|
|
sub ffs_align
|
|
|
|
{
|
2018-01-27 03:44:26 +00:00
|
|
|
my $pad_offset = shift || 0;
|
|
|
|
my $pad_align = shift || 0;
|
2018-01-22 20:43:40 +00:00
|
|
|
my $align = 4;
|
|
|
|
my $data = '';
|
|
|
|
|
|
|
|
for my $sec (@_)
|
|
|
|
{
|
2018-01-27 03:44:26 +00:00
|
|
|
# sections must be 4 byte aligned,
|
2018-01-22 20:43:40 +00:00
|
|
|
my $unaligned = length($data) % $align;
|
|
|
|
$data .= chr(0x00) x ($align - $unaligned)
|
|
|
|
if $unaligned != 0;
|
2018-01-27 03:44:26 +00:00
|
|
|
|
|
|
|
# if we need more alignment, add a pad section
|
|
|
|
if ($pad_align)
|
|
|
|
{
|
|
|
|
$unaligned = (length($data) + $pad_offset) % $pad_align;
|
|
|
|
$unaligned += $pad_align
|
|
|
|
if $unaligned < $sec_hdr_len;
|
|
|
|
|
|
|
|
$data .= section_pad($pad_align - $unaligned)
|
|
|
|
if $unaligned != 0;
|
|
|
|
}
|
|
|
|
|
2018-01-22 20:43:40 +00:00
|
|
|
$data .= $sec;
|
|
|
|
}
|
|
|
|
|
|
|
|
return $data;
|
|
|
|
}
|
|
|
|
|
2018-01-21 03:54:16 +00:00
|
|
|
|
|
|
|
sub ffs
|
|
|
|
{
|
|
|
|
my $file_type = shift;
|
2018-01-22 20:43:40 +00:00
|
|
|
my $guid = shift;
|
2018-01-27 03:44:26 +00:00
|
|
|
my $data = ffs_align(0, 0, @_);
|
2018-01-22 20:43:40 +00:00
|
|
|
|
|
|
|
# if they did not provide a GUID, generate one
|
|
|
|
$guid ||= substr(sha1($data), 0, 16);
|
2018-01-21 03:54:16 +00:00
|
|
|
|
|
|
|
my $len = length($data) + $ffs_hdr_len;
|
|
|
|
|
|
|
|
my $type_byte = $file_types{$file_type}
|
|
|
|
or die "$file_type: Unknown file type\n";
|
|
|
|
|
2018-01-22 20:43:40 +00:00
|
|
|
my $attr = 0x28; # == aligned?
|
2018-01-21 03:54:16 +00:00
|
|
|
my $state = 0x07;
|
|
|
|
if ($file_type eq 'FFS_PAD')
|
|
|
|
{
|
|
|
|
$attr = 0x00;
|
|
|
|
$state = 0xF8;
|
|
|
|
}
|
|
|
|
|
2018-01-22 19:58:57 +00:00
|
|
|
# since we make everything a large file, set the bit
|
|
|
|
#$attr |= 0x01;
|
|
|
|
|
2018-01-21 03:54:16 +00:00
|
|
|
my $ffs = ''
|
|
|
|
. $guid # 0x00
|
|
|
|
. chr(0x00) # 0x10 header checksum
|
|
|
|
. chr(0x00) # 0x11 FFS_FIXED_CHECKSUM
|
|
|
|
. chr(hex $type_byte) # 0x12
|
|
|
|
. chr($attr) # 0x13 attributes
|
2018-01-25 17:20:42 +00:00
|
|
|
. write24($len) # 0x14 length (24-bit)
|
2018-01-21 03:54:16 +00:00
|
|
|
. chr($state) # 0x17 state (not included in checksum)
|
2018-01-22 19:58:57 +00:00
|
|
|
# . pack("Q", $len) # 0x18 64-bit length
|
2018-01-21 03:54:16 +00:00
|
|
|
;
|
|
|
|
|
|
|
|
# fixup the header checksum
|
|
|
|
my $sum = 0;
|
|
|
|
for my $i (0..length($ffs)-2) {
|
|
|
|
$sum -= ord(substr($ffs, $i, 1));
|
|
|
|
}
|
|
|
|
|
|
|
|
substr($ffs, 0x10, 2) = chr($sum & 0xFF) . chr(0xAA);
|
|
|
|
|
|
|
|
# Add the rest of the data
|
|
|
|
return $ffs . $data;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2018-02-02 13:39:32 +00:00
|
|
|
# Generate a padding firmware file
|
2018-01-21 03:54:16 +00:00
|
|
|
sub ffs_pad
|
|
|
|
{
|
|
|
|
my $len = shift;
|
2018-01-21 03:58:33 +00:00
|
|
|
return '' if $len <= $ffs_hdr_len;
|
2018-01-21 03:54:16 +00:00
|
|
|
|
|
|
|
my $ffs = ffs(FFS_PAD =>
|
|
|
|
chr(0xFF) x 16, # GUID
|
2018-01-22 20:43:40 +00:00
|
|
|
chr(0xFF) x ($len - $ffs_hdr_len), # data
|
2018-01-21 03:54:16 +00:00
|
|
|
);
|
|
|
|
|
|
|
|
return $ffs;
|
|
|
|
}
|
|
|
|
|
|
|
|
# Generate a DEPEX section
|
|
|
|
sub depex
|
|
|
|
{
|
|
|
|
my $type = shift;
|
|
|
|
return unless @_;
|
|
|
|
|
|
|
|
my $section_type = $depex_type_map{$type}
|
|
|
|
or die "$type: DEPEX is not supported\n";
|
|
|
|
|
|
|
|
if ($_[0] eq 'TRUE')
|
|
|
|
{
|
|
|
|
# Special case for short-circuit
|
|
|
|
return section($section_type, chr(0x06) . chr(0x08));
|
|
|
|
}
|
|
|
|
|
|
|
|
my $data = '';
|
|
|
|
my $count = 0;
|
|
|
|
|
|
|
|
for my $guid (@_)
|
|
|
|
{
|
|
|
|
# push the guid
|
|
|
|
$data .= chr(0x02) . guid($guid);
|
|
|
|
$count++;
|
|
|
|
}
|
|
|
|
|
|
|
|
# AND them all together (1 minus the number of GUIDs)
|
|
|
|
$data .= chr(0x03) for 1..$count-1;
|
|
|
|
$data .= chr(0x08);
|
|
|
|
|
|
|
|
return section($section_type, $data);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
# compress a section and Wrap a GUIDed section around it
|
|
|
|
sub compress
|
|
|
|
{
|
2018-01-27 03:44:26 +00:00
|
|
|
# We could force 128-byte alignment for compressed sections
|
|
|
|
# but it doesn't seem to matter.
|
|
|
|
my $data = ffs_align(0, 00, @_);
|
2018-01-22 19:58:57 +00:00
|
|
|
|
2018-01-21 03:54:16 +00:00
|
|
|
my ($fh,$filename) = tempfile();
|
|
|
|
print $fh $data;
|
|
|
|
close $fh;
|
|
|
|
|
|
|
|
# -7 produces the same bit-stream as the UEFI tools
|
2018-01-22 19:58:57 +00:00
|
|
|
#my $lz = new Compress::Raw::Lzma::EasyEncoder(Preset => 7);
|
|
|
|
#my $lz_data;
|
|
|
|
#$lz->code($data, $lz_data);
|
2018-01-21 03:54:16 +00:00
|
|
|
my $lz_data = `lzma --compress --stdout -7 $filename`;
|
2018-01-21 12:40:03 +00:00
|
|
|
#printf STDERR "%d compressed to %d\n", length($data), length($lz_data);
|
2018-01-21 03:54:16 +00:00
|
|
|
|
|
|
|
# fixup the size field in the lzma compressed data
|
2018-01-25 17:20:42 +00:00
|
|
|
substr($lz_data, 5, 8) = write64(length $data);
|
2018-01-21 03:54:16 +00:00
|
|
|
|
|
|
|
# wrap the lzdata in a GUIDed section
|
|
|
|
my $lz_header = ''
|
2018-01-26 20:08:48 +00:00
|
|
|
. guid($lzma_guid)
|
|
|
|
. chr($ffs_hdr_len) # data offset, should this be 0x14?
|
2018-01-21 03:54:16 +00:00
|
|
|
. chr(0x00)
|
|
|
|
. chr(0x01) # Processing required
|
|
|
|
. chr(0x00)
|
|
|
|
;
|
|
|
|
|
|
|
|
# and replace our data with the GUID defined LZ compressed data
|
|
|
|
return section(GUID_DEFINED => $lz_header . $lz_data);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2018-02-02 16:02:56 +00:00
|
|
|
# Create a FV for a given file size with the included files
|
2018-01-21 03:54:16 +00:00
|
|
|
sub fv
|
|
|
|
{
|
|
|
|
my $size = shift;
|
2018-01-22 19:58:57 +00:00
|
|
|
my $guid = guid("8C8CE578-8A3D-4F1C-9935-896185C32DD3"); # FFSv2
|
|
|
|
#my $guid = guid("5473c07a-3dcb-4dca-bd6f-1e9689e7349a"); # FFSv3 for large sections
|
2018-01-21 03:54:16 +00:00
|
|
|
|
|
|
|
my $fv_hdr = ''
|
|
|
|
. (chr(0x00) x 0x10) # 0x00 Zero vector
|
|
|
|
. $guid # 0x10
|
2018-01-25 17:20:42 +00:00
|
|
|
. write64($size) # 0x20 length (64-bit)
|
2018-01-22 19:58:57 +00:00
|
|
|
. '_FVH' # 0x28 signature
|
2018-01-21 03:54:16 +00:00
|
|
|
. pack("V", 0x000CFEFF) # 0x2C attributes
|
2018-01-22 19:58:57 +00:00
|
|
|
. pack("v", $fv_hdr_len) # 0x30 header length (32-bit)
|
2018-01-21 03:54:16 +00:00
|
|
|
. pack("v", 0x0000) # 0x32 checksum
|
2018-01-22 19:58:57 +00:00
|
|
|
. chr(0x00) # 0x34 reserved?
|
|
|
|
. chr(0x00) # 0x35 reserved
|
|
|
|
. chr(0x00) # 0x36 reserved
|
2018-01-21 03:54:16 +00:00
|
|
|
. chr(0x02) # 0x37 version
|
2018-01-22 19:58:57 +00:00
|
|
|
. pack("V", $size / $fv_block_size) # 0x38 number blocks (32-bit)
|
|
|
|
. pack("V", $fv_block_size) # 0x3C block size (32-bit)
|
|
|
|
. pack("V", 0) # 0x40 number blocks (unused)
|
|
|
|
. pack("V", 0) # 0x44 block size (unused)
|
2018-01-21 03:54:16 +00:00
|
|
|
;
|
|
|
|
|
|
|
|
die "FV Header length ", length $fv_hdr, " != $fv_hdr_len\n"
|
|
|
|
unless $fv_hdr_len == length $fv_hdr;
|
|
|
|
|
|
|
|
# update the header checksum
|
|
|
|
my $sum = 0;
|
|
|
|
for(my $i = 0 ; $i < $fv_hdr_len ; $i += 2)
|
|
|
|
{
|
|
|
|
$sum -= unpack("v", substr($fv_hdr, $i, 2));
|
|
|
|
}
|
|
|
|
|
|
|
|
substr($fv_hdr, 0x32, 2) = pack("v", $sum & 0xFFFF);
|
|
|
|
|
2018-02-02 16:02:56 +00:00
|
|
|
for my $ffs (@_)
|
|
|
|
{
|
|
|
|
next if fv_append(\$fv_hdr, $ffs);
|
|
|
|
|
|
|
|
warn "FV append failed\n";
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
return $fv_hdr if fv_pad(\$fv_hdr);
|
|
|
|
|
|
|
|
warn "FV pad failed\n";
|
|
|
|
return;
|
2018-01-21 03:54:16 +00:00
|
|
|
}
|
|
|
|
|
2018-02-02 16:02:56 +00:00
|
|
|
|
|
|
|
# Append a file to an FV, adding an initial pad if necessary
|
|
|
|
# This is used internally by EFI::fv() and should not need to be called
|
|
|
|
# by users of the EFI library.
|
2018-01-21 03:54:16 +00:00
|
|
|
sub fv_append
|
|
|
|
{
|
|
|
|
my $fv_ref = shift;
|
|
|
|
my $ffs = shift;
|
|
|
|
|
|
|
|
# quick sanity check on the file
|
|
|
|
my $length = length $ffs;
|
|
|
|
|
|
|
|
my $ffs_length = unpack("V", substr($ffs, 0x14, 4)) & 0xFFFFFF;
|
|
|
|
if ($ffs_length == 0xFFFFFF)
|
|
|
|
{
|
|
|
|
# ffs2 with extended length field
|
|
|
|
$ffs_length = unpack("Q", substr($ffs, 0x18, 8));
|
|
|
|
}
|
|
|
|
|
2018-01-21 03:58:33 +00:00
|
|
|
# if the size of the file doesn't match the header size
|
|
|
|
# we do not want to add it to our output. signal an error
|
2018-01-21 03:54:16 +00:00
|
|
|
return if $ffs_length != $length;
|
|
|
|
|
|
|
|
# force at least 8 byte alignment for the section
|
|
|
|
my $unaligned = $length % 8;
|
|
|
|
$ffs .= chr(0xFF) x (8 - $unaligned)
|
|
|
|
if $unaligned != 0;
|
|
|
|
|
|
|
|
# if the current offset does not align with the block size,
|
|
|
|
# we should add a pad section until the next block
|
2018-02-21 22:28:36 +00:00
|
|
|
# The firmware files can specify their desired alignment
|
|
|
|
# we just force 4KB is they want anything
|
|
|
|
my $attr = ord(substr($ffs, 0x13, 1));
|
|
|
|
my $alignment = ($attr & 0x38) >> 3;
|
|
|
|
if ($alignment == 0)
|
|
|
|
{
|
|
|
|
$alignment = 0x10;
|
|
|
|
} else {
|
|
|
|
warn sprintf "alignment attribute %02x\n", $alignment;
|
|
|
|
$alignment = 0x1000;
|
|
|
|
}
|
|
|
|
|
|
|
|
my $block_unaligned = $alignment - (length($$fv_ref) % $alignment);
|
|
|
|
$block_unaligned += $alignment if $block_unaligned < $ffs_hdr_len;
|
2018-01-21 03:54:16 +00:00
|
|
|
|
|
|
|
$$fv_ref .= EFI::ffs_pad($block_unaligned - $ffs_hdr_len);
|
|
|
|
my $ffs_offset = length($$fv_ref);
|
|
|
|
|
2018-01-22 19:58:57 +00:00
|
|
|
# Due to a stupid design in edk2's GenFfs, the state field in
|
|
|
|
# the FFS will not be set correctly. We have to flip it if
|
|
|
|
# the top bit is not set. This should depend on the erase
|
|
|
|
# polarity bit in the FV header, but no one ever changes it.
|
|
|
|
my $state = ord(substr($ffs, 0x17, 1));
|
|
|
|
if (($state & 0x80) == 0)
|
|
|
|
{
|
|
|
|
substr($ffs, 0x17, 1) = chr((~$state) & 0xFF);
|
|
|
|
}
|
|
|
|
|
2018-01-21 03:54:16 +00:00
|
|
|
# finally add the section
|
|
|
|
$$fv_ref .= $ffs;
|
|
|
|
|
|
|
|
return $ffs_offset;
|
|
|
|
}
|
|
|
|
|
|
|
|
# Finish the FV by padding it to its proper size
|
|
|
|
sub fv_pad
|
|
|
|
{
|
|
|
|
my $fv_ref = shift;
|
|
|
|
my $fv_size = unpack("Q", substr($$fv_ref, 0x20, 8));
|
|
|
|
|
|
|
|
my $size = length($$fv_ref);
|
|
|
|
|
|
|
|
# check for the overflow: if the header size is smaller than the actual size
|
|
|
|
return if $fv_size < $size;
|
|
|
|
|
|
|
|
# pad out so that actual size is the same as header size
|
|
|
|
$$fv_ref .= chr(0xFF) x ($fv_size - $size);
|
|
|
|
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2018-01-25 17:20:42 +00:00
|
|
|
# Helpers for reading values from the ROM images
|
2018-02-21 22:00:32 +00:00
|
|
|
sub read8
|
|
|
|
{
|
|
|
|
my $data = shift;
|
|
|
|
my $offset = shift;
|
|
|
|
return unpack("C", substr($data, $offset, 1));
|
|
|
|
}
|
|
|
|
|
2018-01-25 17:20:42 +00:00
|
|
|
sub read16
|
|
|
|
{
|
|
|
|
my $data = shift;
|
|
|
|
my $offset = shift;
|
|
|
|
return unpack("v", substr($data, $offset, 2));
|
|
|
|
}
|
|
|
|
|
2018-02-21 22:00:32 +00:00
|
|
|
sub write16
|
|
|
|
{
|
|
|
|
my $len = shift;
|
|
|
|
return ''
|
|
|
|
. chr(($len >> 0) & 0xFF)
|
|
|
|
. chr(($len >> 8) & 0xFF)
|
|
|
|
;
|
|
|
|
}
|
|
|
|
|
2018-01-25 17:20:42 +00:00
|
|
|
sub read24
|
|
|
|
{
|
|
|
|
my $data = shift;
|
|
|
|
my $offset = shift;
|
|
|
|
return 0
|
|
|
|
| ord(substr($data, $offset+2, 1)) << 16
|
|
|
|
| ord(substr($data, $offset+1, 1)) << 8
|
|
|
|
| ord(substr($data, $offset+0, 1)) << 0
|
|
|
|
;
|
|
|
|
}
|
|
|
|
|
|
|
|
sub write24
|
|
|
|
{
|
|
|
|
my $len = shift;
|
|
|
|
return ''
|
|
|
|
. chr(($len >> 0) & 0xFF)
|
|
|
|
. chr(($len >> 8) & 0xFF)
|
|
|
|
. chr(($len >> 16) & 0xFF)
|
|
|
|
;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
sub read32
|
|
|
|
{
|
|
|
|
my $data = shift;
|
|
|
|
my $offset = shift;
|
|
|
|
return unpack("V", substr($data, $offset, 4));
|
|
|
|
}
|
|
|
|
|
2018-02-21 22:00:32 +00:00
|
|
|
sub write32
|
|
|
|
{
|
|
|
|
my $len = shift;
|
|
|
|
return ''
|
|
|
|
. chr(($len >> 0) & 0xFF)
|
|
|
|
. chr(($len >> 8) & 0xFF)
|
|
|
|
. chr(($len >> 16) & 0xFF)
|
|
|
|
. chr(($len >> 24) & 0xFF)
|
|
|
|
;
|
|
|
|
}
|
|
|
|
|
2018-01-25 17:20:42 +00:00
|
|
|
sub read64
|
|
|
|
{
|
|
|
|
my $data = shift;
|
|
|
|
my $offset = shift;
|
|
|
|
return read32($data, $offset+4) << 32 | read32($data, $offset+0);
|
|
|
|
}
|
|
|
|
|
|
|
|
sub write64
|
|
|
|
{
|
|
|
|
my $data = shift;
|
|
|
|
return pack("VV", $data >> 0, $data >> 32);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
sub read_guid
|
|
|
|
{
|
|
|
|
my $data = shift;
|
|
|
|
my $offset = shift;
|
|
|
|
|
|
|
|
my ($g1,$g2,$g3,$g4,@g5) = unpack("VvvnCCCCCC", substr($data, $offset, 16));
|
|
|
|
|
|
|
|
return sprintf "%08x-%04x-%04x-%04x-%02x%02x%02x%02x%02x%02x",
|
|
|
|
$g1,
|
|
|
|
$g2,
|
|
|
|
$g3,
|
|
|
|
$g4,
|
|
|
|
$g5[0],
|
|
|
|
$g5[1],
|
|
|
|
$g5[2],
|
|
|
|
$g5[3],
|
|
|
|
$g5[4],
|
|
|
|
$g5[5],
|
|
|
|
;
|
|
|
|
}
|
|
|
|
|
2018-02-21 22:00:32 +00:00
|
|
|
|
|
|
|
#
|
|
|
|
# Parse the older NVAR (non-volatile variable) structures
|
|
|
|
#
|
|
|
|
package EFI::NVRAM::NVAR;
|
|
|
|
|
|
|
|
my $nvar_sig = 0x5241564e; # 'NVAR'
|
|
|
|
my $nvar_entry_ascii_name = 0x02;
|
|
|
|
my $nvar_entry_data_only = 0x08;
|
|
|
|
my $nvar_entry_valid = 0x80;
|
|
|
|
|
|
|
|
|
|
|
|
#
|
|
|
|
# Create a NVAR object from an in-memory representation
|
|
|
|
# of the structure.
|
|
|
|
#
|
|
|
|
sub parse
|
|
|
|
{
|
|
|
|
my $class = shift;
|
|
|
|
my $ffs = shift;
|
|
|
|
my $offset = shift || 0;
|
|
|
|
|
|
|
|
my $ffs_len = length($ffs);
|
|
|
|
|
|
|
|
my $sig = EFI::read32($ffs, $offset + 0x00);
|
|
|
|
|
|
|
|
# we've reached the end of the data;
|
|
|
|
return if $sig == 0xFFFFFFFF;
|
|
|
|
|
|
|
|
die sprintf "0x%x: sig %08x != nvram %08x\n",
|
|
|
|
$offset, $sig, $nvar_sig
|
|
|
|
unless $sig eq $nvar_sig;
|
|
|
|
|
|
|
|
my $len = EFI::read16($ffs, $offset + 0x04);
|
|
|
|
|
|
|
|
die sprintf "0x%x: len %x > length %x\n",
|
|
|
|
$offset, $len, $ffs_len
|
|
|
|
unless $offset + $len <= $ffs_len;
|
|
|
|
|
|
|
|
my $nvar = substr($ffs, $offset, $len);
|
|
|
|
my $next = EFI::read24($nvar, 0x06);
|
|
|
|
my $attr = EFI::read8($nvar, 0x09);
|
|
|
|
my $data = substr($nvar, 0x0a);
|
|
|
|
my $name;
|
|
|
|
my $guid_id;
|
|
|
|
|
|
|
|
# depending on the attribute we might have a
|
|
|
|
# nul terminated string and a guid index in the guidstore
|
|
|
|
if ($attr & $nvar_entry_ascii_name)
|
|
|
|
{
|
|
|
|
$guid_id = EFI::read8($data, 0);
|
|
|
|
my $name_end = index($data, chr(0), 1);
|
|
|
|
die sprintf "offset 0x%x: ascii name, but no nul\n", $offset
|
|
|
|
if $name_end == -1;
|
|
|
|
|
|
|
|
$name = substr($data, 1, $name_end-1);
|
|
|
|
|
|
|
|
# remove the guid and name from the data
|
|
|
|
$data = substr($data, $name_end+1);
|
|
|
|
}
|
|
|
|
|
|
|
|
my $self = bless {
|
|
|
|
nvar => $nvar,
|
|
|
|
offset => $offset,
|
|
|
|
attr => $attr,
|
|
|
|
name => $name,
|
|
|
|
guid => $guid_id,
|
|
|
|
next => $next,
|
|
|
|
data => $data,
|
|
|
|
}, $class;
|
|
|
|
|
|
|
|
return $self;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
#
|
|
|
|
# Serialize an NVRAM NVAR structure
|
|
|
|
#
|
|
|
|
sub output
|
|
|
|
{
|
|
|
|
my $self = shift;
|
|
|
|
|
|
|
|
my $data = $self->{data};
|
|
|
|
my $attr = $self->{attr};
|
|
|
|
my $name = $self->{name};
|
|
|
|
if ($name)
|
|
|
|
{
|
|
|
|
# update the attribute that we have name
|
|
|
|
$attr |= $nvar_entry_ascii_name;
|
|
|
|
$attr &= ~$nvar_entry_data_only;
|
|
|
|
$data = chr($self->{guid}) . $self->{name} . chr(0x00) . $data;
|
|
|
|
}
|
|
|
|
|
|
|
|
return ''
|
|
|
|
. EFI::write32($nvar_sig) # 0x00
|
|
|
|
. EFI::write16(length($data) + 0x0A) # 0x04
|
|
|
|
. EFI::write24(0xFFFFFF) # no next # 0x06
|
|
|
|
. chr($attr) # 0x09
|
|
|
|
. $data; # 0x0A
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
sub length
|
|
|
|
{
|
|
|
|
my $self = shift;
|
|
|
|
return length $self->{nvar};
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
sub valid
|
|
|
|
{
|
|
|
|
my $self = shift;
|
|
|
|
return $self->{attr} & $nvar_entry_valid;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
sub next
|
|
|
|
{
|
|
|
|
my $self = shift;
|
|
|
|
# all F is no next, since that can be inverted in the flash
|
|
|
|
return if $self->{next} eq 0xFFFFFF;
|
|
|
|
|
|
|
|
# the next pointer is relative to the offset of this one,
|
|
|
|
# so shift it by the amount that we've advanced
|
|
|
|
return $self->{next} + $self->{offset};
|
|
|
|
}
|
|
|
|
|
|
|
|
sub name { my $self = shift; return $self->{name}; }
|
|
|
|
sub guid { my $self = shift; return $self->{guid}; }
|
|
|
|
sub data { my $self = shift; return $self->{data}; }
|
|
|
|
|
|
|
|
|
2018-01-21 03:54:16 +00:00
|
|
|
"0, but true";
|
|
|
|
__END__
|