首页
社区
课程
招聘
[原创] Intel 酷睿 CPU Management Engine 固件研究与分析逆向 (一) 前置准备与解包
发表于: 8小时前 162

[原创] Intel 酷睿 CPU Management Engine 固件研究与分析逆向 (一) 前置准备与解包

8小时前
162

前言

此帖是记录笔者研究Intel Management Engine 以及 Intel-SA-00086 安全漏洞的记录笔记。笔者也不知道能研究多深入,所以研究一点是一点,顺便发出来也给各位大佬参考以及讨论。(也是下班给自己找点事做,上班一直做Agent开发太无聊了)

平台选择与前置准备

基于之前Kaokao那篇文章(具体可以去看github或者其他平台由相关参考)。Github仓公开爆料的是TXE平台,并没有我们常规的酷睿平台。由于在国内外网站没找到一模一样的硬件并且Intel和华硕官网都找不到相关硬件的固件和资料想要复现极难。
我的平台的选择:
主板:华硕 Rog Z370 -F Game
Cpu:i7 8700k
Management Engine 版本:11.7.4 (根据Intel-SA-00086 说明影响最后一个版本是 11.8.50.3399)
CH341A Pro编程器(华硕有8Ping接口可以不用买夹子)
线缆:Usb3.0 A to A 线 或 Intel SVT DCI DbC2/3 A-to-A
展示一下我的硬件平台实验台:
图片描述
AMD散热器散热Intel CPU HHH:
图片描述

Dump Bios

CH341A Pro编程器组装:

图片描述
抬起拉杆把转接器按照顺序插入最后压下压杆就行了,非常简单。

找到SPI芯片接口

首先我们需要找到SPI芯片,并且Dump当前Bios中的数据。
图片描述
这里需要注意的是,如果是华硕的主板是自带8PING接口的,所以可以直接使用转8Pin线来dump。如果是其他匹配则需要夹子夹在主板上。华硕主板接口顺序0号位是有一个白色角的,这一点要注意别接反了烧。
图片描述

主板Bios Dci解锁

使用我们的NeoProgrammer工具Dump下来我们的文件
图片描述
点击这个位置进行时保存就可以保存

Bios解包

首先我们需要对dump数据进行解包,并且拿到我们的bios分区。在主板中并没有开启DCI接口的选项,所以我们需要通过魔改Bios来做到。
首先根据 Intel SPI Flash 规范可以写一个检查脚本。规范可以查询文档:Intel® 100 Series and Intel® C230
Series Chipset Family Platform Controller Hub (PCH)。这里我用AI和我自己写的PDF MCP 让AI扫描了一下快速了解了一下内存布局。

  ┌─────────────────────┬───────────┬──────────────────────────────────────────────────────┐
  │        字段         │   偏移    │                         内容                         │
  ├─────────────────────┼───────────┼──────────────────────────────────────────────────────┤
  │ FD Signature        │ 0x10      │ 固定 4 字节 5A A5 F0 0F                              │
  ├─────────────────────┼───────────┼──────────────────────────────────────────────────────┤
  │ FLMAP0              │ 0x14      │ 中间字节 << 4 = FRBA(Region 表偏移)                │
  ├─────────────────────┼───────────┼──────────────────────────────────────────────────────┤
  │ Region Table (FRBA) │ 通常 0x405 条 ×4 字节,分别描述 FD / BIOS / ME / GbE / PDR 区 │
  └─────────────────────┴───────────┴──────────────────────────────────────────────────────┘

整理一下就如上表格,主要有魔术字段和FLMAP0偏移以及Region Table组成。有这些才能把Bios从Bin中切出来。
解析一下拷贝出来的固件:
魔术字:
图片描述
FLMAP0:
图片描述
说一下如何解析:

00 04 00 03

注意这个04实际上就是Offset指向 Region Table表。算法就是 0x04 << 4 = 0x40
图片描述
根据顺序来说第一个是FD,从00开始,第二个是Bios区域,第三个是ME, 第四个是GbE。在白皮书中可以找到对应的解释:
图片描述
Region Table 解析规则如下:

base_enc  = 0x0280 << 12
limit_enc = 0x0FFF << 12 OR  0xFFF

所以地址就是0X280000,范围是 0XFFFFFF:
图片描述
图片描述
接下来使用 uefi-firmware-parser 将 我们的固件进行解包,注意是整个固件,不需要手动切块:
图片描述
图片描述
ME和Bios区:
图片描述
FFS介绍:

  SPI Flash 物理芯片
  └── Region 1 = BIOS 区 (我们关心的部分, 13.5MB)
      └── Firmware Volume (FV) ← UEFI 标准的容器格式, 一个 BIOS 区可能有多个 FV
          └── Firmware File (FFS) ← FV 里的"文件"
              └── Section (节) ← FFS 里再分若干节, 可能有压缩
                  └── PE32 代码 / 数据 / IFR / 字符串 ...

解包出来就是这个:
图片描述
FFS的命名是GUID标注,也就是不同厂家里面的GUID都是一样的属于标准了。FFS说白了就是Bios的各种模块的标准,主板厂太多了,标准必须要统一。
899407d7-99fe-43d8-9a21-79ec328cac21 这个GUID 是 AMI Setup FFS的,我们需要的DCI的选项就在这个里面。
AMI FFS结构如下:

  file-899407d7-.../
  ├── file.obj              ← FFS 原始内容 (含 24B FFS header)
  ├── section0.dxe.depex    ← Section 0: DXE 依赖描述
  ├── section1.guid         ← Section 1 的 GUID (告诉你 section1 用什么算法封装)
  ├── section1/             ← Section 1 解开后的子目录
  │   ├── guided.certs
  │   ├── guided.preamble
  │   ├── section0.pe       ←  解出来的 PE32 (1.4MB)
  │   └── section1.version

图片描述
图片描述
FFS 里有 2 个 section:

Section 类型 内容
section0 DXE Dependency Expression 一段几字节的小数据,告诉 BIOS "我这个 Setup 模块依赖 PCH_INIT 完成后才能加载"。
section1 GUID-Defined Section 这里是个被特定 GUID 算法 压缩的容器,里面又套了一层 PE32

核心实际上就是这个:section0.pe
图片描述
标准的4D5A PE头。

  section0.pe (1.4MB)
  ├── PE Header                ← 标准 PE 头
  ├── .text 段                  ← Setup 模块的 x86 机器码 (BIOS 启动时跑的)
  ├── .data / .rdata 段         ← 静态数据
  └── HII Resource 区          ←  重点
      ├── HII Package List Header
      ├── String Package (类型 0x04)  ← 8000+ 个菜单字符串
      └── Forms Package (类型 0x02)   ← 4000+ 个 IFR Question 定义

我们的核心就是解析 String Package 和 Forms Package 并且将他们映射起来,就能找到我们想要的开关。
解析所有字符串找到和DCI有关系的字符串:
这里就让Ai给我写了一个解析脚本:

import re, struct, json, sys, io, os
sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding='utf-8', errors='replace')

SETUP_PE = r'D:\Z370_DICOPEN\z370.bin_output\regions\region-bios\volume-0\file-899407d7-99fe-43d8-9a21-79ec328cac21\section1\section0.pe'
OUTPUT_JSON = r'D:\Z370_DICOPEN\setup_strings.json'

if not os.path.isfile(SETUP_PE):
    print(f"[ERR] Setup PE not found: {SETUP_PE}")
    print("Did you run uefi-firmware-parser on z370.bin yet?")
    sys.exit(1)

blob = open(SETUP_PE, 'rb').read()
print(f"PE32 size: {len(blob)} bytes ({len(blob)/1024:.1f} KB)")

# --- Step 1: Locate HII String Package ---
# Strategy: scan for ASCII "en-US\0", back up 46 bytes to find package header start.
# Package header (after 4-byte common header):
#   HdrSize (4B), StringInfoOffset (4B), LanguageWindow (32B), LanguageName StringId (2B), Language ASCII null-term
# So Language ASCII starts at package_start + 46.
print("\n[Step 1] Locate HII String Package (type 0x04) via 'en-US\\0' marker")
candidates = []
for m in re.finditer(rb'en-US\x00', blob):
    p = m.start()
    cand = p - 46
    if cand < 0: continue
    plen = int.from_bytes(blob[cand:cand+3], 'little')
    ptype = blob[cand+3]
    if ptype != 0x04: continue           # 0x04 = Strings package
    if not (0x80 <= plen <= 0x800000): continue
    hdr_sz = struct.unpack('<I', blob[cand+4:cand+8])[0]
    info_off = struct.unpack('<I', blob[cand+8:cand+12])[0]
    candidates.append((cand, plen, hdr_sz, info_off))

