4、比特流格式概述:厂商差异与文件结构

各位同学,今天我们聊聊比特流格式。说实话,这是逆向工程里最让人头疼的部分——不同厂商的比特流,长得完全不一样。我当年第一次接触Xilinx的比特流时,打开一看全是0xFFFFFFFF的填充,差点以为文件坏了。

先给大家一个整体概念:比特流本质上就是FPGA的“机器码”。它告诉芯片:哪些LUT该连起来,哪些BRAM存什么数据,IO口怎么配置。但每家厂商的实现方式,嗯,各有各的“脾气”。

核心观点:比特流逆向,说白了就是“猜厂商的配置协议”。猜对了,你就能还原出RTL;猜错了,满屏都是乱码。

4.1 三大厂商的比特流哲学

我这些年接触过Xilinx、Altera(现在叫Intel了)和Lattice的芯片。三家的设计思路完全不同:

  • Xilinx: 喜欢用“帧”结构。每个配置帧对应芯片上的一列逻辑资源。我个人觉得,Xilinx的比特流最“规整”,但也是最难解析的——因为帧之间还有同步头、校验码这些“噪音”。
  • Altera/Intel: 采用“块”结构。配置数据按功能模块打包,比如LAB块、DSP块、存储器块。我遇到过一块Altera的Cyclone V,它的比特流里居然混着ARM硬核的配置数据,当时排查了好久。
  • Lattice: 走的是“精简”路线。比特流体积小,结构简单。但简单也有简单的麻烦——它的配置寄存器映射关系藏得很深,我曾在Lattice的iCE40上花了两周才摸清一个IO口的配置位。
特性 Xilinx Altera/Intel Lattice
配置单元 帧(Frame) 块(Block) 页(Page)
同步头 0xFFFFFFFF + 0xAA995566 0xFFFFFFFF + 0xAAAAAAAA 0xFFFFFFFF + 0x7E
校验方式 CRC-32(每帧校验) CRC-16(每块校验) 奇偶校验(每页校验)
压缩支持 支持(MultiBoot) 支持(压缩模式) 不支持
逆向难度 ★★★★★ ★★★★☆ ★★★☆☆

我的经验:如果你刚开始做逆向,建议从Lattice的芯片入手。它的比特流结构最“友好”,容易建立信心。Xilinx的7系列以后,比特流里加了加密和认证,逆向难度直接翻倍。

4.2 比特流文件结构:从头部到尾部

不管哪家厂商,比特流文件都有个通用骨架。我把它总结成四个部分:

  1. 同步头(Sync Header): 告诉芯片“我要开始配置了”。Xilinx用0xFFFFFFFF + 0xAA995566,Altera用0xFFFFFFFF + 0xAAAAAAAA。嗯,这里要注意:有些芯片的同步头长度不一样,比如Lattice的iCE40只有3个字节。
  2. 配置数据区(Configuration Data): 核心部分。里面包含了LUT的查找表内容、布线开关的状态、BRAM的初始化数据等。这部分是逆向的重点。
  3. 校验区(CRC/Checksum): 防止配置过程中数据出错。Xilinx的CRC是32位的,覆盖整个比特流;Altera是16位的,每块单独校验。
  4. 结束标志(Footer): 告诉芯片“配置完成”。Xilinx用0x20000000,Altera用0x00000000,Lattice用0x00。

我举个例子,一个典型的Xilinx比特流文件结构长这样:

// Xilinx .bit 文件结构(简化版)
0xFFFFFFFF          // 同步头(32位)
0xAA995566          // 同步字
0x30008001          // 配置命令:写入CRC
0x00000000          // CRC初始值
0x3001C001          // 配置命令:写入帧数据
[帧数据...]         // 实际配置数据(长度可变)
0x30008001          // 配置命令:写入CRC
[CRC值]             // 32位CRC校验
0x3000A001          // 配置命令:启动
0x20000000          // 结束标志

