2017-06-30 01:03:49 +00:00
|
|
|
#!/usr/bin/env perl
|
|
|
|
use warnings;
|
|
|
|
use strict;
|
|
|
|
#
|
2019-02-14 12:57:10 +00:00
|
|
|
# Copyright (C) 2017-2019 Hamish Coleman
|
2018-11-30 01:27:50 +00:00
|
|
|
#
|
2017-06-30 01:03:49 +00:00
|
|
|
# Given an FL2 file from the Lenovo BIOS update, try to recognise which
|
|
|
|
# packing system was used and allow the EC firmware image inside it to
|
|
|
|
# be extracted or reinserted
|
|
|
|
#
|
|
|
|
# Note that this script is just intended to deal with the container and
|
|
|
|
# any actions needed to extract or insert into the container - It does
|
|
|
|
# not check any checksums within the IMG - that should be left to
|
|
|
|
# the img manipulating tools (like mec tools).
|
2017-09-05 03:36:01 +00:00
|
|
|
#
|
|
|
|
# This script now supports extracting a couple of FL1 files, so its name
|
|
|
|
# is starting to be wrong.
|
2017-06-30 01:03:49 +00:00
|
|
|
|
2020-04-07 09:39:44 +00:00
|
|
|
package log;
|
|
|
|
use warnings;
|
|
|
|
use strict;
|
|
|
|
#
|
|
|
|
# Some simple logging
|
|
|
|
#
|
|
|
|
|
|
|
|
my @log;
|
|
|
|
|
|
|
|
sub add {
|
|
|
|
my @entry = caller();
|
|
|
|
push @entry, @_;
|
|
|
|
push @log, \@entry;
|
|
|
|
}
|
|
|
|
|
|
|
|
sub print {
|
|
|
|
my $self = shift;
|
|
|
|
for my $entry (@log) {
|
|
|
|
my $package = shift @{$entry};
|
|
|
|
my $filename = shift @{$entry};
|
|
|
|
my $line = shift @{$entry};
|
|
|
|
printf("%s:%i %s ", $filename, $line, $package);
|
2020-04-07 10:30:04 +00:00
|
|
|
printf(@{$entry});
|
|
|
|
print("\n");
|
2020-04-07 09:39:44 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
1;
|
|
|
|
|
2017-06-30 01:03:49 +00:00
|
|
|
package FL2::base;
|
|
|
|
use warnings;
|
|
|
|
use strict;
|
|
|
|
#
|
|
|
|
# The base class for all the various types of FL2 encapsulation
|
|
|
|
#
|
|
|
|
|
|
|
|
use IO::File;
|
|
|
|
|
|
|
|
sub new {
|
|
|
|
my $class = shift;
|
|
|
|
my $fh = shift || return undef; # must have a file handle
|
|
|
|
my $self = {};
|
|
|
|
bless $self, $class;
|
|
|
|
$self->{fh} = $fh;
|
|
|
|
$self->{filesize} = (stat($self->{fh}))[7];
|
|
|
|
|
|
|
|
return $self->_check();
|
|
|
|
}
|
|
|
|
|
|
|
|
sub set_offset_size {
|
|
|
|
my $self = shift;
|
|
|
|
$self->{offset} = shift;
|
|
|
|
$self->{size} = shift;
|
|
|
|
return $self;
|
|
|
|
}
|
|
|
|
|
|
|
|
sub offset { return shift->{offset}; }
|
|
|
|
sub size { return shift->{size}; }
|
|
|
|
|
|
|
|
|
|
|
|
sub get_block {
|
|
|
|
my $self = shift;
|
|
|
|
my $offset = shift;
|
|
|
|
my $size = shift;
|
|
|
|
|
|
|
|
return undef if (!$self->{fh}->seek($offset, 0));
|
|
|
|
|
|
|
|
my $buf;
|
|
|
|
my $count = $self->{fh}->sysread($buf, $size);
|
|
|
|
|
|
|
|
return undef if ($count != $size);
|
|
|
|
|
2017-06-30 01:35:53 +00:00
|
|
|
return \$buf;
|
2017-06-30 01:03:49 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
# The base class simply says "no" to any checks - override to use
|
|
|
|
sub _check {
|
|
|
|
return undef;
|
|
|
|
}
|
|
|
|
|
|
|
|
# Once a subclass has set the length and size, we can go and double check it
|
|
|
|
# has the expected copyright message
|
|
|
|
sub _check_copyright {
|
|
|
|
my $self = shift;
|
|
|
|
|
|
|
|
my $expect = "(C) Copyright IBM Corp. 2001, 2005 All Rights Reserved ";
|
|
|
|
my $check_size = length($expect);
|
|
|
|
my @offsets = (
|
|
|
|
0x268,
|
|
|
|
0x264,
|
|
|
|
);
|
|
|
|
|
|
|
|
while (@offsets) {
|
|
|
|
my $check_offset = shift @offsets;
|
|
|
|
|
|
|
|
my $buf = $self->get_block($self->offset()+$check_offset, $check_size);
|
2017-09-05 03:19:38 +00:00
|
|
|
|
2017-06-30 01:03:49 +00:00
|
|
|
return undef if (!defined($buf));
|
|
|
|
|
2017-06-30 01:35:53 +00:00
|
|
|
if ($$buf eq $expect) {
|
2017-06-30 01:03:49 +00:00
|
|
|
$self->{flag}{copyright}=$check_offset;
|
|
|
|
return $self;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-04-07 10:30:04 +00:00
|
|
|
log::add("no copyright (offset=0x%x)",$self->offset());
|
2017-06-30 01:03:49 +00:00
|
|
|
return undef;
|
|
|
|
}
|
|
|
|
|
2017-06-30 01:48:04 +00:00
|
|
|
# Helper function, called by the real classes if they support extraction
|
|
|
|
sub _extract {
|
|
|
|
my $self = shift;
|
|
|
|
my $imgfile = shift;
|
|
|
|
|
|
|
|
my $buf = $self->get_block($self->offset(),$self->size());
|
|
|
|
die("bad read") if (!defined($buf));
|
|
|
|
|
|
|
|
my $fh = IO::File->new($imgfile, "w");
|
|
|
|
if (!defined($fh)) {
|
|
|
|
warn("Could not open $imgfile: $!");
|
|
|
|
return undef;
|
|
|
|
}
|
|
|
|
|
|
|
|
my $count = $fh->syswrite($$buf);
|
|
|
|
if ($count != $self->size()) {
|
|
|
|
unlink($imgfile);
|
|
|
|
die("bad write");
|
|
|
|
}
|
|
|
|
|
|
|
|
return $self;
|
|
|
|
}
|
|
|
|
|
2017-07-01 01:22:52 +00:00
|
|
|
# Helper function, called by the real classes if they support insertion
|
|
|
|
sub _insert {
|
|
|
|
my $self = shift;
|
|
|
|
my $imgfile = shift;
|
|
|
|
|
|
|
|
my $fh = IO::File->new($imgfile, "r");
|
|
|
|
if (!defined($fh)) {
|
|
|
|
warn("Could not open $imgfile: $!");
|
|
|
|
return undef;
|
|
|
|
}
|
|
|
|
|
|
|
|
my $buf;
|
|
|
|
my $count = $fh->sysread($buf, $self->size());
|
|
|
|
if ($count != $self->size()) {
|
|
|
|
unlink($imgfile);
|
|
|
|
die("bad read");
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!$self->{fh}->seek($self->offset(), 0)) {
|
|
|
|
unlink($imgfile);
|
|
|
|
die("bad seek");
|
|
|
|
}
|
|
|
|
|
|
|
|
$count = $self->{fh}->syswrite($buf);
|
|
|
|
if ($count != $self->size()) {
|
|
|
|
unlink($imgfile);
|
|
|
|
die("bad write");
|
|
|
|
}
|
|
|
|
|
|
|
|
return $self;
|
|
|
|
}
|
|
|
|
|
2017-06-30 01:03:49 +00:00
|
|
|
1;
|
|
|
|
|
|
|
|
package FL2::prefix_ff;
|
|
|
|
use warnings;
|
|
|
|
use strict;
|
|
|
|
#
|
|
|
|
# Look for FL2 files that consist entirely of 0xff up until the IMG
|
|
|
|
#
|
|
|
|
|
|
|
|
use base qw(FL2::base);
|
|
|
|
|
|
|
|
sub _check {
|
|
|
|
my $self = shift;
|
|
|
|
|
|
|
|
# List of known file sizes (basically a doublecheck on the signature)
|
|
|
|
my $known = {
|
|
|
|
8523776 => [0x500000, 0x20000],
|
|
|
|
12718080 => [0x500000, 0x30000],
|
|
|
|
16912384 => [0x500000, 0x30000],
|
|
|
|
};
|
|
|
|
|
|
|
|
my $check_offset = 0;
|
|
|
|
my $check_size = 0x1000;
|
|
|
|
my $buf = $self->get_block($check_offset, $check_size);
|
|
|
|
|
2020-04-07 10:04:01 +00:00
|
|
|
if (!defined($buf)) {
|
|
|
|
log::add("bad get_block");
|
|
|
|
return undef;
|
|
|
|
}
|
2017-06-30 01:03:49 +00:00
|
|
|
|
2020-04-07 10:04:01 +00:00
|
|
|
my $try_data = $known->{$self->{filesize}};
|
|
|
|
if (!defined($try_data)) {
|
|
|
|
log::add("not in known sizes");
|
|
|
|
return undef;
|
|
|
|
}
|
|
|
|
|
|
|
|
if ($$buf ne "\xff"x$check_size) {
|
|
|
|
log::add("failed 0xff check");
|
|
|
|
return undef;
|
|
|
|
}
|
2017-06-30 01:03:49 +00:00
|
|
|
|
|
|
|
$self->{flag}{encrypted}="yes";
|
|
|
|
# All current examples of this format were encrypted
|
|
|
|
|
2019-11-29 01:56:21 +00:00
|
|
|
# Loop through the known offset,size pairs for this filesize,
|
|
|
|
# looking for one that has a matching copyright
|
|
|
|
while (scalar(@{$try_data})) {
|
|
|
|
my $try_offset = shift @{$try_data};
|
|
|
|
my $try_size = shift @{$try_data};
|
|
|
|
$self->set_offset_size($try_offset, $try_size);
|
|
|
|
|
|
|
|
my $match = $self->_check_copyright();
|
|
|
|
if ($match) {
|
|
|
|
return $match;
|
|
|
|
}
|
|
|
|
}
|
2020-04-07 10:04:01 +00:00
|
|
|
log::add("no copyright message");
|
|
|
|
|
2019-11-29 01:56:21 +00:00
|
|
|
return undef;
|
2017-06-30 01:03:49 +00:00
|
|
|
}
|
|
|
|
|
2017-06-30 01:48:04 +00:00
|
|
|
sub extract {
|
|
|
|
return shift->_extract(shift);
|
|
|
|
}
|
|
|
|
|
2017-07-01 01:22:52 +00:00
|
|
|
sub insert {
|
|
|
|
return shift->_insert(shift);
|
|
|
|
}
|
|
|
|
|
2017-06-30 01:03:49 +00:00
|
|
|
1;
|
|
|
|
|
|
|
|
package FL2::prefix_garbage;
|
|
|
|
use warnings;
|
|
|
|
use strict;
|
|
|
|
#
|
|
|
|
# Look for FL2 files that have garbage at the beginning and large areas of
|
|
|
|
# 0xff before the IMG
|
|
|
|
#
|
|
|
|
|
|
|
|
use base qw(FL2::base);
|
|
|
|
|
|
|
|
sub _check {
|
|
|
|
my $self = shift;
|
|
|
|
|
|
|
|
# List of known file sizes (basically a doublecheck on the signature)
|
2019-02-14 13:14:07 +00:00
|
|
|
# When found, Data is:
|
|
|
|
# [0] offset of EC firmware
|
|
|
|
# [1] size of EC firmware
|
2019-02-14 13:17:32 +00:00
|
|
|
# [2] offset of block to check for all 0xff as a signature check
|
2017-06-30 01:03:49 +00:00
|
|
|
my $known = {
|
2019-02-14 13:17:32 +00:00
|
|
|
4213270 => [0x290000, 0x20000, 0x21000], # Seen in X200
|
|
|
|
4240490 => [0x290000, 0x20000, 0x21000],
|
2017-06-30 01:03:49 +00:00
|
|
|
};
|
|
|
|
|
2019-02-14 13:08:01 +00:00
|
|
|
# If the filesize is not in our known database, return no match
|
2019-02-14 13:14:07 +00:00
|
|
|
my $known_data = $known->{$self->{filesize}};
|
2020-04-07 10:04:01 +00:00
|
|
|
if (!defined($known_data)) {
|
|
|
|
log::add("not in known sizes");
|
|
|
|
return undef;
|
|
|
|
}
|
2019-02-14 13:08:01 +00:00
|
|
|
|
2017-06-30 01:03:49 +00:00
|
|
|
my $check_size = 0x1000;
|
2019-02-14 13:17:32 +00:00
|
|
|
my $buf = $self->get_block($known_data->[2], $check_size);
|
2017-06-30 01:03:49 +00:00
|
|
|
|
2020-04-07 10:04:01 +00:00
|
|
|
if (!defined($buf)) {
|
|
|
|
log::add("bad get_block");
|
|
|
|
return undef;
|
|
|
|
}
|
2017-06-30 01:03:49 +00:00
|
|
|
|
2020-04-07 10:04:01 +00:00
|
|
|
if ($$buf ne "\xff"x$check_size) {
|
|
|
|
log::add("failed 0xff check");
|
|
|
|
return undef;
|
|
|
|
}
|
2017-06-30 01:03:49 +00:00
|
|
|
|
|
|
|
$self->{flag}{encrypted}="no";
|
|
|
|
# All current examples of this format were not encrypted
|
|
|
|
|
2019-02-14 13:14:07 +00:00
|
|
|
$self->set_offset_size(@{$known_data});
|
2017-06-30 01:03:49 +00:00
|
|
|
return $self->_check_copyright();
|
|
|
|
}
|
|
|
|
|
2017-06-30 01:48:04 +00:00
|
|
|
sub extract {
|
|
|
|
return shift->_extract(shift);
|
|
|
|
}
|
|
|
|
|
2017-07-01 01:22:52 +00:00
|
|
|
sub insert {
|
|
|
|
return shift->_insert(shift);
|
|
|
|
}
|
|
|
|
|
2017-06-30 01:03:49 +00:00
|
|
|
1;
|
|
|
|
|
2019-02-15 01:35:48 +00:00
|
|
|
package FL2::prefix_head_NAPI;
|
|
|
|
use warnings;
|
|
|
|
use strict;
|
|
|
|
#
|
|
|
|
# Look for FL2 files that have a "NAPI" header, followed by 0xff, followed
|
|
|
|
# by the IMG
|
|
|
|
#
|
|
|
|
# This was first seen in old x60 FL2 files
|
|
|
|
|
|
|
|
use base qw(FL2::base);
|
|
|
|
|
2019-03-11 11:14:46 +00:00
|
|
|
sub _find_napi {
|
|
|
|
my $self = shift;
|
|
|
|
my $offset = 0;
|
|
|
|
|
|
|
|
while ($offset < $self->{filesize}) {
|
|
|
|
my $buf = $self->get_block($offset, 4);
|
|
|
|
return undef if (!defined($buf));
|
|
|
|
$buf = $$buf;
|
|
|
|
|
|
|
|
if ($buf eq 'NAPI') {
|
|
|
|
return $offset;
|
|
|
|
}
|
|
|
|
|
|
|
|
$offset+=0x10000;
|
|
|
|
}
|
|
|
|
|
|
|
|
return undef; # not found
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2019-02-15 01:35:48 +00:00
|
|
|
sub _check {
|
|
|
|
my $self = shift;
|
|
|
|
|
2019-03-11 11:14:46 +00:00
|
|
|
my $header_offset = $self->_find_napi();
|
2020-04-07 10:04:01 +00:00
|
|
|
if (!defined($header_offset)) {
|
|
|
|
log::add("no NAPI header found");
|
|
|
|
return undef;
|
|
|
|
}
|
2019-02-15 01:35:48 +00:00
|
|
|
|
2019-03-11 11:14:46 +00:00
|
|
|
my $header_size = 0x20;
|
2019-02-15 01:35:48 +00:00
|
|
|
my $buf = $self->get_block($header_offset, $header_size);
|
|
|
|
|
|
|
|
if (!defined($buf)) {
|
2020-04-07 10:04:01 +00:00
|
|
|
log::add("bad get_block");
|
2019-02-15 01:35:48 +00:00
|
|
|
return undef;
|
|
|
|
}
|
|
|
|
|
|
|
|
my @fields = qw(
|
|
|
|
signature1 unk1 unk2 signature2 unk3 unk4 unk5 signature3
|
|
|
|
unk6 unk7 unk8 all_00
|
|
|
|
);
|
|
|
|
my @values = unpack("a4vCa4vvva4vVVc",$$buf);
|
|
|
|
map { $self->{header}{$fields[$_]} = $values[$_] } (0..scalar(@fields)-1);
|
|
|
|
|
2020-04-07 10:04:01 +00:00
|
|
|
if ($self->{header}{signature1} ne "NAPI") {
|
|
|
|
log::add("failed signature");
|
|
|
|
return undef;
|
|
|
|
}
|
|
|
|
if ($self->{header}{signature2} ne "ESCD") {
|
|
|
|
log::add("failed signature");
|
|
|
|
return undef;
|
|
|
|
}
|
|
|
|
if ($self->{header}{signature3} ne "ACFG") {
|
|
|
|
log::add("failed signature");
|
|
|
|
return undef;
|
|
|
|
}
|
2019-02-15 01:35:48 +00:00
|
|
|
|
|
|
|
# because I have no idea about the actual format of this header, we check
|
|
|
|
# every field
|
|
|
|
if (($self->{header}{unk1} != 0x000f) ||
|
|
|
|
($self->{header}{unk2} != 0x8d) ||
|
|
|
|
($self->{header}{unk3} != 0x000f) ||
|
|
|
|
($self->{header}{unk4} != 0x000e) ||
|
|
|
|
($self->{header}{unk5} != 0x000e) ||
|
|
|
|
($self->{header}{unk6} != 0x0200) ||
|
|
|
|
($self->{header}{unk7} != 0x00000000) ||
|
|
|
|
($self->{header}{unk8} != 0x0000fedf) ||
|
|
|
|
($self->{header}{all_00} != 0x00)
|
|
|
|
) {
|
|
|
|
die("Unexpected NAPI header data");
|
2020-04-07 10:04:01 +00:00
|
|
|
# die instead of simply logging, since we have matched a signature
|
|
|
|
# and dont want to let this fall through to another checker
|
2019-02-15 01:35:48 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
# TODO
|
|
|
|
# - there is one more field at offset 0x1fff, containing a 0x02
|
|
|
|
|
|
|
|
my $check_offset = 0x2000;
|
|
|
|
my $check_size = 0x1000;
|
|
|
|
$buf = $self->get_block($check_offset, $check_size);
|
|
|
|
|
2020-04-07 10:04:01 +00:00
|
|
|
if (!defined($buf)) {
|
|
|
|
log::add("bad get_block");
|
|
|
|
return undef;
|
|
|
|
}
|
|
|
|
|
|
|
|
if ($$buf ne "\xff"x$check_size) {
|
|
|
|
log::add("failed 0xff check");
|
|
|
|
return undef;
|
|
|
|
}
|
2019-02-15 01:35:48 +00:00
|
|
|
|
|
|
|
$self->{flag}{encrypted}="no";
|
|
|
|
# All current examples of this format were not encrypted
|
|
|
|
|
2019-03-11 11:14:46 +00:00
|
|
|
$self->set_offset_size($header_offset + 0x10000, 0x20000);
|
2019-02-15 01:35:48 +00:00
|
|
|
return $self->_check_copyright();
|
|
|
|
}
|
|
|
|
|
|
|
|
sub extract {
|
|
|
|
return shift->_extract(shift);
|
|
|
|
}
|
|
|
|
|
|
|
|
sub insert {
|
|
|
|
return shift->_insert(shift);
|
|
|
|
}
|
|
|
|
|
|
|
|
1;
|
|
|
|
|
2017-06-30 01:03:49 +00:00
|
|
|
package FL2::prefix_nothing;
|
|
|
|
use warnings;
|
|
|
|
use strict;
|
|
|
|
#
|
|
|
|
# Some FL2 files are simply the IMG data, with no header or container.
|
|
|
|
# For these, we can check the copyright string from inside the IMG
|
|
|
|
#
|
|
|
|
|
|
|
|
use base qw(FL2::base);
|
|
|
|
|
|
|
|
sub _check {
|
|
|
|
my $self = shift;
|
|
|
|
|
|
|
|
# List of known file sizes (basically a doublecheck on the signature)
|
|
|
|
my $known = {
|
|
|
|
196608 => [0, 0x30000],
|
|
|
|
};
|
|
|
|
|
2020-04-07 10:04:01 +00:00
|
|
|
if (!defined($known->{$self->{filesize}})) {
|
|
|
|
log::add("not in known sizes");
|
|
|
|
return undef;
|
|
|
|
}
|
2017-06-30 01:03:49 +00:00
|
|
|
|
|
|
|
$self->{flag}{encrypted}="yes";
|
|
|
|
# All current examples of this format were encrypted
|
|
|
|
|
|
|
|
$self->set_offset_size(@{$known->{$self->{filesize}}});
|
|
|
|
return $self->_check_copyright();
|
|
|
|
}
|
|
|
|
|
2017-06-30 01:48:04 +00:00
|
|
|
sub extract {
|
|
|
|
return shift->_extract(shift);
|
|
|
|
}
|
|
|
|
|
2017-07-01 01:22:52 +00:00
|
|
|
sub insert {
|
|
|
|
return shift->_insert(shift);
|
|
|
|
}
|
|
|
|
|
2017-06-30 01:03:49 +00:00
|
|
|
1;
|
|
|
|
|
2019-02-15 01:03:02 +00:00
|
|
|
package FL2::prefix_head_EC;
|
2017-06-30 01:03:49 +00:00
|
|
|
use warnings;
|
|
|
|
use strict;
|
|
|
|
#
|
|
|
|
# Look for FL2 files that have a "_EC" prefix header at their start
|
|
|
|
#
|
|
|
|
|
|
|
|
use base qw(FL2::base);
|
|
|
|
|
|
|
|
sub _check {
|
|
|
|
my $self = shift;
|
|
|
|
|
|
|
|
# List of known file sizes (basically a doublecheck on the signature)
|
|
|
|
# We also store the encryption flag in here
|
|
|
|
my $known = {
|
|
|
|
196896 => 'yes',
|
2018-03-30 05:17:43 +00:00
|
|
|
262176 => 'no',
|
2017-06-30 01:03:49 +00:00
|
|
|
286752 => 'no',
|
|
|
|
};
|
|
|
|
|
|
|
|
my $header_offset = 0;
|
|
|
|
my $header_size = 0x20;
|
|
|
|
my $trailer_size = 0x100;
|
|
|
|
|
|
|
|
my $buf = $self->get_block($header_offset, $header_size);
|
|
|
|
|
2018-03-30 05:18:19 +00:00
|
|
|
if (!defined($buf)) {
|
2020-04-07 10:04:01 +00:00
|
|
|
log::add("bad get_block");
|
2018-03-30 05:18:19 +00:00
|
|
|
return undef;
|
|
|
|
}
|
2017-06-30 01:03:49 +00:00
|
|
|
|
|
|
|
my @fields = qw(
|
2019-09-01 23:37:13 +00:00
|
|
|
signature version filesize imgsize hash_algo sign_algo
|
|
|
|
hash_crc16 header_crc16 unk1
|
2017-06-30 01:03:49 +00:00
|
|
|
);
|
2019-09-01 23:37:13 +00:00
|
|
|
my @values = unpack("a3CVVVVSSC",$$buf);
|
2017-06-30 01:03:49 +00:00
|
|
|
map { $self->{header}{$fields[$_]} = $values[$_] } (0..scalar(@fields)-1);
|
|
|
|
|
2020-04-07 10:04:01 +00:00
|
|
|
if ($self->{header}{signature} ne "_EC") {
|
|
|
|
log::add("failed signature");
|
|
|
|
return undef;
|
|
|
|
}
|
|
|
|
if ($self->{header}{version} != 1) {
|
|
|
|
log::add("failed version");
|
|
|
|
return undef;
|
|
|
|
}
|
|
|
|
if ($self->{header}{filesize} != $self->{filesize}) {
|
|
|
|
log::add("failed header filesize");
|
|
|
|
return undef;
|
|
|
|
}
|
2017-06-30 01:03:49 +00:00
|
|
|
|
2018-03-30 05:18:19 +00:00
|
|
|
if (!defined($known->{$self->{filesize}})) {
|
2020-04-07 10:04:01 +00:00
|
|
|
log::add("not in known sizes");
|
2018-03-30 05:18:19 +00:00
|
|
|
return undef;
|
|
|
|
}
|
|
|
|
|
2017-06-30 01:03:49 +00:00
|
|
|
if ($self->{header}{imgsize}+$header_size+$trailer_size == $self->{filesize}) {
|
|
|
|
# there is an additional block of 0x100 appended to the FL2, outside
|
|
|
|
# of the header defined IMG file
|
|
|
|
# - I expect this is the digital signature
|
|
|
|
$self->{flag}{trailer}="external";
|
|
|
|
} elsif ($self->{header}{imgsize}+$header_size == $self->{filesize}) {
|
|
|
|
# the additional block still looks like it is there, but they have
|
|
|
|
# changed the accounting to count it in the IMG - these files are also
|
|
|
|
# significantly larger in size
|
|
|
|
$self->{flag}{trailer}="internal";
|
|
|
|
} else {
|
2020-04-07 10:04:01 +00:00
|
|
|
log::add("unexpected filesize/imgsize results");
|
2017-06-30 01:03:49 +00:00
|
|
|
return undef;
|
|
|
|
}
|
|
|
|
|
|
|
|
# FIXME
|
|
|
|
# - is that a csum? how to generate it and check it?
|
|
|
|
|
|
|
|
$self->{flag}{encrypted} = $known->{$self->{filesize}};
|
|
|
|
|
|
|
|
$self->set_offset_size($header_size, $self->{header}{imgsize});
|
|
|
|
return $self->_check_copyright();
|
|
|
|
}
|
|
|
|
|
2017-06-30 01:48:04 +00:00
|
|
|
sub extract {
|
|
|
|
return shift->_extract(shift);
|
|
|
|
}
|
|
|
|
|
|
|
|
# Note, no insert() will work until we know how to generate the checksum
|
2017-07-01 01:22:52 +00:00
|
|
|
#sub insert {
|
|
|
|
# my $self = shift;
|
|
|
|
# my $imgfile = shift;
|
|
|
|
#
|
|
|
|
# my $write = $self->_insert($imgfile);
|
|
|
|
#
|
|
|
|
# Calculate checksum
|
|
|
|
# write checksum to header
|
|
|
|
#}
|
|
|
|
|
2017-06-30 01:48:04 +00:00
|
|
|
|
2017-09-05 03:36:01 +00:00
|
|
|
1;
|
|
|
|
|
|
|
|
package FL1::PFH_header;
|
|
|
|
use warnings;
|
|
|
|
use strict;
|
|
|
|
#
|
|
|
|
# Look for FL1 files that have a "$PFH" prefix header near their end
|
|
|
|
#
|
|
|
|
# Seen in at least the l530 firmware updates
|
2017-09-05 04:56:31 +00:00
|
|
|
#
|
|
|
|
# Thanks for skochinsky for the details:
|
|
|
|
# https://github.com/hamishcoleman/thinkpad-ec/issues/46
|
|
|
|
#
|
2017-09-05 03:36:01 +00:00
|
|
|
|
|
|
|
use base qw(FL2::base);
|
|
|
|
|
|
|
|
sub _find_capsule_header {
|
|
|
|
my $self = shift;
|
|
|
|
my $buf = $self->get_block(0,16);
|
|
|
|
return undef if (!defined($buf));
|
|
|
|
$buf = $$buf;
|
|
|
|
|
|
|
|
my $capsule_uuid = "\xbd\x86\x66\x3b\x76\x0d\x30\x40\xb7\x0e\xb5\x51\x9e\x2f\xc5\xa0";
|
|
|
|
|
|
|
|
if ($buf eq $capsule_uuid) {
|
|
|
|
# TODO
|
|
|
|
# - while I am reading the capsule signature, I dont understand
|
|
|
|
# why the magic offset doesnt match anything in the capsule header
|
2020-04-07 10:30:04 +00:00
|
|
|
log::add("capsule_uuid signature found (offset hack applied)");
|
2017-09-05 03:36:01 +00:00
|
|
|
return 0x1d0;
|
|
|
|
}
|
|
|
|
return undef;
|
|
|
|
}
|
|
|
|
|
|
|
|
sub _find_pfh {
|
|
|
|
my $self = shift;
|
|
|
|
my $offset = 0;
|
|
|
|
|
|
|
|
while ($offset < $self->{filesize}) {
|
|
|
|
my $buf = $self->get_block($offset, 4);
|
|
|
|
return undef if (!defined($buf));
|
|
|
|
$buf = $$buf;
|
|
|
|
|
|
|
|
if ($buf eq '$PFH') {
|
2020-04-07 10:34:54 +00:00
|
|
|
log::add("found PFH header signature at offset=0x%x", $offset);
|
2017-09-05 03:36:01 +00:00
|
|
|
return $offset;
|
|
|
|
}
|
|
|
|
|
2020-04-07 10:34:54 +00:00
|
|
|
$offset+=0x08;
|
2017-09-05 03:36:01 +00:00
|
|
|
}
|
|
|
|
|
2020-04-07 10:34:54 +00:00
|
|
|
log::add("no PFH found");
|
2017-09-05 03:36:01 +00:00
|
|
|
return undef; # not found
|
|
|
|
}
|
|
|
|
|
|
|
|
sub _check {
|
|
|
|
my $self = shift;
|
|
|
|
|
|
|
|
# List of known file sizes (basically a doublecheck on the signature)
|
|
|
|
my $known = {
|
|
|
|
9437264 => 1,
|
|
|
|
12587008 => 1,
|
|
|
|
};
|
|
|
|
|
|
|
|
my $capsule_offset_hack = $self->_find_capsule_header() ||0;
|
|
|
|
|
|
|
|
my $header_offset = $self->_find_pfh();
|
2020-04-07 10:34:54 +00:00
|
|
|
return undef if (!defined($header_offset));
|
2017-09-05 03:36:01 +00:00
|
|
|
|
|
|
|
my $header_size = 4+4+4+2+4+2+4+4;
|
|
|
|
|
|
|
|
my $buf = $self->get_block($header_offset, $header_size);
|
|
|
|
|
2020-04-07 10:04:01 +00:00
|
|
|
if (!defined($buf)) {
|
|
|
|
log::add("bad get_block");
|
|
|
|
return undef;
|
|
|
|
}
|
|
|
|
if (!defined($known->{$self->{filesize}})) {
|
|
|
|
log::add("not in known sizes");
|
|
|
|
}
|
2017-09-05 03:36:01 +00:00
|
|
|
|
|
|
|
my @fields = qw(
|
|
|
|
signature version headersize headerchecksum
|
|
|
|
totalimagesize totalimagechecksum
|
|
|
|
numberofimages imagetableoffset
|
|
|
|
);
|
|
|
|
my @values = unpack("a4VVvVvVV",$$buf);
|
|
|
|
map { $self->{header}{$fields[$_]} = $values[$_] } (0..scalar(@fields)-1);
|
|
|
|
|
2020-04-07 10:04:01 +00:00
|
|
|
if ($self->{header}{signature} ne '$PFH') {
|
|
|
|
log::add("failed signature");
|
|
|
|
return undef;
|
|
|
|
}
|
2017-09-05 03:36:01 +00:00
|
|
|
|
|
|
|
# now load the partition table
|
|
|
|
$buf = $self->get_block(
|
|
|
|
$self->{header}{imagetableoffset}+$capsule_offset_hack,
|
|
|
|
(4+4+8+4)*$self->{header}{numberofimages}
|
|
|
|
);
|
2020-04-07 10:04:01 +00:00
|
|
|
if (!defined($buf)) {
|
|
|
|
log::add("bad get_block");
|
|
|
|
return undef;
|
|
|
|
}
|
2017-09-05 03:36:01 +00:00
|
|
|
|
|
|
|
$buf = $$buf;
|
|
|
|
|
|
|
|
while ($buf) {
|
|
|
|
my ($FileOffset, $Size, $FlashAddress, $NameOffset, $rest) =
|
|
|
|
unpack("VVQVa*",$buf);
|
|
|
|
$buf = $rest;
|
|
|
|
|
|
|
|
my $buf2 = $self->get_block(
|
|
|
|
$NameOffset+$capsule_offset_hack,
|
|
|
|
32 # TODO - just a guess at the max name size
|
|
|
|
);
|
2020-04-07 10:04:01 +00:00
|
|
|
if (!defined($buf2)) {
|
|
|
|
log::add("bad get_block");
|
|
|
|
return undef;
|
|
|
|
}
|
2017-09-05 03:36:01 +00:00
|
|
|
|
|
|
|
my $name = unpack("Z*",$$buf2);
|
|
|
|
|
|
|
|
if ($name eq 'Ec') {
|
|
|
|
$self->set_offset_size(
|
|
|
|
$FileOffset+$capsule_offset_hack,
|
|
|
|
$Size,
|
|
|
|
);
|
|
|
|
return $self;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-04-07 10:04:01 +00:00
|
|
|
log::add("no subsection found with name 'Ec'");
|
2017-09-05 03:36:01 +00:00
|
|
|
return undef;
|
|
|
|
}
|
|
|
|
|
|
|
|
sub extract {
|
|
|
|
return shift->_extract(shift);
|
|
|
|
}
|
|
|
|
|
|
|
|
# no insert() will work until we know how to generate the checksums
|
|
|
|
|
|
|
|
|
2017-06-30 01:03:49 +00:00
|
|
|
1;
|
|
|
|
|
|
|
|
# TODO
|
|
|
|
# - 8muj19us.iso has yet another format:
|
|
|
|
# It is all 0xff until 0x500000, however it then looks like it has a header.
|
|
|
|
# There is certainly not the normal copyright string, and the EC version
|
|
|
|
# string is in entirely the wrong place
|
|
|
|
# 00500000 45 43 20 49 4d 41 47 45 30 00 00 00 00 00 00 00 |EC IMAGE0.......|
|
|
|
|
# 00500010 d0 08 38 4d 48 54 37 39 57 57 00 00 00 20 02 00 |..8MHT79WW... ..|
|
|
|
|
# 00500020 00 00 00 00 31 36 01 00 00 00 00 00 0d b3 9e 00 |....16..........|
|
|
|
|
|
|
|
|
package main;
|
|
|
|
use warnings;
|
|
|
|
use strict;
|
|
|
|
|
|
|
|
use Data::Dumper;
|
|
|
|
$Data::Dumper::Indent = 1;
|
|
|
|
$Data::Dumper::Sortkeys = 1;
|
|
|
|
$Data::Dumper::Quotekeys = 0;
|
|
|
|
|
|
|
|
use IO::File;
|
|
|
|
|
|
|
|
# Call each of the detectors in turn to try to find the location of the IMG
|
|
|
|
sub detect_img {
|
|
|
|
my $fl2name = shift;
|
|
|
|
|
2017-07-01 01:22:52 +00:00
|
|
|
my $fh = IO::File->new($fl2name, "r+");
|
2017-06-30 01:03:49 +00:00
|
|
|
if (!defined($fh)) {
|
|
|
|
warn("Could not open $fl2name: $!");
|
|
|
|
return undef;
|
|
|
|
}
|
|
|
|
|
|
|
|
my $object;
|
|
|
|
if (!defined($object)) {
|
2019-03-11 11:22:06 +00:00
|
|
|
$object = FL2::prefix_head_EC->new($fh);
|
2017-06-30 01:03:49 +00:00
|
|
|
}
|
|
|
|
if (!defined($object)) {
|
2019-03-11 11:22:06 +00:00
|
|
|
$object = FL1::PFH_header->new($fh);
|
2017-06-30 01:03:49 +00:00
|
|
|
}
|
|
|
|
if (!defined($object)) {
|
2019-03-11 11:22:06 +00:00
|
|
|
$object = FL2::prefix_head_NAPI->new($fh);
|
2017-06-30 01:03:49 +00:00
|
|
|
}
|
2019-03-11 11:22:06 +00:00
|
|
|
|
|
|
|
# these checks have very little in the way of signatures, so only
|
|
|
|
# check for them after everything else
|
2017-06-30 01:03:49 +00:00
|
|
|
if (!defined($object)) {
|
2019-03-11 11:22:06 +00:00
|
|
|
$object = FL2::prefix_ff->new($fh);
|
2017-06-30 01:03:49 +00:00
|
|
|
}
|
2017-09-05 03:36:01 +00:00
|
|
|
if (!defined($object)) {
|
2019-03-11 11:22:06 +00:00
|
|
|
$object = FL2::prefix_garbage->new($fh);
|
2017-09-05 03:36:01 +00:00
|
|
|
}
|
2019-02-15 01:35:48 +00:00
|
|
|
if (!defined($object)) {
|
2019-03-11 11:22:06 +00:00
|
|
|
$object = FL2::prefix_nothing->new($fh);
|
2019-02-15 01:35:48 +00:00
|
|
|
}
|
2017-06-30 01:03:49 +00:00
|
|
|
|
|
|
|
return $object;
|
|
|
|
}
|
|
|
|
|
|
|
|
sub main() {
|
|
|
|
# get args
|
|
|
|
my $cmd = shift @ARGV;
|
|
|
|
if (!defined($cmd)) {
|
|
|
|
die("Need command");
|
|
|
|
}
|
|
|
|
|
|
|
|
my $fl2file = shift @ARGV;
|
|
|
|
if (!defined($fl2file)) {
|
|
|
|
die("Need FL2 filename");
|
|
|
|
}
|
|
|
|
|
|
|
|
my $imgfile = shift @ARGV;
|
|
|
|
if ($cmd ne 'check' && !defined($imgfile)) {
|
|
|
|
die("Need IMG filename");
|
|
|
|
}
|
|
|
|
|
|
|
|
# validity check args
|
|
|
|
if ($cmd ne "from_fl2" && $cmd ne "to_fl2" && $cmd ne "check") {
|
|
|
|
die("direction must be one of 'from_fl2' or 'to_fl2'");
|
|
|
|
}
|
|
|
|
|
2019-09-01 23:37:42 +00:00
|
|
|
my $verbose = shift @ARGV;
|
|
|
|
if (defined($verbose) && $verbose eq 'verbose') {
|
|
|
|
$verbose = 1;
|
|
|
|
} else {
|
|
|
|
$verbose = 0;
|
|
|
|
}
|
|
|
|
|
2017-06-30 01:03:49 +00:00
|
|
|
if ( ! -e $fl2file ) {
|
|
|
|
die("FL2 file $fl2file must exist");
|
|
|
|
}
|
|
|
|
|
|
|
|
my $object = detect_img($fl2file);
|
|
|
|
if (!defined($object)) {
|
|
|
|
printf("Could not determine IMG details for %s\n", $fl2file);
|
2020-04-07 09:39:44 +00:00
|
|
|
if ($verbose) {
|
|
|
|
log::print();
|
|
|
|
}
|
2018-07-12 07:24:18 +00:00
|
|
|
exit(1);
|
2017-06-30 01:03:49 +00:00
|
|
|
}
|
|
|
|
printf("IMG at offset 0x%x size 0x%x (%s %s)\n",
|
|
|
|
$object->offset(),
|
|
|
|
$object->size(),
|
|
|
|
ref($object),
|
|
|
|
$fl2file,
|
|
|
|
);
|
|
|
|
|
2019-09-01 23:37:42 +00:00
|
|
|
if ($verbose) {
|
|
|
|
print(Dumper($object));
|
2020-04-07 09:39:44 +00:00
|
|
|
log::print();
|
2019-09-01 23:37:42 +00:00
|
|
|
}
|
2017-06-30 01:03:49 +00:00
|
|
|
|
|
|
|
if ($cmd eq 'from_fl2') {
|
|
|
|
if (!$object->can('extract')) {
|
|
|
|
die("FL2 container type cannot extract");
|
|
|
|
}
|
|
|
|
return $object->extract($imgfile);
|
|
|
|
}
|
|
|
|
|
|
|
|
if ($cmd eq 'to_fl2') {
|
|
|
|
if (!$object->can('insert')) {
|
|
|
|
die("FL2 container type cannot insert");
|
|
|
|
}
|
|
|
|
return $object->insert($imgfile);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
unless(caller) {
|
|
|
|
main();
|
|
|
|
}
|