if not candidates:
    print("[ERR] no HII string package found")
    sys.exit(1)

for c in candidates:
    print(f"  candidate @ blob_offset=0x{c[0]:x}  pkg_len=0x{c[1]:x}  HdrSize={c[2]}  InfoOffset=0x{c[3]:x}")

# Use the first/largest candidate
sp, splen, hdr_sz, info_off = candidates[0]
print(f"\n[Step 2] Parsing SIBT blocks from package @ 0x{sp:x}")

# --- Step 2: Parse SIBT blocks ---
# SIBT block types (UEFI Spec ch 33):
#   0x00 END
#   0x14 STRING_UCS2          (1B type + UCS2 nul-term)
#   0x16 STRINGS_UCS2         (1B type + 2B count + count of UCS2 nul-term)
#   0x20 DUPLICATE            (1B type + 2B StringId reference)
#   0x21 SKIP2                (1B type + 2B count)
#   0x22 SKIP1                (1B type + 1B count)
#   0x30 EXT1 (1B type + 1B subtype + 1B length)
#   0x31 EXT2 (1B type + 1B subtype + 2B length)
#   0x32 EXT4 (1B type + 1B subtype + 4B length)
#   0x40 FONT (1B type + 4B size + font data)
p = sp + info_off
end = sp + splen
strings = {}
sid = 1  # StringId starts at 1, 0 is reserved
while p < end:
    t = blob[p]
    if t == 0x00:
        break
    elif t == 0x14:  # SIBT_STRING_UCS2
        s_start = p + 1
        s_end = s_start
        while s_end + 1 < end and blob[s_end:s_end+2] != b'\x00\x00':
            s_end += 2
        strings[sid] = blob[s_start:s_end].decode('utf-16-le', errors='replace')
        sid += 1
        p = s_end + 2
    elif t == 0x16:  # SIBT_STRINGS_UCS2
        cnt = struct.unpack('<H', blob[p+1:p+3])[0]
        p += 3
        for _ in range(cnt):
            s_end = p
            while s_end + 1 < end and blob[s_end:s_end+2] != b'\x00\x00':
                s_end += 2
            strings[sid] = blob[p:s_end].decode('utf-16-le', errors='replace')
            sid += 1
            p = s_end + 2
    elif t == 0x20:  # SIBT_DUPLICATE
        ref = struct.unpack('<H', blob[p+1:p+3])[0]
        strings[sid] = strings.get(ref, '?dup?')
        sid += 1
        p += 3
    elif t == 0x21:  # SIBT_SKIP2
        sid += struct.unpack('<H', blob[p+1:p+3])[0]
        p += 3
    elif t == 0x22:  # SIBT_SKIP1
        sid += blob[p+1]
        p += 2
    elif t == 0x30:  # EXT1
        p += blob[p+2]
    elif t == 0x31:  # EXT2
        p += struct.unpack('<H', blob[p+2:p+4])[0]
    elif t == 0x32:  # EXT4
        p += struct.unpack('<I', blob[p+2:p+6])[0]
    elif t == 0x40:  # FONT
        p += struct.unpack('<I', blob[p+1:p+5])[0]
    else:
        print(f"[WARN] unknown SIBT type {t:#x} @ 0x{p:x}")
        break

print(f"  parsed {len(strings)} strings, last StringId = {sid-1}")

# --- Step 3: Find DCI-related StringIds ---
print("\n[Step 3] Searching for DCI / Debug menu strings")
KEYWORDS = ['DCI enable', 'Direct Connect', 'Debug Interface', 'JTAG C10', 'HDCIEN',
            'Platform Debug', 'Debug Consent']
found = []
for sid, txt in strings.items():
    if any(kw in txt for kw in KEYWORDS):
        found.append((sid, txt))

print(f"  found {len(found)} DCI/debug-related strings:")
for sid, txt in found:
    safe = ''.join(c if c.isascii() and c.isprintable() else '.' for c in txt)
    print(f"    StringId = 0x{sid:04X} ({sid:>4})   '{safe[:80]}'")

# --- Step 4: Save full map ---
with open(OUTPUT_JSON, 'w', encoding='utf-8') as f:
    json.dump(strings, f, ensure_ascii=False)
print(f"\nSaved {len(strings)} strings → {OUTPUT_JSON}")

找到对应的dci的偏移值:

import re, struct, json, sys, io, os
sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding='utf-8', errors='replace')

SETUP_PE = r'D:\Z370_DICOPEN\z370.bin_output\regions\region-bios\volume-0\file-899407d7-99fe-43d8-9a21-79ec328cac21\section1\section0.pe'
STRINGS_JSON = r'D:\Z370_DICOPEN\setup_strings.json'
OUTPUT_JSON  = r'D:\Z370_DICOPEN\all_questions.json'

if not os.path.isfile(SETUP_PE):
    print(f"[ERR] need {SETUP_PE}"); sys.exit(1)
if not os.path.isfile(STRINGS_JSON):
    print(f"[ERR] run phase3 first to produce {STRINGS_JSON}"); sys.exit(1)

blob = open(SETUP_PE, 'rb').read()
strings = {int(k):v for k,v in json.load(open(STRINGS_JSON, encoding='utf-8')).items()}
print(f"PE32 size: {len(blob)} bytes, {len(strings)} strings loaded")

# --- Step 1: Locate Forms Package (type 0x02) ---
# Header: 4 bytes -- 3B length + 1B type=0x02
# Right after header should be a FormSet opcode (0x0E).
print("\n[Step 1] Locate Forms Package (type 0x02)")
forms_pkgs = []
for i in range(0, len(blob) - 32):
    plen = int.from_bytes(blob[i:i+3], 'little')
    ptype = blob[i+3]
    if ptype != 0x02: continue
    if plen < 0x100 or plen > 0x800000: continue
    if blob[i+4] == 0x0E and blob[i+5] >= 0x18 and i+plen <= len(blob):
        forms_pkgs.append((i, plen))
for fp in forms_pkgs:
    print(f"  candidate @ 0x{fp[0]:x}, length 0x{fp[1]:x}")
if not forms_pkgs:
    print("[ERR] no forms package found"); sys.exit(1)

fp_pos, fp_len = max(forms_pkgs, key=lambda x: x[1])
print(f"using Forms package @ 0x{fp_pos:x} (length 0x{fp_len:x})")

# --- Step 2: Walk IFR opcodes ---
print("\n[Step 2] Walk IFR opcode stream (with 7-bit length masking)")

OP_NAMES = {0x01:'Form',0x05:'OneOf',0x06:'CheckBox',0x07:'Numeric',0x09:'OneOfOpt',
            0x0A:'SuppressIf',0x0E:'FormSet',0x19:'GrayOutIf',0x1E:'DisableIf',
            0x23:'OrderedList',0x24:'VarStore',0x29:'End',0x5A:'Default'}

varstores = {}
questions = []
form_stack = []

p = fp_pos + 4   # skip 4-byte package header
end = fp_pos + fp_len
while p < end:
    if p + 2 > len(blob): break
    op = blob[p]
    raw_len = blob[p+1]
    ln = raw_len & 0x7F           # <-- critical
    scope = (raw_len >> 7) & 1
    if ln < 2: break
    body = blob[p+2:p+ln]

    if op == 0x24:  # VarStore
        guid = body[:16].hex()
        vsid = struct.unpack('<H', body[16:18])[0]
        vsz  = struct.unpack('<H', body[18:20])[0]
        ne = body.find(b'\x00', 20)
        nm = body[20:ne].decode('ascii','replace') if ne>=0 else '?'
        varstores[vsid] = {'name':nm, 'size':vsz, 'guid':guid}
    elif op == 0x01:  # Form
        fid = struct.unpack('<H', body[:2])[0]
        ttl = struct.unpack('<H', body[2:4])[0]
        form_stack.append({'fid':fid, 'title':strings.get(ttl,'?')})
    elif op in (0x05, 0x06, 0x07, 0x23):
        # Question: prompt(2) help(2) qid(2) vsid(2) voff(2) flags(1)
        if len(body) >= 11:
            prompt = struct.unpack('<H', body[0:2])[0]
            help_  = struct.unpack('<H', body[2:4])[0]
            qid    = struct.unpack('<H', body[4:6])[0]
            vsid   = struct.unpack('<H', body[6:8])[0]
            voff   = struct.unpack('<H', body[8:10])[0]
            flags  = body[10]
            questions.append({
                'op': op, 'opname': OP_NAMES.get(op,'?'),
                'prompt': prompt, 'help': help_, 'qid': qid,
                'vsid': vsid, 'voff': voff, 'flags': flags,
                'len': ln, 'pos': p,
                'form': form_stack[-1]['title'] if form_stack else None,
            })
    elif op == 0x29 and form_stack:  # End closes form scope
        form_stack.pop()
    p += ln

print(f"  walked OK: {len(varstores)} VarStores, {len(questions)} Questions")

# --- Step 3: Show key VarStores (especially Setup) ---
print("\n[Step 3] VarStores discovered (showing first 10)")
for vsid, vs in sorted(varstores.items())[:10]:
    print(f"  VsId=0x{vsid:04X}  name={vs['name']!r:30s}  size={vs['size']:>5}  guid={vs['guid'][:16]}...")

# --- Step 4: Find Questions referencing our target StringIds ---
print("\n[Step 4] Match Questions to DCI/Debug menu StringIds")
TARGETS = {
    0x07BE: 'DCI enable (HDCIEN)',
    0x033D: 'Debug Interface',
    0x033F: 'Debug Interface Lock',
    0x019C: 'JTAG C10 Power',
}
print(f"{'Target StringId':<16} {'Menu Text':<28} {'VsId':<8} {'VarOffset':<15} {'Form'}")
print('-'*100)
for sid, name in TARGETS.items():
    matches = [q for q in questions if q['prompt'] == sid]
    for q in matches:
        vs = varstores.get(q['vsid'], {'name':'?'})
        print(f"  0x{sid:04X}        {name:<28} VsId=0x{q['vsid']:x}  0x{q['voff']:04X} ({q['voff']:>4})    {q['form']}")

# Save all questions for Phase 5
with open(OUTPUT_JSON, 'w', encoding='utf-8') as f:
    json.dump(questions, f, ensure_ascii=False)
print(f"\nSaved {len(questions)} questions → {OUTPUT_JSON}")

图片描述
具体如何解析的可以看代码。
接下来就需要找到 0x904 在哪,这个解析出来的不是 文件office,是NVRAM容器格式的偏移。
NVRAM 容器的Guid是: cef5b9a3-476d-497f-9fdc-e98143e0422c。而我们需要修改的是他出厂的默认设置:

  NVRAM 容器
  └── NVAR 记录 "StdDefaults" (14715 字节)   ← 包所有 Setup 变量的出厂默认值
      └── 嵌套 NVAR "Setup" (4263 字节)       ← 我们的 Setup 变量默认值就这里
          └── 这 4263 字节里第 0x904 个就是 DciEn 字节

首先需要解析 AMI NVAR 的私有格式。好在通过Ai搜寻 UEFITool 源码中是有参考的。这里写了一个脚本来寻找并且Patch一下:
图片描述
解析代码如下:

import struct, sys, io, os
sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding='utf-8', errors='replace')

DUMP = r'D:\Z370_DICOPEN\z370.bin'
NVRAM_FFS = r'D:\Z370_DICOPEN\z370.bin_output\regions\region-bios\volume-0\file-cef5b9a3-476d-497f-9fdc-e98143e0422c\file.obj'

if not os.path.isfile(NVRAM_FFS):
    print(f"[ERR] {NVRAM_FFS} not found — did uefi-firmware-parser finish?")
    sys.exit(1)

blob = open(NVRAM_FFS, 'rb').read()
dump = open(DUMP, 'rb').read()
print(f"NVRAM FFS file.obj: {len(blob)} bytes")
print(f"Full dump z370.bin: {len(dump)} bytes")

# ---- AMI NVAR record format (community-reverse-engineered, no official doc) ----
# Header layout:
#   +0..+3:  Signature "NVAR" (4 bytes)
#   +4..+5:  Size              (2 bytes, includes header + data)
#   +6..+8:  Next offset       (3 bytes)
#   +9:      Attributes        (1 byte)
#              bit 0 (0x01) = Runtime
#              bit 1 (0x02) = ASCII Name
#              bit 2 (0x04) = Use GUID (instead of GuidIdx)
#              bit 3 (0x08) = Data Only (no name, references previous)
#              bit 4 (0x10) = Extended Header
#              bit 7 (0x80) = Valid
# Then if NOT data-only:
#   +10:     GuidIdx (1 byte)  OR  GUID (16 bytes) if Attr.UseGuid
#   +N..    Name (ASCII null-term, or UCS-2 if not Attr.AsciiName)
# Then data until +Size

def parse_nvar(buf, start, end):
    """Walk NVAR records, return list of dicts."""
    records = []
    p = start
    while p < end:
        if buf[p:p+4] != b'NVAR':
            nxt = buf.find(b'NVAR', p+1, end)
            if nxt < 0: break
            p = nxt
            continue
        sz = struct.unpack('<H', buf[p+4:p+6])[0]
        if sz < 12 or sz == 0xFFFF: break
        attr = buf[p+9]
        ASCII_NAME = bool(attr & 0x02)
        USE_GUID   = bool(attr & 0x04)
        DATA_ONLY  = bool(attr & 0x08)
        cursor = p + 10
        if DATA_ONLY:
            name = '(data_only)'
        else:
            cursor += 16 if USE_GUID else 1
            if ASCII_NAME:
                ne = buf.find(b'\x00', cursor, p+sz)
                name = buf[cursor:ne].decode('ascii','replace')
                cursor = ne + 1
            else:
                ne = cursor
                while ne+1 < p+sz and buf[ne:ne+2] != b'\x00\x00':
                    ne += 2
                name = buf[cursor:ne].decode('utf-16-le','replace')
                cursor = ne + 2
        records.append({'pos':p, 'sz':sz, 'attr':attr, 'name':name,
                        'data_start':cursor, 'data_end':p+sz})
        p = p + sz
    return records

# --- Step 1: parse top-level NVARs in file.obj ---
print("\n[Step 1] Walk top-level NVAR records in NVRAM FFS")
tops = parse_nvar(blob, 0x18, len(blob))
for r in tops[:5]:
    print(f"  NVAR @ 0x{r['pos']:x}  size=0x{r['sz']:x}  name={r['name']!r}")

sd = next((r for r in tops if r['name'] == 'StdDefaults'), None)
if not sd:
    print("[ERR] StdDefaults NVAR not found"); sys.exit(1)
print(f"\nStdDefaults found @ file.obj offset 0x{sd['pos']:x}")
print(f"  data 0x{sd['data_start']:x} - 0x{sd['data_end']:x}  ({sd['data_end']-sd['data_start']} bytes)")

# --- Step 2: parse INNER NVARs inside StdDefaults data ---
print("\n[Step 2] Walk INNER NVAR records inside StdDefaults")
inner = parse_nvar(blob, sd['data_start'], sd['data_end'])
print(f"  found {len(inner)} inner records")
for r in inner[:5]:
    print(f"    {r['name']!r:30s}  data_len={r['data_end']-r['data_start']}")

setup = next((r for r in inner if r['name'] == 'Setup'), None)
if not setup:
    print("[ERR] inner 'Setup' NVAR not found"); sys.exit(1)
print(f"\n>>> Inner 'Setup' record found")
print(f"    data within file.obj: 0x{setup['data_start']:x} - 0x{setup['data_end']:x}  ({setup['data_end']-setup['data_start']} bytes)")
assert setup['data_end'] - setup['data_start'] == 4263, "Setup size mismatch"

# --- Step 3: translate file.obj offset → z370.bin absolute offset ---
print("\n[Step 3] Translate file.obj offset → z370.bin absolute offset")
# Strategy: find a fingerprint (40 bytes of StdDefaults record header) in z370.bin
fingerprint = blob[sd['pos']:sd['pos']+40]
abs_sd_pos = dump.find(fingerprint)
if abs_sd_pos < 0:
    print("[ERR] cannot locate StdDefaults fingerprint in z370.bin")
    sys.exit(1)
print(f"  StdDefaults record in z370.bin @ 0x{abs_sd_pos:x}")

offset_within_sd = setup['data_start'] - sd['pos']
setup_default_base = abs_sd_pos + offset_within_sd
print(f"  Setup defaults base in z370.bin = 0x{setup_default_base:x}")
print(f"  ★ This is the byte position of Setup[0] in z370.bin")

# --- Step 4: compute physical patch addresses for each target ---
print("\n[Step 4] Final physical offsets in z370.bin")
TARGETS = [
    (0x4F3, 'JTAG C10 Power',     0x00, 0x01),
    (0x5D7, 'DebugInterface',     0x00, 0x01),
    (0x5D8, 'DebugInterfaceLock', 0x01, 0x00),  # unlock
    (0x904, 'DciEn (HDCIEN)',     0x00, 0x01),
]
print(f"{'Field':<25} {'VarOff':<8} {'Phys Addr':<12} {'Current':<10} {'Target':<10}")
print('-' * 75)
for voff, name, expected_old, new_val in TARGETS:
    phys = setup_default_base + voff
    current = dump[phys]
    mark = 'OK' if current == expected_old else 'MISMATCH'
    print(f"  {name:<25} 0x{voff:04X}   0x{phys:08X}  0x{current:02X}      0x{new_val:02X}   [{mark}]")

print()
print(f">>> phase5 result: setup_default_base = 0x{setup_default_base:X}")
print(f">>> DciEn physical address = 0x{setup_default_base + 0x904:X}")

这里已经解析并且计算出真实的偏移是多少,由于NVRAM 容器是没有被压缩,就可以在对应文件中进行patch就完事了这里具体不展开了。
接下来就是刷入固件然后启动。到现在为止,我们才有资格调试ME.在KAOKAO中由于他那个设备是默认就开启了这些接口所以不需要任何Patch技术就可以解锁了。
刷入:
图片描述

Usb线缆制作

两个方案:

  1. 直接买根调试线
  2. 购买一条公对公的线屏蔽部分触角来做到如图:
    图片描述
    我之前是用的胶布缠住的会有点问题,插拔的时候就出问题了。

ME固件解包

因为uefi-firmware-parser 解包出来得缺少body(说白了切得时候少切了内容,这也是我遇到的一个坑)。我们需要使用UEFIExtract 进行解包:
图片描述
body本身是压缩算法处理过的,这里写一个解密脚本用来解密一下body,代码如下:

def huffman_decompress(module_contents, compressed_size, decompressed_size, print_msg):
    chunk_count = int(decompressed_size / CHUNK_SIZE)
    header_size = chunk_count * 0x4

    module_buffer = bytearray(module_contents)
    header_buffer = module_buffer[0:header_size]
    compressed_buffer = module_buffer[header_size:compressed_size]
    decompressed_array = []

    header_entries = struct.unpack('<{:d}I'.format(chunk_count), header_buffer)
    start_offsets, flags = zip(*[(x & 0x1FFFFFF, (x >> 25) & 0x7F) for x in header_entries])
    end_offsets = itertools.chain(start_offsets[1:], [compressed_size - header_size])

    for index, dictionary_type, compressed_position, compressed_limit in zip(range(chunk_count), flags, start_offsets, end_offsets):
        if print_msg == 'all' :
            print('==Processing chunk 0x{:X} at compressed offset 0x{:X} with dictionary 0x{:X}=='.format(index, compressed_position, dictionary_type))

        dictionary = HUFFMAN_SYMBOLS[dictionary_type]

        decompressed_position, decompressed_limit = index * CHUNK_SIZE, (index + 1) * CHUNK_SIZE

        bit_buffer = 0
        available_bits = 0

        while decompressed_position < decompressed_limit:
            while available_bits <= 24 and compressed_position < compressed_limit:
                bit_buffer = bit_buffer | compressed_buffer[compressed_position] << (24 - available_bits)
                compressed_position = compressed_position + 1
                available_bits = available_bits + 8

            codeword_length, base_codeword = 0, 0
            for length, shape, base in HUFFMAN_SHAPE:
                if bit_buffer >= shape:
                    codeword_length, base_codeword = length, base
                    break

            if available_bits >= codeword_length:
                codeword = bit_buffer >> (32 - codeword_length)
                bit_buffer = (bit_buffer << codeword_length) & 0xFFFFFFFF
                available_bits = available_bits - codeword_length

                symbol = dictionary[codeword_length][base_codeword - codeword]
                symbol_length = len(symbol)

                if decompressed_limit - decompressed_position >= symbol_length:
                    decompressed_array.extend(symbol)
                    decompressed_position += symbol_length
                else:
                    # Shouldn't ever happen now that the dictionaries are complete
                    if print_msg in ['all','error'] :
                        print('Skipping overflowing codeword {: <15s} (dictionary 0x{:X}, codeword length {: >2d}, codeword {: >5s}, symbol length {:d}) at decompressed offset 0x{:X}'.format(
                        ('{:0>' + str(codeword_length) + 'b}').format(codeword), dictionary_type, codeword_length, '0x{:X}'.format(codeword), symbol_length, decompressed_position))
                    filler = itertools.repeat(0x7F, decompressed_limit - decompressed_position)
                    decompressed_array.extend(filler)
                    decompressed_position = decompressed_limit
            else:
                # Shouldn't ever happen now that the dictionaries are complete
                if print_msg in ['all','error'] :
                    print('Reached end of compressed stream early at decompressed offset 0x{:X}'.format(decompressed_position))
                filler = itertools.repeat(0x7F, decompressed_limit - decompressed_position)
                decompressed_array.extend(filler)
                decompressed_position = decompressed_limit
    
    return bytes(decompressed_array)

def main():
    if len(sys.argv) in [5, 6] :
        with open(sys.argv[1], 'rb') as file_input:
            decompressed = huffman_decompress(file_input.read(), int(sys.argv[3], 0), int(sys.argv[4], 0), (sys.argv[5] if len(sys.argv) == 6 else 'all'))
        with open(sys.argv[2], 'wb') as file_output:
            file_output.write(decompressed)
    else :
        print('\nUsage: HuffmanDecompress <compressed file path> <decompressed file path> <compressed file size> <decompressed file size> <info all|error|none>')
        print('\nExample: HuffmanDecompress bup.huff bup.mod 0x28E00 0x31000 all')

if __name__ == "__main__":
    main()

字典参考:4f6K9s2c8@1M7s2y4Q4x3@1q4Q4x3V1k6Q4x3V1k6Y4K9i4c8Z5N6h3u0Q4x3X3g2U0L8$3#2Q4x3V1k6H3N6s2u0W2M7$3g2S2M7X3y4Z5i4K6u0r3N6h3&6y4c8e0p5I4 这个项目。

项目进展

这个项目实际上研究时间大概在一个月,并且中间烧掉了两块主板,包括文中写的这个8700k平台以及废掉了。写文章的时候是两周前,最近才把剩下的坑填完。也是在不停的价钱在咸鱼收集符合版本的设备。

感谢您看到这里了,如果你看到这里觉得好玩,或者对你有帮助请点个赞,有啥建议或者想说的可以留言,文章不做任何限制要求。

剧透

已经对ME的启动流程进行完整分析。从rbe -> bup.以及kaokao 所说的漏洞的定位(分析主要依靠我构建的ai并行逆向框架与手工校对,感兴趣可以看一下我其他的文章,感谢感谢)。
图片描述
图片描述
其实酷睿也存在一模一样的洞hhh,不仅仅是低功耗平台。


传播安全知识、拓宽行业人脉——看雪讲师团队等你加入!

收藏
免费 2
支持
分享
最新回复 (0)
游客
登录 | 注册 方可回帖
返回