Browse Source

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.
Nicola Corna 5 years ago
parent
commit
57b3fc765e
2 changed files with 196 additions and 131 deletions
  1. 13 10
      README.md
  2. 183 121
      me_cleaner.py

+ 13 - 10
README.md

@@ -56,16 +56,19 @@ SPI programmer.
 
 ## Results
 
-For pre-Skylake firmware (ME version < 11) this tool removes almost everything,
-leaving only the two fundamental modules needed for the correct boot, `ROMP` and
-`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
-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 code size is ~300 kB of compressed
-code (from the 2 MB of the non-AMT firmware and the 7 MB of the AMT one).
+For generation 1 (before Nehalem, ME version <= 5) this tool removes the whole
+ME firmware and disables it completely.
+
+For generation 2 (Nehalem-Broadwell, ME version between 6 and 10) this tool
+removes almost everything, leaving only the two fundamental modules needed for
+the correct boot, `ROMP` and `BUP`. The firmware size is reduced from 1.5 MB
+(non-AMT firmware) or 5 MB (AMT firmware) to ~90 kB.
+
+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;
 in the other cases the system should work with minor inconveniences (like longer

+ 183 - 121
me_cleaner.py

@@ -28,10 +28,16 @@ from struct import pack, unpack
 min_ftpr_offset = 0x400
 spared_blocks = 4
 unremovable_modules = ("ROMP", "BUP")
-unremovable_modules_me11 = ("rbe", "kernel", "syslib", "bup")
+unremovable_modules_gen3 = ("rbe", "kernel", "syslib", "bup")
 unremovable_partitions = ("FTPR",)
 
 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",)),
     "3a98c847d609c253e145bd36512629cb": ("ME",  ("6.0.50.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
 
 
-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,
                                   keep_modules):
 
@@ -431,7 +437,7 @@ def check_and_remove_modules_me11(f, me_end, partition_offset,
                 print("NOT removed, partition manif.")
             elif name.endswith(".met"):
                 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")
             else:
                 removed = True
@@ -449,10 +455,11 @@ def check_and_remove_modules_me11(f, me_end, partition_offset,
     return end_data, partition_offset
 
 
-def check_mn2_tag(f, offset):
+def check_mn2_tag(f, offset, gen):
     f.seek(offset + 0x1c)
     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"
                  .format(tag))
 
@@ -536,11 +543,14 @@ if __name__ == "__main__":
         sys.exit("Relocation is not yet supported with custom whitelist or "
                  "blacklist")
 
+    gen = None
+
     f = open(args.file, "rb" if args.check or args.output else "r+b")
+    magic0 = f.read(4)
     f.seek(0x10)
-    magic = f.read(4)
+    magic10 = f.read(4)
 
-    if magic == b"$FPT":
+    if b"$FPT" in {magic0, magic10}:
         print("ME/TXE image detected")
 
         if args.descriptor or args.extract_descriptor or args.extract_me or \
@@ -552,17 +562,20 @@ if __name__ == "__main__":
         me_end = f.tell()
         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")
 
-        if args.truncate and not args.extract_me:
-            sys.exit("-t requires a separated ME/TXE image (or --extract-me)")
-
-        f.seek(0x14)
-        flmap0, flmap1 = unpack("<II", f.read(8))
+        f.seek(0x4 if magic0 == b"\x5a\xa5\xf0\x0f" else 0x14)
+        flmap0, flmap1, flmap2 = unpack("<III", f.read(0xc))
         frba = flmap0 >> 12 & 0xff0
         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)
         flreg = unpack("<III", f.read(12))
@@ -572,119 +585,162 @@ if __name__ == "__main__":
         me_start, me_end = flreg_to_start_end(flreg[2])
 
         if me_start >= me_end:
-            sys.exit("The ME/TXE region in this image has been disabled")
-
-        mef = RegionFile(f, me_start, me_end)
+            print("The ME region in this image has already been disabled")
+        else:
+            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")
+        if magic0 == b"\x5a\xa5\xf0\x0f":
+            gen = 1
 
-        print("The ME/TXE region goes from {:#x} to {:#x}"
-              .format(me_start, me_end))
     else:
         sys.exit("Unknown image")
 
-    end_addr = me_end
+    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
 
-    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)
-    entries = unpack("<I", mef.read(4))[0]
-    print("Found {} partition(s)".format(entries))
+        mef.seek(fpt_offset + 0x4)
+        entries = unpack("<I", mef.read(4))[0]
+        print("Found {} partition(s)".format(entries))
 
-    mef.seek(0x30)
-    partitions = mef.read(entries * 0x20)
+        mef.seek(fpt_offset + 0x20)
+        partitions = mef.read(entries * 0x20)
 
-    ftpr_header = b""
+        ftpr_header = b""
 
-    for i in range(entries):
-        if partitions[i * 0x20:(i * 0x20) + 4] == b"FTPR":
-            ftpr_header = partitions[i * 0x20:(i + 1) * 0x20]
-            break
+        for i in range(entries):
+            if partitions[i * 0x20:(i * 0x20) + 4] in {b"CODE", b"FTPR"}:
+                ftpr_header = partitions[i * 0x20:(i + 1) * 0x20]
+                break
 
-    if ftpr_header == b"":
-        sys.exit("FTPR header not found, this image doesn't seem to be valid")
+        if ftpr_header == b"":
+            sys.exit("FTPR header not found, this image doesn't seem to be "
+                     "valid")
 
-    ftpr_offset, ftpr_length = unpack("<II", ftpr_header[0x08:0x10])
-    print("Found FTPR header: FTPR partition spans from {:#x} to {:#x}"
-          .format(ftpr_offset, ftpr_offset + ftpr_length))
+        if ftpr_header[0x0:0x4] == b"CODE":
+            gen = 1
 
-    mef.seek(ftpr_offset)
-    if mef.read(4) == b"$CPD":
-        me11 = True
-        num_entries = unpack("<I", mef.read(4))[0]
+        ftpr_offset, ftpr_length = unpack("<II", ftpr_header[0x08:0x10])
+        print("Found FTPR header: FTPR partition spans from {:#x} to {:#x}"
+              .format(ftpr_offset, ftpr_offset + ftpr_length))
 
-        mef.seek(ftpr_offset + 0x10)
-        ftpr_mn2_offset = -1
+        mef.seek(ftpr_offset)
+        if mef.read(4) == b"$CPD":
+            gen = 3
+            num_entries = unpack("<I", mef.read(4))[0]
 
-        for i in range(0, num_entries):
-            data = mef.read(0x18)
-            name = data[0x0:0xc].rstrip(b"\x00").decode("ascii")
-            offset = unpack("<I", data[0xc:0xf] + b"\x00")[0]
+            mef.seek(ftpr_offset + 0x10)
+            ftpr_mn2_offset = -1
 
-            if name == "FTPR.man":
-                ftpr_mn2_offset = offset
-                break
+            for i in range(0, num_entries):
+                data = mef.read(0x18)
+                name = data[0x0:0xc].rstrip(b"\x00").decode("ascii")
+                offset = unpack("<I", data[0xc:0xf] + b"\x00")[0]
 
-        if ftpr_mn2_offset >= 0:
-            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")
+                if name == "FTPR.man":
+                    ftpr_mn2_offset = offset
+                    break
+
+            if ftpr_mn2_offset >= 0:
+                check_mn2_tag(mef, ftpr_offset + ftpr_mn2_offset, gen)
+                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(mef, ftpr_offset)
-        me11 = False
-        ftpr_mn2_offset = 0
-
-    mef.seek(ftpr_offset + ftpr_mn2_offset + 0x24)
-    version = unpack("<HHHH", mef.read(0x08))
-    print("ME/TXE firmware version {}"
-          .format('.'.join(str(i) for i in version)))
-
-    mef.seek(ftpr_offset + ftpr_mn2_offset + 0x80)
-    pubkey_md5 = hashlib.md5(mef.read(0x104)).hexdigest()
-
-    if pubkey_md5 in pubkeys_md5:
-        variant, pubkey_versions = pubkeys_md5[pubkey_md5]
-        print("Public key match: Intel {}, firmware versions {}"
-              .format(variant, ", ".join(pubkey_versions)))
-    else:
-        if version[0] >= 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))
+            check_mn2_tag(mef, ftpr_offset, gen)
+            ftpr_mn2_offset = 0
+            if not gen:
+                gen = 2
+
+        mef.seek(ftpr_offset + ftpr_mn2_offset + 0x24)
+        version = unpack("<HHHH", mef.read(0x08))
+        print("ME/TXE firmware version {} (generation {})"
+              .format('.'.join(str(i) for i in version), gen))
+
+        mef.seek(ftpr_offset + ftpr_mn2_offset + 0x80)
+        pubkey_md5 = hashlib.md5(mef.read(0x104)).hexdigest()
+
+        if pubkey_md5 in pubkeys_md5:
+            variant, pubkey_versions = pubkeys_md5[pubkey_md5]
+            print("Public key match: Intel {}, firmware versions {}"
+                  .format(variant, ", ".join(pubkey_versions)))
+        else:
+            if version[0] >= 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))
 
     if not args.check and args.output:
         f.close()
         shutil.copy(args.file, args.output)
         f = open(args.output, "r+b")
 
-    mef = RegionFile(f, me_start, me_end)
+    if me_start < me_end:
+        mef = RegionFile(f, me_start, me_end)
 
     if me_start > 0:
         fdf = RegionFile(f, fd_start, fd_end)
 
-        if me11:
-            fdf.seek(fpsba)
-            pchstrp0 = unpack("<I", fdf.read(4))[0]
-            print("The HAP bit is " +
-                  ("SET" if pchstrp0 & 1 << 16 else "NOT SET"))
-        else:
+        if gen == 1:
+            for (ba, name) in ((fisba, "ICHSTRP0"), (fmsba, "MCHSTRP0")):
+                fdf.seek(ba)
+                strp = unpack("<I", fdf.read(4))[0]
+                print("The meDisable bit in " + name + " is ", end="")
+                if strp & 1:
+                    print("SET")
+                else:
+                    print("NOT SET, setting it now...")
+                    fdf.write_to(ba, pack("<I", strp | 1))
+        elif gen == 2:
             fdf.seek(fpsba + 0x28)
             pchstrp10 = unpack("<I", fdf.read(4))[0]
             print("The AltMeDisable bit is " +
                   ("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
     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:
         mef.seek(ftpr_offset + 0x20)
         num_modules = unpack("<I", mef.read(4))[0]
@@ -696,7 +752,7 @@ if __name__ == "__main__":
             mef.fill_all(b"\xff")
             me6_ignition = True
 
-    if not args.check:
+    if gen != 1 and not args.check:
         if not args.soft_disable_only and not me6_ignition:
             print("Reading partitions list...")
             unremovable_part_fpt = b""
@@ -769,7 +825,7 @@ if __name__ == "__main__":
                 flags &= ~(0x00000001)
                 mef.write_to(0x24, pack("<I", flags))
 
-            if me11:
+            if gen == 3:
                 mef.seek(0x10)
                 header = bytearray(mef.read(0x20))
                 header[0x0b] = 0x00
@@ -787,9 +843,9 @@ if __name__ == "__main__":
             mef.write_to(0x1b, pack("B", checksum))
 
             print("Reading FTPR modules list...")
-            if me11:
+            if gen == 3:
                 end_addr, ftpr_offset = \
-                    check_and_remove_modules_me11(mef, me_end,
+                    check_and_remove_modules_gen3(mef, me_end,
                                                   ftpr_offset, ftpr_length,
                                                   min_ftpr_offset,
                                                   args.relocate,
@@ -817,7 +873,7 @@ if __name__ == "__main__":
                     f.truncate(end_addr)
 
         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...")
                 pchstrp0 |= (1 << 16)
                 fdf.write_to(fpsba, pack("<I", pchstrp0))
@@ -829,7 +885,7 @@ if __name__ == "__main__":
 
     if args.descriptor:
         print("Removing ME/TXE R/W access to the other flash regions...")
-        if me11:
+        if gen == 3:
             flmstr2 = 0x00400500
         else:
             fdf.seek(fmba + 0x4)
@@ -853,15 +909,20 @@ if __name__ == "__main__":
                               me_start + end_addr, bios_end - 1))
 
                 flreg1 = start_end_to_flreg(me_start + end_addr, bios_end)
-                flreg2 = start_end_to_flreg(me_start, me_start + end_addr)
+                if gen != 1:
+                    flreg2 = start_end_to_flreg(me_start, me_start + end_addr)
 
                 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:
-                print("\nWARNING:\n The start address of the BIOS region "
-                      "isn't equal to the end address of the ME\n region: if "
-                      "you want to recover the space from the ME region you "
-                      "have to\n manually modify the descriptor.\n")
+                print("\nWARNING:\n"
+                      "The start address of the BIOS region (0x{:08x}) isn't "
+                      "equal to the end address\nof the ME region (0x{:08x}): "
+                      "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:
             print("Extracting the descriptor to \"{}\"..."
                   .format(args.extract_descriptor))
@@ -869,26 +930,27 @@ if __name__ == "__main__":
 
         fdf_copy.close()
 
-    if args.extract_me:
-        if args.truncate:
-            print("Extracting and truncating the ME image to \"{}\"..."
-                  .format(args.extract_me))
-            mef_copy = mef.save(args.extract_me, end_addr)
-        else:
-            print("Extracting the ME image to \"{}\"..."
-                  .format(args.extract_me))
-            mef_copy = mef.save(args.extract_me, me_end - me_start)
+    if gen != 1:
+        if args.extract_me:
+            if args.truncate:
+                print("Extracting and truncating the ME image to \"{}\"..."
+                      .format(args.extract_me))
+                mef_copy = mef.save(args.extract_me, end_addr)
+            else:
+                print("Extracting the ME image to \"{}\"..."
+                      .format(args.extract_me))
+                mef_copy = mef.save(args.extract_me, me_end - me_start)
+
+            if not me6_ignition:
+                print("Checking the FTPR RSA signature of the extracted ME "
+                      "image... ", end="")
+                print_check_partition_signature(mef_copy,
+                                                ftpr_offset + ftpr_mn2_offset)
+            mef_copy.close()
 
         if not me6_ignition:
-            print("Checking the FTPR RSA signature of the extracted ME "
-                  "image... ", end="")
-            print_check_partition_signature(mef_copy,
-                                            ftpr_offset + ftpr_mn2_offset)
-        mef_copy.close()
-
-    if not me6_ignition:
-        print("Checking the FTPR RSA signature... ", end="")
-        print_check_partition_signature(mef, ftpr_offset + ftpr_mn2_offset)
+            print("Checking the FTPR RSA signature... ", end="")
+            print_check_partition_signature(mef, ftpr_offset + ftpr_mn2_offset)
 
     f.close()