Created
June 22, 2020 05:12
-
-
Save classilla/4a2c907d0acec5b537cd4992a00801c3 to your computer and use it in GitHub Desktop.
Perl script to extract partitions from OpenPOWER PNOR firmware images.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/perl -s | |
# v1.0 by Cameron Kaiser, talospace@floodgap.com | |
# Public domain | |
# By default extract Skiboot and Petitboot. | |
# Say -parts=DIFFERENT,PARTS to extract, you know, different parts. | |
# We do not support obtaining the 'part' partition yet (you can just do | |
# that by truncating the rest of the file anyway). | |
# Specifying a part multiple times is considered undefined behaviour. | |
$parts ||= "PAYLOAD,BOOTKERNEL"; | |
# Say -prefix=prefix to use a different filename prefix. | |
$prefix ||= "pnor."; | |
# Say -verbose or -v for more stuff. | |
$verbose ||= $v; | |
### NO USER SERVICEABLE PARTS BELOW THIS POINT ### | |
(@parts) = split(/,/, $parts); | |
die("Partition list doesn't make sense: $parts\n") if (!scalar(@parts)); | |
foreach(@parts) { | |
die("Obtaining 'part' is not supported yet\n") if ($_ eq 'part'); | |
$f = $prefix . $_; | |
die("$f already exists, not overwriting!\n") if (-e $f); | |
} | |
# Abuse <> to read either from stdin or a file argument. | |
# Slurp the magic number and set the filehandle all in one go. | |
$/ = \4; $magic = <>; | |
die("This isn't a PNOR archive: @{[ unpack('H*', $magic) ]}\n") | |
if ($magic ne 'PART'); | |
# Read remainder of PNOR header. | |
# These are big-endian 32-bit integers. | |
read(ARGV, $buf, 44); | |
die("PNOR header is incomplete!\n") if (length($buf) != 44); | |
($version, | |
$size, | |
$entrysize, | |
$entrycount, | |
$blocksize, | |
$blockcount, | |
$x, # reserved? | |
$x, | |
$x, | |
$x, | |
$chksum) = unpack("N11", $buf); | |
print "Version $version PNOR archive with $entrycount entries.\n"; | |
print "Warning: unexpected PNOR version!\n" if ($version != 1); | |
die "At least two entries must be in PNOR, aborting!\n" if ($entrycount < 2); | |
undef @finder; | |
undef %did_find; | |
$bytes_read = 48; | |
$first_non_part = 0; | |
# Read all the entries and figure out which ones we want. | |
for($i=0; $i<$entrycount; $i++) { | |
read(ARGV, $buf, $entrysize); | |
die("Truncated directory entry found, aborting!\n") | |
if (length($buf) != $entrysize); | |
# Extract partition name, base offset and length (in 4K pages). | |
# We don't care about the rest here. | |
# Partition name is the first 16 bytes, null padded. | |
($pname, | |
$base, | |
$length, | |
$x) = unpack("H32NNH*", $buf); | |
# Length must be at least 2 pages. | |
die("Illegal length $length, aborting\n") if ($length < 2); | |
1 while ($pname =~ s/00$//); | |
die("Null partition name in directory entry, aborting\n") | |
if (!length($pname)); | |
$pname = pack("H*", $pname); | |
# We assume that the partition table is in the lead position. | |
die("Partition table was not in position zero!\n") | |
if ($i == 0 && $pname != "part"); | |
print "$pname (offset $base, length $length)\n" if ($verbose); | |
print "Warning: duplicate partition $pname, last one will be used\n" | |
if ($did_find{$pname}++); | |
foreach $w (@parts) { | |
if ($w eq $pname) { | |
# Found it. Extract base offset and length (in | |
# 4K pages). | |
push(@finder, [$w, $base, $length]); | |
} | |
} | |
# Remember the partition *after* "part", because our data starts there. | |
$first_non_part = $base if ($i == 1); | |
$bytes_read += $entrysize; | |
} | |
foreach (@parts) { | |
print "Warning: partition $_ was not found, skipping\n" | |
if (!$did_find{$_}); | |
} | |
if (!scalar(@finder)) { | |
print "No partitions extracted.\n"; | |
print "Use -verbose to see more info.\n" unless ($verbose); | |
exit 1; | |
} | |
# Skip over the rest of "part" since we know it must be first. | |
$skip = ($first_non_part * 4096) - $bytes_read; | |
print "Skipping partition table ($skip bytes to offset $first_non_part)\n" | |
if ($verbose); | |
read(ARGV, $buf, $skip); | |
die("Truncated file, aborting!\n") if (length($buf) != $skip); | |
# We now have a sorted "edit decision" list. Extract in file order. | |
# Read in 4K pages until we get the right spot since we can't use seek() | |
# in case this is coming in over stdin. | |
$fpos = 0; | |
foreach $entry (@finder) { | |
$w = $entry->[0]; | |
$base = ($entry->[1] - $first_non_part); | |
$length = $entry->[2]; | |
while ($fpos < $base) { | |
read(ARGV, $buf, 4096); | |
die("Truncated partition, aborting!\n") | |
if (length($buf) != 4096); | |
$fpos++; | |
} | |
print "Extracting $w at offset $base.\n"; | |
open(K, ">${prefix}${w}") || die("Unable to write: $!\n"); | |
for($i=0; $i<$length; $i++) { | |
read(ARGV, $buf, 4096); | |
die("Truncated partition, aborting!\n") | |
if (length($buf) != 4096); | |
$fpos++; | |
# The first page contains metadata and is skipped. | |
next unless ($i); | |
print K $buf; | |
# For the second page only, analyse the magic number. | |
next unless ($i == 1); | |
$magic = unpack("H8", $buf); | |
if ($magic eq 'fd377a58') { | |
print "This is a xz format image.\n"; | |
} elsif ($magic eq '7f454c46') { | |
print "This is an ELF executable image.\n"; | |
} | |
} | |
close(K); | |
$length--; | |
print "Wrote @{[ $length * 4 ]}K successfully.\n"; | |
} | |
print "Extracted @{[ scalar(@finder) ]} partitions successfully.\n"; | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment