From 8882ac55efd84142971657b17d9ab9733bb8348f Mon Sep 17 00:00:00 2001 From: Nicola Corna Date: Sat, 31 Mar 2018 16:25:03 +0200 Subject: util/me_cleaner: Update to v1.2 Changelog: * Add support for the HAP/AltMeDisable bit * Add support for selective partition removal * Fix the ME permission removal on gen. 3 * Add public key match * Print the compressed size of the Huffman modules on gen. 2 * Wipe the ME6 Ignition firmware images * Fix the removal of the last partition on ME6 * Various region size fixes * Add manpage * Add setup.py * Print the value of the HAP/AltMeDisable bit The output image should be identical, except for the platforms affected by bugs (ME 6.x, but it's not supported by coreboot and ME 11.x with the -d option, but it's not being used in our build process). Overall, nothing should change when it's used with the CONFIG_USE_ME_CLEANER option. Tested on a Lenovo X220 and Sapphire Pure Platinum H61. Change-Id: I3d5e0d9af0a36cc7476a964cf753914c2f3df9d2 Signed-off-by: Nicola Corna Reviewed-on: https://review.coreboot.org/25506 Tested-by: build bot (Jenkins) Reviewed-by: Philipp Deppenwiese --- util/me_cleaner/me_cleaner.py | 568 ++++++++++++++++++++++++++++-------------- 1 file changed, 377 insertions(+), 191 deletions(-) (limited to 'util/me_cleaner/me_cleaner.py') diff --git a/util/me_cleaner/me_cleaner.py b/util/me_cleaner/me_cleaner.py index 11f077d207..03fbbcb56b 100755 --- a/util/me_cleaner/me_cleaner.py +++ b/util/me_cleaner/me_cleaner.py @@ -1,7 +1,7 @@ #!/usr/bin/python -# me_cleaner - Tool for partial deblobbing of Intel ME/TXE firmware images -# Copyright (C) 2016, 2017 Nicola Corna +# me_cleaner - Tool for partial deblobbing of Intel ME/TXE firmware images +# Copyright (C) 2016-2018 Nicola Corna # # 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 @@ -14,12 +14,14 @@ # GNU General Public License for more details. # -import sys -import itertools +from __future__ import division, print_function + +import argparse import binascii import hashlib -import argparse +import itertools import shutil +import sys from struct import pack, unpack @@ -27,6 +29,23 @@ min_ftpr_offset = 0x400 spared_blocks = 4 unremovable_modules = ("ROMP", "BUP") unremovable_modules_me11 = ("rbe", "kernel", "syslib", "bup") +unremovable_partitions = ("FTPR",) + +pubkeys_md5 = { + "763e59ebe235e45a197a5b1a378dfa04": ("ME", ("6.x.x.x",)), + "3a98c847d609c253e145bd36512629cb": ("ME", ("6.0.50.x",)), + "0903fc25b0f6bed8c4ed724aca02124c": ("ME", ("7.x.x.x", "8.x.x.x")), + "2011ae6df87c40fba09e3f20459b1ce0": ("ME", ("9.0.x.x", "9.1.x.x")), + "e8427c5691cf8b56bc5cdd82746957ed": ("ME", ("9.5.x.x", "10.x.x.x")), + "986a78e481f185f7d54e4af06eb413f6": ("ME", ("11.x.x.x",)), + "bda0b6bb8ca0bf0cac55ac4c4d55e0f2": ("TXE", ("1.x.x.x",)), + "b726a2ab9cd59d4e62fe2bead7cf6997": ("TXE", ("1.x.x.x",)), + "0633d7f951a3e7968ae7460861be9cfb": ("TXE", ("2.x.x.x",)), + "1d0a36e9f5881540d8e4b382c6612ed8": ("TXE", ("3.x.x.x",)), + "be900fef868f770d266b1fc67e887e69": ("SPS", ("2.x.x.x",)), + "4622e3f2cb212a89c90a4de3336d88d2": ("SPS", ("3.x.x.x",)), + "31ef3d950eac99d18e187375c0764ca4": ("SPS", ("4.x.x.x",)) +} class OutOfRegionException(Exception): @@ -40,59 +59,71 @@ class RegionFile: self.region_end = region_end def read(self, n): - return self.f.read(n) + if f.tell() + n <= self.region_end: + return self.f.read(n) + else: + raise OutOfRegionException() def readinto(self, b): - return self.f.readinto(b) + if f.tell() + len(b) <= self.region_end: + return self.f.readinto(b) + else: + raise OutOfRegionException() def seek(self, offset): - return self.f.seek(offset) + if self.region_start + offset <= self.region_end: + return self.f.seek(self.region_start + offset) + else: + raise OutOfRegionException() def write_to(self, offset, data): - if offset >= self.region_start and \ - offset + len(data) <= self.region_end: - self.f.seek(offset) + if self.region_start + offset + len(data) <= self.region_end: + self.f.seek(self.region_start + offset) return self.f.write(data) else: raise OutOfRegionException() def fill_range(self, start, end, fill): - if start >= self.region_start and end <= self.region_end: + if self.region_start + end <= self.region_end: if start < end: block = fill * 4096 - self.f.seek(start) + self.f.seek(self.region_start + start) self.f.writelines(itertools.repeat(block, (end - start) // 4096)) self.f.write(block[:(end - start) % 4096]) else: raise OutOfRegionException() + def fill_all(self, fill): + self.fill_range(0, self.region_end - self.region_start, fill) + def move_range(self, offset_from, size, offset_to, fill): - if offset_from >= self.region_start and \ - offset_from + size <= self.region_end and \ - offset_to >= self.region_start and \ - offset_to + size <= self.region_end: + if self.region_start + offset_from + size <= self.region_end and \ + self.region_start + offset_to + size <= self.region_end: for i in range(0, size, 4096): - self.f.seek(offset_from + i, 0) - block = self.f.read(4096 if size - i >= 4096 else size - i) - self.f.seek(offset_from + i, 0) + self.f.seek(self.region_start + offset_from + i, 0) + block = self.f.read(min(size - i, 4096)) + self.f.seek(self.region_start + offset_from + i, 0) self.f.write(fill * len(block)) - self.f.seek(offset_to + i, 0) + self.f.seek(self.region_start + offset_to + i, 0) self.f.write(block) else: raise OutOfRegionException() def save(self, filename, size): - self.f.seek(self.region_start) - copyf = open(filename, "w+b") - for i in range(0, size, 4096): - copyf.write(self.f.read(4096 if size - i >= 4096 else size - i)) - return copyf + if self.region_start + size <= self.region_end: + self.f.seek(self.region_start) + copyf = open(filename, "w+b") + for i in range(0, size, 4096): + copyf.write(self.f.read(min(size - i, 4096))) + return copyf + else: + raise OutOfRegionException() -def get_chunks_offsets(llut, me_start): +def get_chunks_offsets(llut): chunk_count = unpack("> 4) & 7 - sys.stdout.write(" {:<16} ({:<7}, ".format(name, comp_str[comp_type])) + print(" {:<16} ({:<7}, ".format(name, comp_str[comp_type]), end="") if comp_type == 0x00 or comp_type == 0x02: - sys.stdout.write("0x{:06x} - 0x{:06x}): " - .format(offset, offset + size)) + print("0x{:06x} - 0x{:06x} ): " + .format(offset, offset + size), end="") if name in unremovable_modules: end_addr = max(end_addr, offset + size) @@ -146,7 +177,6 @@ def remove_modules(f, mod_headers, ftpr_offset, me_end): print("removed") elif comp_type == 0x01: - sys.stdout.write("fragmented data ): ") if not chunks_offsets: f.seek(offset) llut = f.read(4) @@ -158,16 +188,25 @@ def remove_modules(f, mod_headers, ftpr_offset, me_end): chunk_size = unpack("> 12 & 0xff0 fmba = (flmap1 & 0xff) << 4 + fpsba = flmap1 >> 12 & 0xff0 f.seek(frba) flreg = unpack("= me_end: sys.exit("The ME/TXE region in this image has been disabled") - f.seek(me_start + 0x10) - if f.read(4) != b"$FPT": + mef = RegionFile(f, me_start, me_end) + + mef.seek(0x10) + if mef.read(4) != b"$FPT": sys.exit("The ME/TXE region is corrupted or missing") print("The ME/TXE region goes from {:#x} to {:#x}" @@ -521,17 +585,16 @@ if __name__ == "__main__": else: sys.exit("Unknown image") - print("Found FPT header at {:#x}".format(me_start + 0x10)) + end_addr = me_end - f.seek(me_start + 0x14) - entries = unpack("= 0: - check_mn2_tag(f, ftpr_offset + ftpr_mn2_offset) + check_mn2_tag(mef, ftpr_offset + ftpr_mn2_offset) print("Found FTPR manifest at {:#x}" .format(ftpr_offset + ftpr_mn2_offset)) else: sys.exit("Can't find the manifest of the FTPR partition") else: - check_mn2_tag(f, ftpr_offset) + check_mn2_tag(mef, ftpr_offset) me11 = False ftpr_mn2_offset = 0 - f.seek(ftpr_offset + ftpr_mn2_offset + 0x24) - version = unpack("= 6: + variant = "ME" + else: + variant = "TXE" + print("WARNING Unknown public key {}\n" + " Assuming Intel {}\n" + " Please report this warning to the project's maintainer!" + .format(pubkey_md5, variant)) - print("Removing extra partitions...") - mef.fill_range(me_start + 0x30, ftpr_offset, b"\xff") - mef.fill_range(ftpr_offset + ftpr_lenght, me_end, b"\xff") + if not args.check and args.output: + f.close() + shutil.copy(args.file, args.output) + f = open(args.output, "r+b") - print("Removing extra partition entries in FPT...") - mef.write_to(me_start + 0x30, ftpr_header) - mef.write_to(me_start + 0x14, pack(" 0: + fdf = RegionFile(f, fd_start, fd_end) if me11: - mef.seek(me_start + 0x10) - header = bytearray(mef.read(0x20)) - header[0x0b] = 0x00 - else: - mef.seek(me_start) - header = bytearray(mef.read(0x30)) - header[0x1b] = 0x00 - checksum = (0x100 - sum(header) & 0xff) & 0xff - - print("Correcting checksum (0x{:02x})...".format(checksum)) - # The checksum is just the two's complement of the sum of the first - # 0x30 bytes in ME < 11 or bytes 0x10:0x30 in ME >= 11 (except for - # 0x1b, the checksum itself). In other words, the sum of those bytes - # must be always 0x00. - mef.write_to(me_start + 0x1b, pack("B", checksum)) - - print("Reading FTPR modules list...") - if me11: - end_addr, ftpr_offset = \ - check_and_remove_modules_me11(mef, me_start, me_end, - ftpr_offset, ftpr_lenght, - min_ftpr_offset, args.relocate, - args.keep_modules) + fdf.seek(fpsba) + pchstrp0 = unpack(" 0: - end_addr = (end_addr // 0x1000 + 1) * 0x1000 - end_addr += spared_blocks * 0x1000 + fdf.seek(fpsba + 0x28) + pchstrp10 = unpack(" 0: - print("The ME region can be reduced up to:\n" - " {:08x}:{:08x} me".format(me_start, end_addr - 1)) - elif args.truncate: - print("Truncating file at {:#x}...".format(end_addr)) - f.truncate(end_addr) + if not args.check: + if not args.soft_disable_only and not me6_ignition: + print("Reading partitions list...") + unremovable_part_fpt = b"" + extra_part_end = 0 + whitelist = [] + blacklist = [] + + whitelist += unremovable_partitions + + if args.blacklist: + blacklist = args.blacklist.split(",") + elif args.whitelist: + whitelist += args.whitelist.split(",") + + for i in range(entries): + partition = partitions[i * 0x20:(i + 1) * 0x20] + flags = unpack(" me_end: + print(" {:<4} ({:^24}, 0x{:08x} total bytes): nothing to " + "remove" + .format(part_name, "no data here", part_length)) + else: + print(" {:<4} (0x{:08x} - 0x{:09x}, 0x{:08x} total bytes): " + .format(part_name, part_start, part_end, part_length), + end="") + if part_name in whitelist or (blacklist and + part_name not in blacklist): + unremovable_part_fpt += partition + if part_name != "FTPR": + extra_part_end = max(extra_part_end, part_end) + print("NOT removed") + else: + mef.fill_range(part_start, part_end, b"\xff") + print("removed") + + print("Removing partition entries in FPT...") + mef.write_to(0x30, unremovable_part_fpt) + mef.write_to(0x14, + pack("= 11 (except for + # 0x1b, the checksum itself). In other words, the sum of those + # bytes must be always 0x00. + mef.write_to(0x1b, pack("B", checksum)) + + print("Reading FTPR modules list...") + if me11: + end_addr, ftpr_offset = \ + check_and_remove_modules_me11(mef, me_end, + ftpr_offset, ftpr_length, + min_ftpr_offset, + args.relocate, + args.keep_modules) + else: + end_addr, ftpr_offset = \ + check_and_remove_modules(mef, me_end, ftpr_offset, + min_ftpr_offset, args.relocate, + args.keep_modules) + + if end_addr > 0: + end_addr = max(end_addr, extra_part_end) + end_addr = (end_addr // 0x1000 + 1) * 0x1000 + end_addr += spared_blocks * 0x1000 + + print("The ME minimum size should be {0} bytes " + "({0:#x} bytes)".format(end_addr)) + + if me_start > 0: + print("The ME region can be reduced up to:\n" + " {:08x}:{:08x} me" + .format(me_start, me_start + end_addr - 1)) + elif args.truncate: + print("Truncating file at {:#x}...".format(end_addr)) + f.truncate(end_addr) + + if args.soft_disable or args.soft_disable_only: + if me11: + print("Setting the HAP bit in PCHSTRP0 to disable Intel ME...") + pchstrp0 |= (1 << 16) + fdf.write_to(fpsba, pack(" {:08x}:{:08x} me" - .format(me_start, me_end - 1, me_start, end_addr - 1)) + .format(me_start, me_end - 1, + me_start, me_start + end_addr - 1)) print(" {:08x}:{:08x} bios --> {:08x}:{:08x} bios" - .format(bios_start, bios_end - 1, end_addr, bios_end - 1)) + .format(bios_start, bios_end - 1, + me_start + end_addr, bios_end - 1)) - flreg1 = start_end_to_flreg(end_addr, bios_end) - flreg2 = start_end_to_flreg(me_start, end_addr) + flreg1 = start_end_to_flreg(me_start + end_addr, bios_end) + flreg2 = start_end_to_flreg(me_start, me_start + end_addr) fdf_copy.seek(frba + 0x4) fdf_copy.write(pack("