避坑指南:我曾经在逆向一块Xilinx Kintex-7时,发现比特流里有一段0xFFFFFFFF的填充。一开始以为是同步头,后来才发现那是“空帧”——芯片在配置过程中会插入这些填充来对齐时钟。如果你不跳过这些填充,解析出来的RTL会多出一堆“幽灵逻辑”。

4.3 厂商差异的深层原因

为什么三家厂商的比特流格式差这么多?我个人觉得,这跟他们的芯片架构有关:

  • Xilinx: 采用“岛式”架构,逻辑单元像小岛一样排列。每个岛对应一帧,帧之间通过交换矩阵连接。所以它的比特流是“帧序列”,每帧描述一列资源。
  • Altera/Intel: 采用“行式”架构,逻辑资源按行排列。每行包含多个LAB块,块内又有多个LE(逻辑单元)。所以它的比特流是“块序列”,每块描述一行资源。
  • Lattice: 采用“页式”架构,芯片内部按页组织。每页包含多个逻辑单元和布线资源。它的比特流是“页序列”,每页描述一个功能区域。

你想想看,架构不同,配置数据的组织方式自然不同。这就好比不同国家的建筑风格——Xilinx是欧式城堡,规整但复杂;Altera是中式庭院,层次分明;Lattice是日式小屋,简洁但细节多。

4.4 实战:如何识别比特流厂商

拿到一个未知的比特流文件,第一步是识别它是哪家厂商的。我一般用这个方法:

  1. 看文件头: 用十六进制编辑器打开,看前几个字节。Xilinx的.bit文件以0xFFFFFFFF开头,Altera的.rbf文件以0xFFFFFFFF开头但后面跟0xAAAAAAAA,Lattice的.bit文件以0xFFFFFFFF开头但后面跟0x7E。
  2. 看文件大小: Xilinx的比特流通常比较大(几MB到几十MB),Altera的适中(几百KB到几MB),Lattice的很小(几十KB到几百KB)。
  3. 看文件扩展名: Xilinx用.bit或.bin,Altera用.rbf或.pof,Lattice用.bit或.bin。但注意,有些厂商会改扩展名,所以不能完全依赖这个。

我的经验:有一次客户给我一个比特流文件,扩展名是.bin,我以为是Xilinx的。结果一分析,发现同步头是0x7E,才知道是Lattice的。从那以后,我养成了“先看头,再分析”的习惯。

4.5 比特流逆向的通用流程

最后,我给大家总结一下比特流逆向的通用流程。不管哪家厂商,基本都遵循这个步骤:

  1. 解析同步头: 找到配置数据的起始位置。
  2. 提取配置数据: 跳过填充和校验,提取出纯配置数据。
  3. 识别配置单元: 判断是帧、块还是页,确定每个单元的大小。
  4. 映射到芯片资源: 将配置单元映射到具体的LUT、BRAM、IO等资源。
  5. 还原RTL: 根据映射关系,还原出逻辑网表和RTL代码。

嗯,这里要注意:第4步是最难的。因为厂商不会公开配置位的映射关系,你需要通过“黑盒测试”来推断——比如改变一个LUT的输入,看比特流里哪些位变了。

比特流逆向通用流程 解析同步头 提取配置数据 识别配置单元 映射到芯片资源 还原RTL 迭代优化(黑盒测试) 注:虚线表示逆向过程中需要反复迭代的步骤 Xilinx: 帧结构 | Altera: 块结构 | Lattice: 页结构

好了,关于比特流格式的厂商差异和文件结构,我就讲这么多。记住一句话:比特流是芯片的“指纹”,每家厂商都有自己的“指纹”特征。掌握了这些特征,逆向工程就成功了一半。

最后提醒:实际项目中,比特流可能被加密或压缩。比如Xilinx的7系列支持AES-256加密,Altera的Stratix系列支持压缩模式。遇到这种情况,先解密或解压缩,再按上面的流程分析。

专注资料整理