#!/usr/bin/python # me_cleaner - Tool for partial deblobbing of Intel ME/TXE firmware images # Copyright (C) 2016, 2017 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 # 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. # import sys import itertools import binascii import hashlib import argparse import shutil from struct import pack, unpack min_ftpr_offset = 0x400 spared_blocks = 4 unremovable_modules = ("BUP", "ROMP") class OutOfRegionException(Exception): pass class regionFile: def __init__(self, f, region_start, region_end): self.f = f self.region_start = region_start self.region_end = region_end def read(self, n): return self.f.read(n) def readinto(self, b): return self.f.readinto(b) def seek(self, offset): return self.f.seek(offset) def write_to(self, offset, data): if offset >= self.region_start and \ offset + len(data) <= self.region_end: self.f.seek(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 start < end: block = fill * 4096 self.f.seek(start) self.f.writelines(itertools.repeat(block, (end - start) // 4096)) self.f.write(block[:(end - start) % 4096]) else: raise OutOfRegionException() 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: 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.write(fill * len(block)) self.f.seek(offset_to + i, 0) self.f.write(block) else: raise OutOfRegionException() def get_chunks_offsets(llut, me_start): chunk_count = unpack("> 4) & 7 sys.stdout.write(" {:<16} ({:<7}, ".format(name, comp_str[comp_type])) if comp_type == 0x00 or comp_type == 0x02: sys.stdout.write("0x{:06x} - 0x{:06x}): " .format(offset, offset + size)) if name in unremovable_modules: end_addr = max(end_addr, offset + size) print("NOT removed, essential") else: end = min(offset + size, me_end) f.fill_range(offset, end, b"\xff") print("removed") elif comp_type == 0x01: sys.stdout.write("fragmented data ): ") if not chunks_offsets: f.seek(offset) llut = f.read(4) if llut == b"LLUT": llut += f.read(0x3c) chunk_count = unpack(" removable_chunk[0]: end = min(removable_chunk[1], me_end) f.fill_range(removable_chunk[0], end, b"\xff") end_addr = max(end_addr, max(unremovable_huff_chunks, key=lambda x: x[1])[1]) return end_addr def check_partition_signature(f, offset): f.seek(offset) header = f.read(0x80) modulus = int(binascii.hexlify(f.read(0x100)[::-1]), 16) public_exponent = unpack("> 4) & 7 == 0x01: llut_start = unpack("> 24 & 0x7 frba = flmap0 >> 12 & 0xff0 fmba = (flmap1 & 0xff) << 4 if nr >= 2: f.seek(frba) flreg0, flreg1, flreg2 = unpack("> 4 & 0x1fff000 | 0xfff + 1 me_start = (flreg2 & 0x1fff) << 12 me_end = flreg2 >> 4 & 0x1fff000 | 0xfff + 1 if me_start >= 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": sys.exit("The ME/TXE region is corrupted or missing") print("The ME/TXE region goes from {:#x} to {:#x}" .format(me_start, me_end)) else: sys.exit("This image does not contains a ME/TXE firmware NR = {})" .format(nr)) else: sys.exit("Unknown image") print("Found FPT header at {:#x}".format(me_start + 0x10)) f.seek(me_start + 0x14) entries = unpack("= 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)) if not me11: print("Reading FTPR modules list...") mef.seek(ftpr_offset + 0x1c) tag = mef.read(4) if tag == b"$MN2": mef.seek(ftpr_offset + 0x20) num_modules = unpack(" 0: print("The ME region can be reduced up to:\n" " {:08x}:{:08x} me" .format(me_start, end_addr - 1)) else: print("Found less modules than expected in the FTPR " "partition; skipping modules removal") else: print("Can't find the module header size; skipping " "modules removal") else: print("Wrong FTPR partition tag ({}); skipping modules removal" .format(tag)) else: print("Modules removal in ME v11 or greater is not yet supported") sys.stdout.write("Checking FTPR RSA signature... ") if check_partition_signature(f, ftpr_offset + ftpr_mn2_offset): print("VALID") else: print("INVALID!!") sys.exit("The FTPR partition signature is not valid. Is the input " "ME/TXE image valid?") f.close() if not args.check: print("Done! Good luck!")