diff --git a/util/me_cleaner/README.md b/util/me_cleaner/README.md index 72c15986df..d4c20598d6 100644 --- a/util/me_cleaner/README.md +++ b/util/me_cleaner/README.md @@ -1,44 +1,85 @@ -# me_cleaner +# me\_cleaner -Intel ME is a coprocessor integrated in all post-2006 Intel boards, for which -this [Libreboot page](https://libreboot.org/faq.html#intelme) has an excellent -description. The main component of Intel ME is Intel AMT, and I suggest you to -read [this Wikipedia page](https://en.wikipedia.org/wiki/Intel_Active_Management_Technology) -for more information about it. In short, Intel ME is an irremovable environment -with an obscure signed proprietary firmware, with full network and memory -access, which poses a serious security threat. -Even when disabled from the BIOS settings, Intel ME is active: the only way to -be sure it is disabled is to remove its firmware from the flash chip. +_me\_cleaner_ is a Python script able to modify an Intel ME firmware image with +the final purpose of reducing its ability to interact with the system. + +## Intel ME + +Intel ME is a co-processor integrated in all post-2006 Intel boards, which is +the base hardware for many Intel features like Intel AMT, Intel Boot Guard, +Intel PAVP and many others. To provide such features, it requires full access to +the system, including memory (through DMA) and network access (transparent to +the user). + +Unlike many other firmware components, the Intel ME firmware can't be neither +disabled nor reimplemented, as it is tightly integrated in the boot process and +it is signed. + +This poses an issue both to the free firmware implementations like [coreboot]( +https://www.coreboot.org/), which are forced to rely on a proprietary, obscure +and always-on blob, and to the privacy-aware users, who are reasonably worried +about such firmware, running on the lowest privilege ring on x86. + +## What can be done Before Nehalem (ME version 6, 2008/2009) the ME firmware could be removed completely from the flash chip by setting a couple of bits inside the flash -descriptor, without the need to reverse-engineer the ME firmware. +descriptor, effectively disabling it. Starting from Nehalem the Intel ME firmware can't be removed anymore: without a -valid firmware the PC shuts off forcefully after 30 minutes. This project is an -attempt to remove as much code as possible from such firmware without falling -into the 30 minutes recovery mode. +valid firmware the PC shuts off forcefully after 30 minutes, probably as an +attempt to enforce the Intel Anti-Theft policies. -me_cleaner currently works on most architectures, see [me_cleaner status](https://github.com/corna/me_cleaner/wiki/me_cleaner-status) (or [its discussion](https://github.com/corna/me_cleaner/issues/3)) -for more info about them. me_cleaner works also on the TXE and SPS firmware. +However, while Intel ME can't be turned off completely, it is still possible to +modify its firmware up to a point where Intel ME is active only during the boot +process, effectively disabling it during the normal operation, which is what +_me\_cleaner_ tries to accomplish. -If you want to understand how me_cleaner works, you can read the ["How does it work?" page](https://github.com/corna/me_cleaner/wiki/How-does-it-work%3F). +## Platform support -If you want to apply me_cleaner on your platform I suggest you to read the -["How does it work?" page](https://github.com/corna/me_cleaner/wiki/How-does-it-work%3F) -and then follow the guide ["How to apply me_cleaner"](https://github.com/corna/me_cleaner/wiki/How-to-apply-me_cleaner). +_me\_cleaner_ currently works on [most of the Intel platforms]( +https://github.com/corna/me_cleaner/wiki/me_cleaner-status); while this doesn't +mean it works on all the boards (due to the different firmware implementations), +it has been proven quite reliable on a great number of them. + +## Usage + +_me\_cleaner_ should handle all the steps necessary to the modification of an +Intel ME firmware with the command: + + $ python me_cleaner.py -S -O modified_image.bin original_dump.bin + +However, obtaining the original firmware and flashing back the modified one is +usually not trivial, as the Intel ME firmware region is often non-writable from +the OS (and it's not a safe option anyways), requiring the use of an external +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 +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). +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). -This project is based on the work of the community; in particular I thank Igor -Skochinsky, for the core information about Intel ME and its firmware structure, -and Federico Amedeo Izzo, for its help during the study of Intel ME. +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 +boot times or warning messages) or without issues at all. + +Obviously, the features provided by Intel ME won't be functional anymore after +the modifications. + +## Documentation + +The detailed documentation about the working of _me\_cleaner_ can be found on +the page ["How does it work?" page]( +https://github.com/corna/me_cleaner/wiki/How-does-it-work%3F). + +Various guides and tutorials are available on the Internet, however a good +starting point is the ["How to apply me_cleaner" guide]( +https://github.com/corna/me_cleaner/wiki/How-to-apply-me_cleaner). diff --git a/util/me_cleaner/man/me_cleaner.1 b/util/me_cleaner/man/me_cleaner.1 new file mode 100644 index 0000000000..2a219bcb8e --- /dev/null +++ b/util/me_cleaner/man/me_cleaner.1 @@ -0,0 +1,159 @@ +.TH me_cleaner 1 "MARCH 2018" +.SH me_cleaner +.PP +me_cleaner \- Tool for partial deblobbing of Intel ME/TXE firmware images +.SH SYNOPSIS +.PP +\fB\fCme_cleaner.py\fR [\-h] [\-v] [\-O output_file] [\-S | \-s] [\-r] [\-k] +[\-w whitelist | \-b blacklist] [\-d] [\-t] [\-c] [\-D output_descriptor] +[\-M output_me_image] \fIfile\fP +.SH DESCRIPTION +.PP +\fB\fCme_cleaner\fR is a tool able to disable parts of Intel ME/TXE by: +.RS +.IP \(bu 2 +removing most of the code from its firmware +.IP \(bu 2 +setting a special bit to force it to disable itself after the hardware +initialization +.RE +.PP +Using both the modes seems to be the most reliable way on many platforms. +.PP +The resulting modified firmware needs to be flashed (in most of the cases) with +an external programmer, often a dedicated SPI programmer or a Linux board with +a SPI master interface. +.PP +\fB\fCme_cleaner\fR works at least from Nehalem to Coffee Lake (for Intel ME) and on +Braswell/Cherry Trail (for Intel TXE), but may work as well on newer or +different architectures. +.PP +While \fB\fCme_cleaner\fR have been tested on a great number of platforms, fiddling +with the Intel ME/TXE firmware is \fIvery dangerous\fP and can easily lead to a +dead PC. +.PP +\fIYOU HAVE BEEN WARNED.\fP +.SH POSITIONAL ARGUMENTS +.TP +\fB\fCfile\fR +ME/TXE image or full dump. +.SH OPTIONAL ARGUMENTS +.TP +\fB\fC\-h\fR, \fB\fC\-\-help\fR +Show the help message and exit. +.TP +\fB\fC\-v\fR, \fB\fC\-\-version\fR +Show program's version number and exit. +.TP +\fB\fC\-O\fR, \fB\fC\-\-output\fR +Save the modified image in a separate file, instead of modifying the +original file. +.TP +\fB\fC\-S\fR, \fB\fC\-\-soft\-disable\fR +In addition to the usual operations on the ME/TXE firmware, set the +MeAltDisable bit or the HAP bit to ask Intel ME/TXE to disable itself after +the hardware initialization (requires a full dump). +.TP +\fB\fC\-s\fR, \fB\fC\-\-soft\-disable\-only\fR +Instead of the usual operations on the ME/TXE firmware, just set the +MeAltDisable bit or the HAP bit to ask Intel ME/TXE to disable itself after +the hardware initialization (requires a full dump). +.TP +\fB\fC\-r\fR, \fB\fC\-\-relocate\fR +Relocate the FTPR partition to the top of the ME region to save even more +space. +.TP +\fB\fC\-t\fR, \fB\fC\-\-truncate\fR +Truncate the empty part of the firmware (requires a separated ME/TXE image or +\fB\fC\-\-extract\-me\fR). +.TP +\fB\fC\-k\fR, \fB\fC\-\-keep\-modules\fR +Don't remove the FTPR modules, even when possible. +.TP +\fB\fC\-w\fR, \fB\fC\-\-whitelist\fR +Comma separated list of additional partitions to keep in the final image. +This can be used to specify the MFS partition for example, which stores PCIe +and clock settings. +.TP +\fB\fC\-b\fR, \fB\fC\-\-blacklist\fR +Comma separated list of partitions to remove from the image. This option +overrides the default removal list. +.TP +\fB\fC\-d\fR, \fB\fC\-\-descriptor\fR +Remove the ME/TXE Read/Write permissions to the other regions on the flash +from the Intel Flash Descriptor (requires a full dump). +.TP +\fB\fC\-D\fR, \fB\fC\-\-extract\-descriptor\fR +Extract the flash descriptor from a full dump; when used with \fB\fC\-\-truncate\fR +save a descriptor with adjusted regions start and end. +.TP +\fB\fC\-M\fR, \fB\fC\-\-extract\-me\fR +Extract the ME firmware from a full dump; when used with \fB\fC\-\-truncate\fR save a +truncated ME/TXE image. +.TP +\fB\fC\-c\fR, \fB\fC\-\-check\fR +Verify the integrity of the fundamental parts of the firmware and exit. +.SH SUPPORTED PLATFORMS +.PP +Currently \fB\fCme_cleaner\fR has been tested on the following platforms: +.TS +allbox; +cb cb cb cb +c c c c +c c c c +c c c c +c c c c +c c c c +c c c c +c c c c +c c c c +. +PCH CPU ME SKU +Ibex Peak * Nehalem/Westmere 6.0 Ignition +Ibex Peak * Nehalem/Westmere 6.x 1.5/5 MB +Cougar Point Sandy Bridge 7.x 1.5/5 MB +Panther Point Ivy Bridge 8.x 1.5/5 MB +Lynx/Wildcat Point Haswell/Broadwell 9.x 1.5/5 MB +Wildcat Point LP Broadwell Mobile 10.0 1.5/5 MB +Sunrise Point Skylake/Kabylake 11.x CON/COR +Union Point Kabylake 11.x CON/COR +.TE +.TS +allbox; +cb cb cb +c c c +. +SoC TXE SKU +Braswell/Cherry Trail 2.x 1.375 MB +.TE +.PP +* Not working on coreboot +.PP +All the reports are available on the project's GitHub page \[la]https://github.com/corna/me_cleaner/issues/3\[ra]\&. +.SH EXAMPLES +.PP +Check whether the provided image has a valid structure and signature: +.IP +\fB\fCme_cleaner.py \-c dumped_firmware.bin\fR +.PP +Remove most of the Intel ME firmware modules but don't set the HAP/AltMeDisable +bit: +.IP +\fB\fCme_cleaner.py \-S \-O modified_me_firmware.bin dumped_firmware.bin\fR +.PP +Remove most of the Intel ME firmware modules and set the HAP/AltMeDisable bit, +disable the Read/Write access of Intel ME to the other flash region, then +relocate the code to the top of the image and truncate it, extracting a modified +descriptor and ME image: +.IP +\fB\fCme_cleaner.py \-S \-r \-t \-d \-D ifd_shrinked.bin \-M me_shrinked.bin \-O modified_firmware.bin full_dumped_firmware.bin\fR +.SH BUGS +.PP +Bugs should be reported on the project's GitHub page \[la]https://github.com/corna/me_cleaner\[ra]\&. +.SH AUTHOR +.PP +Nicola Corna \[la]nicola@corna.info\[ra] +.SH SEE ALSO +.PP +.BR flashrom (8), +me_cleaner's Wiki \[la]https://github.com/corna/me_cleaner/wiki\[ra] 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)) + + 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 > 0: + fdf = RegionFile(f, fd_start, fd_end) + + if me11: + fdf.seek(fpsba) + pchstrp0 = 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)) + part_end = part_start + part_length - 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) - else: - end_addr, ftpr_offset = \ - check_and_remove_modules(mef, me_start, me_end, ftpr_offset, - min_ftpr_offset, args.relocate, - args.keep_modules) + if flags & 0x7f == 2: + print(" {:<4} ({:^24}, 0x{:08x} total bytes): nothing to " + "remove" + .format(part_name, "NVRAM partition, no data", + part_length)) + elif part_start == 0 or part_length == 0 or part_end > 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") - if end_addr > 0: - end_addr = (end_addr // 0x1000 + 1) * 0x1000 - end_addr += spared_blocks * 0x1000 + print("Removing partition entries in FPT...") + mef.write_to(0x30, unremovable_part_fpt) + mef.write_to(0x14, + pack(" 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 blacklist and "EFFS" not in whitelist) or \ + "EFFS" in blacklist: + print("Removing EFFS presence flag...") + mef.seek(0x24) + flags = unpack("= 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("