第四章:BRAM内容读取中的地址映射错误

BRAM,说白了就是FPGA里的“内存条”。做逆向的时候,我们经常需要把BRAM里的内容读出来分析。但这里有个坑——地址映射。我见过太多人在这上面栽跟头了。

你可能会想:“地址映射能有多复杂?不就是把地址线对上就行了吗?”嗯,要是真这么简单,我就不用专门写这一章了。

4.1 地址映射的本质

先说说BRAM的地址是怎么工作的。一个典型的BRAM,比如Xilinx的Block RAM,它有地址线、数据线、控制信号。但问题在于——FPGA工具链在综合时,会对地址进行重映射

什么意思呢?你写RTL代码时用的地址,和实际BRAM端口的地址,很可能不是一回事。工具会优化、合并、甚至翻转地址线。我遇到过最夸张的一次,地址线被完全打乱,A[0]变成了A[7],A[3]变成了A[0]。

核心观点:BRAM的地址映射不是固定的。工具会根据你的设计自动调整。逆向时,必须重新建立地址映射关系。

4.2 常见的地址映射错误

我在项目中遇到过几种典型的地址映射错误,列出来给大家参考:

  • 地址线顺序颠倒:这是最常见的。工具可能把高位地址和低位地址互换。比如你以为是A[7:0],实际可能是A[0:7]。
  • 地址线被拆分:有时候工具会把一个BRAM拆成多个小BRAM,地址线被重新分配。你读到的地址可能只是实际地址的一部分。
  • 地址偏移:有些设计会在地址上加上一个固定的偏移量。你读到的地址0,实际对应的是BRAM的地址0x100。
  • 地址交织:多个BRAM之间地址交织。比如地址0在BRAM0,地址1在BRAM1,地址2又在BRAM0。

警告:千万不要假设地址线是按顺序连接的。我见过一个项目,地址线被工具随机打乱,最后花了整整两天才把映射关系理清楚。

4.3 如何识别地址映射错误

怎么知道自己遇到了地址映射问题?有几个信号:

  1. 读出的数据看起来是乱序的:比如你读BRAM的内容,发现数据像是被“打散”了,每个字节都不在正确的位置上。
  2. 地址变化时,数据变化不符合预期:比如你递增地址,但读出的数据不是连续的,而是跳跃的。
  3. 多个BRAM之间数据重复或缺失:这通常意味着地址映射有重叠或空洞。

我曾经遇到一个案例:读一个BRAM的内容,发现数据看起来像是加密过的。后来仔细分析,才发现是地址线被颠倒了。把地址线重新排序后,数据就变成了可读的明文。

4.4 地址映射的逆向方法

那么,怎么解决地址映射问题呢?我总结了一套方法:

4.4.1 写入已知模式

这是最直接的方法。向BRAM写入一个已知的数据模式,比如0x00, 0x01, 0x02...然后读出来,看看数据在哪个地址出现。通过对比,就能推断出地址映射关系。

// 写入已知模式
for (int i = 0; i < BRAM_DEPTH; i++) {
    write_bram(i, i & 0xFF);  // 写入地址的低8位
}

// 读取并分析
for (int i = 0; i < BRAM_DEPTH; i++) {
    uint8_t data = read_bram(i);
    // 如果 data == i,说明地址映射正确
    // 如果 data != i,说明地址有偏移或颠倒
}

4.4.2 使用地址扫描

如果BRAM比较大,手动分析太慢。可以写一个脚本,自动扫描地址映射。基本思路是:

  • 写入一个“地址标记”,比如在地址i写入值i
  • 然后读取所有地址,找出值等于i的地址
  • 这样就建立了地址映射表

小技巧:我习惯用Python写扫描脚本。配合pySerial或者pyVISA,可以自动化完成地址映射的逆向。省时省力。

4.4.3 分析控制逻辑

有时候,地址映射不是简单的线性关系。比如,地址可能经过了一个LUT或者MUX。这时候,需要分析控制逻辑,看看地址是怎么生成的。

我记得有一次,地址映射涉及到两个BRAM之间的切换。控制逻辑里有一个状态机,根据地址的高位选择BRAM。这个状态机被工具优化得很隐蔽,最后是通过分析网表才找到的。

4.5 地址映射的SVG结构图

下面这张图展示了BRAM地址映射的典型流程和常见错误点:

BRAM地址映射流程与常见错误 原始地址 RTL代码中的地址 工具综合/优化 地址重映射 实际BRAM端口 物理地址 常见地址映射错误 地址线顺序颠倒 A[7:0] → A[0:7] 数据完全乱序 地址偏移 实际地址 = 原始地址 + 偏移量 数据整体平移 地址拆分/合并 一个BRAM拆成多个小BRAM 地址线被重新分配 地址交织 多个BRAM之间地址交织 地址0在BRAM0,地址1在BRAM1 提示:使用已知模式写入 + 地址扫描,可以快速建立地址映射关系

4.6 实战案例:一个地址映射错误的修复过程

说个真实的案例。有一次,我在逆向一个通信模块。模块里用了4个BRAM,每个BRAM 1024x8。我读出来的数据看起来完全没规律。

一开始我以为是加密了。但仔细看,发现数据里有一些重复的模式。比如,0x12这个值出现了4次,分别在地址0x100, 0x200, 0x300, 0x400。

这不对劲。我写了个脚本,向BRAM写入地址值,然后读出来。结果发现:

  • 写入地址0x000,读出来在地址0x000
  • 写入地址0x001,读出来在地址0x100
  • 写入地址0x002,读出来在地址0x200
  • 写入地址0x003,读出来在地址0x300

明白了!地址的低2位被用来选择BRAM,而不是作为地址的一部分。也就是说,地址[1:0]被映射到了BRAM选择信号上。

修正了地址映射后,数据就变得有规律了。原来是一个简单的查找表,里面存的是CRC校验值。

经验总结:遇到BRAM数据乱序,先别急着怀疑加密。先检查地址映射。很多时候,问题就出在这里。

4.7 避坑指南

最后,给大家几条实用的建议:

  • 不要相信RTL代码中的地址:综合工具会做很多优化。RTL里的地址和实际BRAM的地址可能完全不同。
  • 先做地址映射分析,再做数据内容分析:顺序很重要。地址映射错了,数据内容分析就是白费功夫。
  • 使用自动化工具:手动分析地址映射太慢了。写个脚本,几分钟就能搞定。
  • 保留地址映射的文档:分析完后,把地址映射关系记录下来。下次遇到类似的设计,可以直接参考。

我的习惯:每次做BRAM逆向,我都会先画一个地址映射图。把原始地址、工具优化后的地址、实际BRAM地址都标出来。这样一目了然,不容易出错。

嗯,地址映射这块就说这么多。说白了,就是不要想当然。工具比你想象的“聪明”,也比你想象的“狡猾”。多留个心眼,多验证几次,就能避开这个坑。

专注资料整理