3. 固件架构识别

任务说明

本案例目标:给定一个未知固件二进制文件,通过分析其文件头与二进制特征,确定目标CPU架构(ARM或MIPS),并据此选择正确的分析工具链。完成本案例后,你将能够独立完成固件架构识别这一IoT固件分析前置步骤。

前置环境

需要一台Linux主机(推荐Ubuntu 20.04+),并安装以下工具:

  • file(系统自带)
  • binutils(包含readelf、objdump)
  • binwalk(可选,用于辅助检测)

核心方法

固件架构识别遵循“由外到内、由浅入深”的流程:先看文件系统外壳,再查ELF头,最后用指令特征交叉验证。本案例使用一个真实固件提取出的内核镜像片段作为样例,演示完整识别过程。

关键完整代码

以下脚本实现自动识别:读取文件头前64字节,解析ELF头中的e_machine字段;若为裸二进制(无ELF头),则通过熵分析与常见ARM/MIPS指令模式匹配进行判别。

#!/usr/bin/env python3
import struct
import sys
import os

def detect_elf_arch(data):
    """解析ELF头,返回架构字符串或None"""
    if len(data) < 20 or data[:4] != b'\x7fELF':
        return None
    ei_class = data[4]  # 1=32位, 2=64位
    ei_data = data[5]   # 1=小端, 2=大端
    endian = '<' if ei_data == 1 else '>'
    
    if ei_class == 1:  # ELF32
        e_machine = struct.unpack(endian + 'H', data[18:20])[0]
    elif ei_class == 2:  # ELF64
        e_machine = struct.unpack(endian + 'H', data[18:20])[0]
    else:
        return None
    
    arch_map = {
        40: 'ARM',      # EM_ARM
        183: 'ARM64',   # EM_AARCH64
        8: 'MIPS',      # EM_MIPS
        10: 'MIPS',     # EM_MIPS_RS3_LE (旧)
        3: 'x86',       # EM_386
        62: 'x86_64',   # EM_X86_64
    }
    return arch_map.get(e_machine, f'UNKNOWN({e_machine})')

def detect_raw_arch(data):
    """对无ELF头的裸二进制,用指令模式启发式判断"""
    if len(data) < 64:
        return 'UNKNOWN(too short)'
    
    # ARM Thumb指令特征: 0x00-0xFF范围内常见模式
    # MIPS常见指令: 0x27xxxxxx (addiu), 0x8fxxxxxx (lw), 0xafxxxxxx (sw)
    mips_count = 0
    arm_count = 0
    
    # 检查前4KB中的指令模式
    sample = data[:4096]
    for i in range(0, len(sample) - 4, 4):
        word = struct.unpack('>I', sample[i:i+4])[0]  # 先按大端尝试
        # MIPS: addiu $reg,$reg,imm -> 0x24/0x25/0x26/0x27开头
        if (word >> 26) in [0x09, 0x0A, 0x0B, 0x0C, 0x0D]:
            mips_count += 1
        # MIPS: lw/sw -> 0x23/0x2B开头
        if (word >> 26) in [0x23, 0x2B]:
            mips_count += 1
        
        # ARM (小端): 常见LDR/STR指令 0xE5xxxxxx
        word_le = struct.unpack('<I', sample[i:i+4])[0]
        if (word_le & 0xFFF00000) == 0xE5900000:  # LDR
            arm_count += 1
        if (word_le & 0xFFF00000) == 0xE5800000:  # STR
            arm_count += 1
    
    if mips_count > arm_count and mips_count > 5:
        return 'MIPS (heuristic)'
    elif arm_count > mips_count and arm_count > 5:
        return 'ARM (heuristic)'
    else:
        return 'UNKNOWN(no clear pattern)'

def main():
    if len(sys.argv) != 2:
        print(f'Usage: {sys.argv[0]} <firmware_file>')
        sys.exit(1)
    
    fpath = sys.argv[1]
    if not os.path.isfile(fpath):
        print(f'Error: {fpath} not found')
        sys.exit(1)
    
    with open(fpath, 'rb') as f:
        header = f.read(64)
    
    print(f'File: {fpath}')
    print(f'Size: {os.path.getsize(fpath)} bytes')
    print(f'First 16 bytes (hex): {header[:16].hex()}')
    
    # 方法1: ELF头识别
    elf_arch = detect_elf_arch(header)
    if elf_arch:
        print(f'[ELF] Architecture: {elf_arch}')
        # 进一步确认字节序
        ei_data = header[5]
        endian_str = 'little' if ei_data == 1 else 'big'
        print(f'[ELF] Endian: {endian_str}')
        print(f'[ELF] Class: {"64-bit" if header[4] == 2 else "32-bit"}')
    else:
        print('[ELF] No ELF header found, trying raw analysis...')
        raw_arch = detect_raw_arch(header + open(fpath, 'rb').read(4096)[64:])
        print(f'[RAW] {raw_arch}')
    
    # 方法2: 调用系统file命令交叉验证
    print('\n--- system file output ---')
    os.system(f'file "{fpath}"')

if __name__ == '__main__':
    main()

运行与验证

将上述脚本保存为 arch_detect.py,赋予执行权限后运行:

chmod +x arch_detect.py
./arch_detect.py /path/to/firmware.bin

验证方法:使用一个已知架构的固件文件(例如从路由器固件中提取的squashfs-root内的/bin/busybox)进行测试。预期输出应包含:

[ELF] Architecture: ARM
[ELF] Endian: little
[ELF] Class: 32-bit
--- system file output ---
... ELF 32-bit LSB executable, ARM, ...

若脚本输出的架构与file命令结果一致,则识别正确。对于无ELF头的裸固件(如U-Boot镜像),脚本会输出启发式判断结果,此时应结合binwalk的扫描结果综合确认。

工具选择指引

识别架构推荐分析工具说明
ARM (32位)Ghidra + ARM插件, objdump -D -m arm小端ARM最常见,注意Thumb模式切换
ARM64Ghidra, aarch64-linux-gnu-objdump现代路由器/手机固件常见
MIPS (大端)Ghidra + MIPS插件, objdump -D -m mips:4000老式路由器(如Broadcom芯片)常见
MIPS (小端)objdump -D -m mips:4000 -EL部分MTK平台使用

常见问题

  • 固件是加密或压缩的:先使用binwalk -e解包,再对解出的内核/文件系统镜像执行上述识别。
  • 大小端判断错误:MIPS固件多为大端,ARM多为小端。若启发式结果与file冲突,以file输出为准。
  • 纯数据文件无指令特征:若固件是JFFS2或SquashFS文件系统镜像,应先用binwalk提取内部可执行文件再识别。