Add support for generation 1

On generation 1 (ME version <= 5), Intel ME can be disabled completely
by:

 * Wiping its firmware
 * Disabling the corresponding region
 * Setting the meDisable bits in ICHSTRP0 and MCHSTRP0

Optionally, with the usual -d flag, me_cleaner can remove the ME's RW
permissions to the other regions (but it probably has no effect, Intel
ME is disabled anyways).

Based on ich9deblob from the libreboot project.
This commit is contained in:
Nicola Corna 2018-05-21 14:29:43 +02:00
parent d5b0806cdf
commit 57b3fc765e
2 changed files with 198 additions and 133 deletions

View file

@ -56,16 +56,19 @@ SPI programmer.
## Results ## Results
For pre-Skylake firmware (ME version < 11) this tool removes almost everything, For generation 1 (before Nehalem, ME version <= 5) this tool removes the whole
leaving only the two fundamental modules needed for the correct boot, `ROMP` and ME firmware and disables it completely.
`BUP`. The code size is reduced from 1.5 MB (non-AMT firmware) or 5 MB (AMT
firmware) to ~90 kB of compressed code.
Starting from Skylake (ME version >= 11) the ME subsystem and the firmware For generation 2 (Nehalem-Broadwell, ME version between 6 and 10) this tool
structure have changed, requiring substantial changes in _me\_cleaner_. removes almost everything, leaving only the two fundamental modules needed for
The fundamental modules required for the correct boot are now four (`rbe`, the correct boot, `ROMP` and `BUP`. The firmware size is reduced from 1.5 MB
`kernel`, `syslib` and `bup`) and the minimum code size is ~300 kB of compressed (non-AMT firmware) or 5 MB (AMT firmware) to ~90 kB.
code (from the 2 MB of the non-AMT firmware and the 7 MB of the AMT one).
For generation 3 (from Skylake onwards, ME version >= 11) the ME subsystem and
the firmware structure have changed, requiring substantial changes
in _me\_cleaner_. The fundamental modules required for the correct boot are now
four (`rbe`, `kernel`, `syslib` and `bup`) and the minimum firmware size is
~300 kB (from the 2 MB of the non-AMT firmware and the 7 MB of the AMT one).
On some boards the OEM firmware fails to boot without a valid Intel ME firmware; On some boards the OEM firmware fails to boot without a valid Intel ME firmware;
in the other cases the system should work with minor inconveniences (like longer in the other cases the system should work with minor inconveniences (like longer

View file

@ -28,10 +28,16 @@ from struct import pack, unpack
min_ftpr_offset = 0x400 min_ftpr_offset = 0x400
spared_blocks = 4 spared_blocks = 4
unremovable_modules = ("ROMP", "BUP") unremovable_modules = ("ROMP", "BUP")
unremovable_modules_me11 = ("rbe", "kernel", "syslib", "bup") unremovable_modules_gen3 = ("rbe", "kernel", "syslib", "bup")
unremovable_partitions = ("FTPR",) unremovable_partitions = ("FTPR",)
pubkeys_md5 = { pubkeys_md5 = {
"8431285d43b0f2a2f520d7cab3d34178": ("ME", ("2.0.x.x", "2.1.x.x",
"2.2.x.x")),
"4c00dd06c28119b5c1e5bb8eb6f30596": ("ME", ("2.5.x.x", "2.6.x.x")),
"9c24077a7f7490967855e9c4c16c6b9e": ("ME", ("3.x.x.x",)),
"bf41464be736f5520d80c67f6789020e": ("ME", ("4.x.x.x",)),
"5c7169b7e7065323fb7b3b5657b4d57a": ("ME", ("5.x.x.x",)),
"763e59ebe235e45a197a5b1a378dfa04": ("ME", ("6.x.x.x",)), "763e59ebe235e45a197a5b1a378dfa04": ("ME", ("6.x.x.x",)),
"3a98c847d609c253e145bd36512629cb": ("ME", ("6.0.50.x",)), "3a98c847d609c253e145bd36512629cb": ("ME", ("6.0.50.x",)),
"0903fc25b0f6bed8c4ed724aca02124c": ("ME", ("7.x.x.x", "8.x.x.x")), "0903fc25b0f6bed8c4ed724aca02124c": ("ME", ("7.x.x.x", "8.x.x.x")),
@ -384,7 +390,7 @@ def check_and_remove_modules(f, me_end, offset, min_offset,
return -1, offset return -1, offset
def check_and_remove_modules_me11(f, me_end, partition_offset, def check_and_remove_modules_gen3(f, me_end, partition_offset,
partition_length, min_offset, relocate, partition_length, min_offset, relocate,
keep_modules): keep_modules):
@ -431,7 +437,7 @@ def check_and_remove_modules_me11(f, me_end, partition_offset,
print("NOT removed, partition manif.") print("NOT removed, partition manif.")
elif name.endswith(".met"): elif name.endswith(".met"):
print("NOT removed, module metadata") print("NOT removed, module metadata")
elif any(name.startswith(m) for m in unremovable_modules_me11): elif any(name.startswith(m) for m in unremovable_modules_gen3):
print("NOT removed, essential") print("NOT removed, essential")
else: else:
removed = True removed = True
@ -449,10 +455,11 @@ def check_and_remove_modules_me11(f, me_end, partition_offset,
return end_data, partition_offset return end_data, partition_offset
def check_mn2_tag(f, offset): def check_mn2_tag(f, offset, gen):
f.seek(offset + 0x1c) f.seek(offset + 0x1c)
tag = f.read(4) tag = f.read(4)
if tag != b"$MN2": expected_tag = b"$MAN" if gen == 1 else b"$MN2"
if tag != expected_tag:
sys.exit("Wrong FTPR manifest tag ({}), this image may be corrupted" sys.exit("Wrong FTPR manifest tag ({}), this image may be corrupted"
.format(tag)) .format(tag))
@ -536,11 +543,14 @@ if __name__ == "__main__":
sys.exit("Relocation is not yet supported with custom whitelist or " sys.exit("Relocation is not yet supported with custom whitelist or "
"blacklist") "blacklist")
f = open(args.file, "rb" if args.check or args.output else "r+b") gen = None
f.seek(0x10)
magic = f.read(4)
if magic == b"$FPT": f = open(args.file, "rb" if args.check or args.output else "r+b")
magic0 = f.read(4)
f.seek(0x10)
magic10 = f.read(4)
if b"$FPT" in {magic0, magic10}:
print("ME/TXE image detected") print("ME/TXE image detected")
if args.descriptor or args.extract_descriptor or args.extract_me or \ if args.descriptor or args.extract_descriptor or args.extract_me or \
@ -552,17 +562,20 @@ if __name__ == "__main__":
me_end = f.tell() me_end = f.tell()
mef = RegionFile(f, me_start, me_end) mef = RegionFile(f, me_start, me_end)
elif magic == b"\x5a\xa5\xf0\x0f": elif b"\x5a\xa5\xf0\x0f" in {magic0, magic10}:
print("Full image detected") print("Full image detected")
if args.truncate and not args.extract_me: f.seek(0x4 if magic0 == b"\x5a\xa5\xf0\x0f" else 0x14)
sys.exit("-t requires a separated ME/TXE image (or --extract-me)") flmap0, flmap1, flmap2 = unpack("<III", f.read(0xc))
f.seek(0x14)
flmap0, flmap1 = unpack("<II", f.read(8))
frba = flmap0 >> 12 & 0xff0 frba = flmap0 >> 12 & 0xff0
fmba = (flmap1 & 0xff) << 4 fmba = (flmap1 & 0xff) << 4
fpsba = flmap1 >> 12 & 0xff0
# Generation 1
fisba = flmap1 >> 12 & 0xff0
fmsba = (flmap2 & 0xff) << 4
# Generation 2-3
fpsba = fisba
f.seek(frba) f.seek(frba)
flreg = unpack("<III", f.read(12)) flreg = unpack("<III", f.read(12))
@ -572,39 +585,59 @@ if __name__ == "__main__":
me_start, me_end = flreg_to_start_end(flreg[2]) me_start, me_end = flreg_to_start_end(flreg[2])
if me_start >= me_end: if me_start >= me_end:
sys.exit("The ME/TXE region in this image has been disabled") print("The ME region in this image has already been disabled")
else:
mef = RegionFile(f, me_start, me_end) mef = RegionFile(f, me_start, me_end)
mef.seek(0x10) if magic0 == b"\x5a\xa5\xf0\x0f":
if mef.read(4) != b"$FPT": gen = 1
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: else:
sys.exit("Unknown image") sys.exit("Unknown image")
if me_start < me_end:
mef.seek(0)
if mef.read(4) == b"$FPT":
fpt_offset = 0
else:
mef.seek(0x10)
if mef.read(4) == b"$FPT":
fpt_offset = 0x10
else:
if me_start > 0:
sys.exit("The ME/TXE region is valid but the firmware is "
"corrupted or missing")
else:
sys.exit("Unknown error")
if gen == 1:
end_addr = 0
else:
end_addr = me_end end_addr = me_end
print("Found FPT header at {:#x}".format(mef.region_start + 0x10)) if me_start < me_end:
print("Found FPT header at {:#x}".format(mef.region_start + fpt_offset))
mef.seek(0x14) mef.seek(fpt_offset + 0x4)
entries = unpack("<I", mef.read(4))[0] entries = unpack("<I", mef.read(4))[0]
print("Found {} partition(s)".format(entries)) print("Found {} partition(s)".format(entries))
mef.seek(0x30) mef.seek(fpt_offset + 0x20)
partitions = mef.read(entries * 0x20) partitions = mef.read(entries * 0x20)
ftpr_header = b"" ftpr_header = b""
for i in range(entries): for i in range(entries):
if partitions[i * 0x20:(i * 0x20) + 4] == b"FTPR": if partitions[i * 0x20:(i * 0x20) + 4] in {b"CODE", b"FTPR"}:
ftpr_header = partitions[i * 0x20:(i + 1) * 0x20] ftpr_header = partitions[i * 0x20:(i + 1) * 0x20]
break break
if ftpr_header == b"": if ftpr_header == b"":
sys.exit("FTPR header not found, this image doesn't seem to be valid") sys.exit("FTPR header not found, this image doesn't seem to be "
"valid")
if ftpr_header[0x0:0x4] == b"CODE":
gen = 1
ftpr_offset, ftpr_length = unpack("<II", ftpr_header[0x08:0x10]) ftpr_offset, ftpr_length = unpack("<II", ftpr_header[0x08:0x10])
print("Found FTPR header: FTPR partition spans from {:#x} to {:#x}" print("Found FTPR header: FTPR partition spans from {:#x} to {:#x}"
@ -612,7 +645,7 @@ if __name__ == "__main__":
mef.seek(ftpr_offset) mef.seek(ftpr_offset)
if mef.read(4) == b"$CPD": if mef.read(4) == b"$CPD":
me11 = True gen = 3
num_entries = unpack("<I", mef.read(4))[0] num_entries = unpack("<I", mef.read(4))[0]
mef.seek(ftpr_offset + 0x10) mef.seek(ftpr_offset + 0x10)
@ -628,21 +661,22 @@ if __name__ == "__main__":
break break
if ftpr_mn2_offset >= 0: if ftpr_mn2_offset >= 0:
check_mn2_tag(mef, ftpr_offset + ftpr_mn2_offset) check_mn2_tag(mef, ftpr_offset + ftpr_mn2_offset, gen)
print("Found FTPR manifest at {:#x}" print("Found FTPR manifest at {:#x}"
.format(ftpr_offset + ftpr_mn2_offset)) .format(ftpr_offset + ftpr_mn2_offset))
else: else:
sys.exit("Can't find the manifest of the FTPR partition") sys.exit("Can't find the manifest of the FTPR partition")
else: else:
check_mn2_tag(mef, ftpr_offset) check_mn2_tag(mef, ftpr_offset, gen)
me11 = False
ftpr_mn2_offset = 0 ftpr_mn2_offset = 0
if not gen:
gen = 2
mef.seek(ftpr_offset + ftpr_mn2_offset + 0x24) mef.seek(ftpr_offset + ftpr_mn2_offset + 0x24)
version = unpack("<HHHH", mef.read(0x08)) version = unpack("<HHHH", mef.read(0x08))
print("ME/TXE firmware version {}" print("ME/TXE firmware version {} (generation {})"
.format('.'.join(str(i) for i in version))) .format('.'.join(str(i) for i in version), gen))
mef.seek(ftpr_offset + ftpr_mn2_offset + 0x80) mef.seek(ftpr_offset + ftpr_mn2_offset + 0x80)
pubkey_md5 = hashlib.md5(mef.read(0x104)).hexdigest() pubkey_md5 = hashlib.md5(mef.read(0x104)).hexdigest()
@ -658,7 +692,8 @@ if __name__ == "__main__":
variant = "TXE" variant = "TXE"
print("WARNING Unknown public key {}\n" print("WARNING Unknown public key {}\n"
" Assuming Intel {}\n" " Assuming Intel {}\n"
" Please report this warning to the project's maintainer!" " Please report this warning to the project's "
"maintainer!"
.format(pubkey_md5, variant)) .format(pubkey_md5, variant))
if not args.check and args.output: if not args.check and args.output:
@ -666,25 +701,46 @@ if __name__ == "__main__":
shutil.copy(args.file, args.output) shutil.copy(args.file, args.output)
f = open(args.output, "r+b") f = open(args.output, "r+b")
if me_start < me_end:
mef = RegionFile(f, me_start, me_end) mef = RegionFile(f, me_start, me_end)
if me_start > 0: if me_start > 0:
fdf = RegionFile(f, fd_start, fd_end) fdf = RegionFile(f, fd_start, fd_end)
if me11: if gen == 1:
fdf.seek(fpsba) for (ba, name) in ((fisba, "ICHSTRP0"), (fmsba, "MCHSTRP0")):
pchstrp0 = unpack("<I", fdf.read(4))[0] fdf.seek(ba)
print("The HAP bit is " + strp = unpack("<I", fdf.read(4))[0]
("SET" if pchstrp0 & 1 << 16 else "NOT SET")) print("The meDisable bit in " + name + " is ", end="")
if strp & 1:
print("SET")
else: else:
print("NOT SET, setting it now...")
fdf.write_to(ba, pack("<I", strp | 1))
elif gen == 2:
fdf.seek(fpsba + 0x28) fdf.seek(fpsba + 0x28)
pchstrp10 = unpack("<I", fdf.read(4))[0] pchstrp10 = unpack("<I", fdf.read(4))[0]
print("The AltMeDisable bit is " + print("The AltMeDisable bit is " +
("SET" if pchstrp10 & 1 << 7 else "NOT SET")) ("SET" if pchstrp10 & 1 << 7 else "NOT SET"))
else:
fdf.seek(fpsba)
pchstrp0 = unpack("<I", fdf.read(4))[0]
print("The HAP bit is " +
("SET" if pchstrp0 & 1 << 16 else "NOT SET"))
# Generation 1: wipe everything and disable the ME region
if gen == 1 and me_start < me_end:
print("Disabling the ME region...")
f.seek(frba + 0x8)
f.write(pack("<I", 0x1fff))
print("Wiping the ME region...")
mef = RegionFile(f, me_start, me_end)
mef.fill_all("\xff")
# ME 6 Ignition: wipe everything # ME 6 Ignition: wipe everything
me6_ignition = False me6_ignition = False
if not args.check and not args.soft_disable_only and \ if gen == 2 and not args.check and not args.soft_disable_only and \
variant == "ME" and version[0] == 6: variant == "ME" and version[0] == 6:
mef.seek(ftpr_offset + 0x20) mef.seek(ftpr_offset + 0x20)
num_modules = unpack("<I", mef.read(4))[0] num_modules = unpack("<I", mef.read(4))[0]
@ -696,7 +752,7 @@ if __name__ == "__main__":
mef.fill_all(b"\xff") mef.fill_all(b"\xff")
me6_ignition = True me6_ignition = True
if not args.check: if gen != 1 and not args.check:
if not args.soft_disable_only and not me6_ignition: if not args.soft_disable_only and not me6_ignition:
print("Reading partitions list...") print("Reading partitions list...")
unremovable_part_fpt = b"" unremovable_part_fpt = b""
@ -769,7 +825,7 @@ if __name__ == "__main__":
flags &= ~(0x00000001) flags &= ~(0x00000001)
mef.write_to(0x24, pack("<I", flags)) mef.write_to(0x24, pack("<I", flags))
if me11: if gen == 3:
mef.seek(0x10) mef.seek(0x10)
header = bytearray(mef.read(0x20)) header = bytearray(mef.read(0x20))
header[0x0b] = 0x00 header[0x0b] = 0x00
@ -787,9 +843,9 @@ if __name__ == "__main__":
mef.write_to(0x1b, pack("B", checksum)) mef.write_to(0x1b, pack("B", checksum))
print("Reading FTPR modules list...") print("Reading FTPR modules list...")
if me11: if gen == 3:
end_addr, ftpr_offset = \ end_addr, ftpr_offset = \
check_and_remove_modules_me11(mef, me_end, check_and_remove_modules_gen3(mef, me_end,
ftpr_offset, ftpr_length, ftpr_offset, ftpr_length,
min_ftpr_offset, min_ftpr_offset,
args.relocate, args.relocate,
@ -817,7 +873,7 @@ if __name__ == "__main__":
f.truncate(end_addr) f.truncate(end_addr)
if args.soft_disable or args.soft_disable_only: if args.soft_disable or args.soft_disable_only:
if me11: if gen == 3:
print("Setting the HAP bit in PCHSTRP0 to disable Intel ME...") print("Setting the HAP bit in PCHSTRP0 to disable Intel ME...")
pchstrp0 |= (1 << 16) pchstrp0 |= (1 << 16)
fdf.write_to(fpsba, pack("<I", pchstrp0)) fdf.write_to(fpsba, pack("<I", pchstrp0))
@ -829,7 +885,7 @@ if __name__ == "__main__":
if args.descriptor: if args.descriptor:
print("Removing ME/TXE R/W access to the other flash regions...") print("Removing ME/TXE R/W access to the other flash regions...")
if me11: if gen == 3:
flmstr2 = 0x00400500 flmstr2 = 0x00400500
else: else:
fdf.seek(fmba + 0x4) fdf.seek(fmba + 0x4)
@ -853,15 +909,20 @@ if __name__ == "__main__":
me_start + end_addr, bios_end - 1)) me_start + end_addr, bios_end - 1))
flreg1 = start_end_to_flreg(me_start + end_addr, bios_end) flreg1 = start_end_to_flreg(me_start + end_addr, bios_end)
if gen != 1:
flreg2 = start_end_to_flreg(me_start, me_start + end_addr) flreg2 = start_end_to_flreg(me_start, me_start + end_addr)
fdf_copy.seek(frba + 0x4) fdf_copy.seek(frba + 0x4)
fdf_copy.write(pack("<II", flreg1, flreg2)) fdf_copy.write(pack("<I", flreg1))
if gen != 1:
fdf_copy.write(pack("<I", flreg2))
else: else:
print("\nWARNING:\n The start address of the BIOS region " print("\nWARNING:\n"
"isn't equal to the end address of the ME\n region: if " "The start address of the BIOS region (0x{:08x}) isn't "
"you want to recover the space from the ME region you " "equal to the end address\nof the ME region (0x{:08x}): "
"have to\n manually modify the descriptor.\n") "if you want to recover the space from the ME \nregion "
"you have to manually modify the descriptor.\n"
.format(bios_start, me_end))
else: else:
print("Extracting the descriptor to \"{}\"..." print("Extracting the descriptor to \"{}\"..."
.format(args.extract_descriptor)) .format(args.extract_descriptor))
@ -869,6 +930,7 @@ if __name__ == "__main__":
fdf_copy.close() fdf_copy.close()
if gen != 1:
if args.extract_me: if args.extract_me:
if args.truncate: if args.truncate:
print("Extracting and truncating the ME image to \"{}\"..." print("Extracting and truncating the ME image to \"{}\"..."