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模式切换 |
| ARM64 | Ghidra, 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提取内部可执行文件再识别。