最近对侧信道攻击比较感兴趣,搞了一台 ChipWhisperer Lite 回来折腾,顺便把学到的写写跟大家分享一下,也希望各位大神能多指点一下。
本文介绍一下入门级的无源侧信道攻击——功耗分析攻击。
CW1173 攻击逻辑 FPGA(左)、CW303 攻击目标(右)。白色的是 20 Pin 的数据线,用来给攻击目标供电、对攻击目标编程下载测试攻击用的目标固件。另外两条信号线分别是用来测量目标芯片的功耗(电压)的(Measure),和向攻击目标发送毛刺信号的(Glitch)。毛刺我们本文用不到,如果之后有机会的话再会跟大家介绍。
CW1173 跟电脑用 USB 连接,电脑上需要安装配套软件,以及驱动(位于安装目录下 chipwhisperer/hardware/capture/chipwhisperer-lite/cwlite_usb_driver.zip)
我们给目标芯片写一个简单的程序:
h0px3
进行比较主要的逻辑代码如下:
int main(void) { char passwd[32]; char correct_passwd[] = "h0px3"; my_read(passwd, 32); uint8_t passbad = 0; for (uint8_t i = 0; i < sizeof(correct_passwd); i++) { if (correct_passwd[i] != passwd[i]) { passbad = 1; break; } } if (passbad) { my_puts("PASSWORD FAIL\n"); } else { my_puts("Access granted, Welcome!\n"); } }
CPU 执行不同指令的时候会有不同的功耗特征,这是因为不同的指令触发的半导体数量不同,有的指令还会访问内存、缓存等等,有的复杂的指令需要的时钟周期(Clock Cycles)还会比别的指令要长,各种各样的因素会导致不同的指令执行的时候会产生不同的功耗特征。
ChipWhisperer 可以通过功耗测量接口,检测目标芯片的 VCC 供电线路上的电压变化,电压减少得越多,说明当前 CPU 的功耗越大。通过高精度的采样功耗变化,我们可以画出 CPU 的功耗变化图,从而寻找可以泄露 CPU 运行信息的相关信号特征,然后加以利用。
运行 ChipWhisperer 的软件套件,会在浏览器打开一个 Jupyter,本文将使用 jupyter/PA_SPA_1-Timing_Analysis_with_Power_for_Password_Bypass.ipynb
这个文件中的功能。
打开这个文件后,修改开头的 PLATFORM
。因为我们使用的是 CW303 攻击目标板,这里的赋值改为 CW303
。如果你用的是别的板子,可以在官网找到对应的 PLATFORM
值。
接下来我们使用这个脚本中的指令编译攻击目标固件、向攻击目标板刷机。如果不太熟悉这些步骤可以先过目一遍 jupyter/PA_Intro_1-Firmware_Build_Setup.ipynb
这个文件。
我们直接使用 Choosing a Reference
一节下面的这个脚本来采集两份不同的功耗数据:
%matplotlib notebook import matplotlib.pyplot as plt ref_trace = cap_pass_trace("a\n") correct_trace = cap_pass_trace("h\n") plt.plot(ref_trace, 'r') plt.plot(correct_trace - 0.5, 'g')
a
(不符合正确密码的第一个字符)下图红色数据h
(符合正确密码的第一个字符)下图绿色数据横座标是时间(ms),纵座标是相对功耗变化(ΔV),为了方便区分两份数据,绿色的那份数据被下移了 0.5V。
我们仅凭肉眼就能观察到,两份数据的起始部分,0-70ms 这段基本相同,而且红色数据从大概 100ms 开始,和绿色数据从大概 150ms 开始,之后的形状都近似相同。唯一的不同在于,红色数据标注了 1 的那个突起,在绿色数据中反映出来的是 1、2 两处几乎一样形状的突起。
由此我们可以猜测,1 号突起对应的是 CPU 在执行第一个字符的对比操作,因为红色数据尝试的是 a
,不符合正确密码的第一个字符,因此直接就退出了对比的循环。而绿色数据尝试的是 h
,符合正确密码的第一个字符,于是循环继续尝试匹配第二个字符,然后因为第二个字符是 \0
所以第二次匹配之后就退出了循环。
为了进一步证实我们的这个猜测,我们稍微修改一下两组数据的测试密码:
%matplotlib notebook import matplotlib.pyplot as plt ref_trace = cap_pass_trace("a\n") correct_trace = cap_pass_trace("h0\n") plt.plot(ref_trace, 'r') plt.plot(correct_trace - 0.5, 'g')
a
(不符合正确密码的第一个字符)下图红色数据尝试密码 h0
(符合正确密码的前两个字符)下图绿色数据
可以看到绿色的数据多了一个 3 号突起,验证了我们上面的猜想。也就是说,通过功耗分析,我们可以看出来有多少个字符被正确匹配了。
根据我们上面的发现,每试中一个正确的字符,我们就可以观察到多一个功耗突起。有了这个功耗分析手段,我们就可以提出一个比盲目 brute-force 高效很多的爆破方法:我们只需要挨个 brute-force 单个字符,直到找到完整的密码。那么问题来了:如何使用脚本确定突起增加了?
我们可以再次观察一下上面采集的数据,我们可以看到,在突起后面跟着一个信号特征,形状不变,每增加一个突起,这个尾随的信号就会向后位移。
我们可以把这一段信号特征单独提取出来,作为一个参考信号。然后我们可以使用一个叫做 Sum of Absolute Difference (SAD) 的统计方法,其实类似于我们常用的方差,来计算两个信号的差异值。
我们可以把上面这个参考信号不断平移,每次计算这个 SAD 值,直到差异值小于我们期望的一个阈值位置,则相对的平移 offset 就是这个参考信号在新的信号中的位移值。我们可以判断这个平移值是否发生明显的变化,来判断这个参考信号是否发生了后移。
SW 提供了一个 find_offset_SAD
函数,可以计算出这个 SAD 值。然后我们可以用提供的脚本尝试密码爆破:
#def guess_password_SAD(cap_pass_trace, find_offset, ref, original_offset, threshold, target): from PA_SPA_1_answers import guess_password_SAD password = guess_password_SAD(cap_pass_trace, find_offset_SAD, ref, offset1, 5, target)
一分钟不到,我们就成功破解了这个密码了:
Success, password now: h Success, password now: h0 Success, password now: h0p Success, password now: h0px Access granted: password = h0px3
这个爆破的时间效率是 O(N)
的,基本上任何长度的密码都可以轻松破解。
本文初步探讨了如何仅靠简单地观察功耗变化就能分析出 CPU 的运行状态,从而做到像爆破密码之类的攻击。下一次将分享如何进一步分析更复杂的功耗信号,完成更加复杂的攻击,敬请期待。
【预售】物联网安全漏洞实战!离物联网安全研究员只有一个课程的距离!报名满30人开班!
最后于 3小时前 被Invert编辑 ,原因: 错别字