- 使用 Intel ME 8.x 的系统上分区EFFS和FCRS的目的是什么?
- 目前是否可以在ME 映像上解析EFFS分区中的数据?
我希望任何人都可以提供帮助,关于这方面的信息很少。
谢谢。
我希望任何人都可以提供帮助,关于这方面的信息很少。
谢谢。
这个周末我正在做这个。事实证明它非常简单。连续文件的简单列表。只需在文件更改时复制文件,并将分配表条目标记为死,以供以后收集。
以下是不完整的,但它应该让您通过特定的 MFS 分区调整和特殊情况。我试图在评论中记录我做出了很大的假设。
编辑:数据中似乎确实有元数据。 例如,在查看UKS 的内容时,SCA 分区中的副本显然0E f4 00 00是80 06 0e f4 00 00,而MFS 中的数据是,因此看起来有领先的元数据。
编辑 2:找出元数据。 与我在没有它的情况下设计的方法相比,它提供了一种更好的方法来确定性地通过块内的编号识别文件。使用描述和相当复杂的状态机更新了代码,以成功处理元数据。
import sys
#litte-endian integer readers
def read_leuint8(file):
data = file.read(1)
return data[0]
def read_leuint16(file):
data = file.read(2)
return data[0] | (data[1] << 8)
def read_leuint24(file):
data = file.read(3)
return data[0] | (data[1] << 8) | (data[2] << 16)
def read_leuint32(file):
data = file.read(4)
return data[0] | (data[1] << 8) | (data[2] << 16) | (data[3] << 24)
class MEFileSystemFileMetadataStateMachine:
"""
MEFileSystemFileMetadata:
Files in MFS have internal metadata entries. Each file begins with a metadata
record, observed values are:
0xa# <bytes ahead until next metadata> <0x01> <next meta block num> <0x00>
0xb# <blocks ahead until next metadata> <0x01> <next meta block num> <0x00>
0x8# <bytes remaining in file (including this metadata)>
where
# -- is the file number within the block. This is the
target for the fno field of the allocation table
entry.
<bytes ahead...> -- the number of bytes to move ahead to find the next
metadata record (including this metadata).
<next meta block...>-- the 0x100-byte block number where the next metadata
record is to be found. This value should be looked
up through the data-page's block-indirection table.
<blocks ahead...> -- the number of 0x100 byte blocks to move ahead to
find the next metadata record (including this
metadata).
<bytes remaining...>-- pretty straight-forward.
So, 0x8# metadata are file-end records; 0xa# are short-range references; and
0xb# are longer range references. This metadata chain provides unamiguous file
numbers within the blocks, and since they're put at the block start of any
block that contains a file-start, it's easy to pick up from an allocation table
reference.
Note: 0x8# records don't point to the next metadata block, so we may have to
consume file-end padding (0xff until the next multiple of 0x10) if we get an
intermediate 0x8# metadata while searching for our target file.
"""
STATE_NEED_META = 0
STATE_NEED_SKIP_DATA = 1
STATE_NEED_FILE_DATA = 2
STATE_COMPLETE = 3
def __init__(self, file_no, file_len):
self.file_no = file_no
self.state = MEFileSystemFileMetadataStateMachine.STATE_NEED_META
self.bytes_needed = 1
self.byte_offset = 0
self.cur_meta = bytearray(5)
self.file_data = bytearray(file_len)
self.file_filled = 0
self.found_fileno = False
self.work_buf = self.cur_meta
def is_complete(self):
return self.state == self.STATE_COMPLETE
def get_file_data(self):
return self.file_data
def get_bytes_needed(self):
return self.bytes_needed
#returns the number of bytes consumed
def add_bytes(self, bytes, start_index, data_len=None, log_file = None):
"""
supplies data to satisfy the state-machine's need for data as reported
via get_bytes_needed().
bytes -- the buffer containing the bytes to be fed in
start_index -- the start location of the bytes within the buffer
data_len -- number of bytes in the array, starting at start_index.
if None, then len(bytes) - start_index is assumed
"""
#shuffling data from potentially multiple calls to fill the data request from the
#state machine (get_bytes_needed)
data_len = len(bytes) - start_index if data_len is None else data_len
if data_len == 0: return 0 # nothing to do
#take the min of what's available and what we need
to_copy = data_len if data_len < self.bytes_needed else self.bytes_needed
if self.work_buf:
self.work_buf[self.byte_offset:(self.byte_offset+to_copy)] = bytes[start_index:(start_index+to_copy)]
self.byte_offset = self.byte_offset + to_copy
self.bytes_needed = self.bytes_needed - to_copy
#if we don't have enough to process, return so they can feed more
if self.bytes_needed > 0:
return to_copy
#we only make it this far once we've got the full bytes_needed data
meta_type = self.cur_meta[0] & 0xf0
if self.state == self.STATE_NEED_META:
if self.byte_offset == 1:
if meta_type in [0xa0, 0xb0]:
self.bytes_needed = 4
else:
self.bytes_needed = 1
else:
#Have we found the file number we seek yet?
if self.found_fileno or (self.file_no == self.cur_meta[0] & 0x0f):
self.found_fileno = True
self.state = self.STATE_NEED_FILE_DATA
self.work_buf = self.file_data
self.byte_offset = self.file_filled
else:
self.state = self.STATE_NEED_SKIP_DATA
self.work_buf = None
self.byte_offset = None
#determine the data required based on metadata type, and whether we're
#skipping (so need to eat EOF padding on type 0x8# entries) or whether
#we're copying out file data.
if meta_type == 0x80:
if self.state == self.STATE_NEED_SKIP_DATA:
#if we're skipping a 0x8# entry, we need to eat EOF padding too
padding = (0x10 - (self.cur_meta[1] & 0xf)) & 0xf
self.bytes_needed = padding + self.cur_meta[1] - 2 #remove header len, too
else:
self.bytes_needed = self.cur_meta[1] - 2 #remove header len
elif meta_type == 0xa0:
self.bytes_needed = self.cur_meta[1] - 5 #remove header len
elif meta_type == 0xb0:
self.bytes_needed = self.cur_meta[1] * 0x100 - 5 #remove header len
else:
if log_file:
log_file.write("That's not a metadata type I've seen before...: 0x%02x\n" % self.cur_meta[0])
return None
elif self.state == self.STATE_NEED_SKIP_DATA: #recall: this is the state just *completed*
self.state = self.STATE_NEED_META
self.work_buf = self.cur_meta
self.byte_offset = 0
self.bytes_needed = 1
elif self.state == self.STATE_NEED_FILE_DATA: #recall: this is the state just *completed*
self.file_filled = self.byte_offset
self.state = self.STATE_NEED_META
self.work_buf = self.cur_meta
self.byte_offset = 0
if meta_type == 0x80: #just completed a file-end record...we're done.
self.bytes_needed = 0
self.state = self.STATE_COMPLETE
elif meta_type in [0xa0, 0xb0]:
self.bytes_needed = 1
else:
if log_file:
log_file.write("That's not a metadata type I've seen before...: 0x%02x\n" % self.cur_meta[0])
return None
elif self.state == self.STATE_COMPLETE: #can't leave this state
pass
else:
if log_file:
log_file.write("Bad state-machine state: %d\n" % self.state)
#recurse to consume as much as we can, for easier calling convention
if to_copy < data_len:
return to_copy + add_bytes(bytes, start_index+to_copy, data_len-to_copy)
#else, return what we consumed
return to_copy
def read_me_fs_file(file_no, file_len, me_file, log_file = sys.stdout):
sm = MEFileSystemFileMetadataStateMachine(file_no, file_len)
while not sm.is_complete():
res = sm.add_bytes(
bytes=me_file.read(sm.get_bytes_needed()),
start_index=0,
data_len=None, #shorthand for len(bytes)-start_index
log_file=log_file)
if not res:
log_file.write("Aborting file read.\n")
break
return sm.get_file_data()
class MEFileSystemDataHeader:
"""
Data Page Header: (Top of a 0x4000-byte data page in the MFS)
0 1 2 3 4 5 6 7 8 9 a b c d e f
+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+
00 | pgno| pgflags | 0x00| 0x00| 0x00| 0x00| mystery bits |
+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+
10 | freed_flags... |
+-----------------------------------------------------------------------------------------------+
... | ... |
+-----------------------------------------------------------------------------------------------+
80 | ... |
+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+
90 | block indirection table... |
+-----------------------------------------------------------------------------------------------+
... | ... |
+-----------------------------------------------------------------------------------------------+
c0 | ... |
+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+
d0+| (file data follows) |
+-----------------------------------------------------------------------------------------------+
pgno -- page identifier within the MFS. All MFS page headers
have this field, be they AT or Data.
pgflags -- have seen 0x78 0xfe 0xff for AT header. Data pages have
had this or 0x78 0xfc 0xff.
(zeros) -- a useless value for flash...must be part of the signature
of the header? Or reserved region for later version of
the format.
myst_bits -- TBD
freed_flags-- each bit marks whether a written 0x10-byte chunk is no
longer required. Bits exist for the header chunks, as
well as the chunks for file data. lsb of the first byte
corresponds to the first chunk of the header. 0=freed.
blk_itab -- translates ATFileEntry's okey value into a block offset
within the data page. offset = blk_itab[okey] * 0x100.
This is the location from which to begin the search. Note
that an offset of 0 must still respect that the page
header consumes bytes [0x00, 0xd0) for the search.
(filedata) -- File are padded with 0xff at the end. Note files
have internal metadata. See routines above.
"""
def __init__(self, page_no, flags, bytes_00000000, myst_bits, freed_flags, blk_itab):
self.page_no = page_no
self.flags = flags
self.zeros_good = bytes_00000000 == b'\x00\x00\x00\x00'
self.myst_bits = myst_bits
self.freed_flags = freed_flags
self.blk_itab = blk_itab
def debug_print(self, log = sys.stdout):
log.write("Debug Print of MEFileSystemDataHeader %x:\n" % id(self))
if self.page_no != 0xff:
log.write(" page no: 0x%x\n" % self.page_no)
log.write(" flags: 0x%06x\n" % self.flags)
log.write(" zeros good: %s\n" % str(self.zeros_good))
log.write(" mystery bits: %s\n" % " ".join("%02x"%x for x in self.myst_bits))
log.write(" freed_flags: %s\n" % "".join("%02x"%x for x in self.freed_flags))
log.write(" block ind. tab.: [%s]\n" % " ".join("%02x"%x for x in self.blk_itab))
else:
log.write(" (empty)\n")
class MEFileSystemATEntry:
"""
Allocation Table Entry:
0 1 2 3 4 5 6 7 8 9 a
+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+
00 |state| flgs| identifier | type| filelen | pgid| okey| fno |
+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+
state -- status of the entry: 0xdc=present;
0xc8=overwritten(i.e., there will be another entry below)
flags -- has value 0xf0 in every example...so hard to tell.
identifier -- 3-byte identifier...seem to be a preference for ASCII,
but there are counterexamples
type -- no idea...maybe permissions? Observed values:
0x00, 0x0a, 0x0c, 0x0d, 0x0e, 0x18, 0x1a
filelen -- 16-bit, little endian. actual content seems to be a few
bytes more...might just be pollution of the structures
used to write the file.
pgid -- the pgno for the MEFileSystemDataHeader that holds the
data. The ATHeader is numbered 0, each data page seems
to get sequentially numbered in my examples, though that
could change with more use. 0xff indicates "not
yet used / nonumber assigned" --- a fact that hints page
numbers aren't guaranteed to be sequentially
okey -- key for indexing the block_indirection table of the
MEFileSystemDataHeader holding this file's content, to
find the right 0x100 byte block from which to start the
file search according to the 'fno' field.
fno -- file number within the block-offset determined from okey.
This is to be looked up using the metadata *within* the
file data of the data pages. See
MEFileSystemFileMetadataStateMachine above
"""
def __init__(self, state, flags, identifier, type, filelen, pgid, okey, fno):
self.state = state
self.flags = flags
self.identifier = identifier
self.type = type
self.filelen = filelen
self.pgid = pgid
self.okey = okey
self.fno = fno
def debug_print(self, log = sys.stdout):
log.write("%15s len=0x%04x [pg=0x%02x k=0x%02x f=0x%02x] ty=0x%02x st=0x%02x, fg=0x%02x" % (str(self.identifier), self.filelen, self.pgid, self.okey, self.fno, self.type, self.state, self.flags))
class MEFileSystemATHeader:
"""
Allocation Table Header:
0 1 2 3 4 5 6 7 8 9 a b c d e f
+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+
00 | pgno| flags | 0x00| 0x00| 0x00| 0x00|"MFS\0" |bitfields? |
+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+
10 | bitfields? |
+-----+-----+-----+-----+
pgno -- page identifier within the MFS. All MFS page headers
have this field, be they AT or Data.
flags -- have seen 0x78 0xfe 0xff for AT header. Data pages have
had this or 0x78 0xfc 0xff.
(zeros) -- a useless value for flash...must be part of the signature
of the header? Or reserved region for later version of
the format.
"MFS\0" -- ASCIIZ signature for the MFS AT Header
bitfields -- 64 bits of apparent bitfields
"""
max_files = int((0x4000 - 0x14) / 11) #page size, less the header, divided by file entry size
def __init__(self, page_no, flags, bytes_00000000, sig, bitfields):
self.page_no = page_no
self.flags = flags
self.zeros_good = bytes_00000000 == b'\x00\x00\x00\x00'
self.sig_good = sig == b'MFS\x00'
self.bitfields = bitfields
def debug_print(self, log = sys.stdout):
log.write("Debug Print of MEFileSystemATHeader %x:\n" % id(self))
log.write(" page no: 0x%x\n" % self.page_no)
log.write(" flags: 0x%06x\n" % self.flags)
log.write(" zeros good: %s\n" % str(self.zeros_good))
log.write(" sig good: %s\n" % str(self.sig_good))
log.write(" bitfields: %s\n" % " ".join("%02x"%x for x in self.bitfields))
class MEFileSystemAT:
"""
Allocation Table (a 0x4000-byte page in the MFS):
0 1 2 3 4 5 6 7 8 9 a b c d e f
+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+
00 | Allocation Table Header... |
+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+
10 |... | File Entry |FE...|
+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+
...|... |
+-----------------------------------------------------------------------------------+-----+-----+
3fe0|... |File Ent...|
+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+
3ff0|... |Padding |
+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+
"""
def __init__(self, header, entry_list):
self.header = header
self.entry_list = entry_list
def debug_print(self, log = sys.stdout):
log.write("Debug Print of MEFileSystemAT %x:\n" % id(self))
self.header.debug_print(log)
log.write("File entries:\n")
for ent in self.entry_list:
if(ent):
log.write(" ")
ent.debug_print(log)
log.write("\n")
class MEFileSystem:
"""
MFS Partition: (Making the assumption the AT is the first page)
+------------------------------------+
0000 | Allocation Table |
+------------------------------------+
4000 | Data Page |
+------------------------------------+
8000 | Data Page |
+------------------------------------+
c000 | Data Page |
+------------------------------------+
... |... |
+------------------------------------+
"""
def __init__(self, allocation_table, data_page_list):
self.allocation_table = allocation_table
self.data_page_list = data_page_list
def debug_print(self, log = sys.stdout):
log.write("Debug Print of MEFileSystem %x:\n" % id(self))
self.allocation_table.debug_print(log)
for page in self.data_page_list:
if(page):
page.debug_print(log)
else:
log.write("(Not parsed)\n")
def parse_me_fs_at_entry(me_file, log_file = sys.stdout):
return MEFileSystemATEntry(
state = read_leuint8(me_file),
flags = read_leuint8(me_file),
identifier = me_file.read(3),
type = read_leuint8(me_file),
filelen = read_leuint16(me_file),
pgid = read_leuint8(me_file),
okey = read_leuint8(me_file),
fno = read_leuint8(me_file))
def parse_me_fs_at_header(me_file, log_file = sys.stdout):
return MEFileSystemATHeader(
page_no = read_leuint8(me_file),
flags = read_leuint24(me_file),
bytes_00000000 = me_file.read(4),
sig = me_file.read(4),
bitfields = me_file.read(8))
def parse_me_fs_at(me_file, log_file = sys.stdout):
hdr = parse_me_fs_at_header(me_file, log_file)
entry_list = [None] * MEFileSystemATHeader.max_files
for i in range(MEFileSystemATHeader.max_files):
ent = parse_me_fs_at_entry(me_file, log_file)
if ent.state == 0xff:
break
entry_list[i] = ent
return MEFileSystemAT(hdr, entry_list)
def parse_me_fs_data_header(me_file, log_file = sys.stdout):
return MEFileSystemDataHeader(
page_no = read_leuint8(me_file),
flags = read_leuint24(me_file),
bytes_00000000 = me_file.read(4),
myst_bits = me_file.read(0x8),
freed_flags = me_file.read(0x80),
blk_itab = me_file.read(0x40))
def parse_me_fs(length, me_file, log_file = sys.stdout):
"""
ARGS:
length -- length in bytes of the ME partition holding the MFS/MFSB data
me_file -- a file handle whose present position is the start of the MFS(B) partition
log_file -- if there is diagnostic output, put it here
RETURNS:
an MEFileSystem instance populated with the allocation table and data-page headers
"""
total_pages = int(length/0x4000)
start_offset = me_file.tell()
#NOTE: I'm presuming the allocation table is the first page...
#that might not be reliable...
at = parse_me_fs_at(me_file, log_file)
data_pages = [None] * (total_pages-1)
for page in range(0, total_pages-1):
me_file.seek(start_offset + 0x4000 * (page+1))
data_pages[page] = parse_me_fs_data_header(me_file, log_file)
return MEFileSystem(
allocation_table=at,
data_page_list = data_pages)
def get_mfs_file(mefs, me_file, mfs_file_offset, id, log_file = sys.stdout):
"""
Example of how to use the MEFileSystem structures to retrieve
MFS and MFSB files.
ARGS:
mefs -- a MEFileSystem instance parsed from mfs_data
me_file -- a filehandle containing the ME image
mfs_file_offset -- the file offset within me_file where the MFS partition begins
id -- a 3-byte byte array with the file identifier
log -- if there is diagnostic output, put it here
RETURNS:
an array containing [state, The data from the corresponding file].
else None if the file identifier does not exist within the data.
Example driver, given the known offset and size for a MFS partition:
MFS_OFFSET = 0x64000 #typical location
MFS_LENGTH = 0x40000 #typical size
spi_image_file.seek(MFS_OFFSET)
mefs = parse_me_fs(MFS_LENGTH, spi_image_file)
result_tuple = get_mfs_file(mefs, spi_image_file, MFS_OFFSET, b'UKS')
if result_tuple:
print("State: %x, data: %s\n" % tuple(result_tuple))
"""
#Find the file identifer in the Allocation Table
best_ent = None
for ent in mefs.allocation_table.entry_list:
if ent and ent.identifier == id:
best_ent = ent
if ent.state == 0xdc:
break; # got a current entry
log.write("Error: found an item w/ state %02x...continuing\n" % ent.state)
#if found, lookup which data page matches the entry's pgid value
if best_ent:
page_found = False
for list_idx in range(len(mefs.data_page_list)):
page = mefs.data_page_list[list_idx]
if page.page_no == best_ent.pgid:
page_found = True
#we found the right data page, so start the file search
search_start = page.blk_itab[best_ent.okey] * 0x100
#In the following lines:
# The value d0 is to skip over the datapage header if we're in the first block
#
# The multiple of 0x4000 selects the data offset that goes with list_idx
# since the parsed data-page list is in the same order as found in the file.
#
# Because mefs.data_page_list doesn't include the allocation table page, we +1
# to the index before multiplying. The result is a set of offsets into the MFS data
# bounding the file search
##
search_off = 0x4000 * (list_idx+1) + (0xd0 if search_start == 0 else search_start)
me_file.seek(mfs_file_offset + search_off)
data = read_me_fs_file(best_ent.fno, best_ent.filelen, me_file, log_file)
if data:
return [best_ent.state, data]
return None
if __name__ == "__main__":
with open("image.bin", "rb") as spi_image_file:
MFS_OFFSET = 0x64000 #typical location
MFS_LENGTH = 0x40000 #typical size
spi_image_file.seek(MFS_OFFSET)
mefs = parse_me_fs(MFS_LENGTH, spi_image_file)
#Dump the allocation table
mefs.allocation_table.debug_print()
print("")
first_file = mefs.allocation_table.entry_list[0].identifier
print("looking up the first file (%s):" % first_file)
result_tuple = get_mfs_file(mefs, spi_image_file, MFS_OFFSET, first_file)
if result_tuple:
print("State: %x, data: %s\n" % tuple(result_tuple))
哦,您可以使用以下命令转储文件列表:
mefs.allocation_table.debug_print()
例如,
b'UKS' len=0x0004 [pg=0x01 k=0x00 f=0x00] ty=0x0a st=0xdc, fg=0xf0
b'LRT' len=0x01c0 [pg=0x01 k=0x00 f=0x01] ty=0x0e st=0xdc, fg=0xf0
b'MIA' len=0x0003 [pg=0x01 k=0x02 f=0x01] ty=0x0e st=0xdc, fg=0xf0
b'BLV' len=0x0004 [pg=0x01 k=0x02 f=0x02] ty=0x0a st=0xdc, fg=0xf0
b'SDV' len=0x00ff [pg=0x01 k=0x02 f=0x03] ty=0x0a st=0xdc, fg=0xf0
b'ICP' len=0x0042 [pg=0x01 k=0x03 f=0x01] ty=0x0e st=0xdc, fg=0xf0
b'\x00NP' len=0x0001 [pg=0x01 k=0x04 f=0x01] ty=0x0e st=0xdc, fg=0xf0
b'PPN' len=0x0042 [pg=0x01 k=0x04 f=0x02] ty=0x0e st=0xdc, fg=0xf0
b'SCO' len=0x00af [pg=0x01 k=0x04 f=0x03] ty=0x0a st=0xdc, fg=0xf0
b'PCO' len=0x0a24 [pg=0x01 k=0x05 f=0x01] ty=0x0a st=0xdc, fg=0xf0
b'GFC' len=0x0004 [pg=0x01 k=0x07 f=0x01] ty=0x18 st=0xdc, fg=0xf0
b'YHP' len=0x06fe [pg=0x01 k=0x07 f=0x02] ty=0x0a st=0xdc, fg=0xf0
b'FCP' len=0x0002 [pg=0x01 k=0x09 f=0x01] ty=0x0b st=0xdc, fg=0xf0
b'PPR' len=0x0001 [pg=0x01 k=0x09 f=0x02] ty=0x0b st=0xdc, fg=0xf0
b'TCM' len=0x0005 [pg=0x01 k=0x09 f=0x03] ty=0x10 st=0xdc, fg=0xf0
b'BHM' len=0x0004 [pg=0x01 k=0x09 f=0x04] ty=0x10 st=0xdc, fg=0xf0
b'GCN' len=0x0018 [pg=0x01 k=0x09 f=0x05] ty=0x0e st=0xdc, fg=0xf0
b'CSM' len=0x000f [pg=0x01 k=0x0a f=0x00] ty=0x0e st=0xdc, fg=0xf0
b'\x00HS' len=0x0127 [pg=0x01 k=0x0a f=0x01] ty=0x0e st=0xdc, fg=0xf0
b'\x01HS' len=0x0127 [pg=0x01 k=0x0b f=0x01] ty=0x0e st=0xdc, fg=0xf0
b'\x02HS' len=0x0127 [pg=0x01 k=0x0c f=0x01] ty=0x0e st=0xdc, fg=0xf0
b'\x03HS' len=0x0127 [pg=0x01 k=0x0d f=0x01] ty=0x0e st=0xdc, fg=0xf0
b'PCF' len=0x0010 [pg=0x01 k=0x0e f=0x01] ty=0x0e st=0xdc, fg=0xf0
...
我没有遇到过 FCRS,但 EFFS 是旧的 Flash 文件系统分区,用于存储配置和运行时 ME 数据。唯一已知的可以解析它的工具(除了 ME 固件本身)是 Intel 的对应固件版本的 Flash Image Tool (FIT)。
但是,格式可能与 ME 11 及更高版本中较新的 MFS 的格式非常相似,因此对 MFS 的研究可能会有所帮助: