第4章:嵌入式目标基础:AVR架构简介、SimpleSerial协议、目标板上的加密算法(AES-128)
好,咱们进入正题。这一章要聊的是嵌入式目标的基础知识。说白了,就是你要攻击的那个“靶子”到底长什么样。
我刚开始接触侧信道攻击时,也犯过嘀咕:不就是个单片机嘛,有什么好研究的?结果第一次采集功耗曲线,波形乱七八糟,完全看不出门道。后来才明白——不了解目标架构,你连功耗曲线都读不懂。
4.1 AVR架构:你攻击的“心脏”
ChipWhisperer 课程里最常用的目标,就是基于 AVR 架构的芯片,比如 ATmega328P 或 ATmega1284P。为什么选它?因为 AVR 的指令集简单、功耗特征明显,非常适合入门。
AVR 是哈佛架构,什么意思?程序存储器和数据存储器是分开的。程序跑在 Flash 里,数据存在 SRAM 里。这跟你的电脑不太一样——电脑是冯·诺依曼架构,代码和数据混在一起。
我个人习惯把 AVR 的寄存器文件看作一个“小操场”。32 个通用寄存器,每个 8 位,它们直接连到 ALU(算术逻辑单元)。你执行一条加法指令,操作数从寄存器里取,结果写回寄存器。整个过程,电流消耗会随着数据变化而波动。
关键点:AVR 的功耗与正在处理的数据强相关。你操作 0x00 和操作 0xFF,电流差很明显。这就是侧信道攻击能成功的基础。
我在项目中遇到过一个问题:用 ChipWhisperer 采集 AVR 的功耗曲线,发现波形每隔一段就有个“尖刺”。排查了半天,原来是 AVR 内部的硬件乘法器在作怪。执行 MUL 指令时,电流会突然增大。嗯,这些小细节,你不做实际测试根本想不到。
AVR 指令执行流程
一条指令的执行,大致分三步:
- 取指(Fetch):从 Flash 中读取指令码。这时候电流相对平稳。
- 译码(Decode):解析指令含义。电流变化不大。
- 执行(Execute):真正干活的时候。如果是算术运算,电流波动最明显。
你想想看,我们采集功耗曲线,其实就是在记录这些电流变化。攻击 AES 时,我们重点关注“执行”阶段——特别是 S 盒查表操作。因为查表时的地址和数据,直接暴露了密钥信息。
小技巧:在 ChipWhisperer 的 Scope 设置里,可以调整采样率和触发位置。我一般把采样率设为 4 倍于目标时钟频率,这样能捕捉到指令执行的细节。
4.2 SimpleSerial 协议:你与目标的“对话”方式
好,现在你知道目标芯片是 AVR 了。但你怎么跟它通信?怎么告诉它“嘿,给我加密一段数据”?
这就用到 SimpleSerial 协议了。这是 ChipWhisperer 项目定义的一个轻量级通信协议,通过 UART 串口传输。说白了,就是一行一行的 ASCII 字符串。
SimpleSerial 的格式很简单:
p<plaintext_hex>\n // 发送明文
k<key_hex>\n // 发送密钥
r<ciphertext_hex>\n // 接收密文
举个例子,你想让目标加密一个全零的明文块,密钥也是全零:
发送: p00000000000000000000000000000000
发送: k00000000000000000000000000000000
接收: r66e94bd4ef8a2c3b884cfa59ca342b2e
我曾经踩过一个坑:发送的十六进制字符串长度不对。AES-128 的明文和密钥都是 16 字节,也就是 32 个十六进制字符。少一个字符,目标板就不响应。排查了半天,才发现是脚本里多了一个空格。
注意:SimpleSerial 协议不包含校验和。如果你在 noisy 的环境下通信,可能会收到错误数据。我建议在 PC 端做一次 CRC 校验,或者多发几次取平均值。
SimpleSerial 在 ChipWhisperer 中的实现
ChipWhisperer 的 Python 库已经封装好了 SimpleSerial 的收发函数。你不需要自己写串口解析代码。调用方式如下:
import chipwhisperer as cw
# 初始化目标
target = cw.target(scope, cw.targets.SimpleSerial)
# 发送明文和密钥
target.simpleserial_write('p', plaintext)
target.simpleserial_write('k', key)
# 读取密文
ciphertext = target.simpleserial_read('r', 16)
嗯,这里要注意:simpleserial_read 的第二个参数是期望接收的字节数。AES-128 的密文是 16 字节,所以填 16。如果你填错了,程序会一直等,直到超时。
4.3 目标板上的加密算法:AES-128
终于到核心了。目标板上跑的加密算法,就是 AES-128。为什么选它?因为 AES 是工业标准,而且它的 S 盒操作对侧信道攻击特别“友好”。
AES-128 的加密过程,我简单过一遍:
- AddRoundKey:明文与初始密钥异或。
- SubBytes:每个字节通过 S 盒替换。这是最关键的步骤。
- ShiftRows:行移位。
- MixColumns:列混合。
- AddRoundKey:与轮密钥异或。
- 重复 9 轮(第 1-9 轮包含 MixColumns,第 10 轮没有)。
你可能会问:为什么 S 盒操作是攻击重点?因为 S 盒是一个非线性替换表。输入是 8 位数据,输出也是 8 位数据。攻击者可以通过功耗曲线,反推出 S 盒的输入值,进而推导出密钥。
核心思想:在 SubBytes 步骤,芯片会读取 S 盒的某个位置。这个位置由“明文异或密钥”决定。如果我们知道明文,再通过功耗曲线猜出 S 盒的输入,那密钥就等于“输入异或明文”。
我在实际项目中,曾经用 ChipWhisperer 攻击过一个 AVR 目标。第一轮 SubBytes 的功耗特征非常明显——波形上会出现 16 个“小鼓包”,每个鼓包对应一个字节的 S 盒查表。你只要对准这些鼓包的位置,就能提取出 16 个字节的密钥。
AES-128 在 AVR 上的实现
ChipWhisperer 的固件里,AES 是用 C 语言写的,编译后烧录到 AVR 里。代码实现很标准,没有加任何防护。说白了,就是“裸奔”的 AES。
我建议你读一下固件源码,位置在 chipwhisperer/hardware/victims/firmware/simpleserial-aes/。重点关注 aes.c 里的 SubBytes 函数:
void SubBytes(uint8_t *state) {
for (int i = 0; i < 16; i++) {
state[i] = sbox[state[i]];
}
}
你看,这个循环就是 16 次 S 盒查表。每次查表,AVR 都要从 Flash 里读取 sbox 数组的一个元素。读取 Flash 时的电流消耗,跟读取的地址和数据都有关。这就是我们攻击的切入点。
实战建议:刚开始练习时,不要一上来就攻击完整的 AES。先攻击第一个字节。只采集第一轮 SubBytes 的功耗曲线,然后尝试恢复第一个字节的密钥。成功之后,再扩展到 16 个字节。这样能帮你建立信心。
4.4 本章知识体系
下面这张图,是我自己画的本章知识结构。你可以把它当作一个“地图”,随时回来查阅。
这张图把本章的三个核心内容串起来了。AVR 是“身体”,SimpleSerial 是“嘴巴”,AES 是“大脑”。你通过嘴巴告诉身体做什么,身体执行大脑的指令,同时泄露功耗信息。
我个人觉得,理解这三者的关系,是入门侧信道攻击最重要的一步。很多初学者一上来就盯着算法看,忽略了硬件和通信协议。结果采集到的数据不对,还以为是算法分析出了问题。
避坑指南:我曾经犯过一个低级错误——忘记检查目标板的时钟频率。AVR 默认跑 7.37MHz,但我的 ChipWhisperer 采样率设成了 10MS/s。结果波形 aliasing,完全没法分析。记住:采样率至少要是时钟频率的 2 倍,我建议 4 倍以上。
好了,这一章的内容就到这里。你掌握了 AVR 的基本架构,知道了 SimpleSerial 怎么用,也理解了 AES-128 的加密流程。下一章,我们会真正动手,用 ChipWhisperer 采集第一条功耗曲线。