1
  1. What is the purpose of partitions EFFS and FCRS on systems with Intel ME 8.x?
  2. Is it currently possible to parse data in EFFS partition on a ME image?

I hope anyone can help, there is so little information about this available.

Thanks.

3

I was working on this this weekend. Turns out it's pretty straight-forward. Simple list of contiguous files. Just copies files when they change, and marks the allocation-table entries as dead, for later collection.

The following is incomplete, but it should let you tweak and special-case your way through a specific MFS partition. I've tried to document in comments where I've made large assumptions.

EDIT: There do seem to be metadata in the data. E.g., when looking at the content of UKS, the copy in the SCA partition is clearly 0E f4 00 00 whereas in MFS, the data there is 80 06 0e f4 00 00, so it looks like there's leading metadata.

EDIT 2: Figured out the metadata. It gives a much better method for deterministically identifying files by number within a block than the method I devised without it. Updated the code with a description and a rather complicated state-machine for successfully processing the metadata.

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))

Oh, you can dump the file list with: mefs.allocation_table.debug_print()

E.g.,

           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
...

| improve this answer | |
  • Amazing work. More than I could hope for. Do you by any chance have an URL for the SPI image you used in testing this? – user31201 Apr 22 at 11:26
  • They were unfortunately proprietary, but derived from a relatively recent AMI BIOS for a Broadwell Xeon board. Dediprog flash programmers support an interesting "clip" accessory that can attach onto the outside of certain SPI chips, with lots of caveats, while they're still on the board. Else, perhaps you can find a board with a socketed SPI chip to read? – lockcmpxchg8b Apr 23 at 12:40
  • No problem, it was just to have something known working to compare with, as I begin adapting the script for my special case, but with "zeros good: True" and "sig good: True" it must ok. One more question. Have you been able to make sense of data in the file list and figure out what kind of file an entry represent? e.g. text file, certificate etc. or have you only seen random data? – user31201 Apr 23 at 17:14
  • Only one I've really looked at is the one named b'\x00\x02\x00'. It seems to capture a date-stamp (in seconds from some epoch) approximately weekly. Has some special case values I haven't looked much into. I presume it's for defense against clock rollback for DRM and things like that, which might imply it gets monthly and yearly writes of a different kind...I don't think I have that kind of patience. :) – lockcmpxchg8b Apr 23 at 18:15
1

I have not encountered FCRS but EFFS is the legacy Flash Filesystem partition, used to store configuration and runtime ME data. The only known tool that can parse it (besides ME firmware itself) is Intel’s Flash Image Tool (FIT) for the corresponding firmware version.

However, the format is likely very similar to that of the newer MFS in ME 11 and later, so possibly the research on MFS would help:

| improve this answer | |
  • Reason for asking is that I'm wondering why loading ME firmware into FIT and just building the SPI image without making ANY changes to the configuration, results in thousands of modified bytes in EFFS partition of output ME firmware, compared binary to the input ME firmware. So I'm trying to find out what FIT is doing to the EFFS partition. – user31201 Feb 23 at 13:44

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy