1322 字
7 分鐘
Gigabyte BMC 韌體逆向分析

拆包流程概覽#

這篇主要記錄我怎麼把技嘉 BMC 的韌體拆包、找到 devmaps,再產生後續會用到的 models.json


使用 binwalk 掃描韌體#

先用 binwalk 看看韌體裡有什麼東西:

Terminal window
binwalk -e <BMC firmware>

實際輸出(節錄):

Terminal window
DECIMAL HEXADECIMAL DESCRIPTION
--------------------------------------------------------------------------------
42092 0xA46C LZMA compressed data, properties: 0x5D, dictionary size: -1073741824 bytes, uncompressed size: 124 bytes
42164 0xA4B4 Flattened device tree, size: 3615 bytes, version: 17
67616 0x10820 CRC32 polynomial table, little endian
118453 0x1CEB5 Certificate in DER format (x509 v3), header length: 4, sequence length: 259
422460 0x6723C SHA256 hash constants, little endian
586693 0x8F3C5 AES Inverse S-Box
597830 0x91F46 AES S-Box
730664 0xB2628 Flattened device tree, size: 33400 bytes, version: 17
1179648 0x120000 JFFS2 filesystem, little endian
5111808 0x4E0000 JFFS2 filesystem, little endian
5636096 0x560000 Squashfs filesystem, little endian, version 4.0, compression:xz, size: 49067396 bytes, 7496 inodes, blocksize: 131072 bytes, created: 2025-08-29 14:54:42
54722624 0x3430040 Flattened device tree, size: 4856980 bytes, version: 17
54722852 0x3430124 Linux kernel ARM boot executable zImage (little-endian)
54738476 0x3433E2C xz compressed data
54738707 0x3433F13 xz compressed data
59205388 0x387670C Flattened device tree, size: 53626 bytes, version: 17
59703296 0x38F0000 Squashfs filesystem, little endian, version 4.0, compression:xz, size: 5241502 bytes, 107 inodes, blocksize: 131072 bytes, created: 2025-08-29 14:54:39

可以看到在 offset = 59703296 的位置有一個 SquashFS 檔案系統,把它解開就能拿到裡面的 rootfs 來研究。


找到 devmaps 與風扇對應關係#

解開 SquashFS 之後,可以在 rootfs 裡找到:

  • 位置:/etc/devmaps/MZB3/
  • 其中包含:G494-ZB4-AAP2-000.xml

搭配之前備份的 fanprofile.json 來看,可以發現 XML 裡有各種風扇的 mapping 設定。這些 mapping 可以用來把 fan profile 解析成「真正的風扇名稱」,方便之後做 UI 顯示或 Debug。


extract.sh:自動解包並提取 devmaps#

為了不要每次都手動找 offset、解 SquashFS,我寫了一個 extract.sh,自動:

  1. 用 binwalk 掃出韌體中的 SquashFS 區塊
  2. 用 dd 把 SquashFS 切出來
  3. 用 unsquashfs 解壓
  4. 搜尋 devmaps 資料夾並拷貝到指定目錄
#!/bin/bash
FILE="$1"
TARGET_DIR="extracted_devmaps"
if [ -z "$FILE" ]; then
echo "用法: $0 <firmware.bin>"
exit 1
fi
# 檢查工具
for cmd in binwalk unsquashfs dd grep awk; do
if ! command -v $cmd &> /dev/null; then
echo "錯誤: 缺少必要工具 $cmd"
exit 1
fi
done
echo "[*] 正在掃描 $FILE (僅讀取表頭,不解壓)..."
# 讀取 binwalk 輸出
binwalk "$FILE" | grep "Squashfs filesystem" | while read -r line; do
# 1. 抓取 Offset (十進位)
OFFSET=$(echo "$line" | awk '{print $1}')
# 2. 抓取 Size (使用嚴格 Regex 避開 blocksize)
SIZE=$(echo "$line" | grep -oP '\bsize: \K[0-9]+' | head -n 1)
if [ -z "$OFFSET" ] || [ -z "$SIZE" ]; then
continue
fi
echo "---------------------------------------------------"
echo "[+] 發現 SquashFS -> Offset: $OFFSET, Size: $SIZE"
IMG_NAME="rootfs_${OFFSET}.sqfs"
# 3. 使用 dd 切割檔案 (高效能模式)
# bs=1M: 每次讀寫 1MB,大幅降低 I/O overhead
# iflag=skip_bytes,count_bytes: 告訴 dd 雖然 bs=1M,但 skip/count 的單位是 bytes
echo "[*] 正在切割檔案 (高效能 dd)..."
dd if="$FILE" bs=1M skip="$OFFSET" count="$SIZE" iflag=skip_bytes,count_bytes of="$IMG_NAME" status=none
if [ $? -ne 0 ]; then
echo "[-] dd 切割失敗,請檢查 dd 版本是否支援 iflag (GNU dd)"
rm -f "$IMG_NAME"
continue
fi
# 4. 解壓 SquashFS
echo "[*] 正在解壓 SquashFS..."
TEMP_EXTRACT_DIR="sqfs_out_${OFFSET}"
# 加上 -no-xattrs 避免 WSL 下的權限屬性報錯
unsquashfs -d "$TEMP_EXTRACT_DIR" -f -no-xattrs "$IMG_NAME" > /dev/null 2>&1
if [ ! -d "$TEMP_EXTRACT_DIR" ]; then
echo "[-] 解壓失敗 (可能是缺少 sasquatch),跳過..."
rm -f "$IMG_NAME"
continue
fi
# 5. 搜尋並提取 devmaps
FOUND_PATH=$(find "$TEMP_EXTRACT_DIR" -type d -name "devmaps" | head -n 1)
if [ -n "$FOUND_PATH" ]; then
echo "[!] 成功在 $IMG_NAME 中找到 devmaps!"
echo "[*] 路徑: $FOUND_PATH"
mkdir -p "$TARGET_DIR"
cp -r "$FOUND_PATH/"* "$TARGET_DIR/"
echo "[ok] 已複製到: $TARGET_DIR"
# 清理暫存
rm -rf "$TEMP_EXTRACT_DIR" "$IMG_NAME"
echo "[*] 已清理暫存檔案,任務完成。"
exit 0
else
echo "[-] 此分區未找到 devmaps,清理並繼續搜尋下一個..."
rm -rf "$TEMP_EXTRACT_DIR" "$IMG_NAME"
fi
done
echo "---------------------------------------------------"
if [ -d "$TARGET_DIR" ]; then
echo "掃描結束。請檢查 $TARGET_DIR"
else
echo "掃描結束。未在任何 SquashFS 分區中找到 devmaps。"
fi

gen_models.py:從 devmaps 生成 models.json#

有了 devmaps 裡一堆 XML 檔之後,接下來用 gen_models.py 生成一個整理好的 models.json,之後可以給前端或其他工具使用。

腳本會:

  • 遞迴掃描目標目錄
  • 找出所有非 empty.xml 的 XML 檔
  • 以檔名當作 id / name
  • 輸出每個檔案在目標目錄下的相對路徑
import os
import json
import argparse
def generate_models_json(target_dir, output_file):
models_list = []
# 確保目標目錄存在
if not os.path.exists(target_dir):
print(f"錯誤: 找不到目錄 '{target_dir}'")
return
print(f"[*] 正在掃描目錄: {target_dir} ...")
# 遞迴遍歷目錄 (os.walk)
for root, dirs, files in os.walk(target_dir):
for file in files:
# 只處理 xml 檔案,並忽略 empty.xml
if file.endswith(".xml") and file != "empty.xml":
# 取得完整路徑 (例如: ./extracted_devmaps/MZ93/R183-Z90.xml)
full_path = os.path.join(root, file)
# 取得檔名不含副檔名 (ID)
# os.path.splitext("R183.xml") -> ("R183", ".xml")
file_id = os.path.splitext(file)[0]
# 取得所在的資料夾名稱 (例如 MZ93),這是主板系列代號
series_folder = os.path.basename(root)
# 建立 JSON 物件結構
model_entry = {
"id": file_id,
"name": file_id,
"file": os.path.relpath(full_path, target_dir),
# "series": series_folder, # (可選) 主板系列
# "path": os.path.relpath(full_path, target_dir) # (可選) 相對路徑
}
models_list.append(model_entry)
# 根據 ID 排序,讓 JSON 比較整齊
models_list.sort(key=lambda x: x['id'])
# 寫入 JSON 檔案
try:
with open(output_file, 'w', encoding='utf-8') as f:
json.dump(models_list, f, indent=2, ensure_ascii=False)
print(f"---------------------------------------------------")
print(f"[OK] 成功生成: {output_file}")
print(f"[+] 總共找到 {len(models_list)} 個機型定義檔")
print(f"---------------------------------------------------")
except IOError as e:
print(f"寫入檔案錯誤: {e}")
if __name__ == "__main__":
# 設定參數解析
parser = argparse.ArgumentParser(description="掃描目錄並生成 models.json")
parser.add_argument("directory", nargs="?", default="extracted_devmaps", help="要掃描的 devmaps 資料夾路徑 (預設: extracted_devmaps)")
parser.add_argument("-o", "--output", default="models.json", help="輸出檔案名稱 (預設: models.json)")
args = parser.parse_args()
generate_models_json(args.directory, args.output)

小結#

  1. 用 binwalk 找到韌體中的 SquashFS 區塊
  2. extract.sh 自動切出並解開 rootfs,抽出 devmaps
  3. gen_models.py 掃描 devmaps,產生 models.json

這樣之後只要拿到新的 BMC 韌體,整個「拆包 → 抽資料 → 生成 models.json」流程就可以一鍵跑完。

Gigabyte BMC 韌體逆向分析
https://blog.rainchi.tw/posts/gigabyte-bmc-analytics/
作者
rainchi
發佈於
2026-01-04
許可協議
CC BY-NC-SA 4.0