diff options
| author | Leah Rowe <leah@libreboot.org> | 2022-11-14 00:51:12 +0000 | 
|---|---|---|
| committer | Leah Rowe <leah@libreboot.org> | 2022-11-14 00:51:12 +0000 | 
| commit | 7af9953463c65fe2f02704e6bce815d830e58d7d (patch) | |
| tree | dce6c19484fd27288c65ac33092040601d8a0622 /resources/blobs | |
| parent | b5c25efed46f0a9121023997c6758eda5c3f5017 (diff) | |
pragmatic system distribution guideline compliancepsdg
osboot is now part of libreboot, and will soon shut down.
libreboot now conforms to osboot policy.
Diffstat (limited to 'resources/blobs')
| -rwxr-xr-x | resources/blobs/me7_update_parser.py | 616 | ||||
| -rw-r--r-- | resources/blobs/sources | 24 | 
2 files changed, 640 insertions, 0 deletions
| diff --git a/resources/blobs/me7_update_parser.py b/resources/blobs/me7_update_parser.py new file mode 100755 index 00000000..e3e91413 --- /dev/null +++ b/resources/blobs/me7_update_parser.py @@ -0,0 +1,616 @@ +#!/usr/bin/env python3 + +"""ME7 Update binary parser.""" + +# Copyright (C) 2020 Tom Hiller <thrilleratplay@gmail.com> +# Copyright (C) 2016-2018 Nicola Corna <nicola@corna.info> +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the +# GNU General Public License for more details. +# + +# Based on the amazing me_cleaner, https://github.com/corna/me_cleaner, parses +# the required signed partition from an ME update file to generate a valid +# flashable ME binary. +# +#  This was written for Heads ROM, https://github.com/osresearch/heads +#  to allow continuous integration reproducible builds for Lenovo xx20 models +#  (X220, T420, T520, etc). +# +#  A full model list can be found: +#   https://download.lenovo.com/ibmdl/pub/pc/pccbbs/mobiles/83rf46ww.txt + + +from struct import pack, unpack +from typing import List +import argparse +import sys +import hashlib +import binascii +import os.path + +############################################################################# + +FTPR_END = 0x76000 +MINIFIED_FTPR_OFFSET = 0x400  # offset start of Factory Partition (FTPR) +ORIG_FTPR_OFFSET = 0xCC000 +PARTITION_HEADER_OFFSET = 0x30  # size of partition header + +DEFAULT_OUTPUT_FILE_NAME = "flashregion_2_intel_me.bin" + +############################################################################# + + +class EntryFlags: +    """EntryFlag bitmap values.""" + +    ExclBlockUse = 8192 +    WOPDisable = 4096 +    Logical = 2048 +    Execute = 1024 +    Write = 512 +    Read = 256 +    DirectAccess = 128 +    Type = 64 + + +def generateHeader() -> bytes: +    """Generate Header.""" +    ROM_BYPASS_INSTR_0 = binascii.unhexlify("2020800F") +    ROM_BYPASS_INSTR_1 = binascii.unhexlify("40000010") +    ROM_BYPASS_INSTR_2 = pack("<I", 0) +    ROM_BYPASS_INSTR_3 = pack("<I", 0) + +    # $FPT Partition table header +    HEADER_TAG = "$FPT".encode() +    HEADER_NUM_PARTITIONS = pack("<I", 1) +    HEADER_VERSION = b"\x20"  # version 2.0 +    HEADER_ENTRY_TYPE = b"\x10" +    HEADER_LENGTH = b"\x30" +    HEADER_CHECKSUM = pack("<B", 0) +    HEADER_FLASH_CYCLE_LIFE = pack("<H", 7) +    HEADER_FLASH_CYCLE_LIMIT = pack("<H", 100) +    HEADER_UMA_SIZE = pack("<H", 32) +    HEADER_FLAGS = binascii.unhexlify("000000FCFFFF") +    HEADER_FITMAJOR = pack("<H", 0) +    HEADER_FITMINOR = pack("<H", 0) +    HEADER_FITHOTFIX = pack("<H", 0) +    HEADER_FITBUILD = pack("<H", 0) + +    FTPR_header_layout = bytearray( +        ROM_BYPASS_INSTR_0 +        + ROM_BYPASS_INSTR_1 +        + ROM_BYPASS_INSTR_2 +        + ROM_BYPASS_INSTR_3 +        + HEADER_TAG +        + HEADER_NUM_PARTITIONS +        + HEADER_VERSION +        + HEADER_ENTRY_TYPE +        + HEADER_LENGTH +        + HEADER_CHECKSUM +        + HEADER_FLASH_CYCLE_LIFE +        + HEADER_FLASH_CYCLE_LIMIT +        + HEADER_UMA_SIZE +        + HEADER_FLAGS +        + HEADER_FITMAJOR +        + HEADER_FITMINOR +        + HEADER_FITHOTFIX +        + HEADER_FITBUILD +    ) + +    # Update checksum +    FTPR_header_layout[27] = (0x100 - sum(FTPR_header_layout) & 0xFF) & 0xFF + +    return FTPR_header_layout + + +def generateFtpPartition() -> bytes: +    """Partition table entry.""" +    ENTRY_NAME = binascii.unhexlify("46545052") +    ENTRY_OWNER = binascii.unhexlify("FFFFFFFF")  # "None" +    ENTRY_OFFSET = binascii.unhexlify("00040000") +    ENTRY_LENGTH = binascii.unhexlify("00600700") +    ENTRY_START_TOKENS = pack("<I", 1) +    ENTRY_MAX_TOKENS = pack("<I", 1) +    ENTRY_SCRATCH_SECTORS = pack("<I", 0) +    ENTRY_FLAGS = pack( +        "<I", +        ( +            EntryFlags.ExclBlockUse +            + EntryFlags.Execute +            + EntryFlags.Write +            + EntryFlags.Read +            + EntryFlags.DirectAccess +        ), +    ) + +    partition = ( +        ENTRY_NAME +        + ENTRY_OWNER +        + ENTRY_OFFSET +        + ENTRY_LENGTH +        + ENTRY_START_TOKENS +        + ENTRY_MAX_TOKENS +        + ENTRY_SCRATCH_SECTORS +        + ENTRY_FLAGS +    ) + +    # offset of the partition - length of partition entry -length of header +    pad_len = MINIFIED_FTPR_OFFSET - (len(partition) + PARTITION_HEADER_OFFSET) +    padding = b"" + +    for i in range(0, pad_len): +        padding += b"\xFF" + +    return partition + padding + + +############################################################################ + + +class OutOfRegionException(Exception): +    """Out of Region Exception.""" + +    pass + + +class clean_ftpr: +    """Clean Factory Parition (FTPR).""" + +    UNREMOVABLE_MODULES = ("ROMP", "BUP") +    COMPRESSION_TYPE_NAME = ("uncomp.", "Huffman", "LZMA") + +    def __init__(self, ftpr: bytes): +        """Init.""" +        self.orig_ftpr = ftpr +        self.ftpr = ftpr +        self.mod_headers: List[bytes] = [] +        self.check_and_clean_ftpr() + +    ##################################################################### +    # tilities +    ##################################################################### +    def slice(self, offset: int, size: int) -> bytes: +        """Copy data of a given size from FTPR starting from offset.""" +        offset_end = offset + size +        return self.ftpr[offset:offset_end] + +    def unpack_next_int(self, offset: int) -> int: +        """Sugar syntax for unpacking a little-endian UINT at offset.""" +        return self.unpack_val(self.slice(offset, 4)) + +    def unpack_val(self, data: bytes) -> int: +        """Sugar syntax for unpacking a little-endian unsigned integer.""" +        return unpack("<I", data)[0] + +    def bytes_to_ascii(self, data: bytes) -> str: +        """Decode bytes into ASCII.""" +        return data.rstrip(b"\x00").decode("ascii") + +    def clear_ftpr_data(self, start: int, end: int) -> None: +        """Replace values in range with 0xFF.""" +        empty_data = bytes() + +        for i in range(0, end - start): +            empty_data += b"\xff" +        self.write_ftpr_data(start, empty_data) + +    def write_ftpr_data(self, start: int, data: bytes) -> None: +        """Replace data in FTPR starting at a given offset.""" +        end = len(data) + start + +        new_partition = self.ftpr[:start] +        new_partition += data + +        if end != FTPR_END: +            new_partition += self.ftpr[end:] + +        self.ftpr = new_partition + +    ###################################################################### +    # FTPR cleanig/checking functions +    ###################################################################### +    def get_chunks_offsets(self, llut: bytes): +        """Calculate Chunk offsets from LLUT.""" +        chunk_count = self.unpack_val(llut[0x04:0x08]) +        huffman_stream_end = sum(unpack("<II", llut[0x10:0x18])) +        nonzero_offsets = [huffman_stream_end] +        offsets = [] + +        for i in range(0, chunk_count): +            llut_start = 0x40 + (i * 4) +            llut_end = 0x44 + (i * 4) + +            chunk = llut[llut_start:llut_end] +            offset = 0 + +            if chunk[3] != 0x80: +                offset = self.unpack_val(chunk[0:3] + b"\x00") + +            offsets.append([offset, 0]) + +            if offset != 0: +                nonzero_offsets.append(offset) + +        nonzero_offsets.sort() + +        for i in offsets: +            if i[0] != 0: +                i[1] = nonzero_offsets[nonzero_offsets.index(i[0]) + 1] + +        return offsets + +    def relocate_partition(self) -> int: +        """Relocate partition.""" +        new_offset = MINIFIED_FTPR_OFFSET +        name = self.bytes_to_ascii(self.slice(PARTITION_HEADER_OFFSET, 4)) + +        old_offset, partition_size = unpack( +            "<II", self.slice(PARTITION_HEADER_OFFSET + 0x8, 0x8) +        ) + +        llut_start = 0 +        for mod_header in self.mod_headers: +            if (self.unpack_val(mod_header[0x50:0x54]) >> 4) & 7 == 0x01: +                llut_start = self.unpack_val(mod_header[0x38:0x3C]) +                llut_start += old_offset +                break + +        if self.mod_headers and llut_start != 0: +            # Bytes 0x9:0xb of the LLUT (bytes 0x1:0x3 of the AddrBase) are +            # added to the SpiBase (bytes 0xc:0x10 of the LLUT) to compute the +            # final start of the LLUT. Since AddrBase is not modifiable, we can +            # act only on SpiBase and here we compute the minimum allowed +            # new_offset. +            llut_start_corr = unpack("<H", self.slice(llut_start + 0x9, 2))[0] +            new_offset = max( +                new_offset, llut_start_corr - llut_start - 0x40 + old_offset +            ) +            new_offset = ((new_offset + 0x1F) // 0x20) * 0x20 +        offset_diff = new_offset - old_offset + +        print( +            "Relocating {} from {:#x} - {:#x} to {:#x} - {:#x}...".format( +                name, +                old_offset, +                old_offset + partition_size, +                new_offset, +                new_offset + partition_size, +            ) +        ) + +        print(" Adjusting FPT entry...") +        self.write_ftpr_data( +            PARTITION_HEADER_OFFSET + 0x08, +            pack("<I", new_offset), +        ) + +        if self.mod_headers: +            if llut_start != 0: +                if self.slice(llut_start, 4) == b"LLUT": +                    print(" Adjusting LUT start offset...") +                    llut_offset = pack( +                        "<I", llut_start + offset_diff + 0x40 - llut_start_corr +                    ) +                    self.write_ftpr_data(llut_start + 0x0C, llut_offset) + +                    print(" Adjusting Huffman start offset...") +                    old_huff_offset = self.unpack_next_int(llut_start + 0x14) +                    ftpr_offset_diff = MINIFIED_FTPR_OFFSET - ORIG_FTPR_OFFSET +                    self.write_ftpr_data( +                        llut_start + 0x14, +                        pack("<I", old_huff_offset + ftpr_offset_diff), +                    ) + +                    print(" Adjusting chunks offsets...") +                    chunk_count = self.unpack_next_int(llut_start + 0x4) +                    offset = llut_start + 0x40 +                    offset_end = chunk_count * 4 +                    chunks = bytearray(self.slice(offset, offset_end)) + +                    for i in range(0, offset_end, 4): +                        i_plus_3 = i + 3 + +                        if chunks[i_plus_3] != 0x80: +                            chunks[i:i_plus_3] = pack( +                                "<I", +                                self.unpack_val(chunks[i:i_plus_3] + b"\x00") +                                + (MINIFIED_FTPR_OFFSET - ORIG_FTPR_OFFSET), +                            )[0:3] +                    self.write_ftpr_data(offset, bytes(chunks)) +                else: +                    sys.exit("Huffman modules present but no LLUT found!") +            else: +                print(" No Huffman modules found") + +        print(" Moving data...") +        partition_size = min(partition_size, FTPR_END - old_offset) + +        if ( +            old_offset + partition_size <= FTPR_END +            and new_offset + partition_size <= FTPR_END +        ): +            for i in range(0, partition_size, 4096): +                block_length = min(partition_size - i, 4096) +                block = self.slice(old_offset + i, block_length) +                self.clear_ftpr_data(old_offset + i, len(block)) + +                self.write_ftpr_data(new_offset + i, block) +        else: +            raise OutOfRegionException() + +        return new_offset + +    def remove_modules(self) -> int: +        """Remove modules.""" +        unremovable_huff_chunks = [] +        chunks_offsets = [] +        base = 0 +        chunk_size = 0 +        end_addr = 0 + +        for mod_header in self.mod_headers: +            name = self.bytes_to_ascii(mod_header[0x04:0x14]) +            offset = self.unpack_val(mod_header[0x38:0x3C]) +            size = self.unpack_val(mod_header[0x40:0x44]) +            flags = self.unpack_val(mod_header[0x50:0x54]) +            comp_type = (flags >> 4) & 7 +            comp_type_name = self.COMPRESSION_TYPE_NAME[comp_type] + +            print(" {:<16} ({:<7}, ".format(name, comp_type_name), end="") + +            # If compresion type uncompressed or LZMA +            if comp_type == 0x00 or comp_type == 0x02: +                offset_end = offset + size +                range_msg = "0x{:06x} - 0x{:06x}       ): " +                print(range_msg.format(offset, offset_end), end="") + +                if name in self.UNREMOVABLE_MODULES: +                    end_addr = max(end_addr, offset + size) +                    print("NOT removed, essential") +                else: +                    offset_end = min(offset + size, FTPR_END) +                    self.clear_ftpr_data(offset, offset_end) +                    print("removed") + +            # Else if compression type huffman +            elif comp_type == 0x01: +                if not chunks_offsets: +                    # Check if Local Look Up Table (LLUT) is present +                    if self.slice(offset, 4) == b"LLUT": +                        llut = self.slice(offset, 0x40) + +                        chunk_count = self.unpack_val(llut[0x4:0x8]) +                        base = self.unpack_val(llut[0x8:0xC]) + 0x10000000 +                        chunk_size = self.unpack_val(llut[0x30:0x34]) + +                        llut = self.slice(offset, (chunk_count * 4) + 0x40) + +                        # calculate offsets of chunks from LLUT +                        chunks_offsets = self.get_chunks_offsets(llut) +                    else: +                        no_llut_msg = "Huffman modules found," +                        no_llut_msg += "but LLUT is not present." +                        sys.exit(no_llut_msg) + +                module_base = self.unpack_val(mod_header[0x34:0x38]) +                module_size = self.unpack_val(mod_header[0x3C:0x40]) +                first_chunk_num = (module_base - base) // chunk_size +                last_chunk_num = first_chunk_num + module_size // chunk_size +                huff_size = 0 + +                chunk_length = last_chunk_num + 1 +                for chunk in chunks_offsets[first_chunk_num:chunk_length]: +                    huff_size += chunk[1] - chunk[0] + +                size_in_kiB = "~" + str(int(round(huff_size / 1024))) + " KiB" +                print( +                    "fragmented data, {:<9}): ".format(size_in_kiB), +                    end="", +                ) + +                # Check if module is in the unremovable list +                if name in self.UNREMOVABLE_MODULES: +                    print("NOT removed, essential") + +                    # add to list of unremovable chunks +                    for x in chunks_offsets[first_chunk_num:chunk_length]: +                        if x[0] != 0: +                            unremovable_huff_chunks.append(x) +                else: +                    print("removed") + +            # Else unknown compression type +            else: +                unkwn_comp_msg = " 0x{:06x} - 0x{:06x}): " +                unkwn_comp_msg += "unknown compression, skipping" +                print(unkwn_comp_msg.format(offset, offset + size), end="") + +        if chunks_offsets: +            removable_huff_chunks = [] + +            for chunk in chunks_offsets: +                # if chunk is not in a unremovable chunk, it must be removable +                if all( +                    not ( +                        unremovable_chk[0] <= chunk[0] < unremovable_chk[1] +                        or unremovable_chk[0] < chunk[1] <= unremovable_chk[1] +                    ) +                    for unremovable_chk in unremovable_huff_chunks +                ): +                    removable_huff_chunks.append(chunk) + +            for removable_chunk in removable_huff_chunks: +                if removable_chunk[1] > removable_chunk[0]: +                    chunk_start = removable_chunk[0] - ORIG_FTPR_OFFSET +                    chunk_end = removable_chunk[1] - ORIG_FTPR_OFFSET +                    self.clear_ftpr_data(chunk_start, chunk_end) + +            end_addr = max( +                end_addr, max(unremovable_huff_chunks, key=lambda x: x[1])[1] +            ) +            end_addr -= ORIG_FTPR_OFFSET + +        return end_addr + +    def find_mod_header_size(self) -> None: +        """Find module header size.""" +        self.mod_header_size = 0 +        data = self.slice(0x290, 0x84) + +        # check header size +        if data[0x0:0x4] == b"$MME": +            if data[0x60:0x64] == b"$MME" or self.num_modules == 1: +                self.mod_header_size = 0x60 +            elif data[0x80:0x84] == b"$MME": +                self.mod_header_size = 0x80 + +    def find_mod_headers(self) -> None: +        """Find module headers.""" +        data = self.slice(0x290, self.mod_header_size * self.num_modules) + +        for i in range(0, self.num_modules): +            header_start = i * self.mod_header_size +            header_end = (i + 1) * self.mod_header_size +            self.mod_headers.append(data[header_start:header_end]) + +    def resize_partition(self, end_addr: int) -> None: +        """Resize partition.""" +        spared_blocks = 4 +        if end_addr > 0: +            end_addr = (end_addr // 0x1000 + 1) * 0x1000 +            end_addr += spared_blocks * 0x1000 + +            # partition header not added yet +            # remove  trailing data the same size as the header. +            end_addr -= MINIFIED_FTPR_OFFSET + +            me_size_msg = "The ME minimum size should be {0} " +            me_size_msg += "bytes ({0:#x} bytes)" +            print(me_size_msg.format(end_addr)) +            print("Truncating file at {:#x}...".format(end_addr)) +            self.ftpr = self.ftpr[:end_addr] + +    def check_and_clean_ftpr(self) -> None: +        """Check and clean FTPR (factory partition).""" +        self.num_modules = self.unpack_next_int(0x20) +        self.find_mod_header_size() + +        if self.mod_header_size != 0: +            self.find_mod_headers() + +            # ensure all of the headers begin with b'$MME' +            if all(hdr.startswith(b"$MME") for hdr in self.mod_headers): +                end_addr = self.remove_modules() +                new_offset = self.relocate_partition() +                end_addr += new_offset + +                self.resize_partition(end_addr) + +                # flip bit +                # XXX: I have no idea why this works and passes RSA signiture +                self.write_ftpr_data(0x39, b"\x00") +            else: +                sys.exit( +                    "Found less modules than expected in the FTPR " +                    "partition; skipping modules removal and exiting." +                ) +        else: +            sys.exit( +                "Can't find the module header size; skipping modules" +                "removal and exiting." +            ) + + +########################################################################## + + +def check_partition_signature(f, offset) -> bool: +    """check_partition_signature copied/shamelessly stolen from me_cleaner.""" +    f.seek(offset) +    header = f.read(0x80) +    modulus = int(binascii.hexlify(f.read(0x100)[::-1]), 16) +    public_exponent = unpack("<I", f.read(4))[0] +    signature = int(binascii.hexlify(f.read(0x100)[::-1]), 16) + +    header_len = unpack("<I", header[0x4:0x8])[0] * 4 +    manifest_len = unpack("<I", header[0x18:0x1C])[0] * 4 +    f.seek(offset + header_len) + +    sha256 = hashlib.sha256() +    sha256.update(header) +    tmp = f.read(manifest_len - header_len) +    sha256.update(tmp) + +    decrypted_sig = pow(signature, public_exponent, modulus) +    return "{:#x}".format(decrypted_sig).endswith(sha256.hexdigest())  # FIXME + + +########################################################################## + + +def generate_me_blob(input_file: str, output_file: str) -> None: +    """Generate ME blob.""" +    print("Starting ME 7.x Update parser.") + +    orig_f = open(input_file, "rb") +    cleaned_ftpr = clean_ftpr(orig_f.read(FTPR_END)) +    orig_f.close() + +    fo = open(output_file, "wb") +    fo.write(generateHeader()) +    fo.write(generateFtpPartition()) +    fo.write(cleaned_ftpr.ftpr) +    fo.close() + + +def verify_output(output_file: str) -> None: +    """Verify Generated ME file.""" +    file_verifiy = open(output_file, "rb") + +    if check_partition_signature(file_verifiy, MINIFIED_FTPR_OFFSET): +        print(output_file + " is VALID") +        file_verifiy.close() +    else: +        print(output_file + " is INVALID!!") +        file_verifiy.close() +        sys.exit("The FTPR partition signature is not valid.") + + +if __name__ == "__main__": +    parser = argparse.ArgumentParser( +        description="Tool to remove as much code " +        "as possible from Intel ME/TXE 7.x firmware " +        "update and create paratition for a flashable ME parition." +    ) + + +parser.add_argument("file", help="ME/TXE image or full dump") +parser.add_argument( +    "-O", +    "--output", +    metavar="output_file", +    help="save " +    "save file name other than the default '" + DEFAULT_OUTPUT_FILE_NAME + "'", +) + +args = parser.parse_args() + +output_file_name = DEFAULT_OUTPUT_FILE_NAME if not args.output else args.output + +# Check if output file exists, ask to overwrite or exit +if os.path.isfile(output_file_name): +    input_msg = output_file_name +    input_msg += " exists.  Do you want to overwrite? [y/N]: " +    if not str(input(input_msg)).lower().startswith("y"): +        sys.exit("Not overwriting file.  Exiting.") + +generate_me_blob(args.file, output_file_name) +verify_output(output_file_name) diff --git a/resources/blobs/sources b/resources/blobs/sources new file mode 100644 index 00000000..0a148f9f --- /dev/null +++ b/resources/blobs/sources @@ -0,0 +1,24 @@ +# This file holds the download sources for various intel blobs +# board shortnames are listed and enclosed by '{}' followed by an opening +# and closing '{}' for all blobs available for the board. +# The board shortname must be the name of the board minus the trailing rom size. +# If you want to make additions, try to add a backup url for download links and  +# list hashes as sha1 sums. + +{x230 x230t x230i t430 x230fhd}{ +	ME_hash 039c89c6d44ae11ae2510cbd5fed756e97ed9a31 +	ME_dl https://download.lenovo.com/pccbbs/mobiles/g1rg24ww.exe +	ME_bkup_dl https://web.archive.org/web/20210706183911/https://download.lenovo.com/pccbbs/mobiles/g1rg24ww.exe +} + +{x220 x220t t420 t520 t420s t520}{ +	ME_hash fa0f96c8f36646492fb8c57ad3296bf5f647d9c5 +	ME_dl https://download.lenovo.com/ibmdl/pub/pc/pccbbs/mobiles/83rf46ww.exe +	ME_bkup_dl https://web.archive.org/web/20220202201637/https://download.lenovo.com/ibmdl/pub/pc/pccbbs/mobiles/83rf46ww.exe +} + +{t440p w541}{ +	ME_hash b2f2a1baa1f0c8139e46b0d3e206386ff197bed5 +	ME_dl https://download.lenovo.com/pccbbs/mobiles/glrg22ww.exe +	ME_bkup_dl https://web.archive.org/web/20211120031520/https://download.lenovo.com/pccbbs/mobiles/glrg22ww.exe +} | 
