简介
最近入手了ChipWhisperer的CW308 的底板,方便进一步学习侧信道攻击,详见官网介绍:https://rtfm.newae.com/Targets/CW308 UFO/
有了底板之后就可以自从官方的github仓库中 https://github.com/newaetech/chipwhisperer-target-cw308t 获取pcb源码,自己打板制作更多的目标板了,打板pcb可以白嫖嘉立创,每月两次免费打板机会(规则貌似是:如果上月消费<20,那么两次免费打板仅限于立创EDA的工程文件,上月消费>=20,那么免费打板没有这个限制,具体以官方说明为准)
虽然嘉立创免费打板可能要受限于要立创EDA设计,但是基于ChipWhisperer开源的Altium Designer的工程文件和原理图,还是可以自己将AD工程移植为立创EDA工程
CW308T_AVR使用
照着官方的原理图和实物图 https://github.com/newaetech/chipwhisperer-target-cw308t/tree/main/CW308T_AVR,自己用立创EDA也画了个CW308T_AVR,然后白嫖了一波嘉立创。之所以选AVR这个,是因为这个工程足够简单,元器件和导线数就没多少,适合像我这样不懂pcb设计的萌新。此外,还有另外一个原因是:之前用面包板和atmega 328p搭了个arduino最小系统去解一道硬件CTF题,虽然当时成功了,但是抓取的波形属实难看的离谱,想看看用CW308T_AVR去解那道题会有什么效果
下图为官方CW308T_AVR实物图,基本上没多少元器件,也就4个电阻和3个电容还有一个atmega328p
在移植CW308T_AVR过程中,才发现原来atmega328p 3.3v供电也可以正常运行,只不过时钟频率需要变为8mhz,而16mhz对应的为5v电压
在反复查看官方原理图过程中,也终于明白,在给Vcc串联一个电阻后是怎么测电压的,实际上也并没有直接测串联的小电阻两端的电压,而是相当于测atmega328p两端的电压,这样也就避免了因为共地直接测电阻两端电压会导致短路的问题,官方原理图如下
接下来以官方的这个jupyter notebook https://github.com/newaetech/chipwhisperer-jupyter/blob/master/courses/sca101/Lab%205_1%20-%20ChipWhisperer%20CPA%20Attacks%20in%20Practice.ipynb 为例,摸索CW308T_AVR的使用方法
首先是PLATFORM选择问题,经过测试应该选'CW301_AVR',而不是'CW308_AVR'(没有这个)
1
2
3
4SCOPETYPE = 'OPENADC'
PLATFORM = 'CW301_AVR'
CRYPTO_TARGET='TINYAES128C'
SS_VER='SS_VER_1_1'编译方面保持原来的即可
1
2
3%%bash -s "$PLATFORM" "$CRYPTO_TARGET" "$SS_VER"
cd ../../../hardware/victims/firmware/simpleserial-aes
make PLATFORM=$1 CRYPTO_TARGET=$2 SS_VER=$3然后就是第一个坑了,这里不能再选
%run "../../Setup_Scripts/Setup_Generic.ipynb"
,而是应该选%run "../../Setup_Scripts/Setup_Notduino.ipynb"
,原因是Setup_Generic.ipynb中没有关于AVR的配置,如下图实际上Setup_Notduino.ipynb中主要起作用的就是下图部分
接着添加以下代码(也可添加在Setup_Notduino.ipynb中),不然,后面烧录编译后的文件到目标板时,会问题
1
prog = cw.programmers.AVRProgrammer
烧录编译的固件,出现Verified flash OK则为正常
1
2
3# If you need to program - run this
fw_path = '../../../hardware/victims/firmware/simpleserial-aes/simpleserial-aes-{}.hex'.format(PLATFORM)
cw.program_target(scope, prog, fw_path)接着修改,samples数和偏移如下
1
2scope.adc.samples=4000
scope.adc.offset=0动态画图
1
2%run "../../Helper_Scripts/plot.ipynb"
plot = real_time_plot(plot_len=24000)抓取波形
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23import time
from tqdm import tnrange
ktp = cw.ktp.Basic()
#Set your project name here
project = cw.create_project("projects/lab51_examplecpa", overwrite = True)
#Set your number of traces here
num_traces = 250
for i in tnrange(num_traces, desc='Capturing traces'):
key, text = ktp.next() # manual creation of a key, text pair can be substituted here
trace = cw.capture_trace(scope, target, text, key)
if trace is None:
continue
project.traces.append(trace)
#Send every 10th trace?
if i % 10 == 0:
plot.send(trace)
time.sleep(0.1)
project.save()如下图,可以看到,官方的例子,抓取的波形还是很清晰明了的
最后也成功恢复出了AES密钥,实际上这里只需抓取25条能量迹就可以恢复出AES密钥而不用250条
1
2
3
4
5import chipwhisperer.analyzer as cwa
#pick right leakage model for your attack
leak_model = cwa.leakage_models.sbox_output
attack = cwa.cpa(project, leak_model)
results = attack.run(cwa.get_jupyter_callback(attack))用官方例子验证完目标板没有问题后,就尝试用来解之前硬件CTF Rhme-2016的piece_of_scake题,详见:https://github.com/Riscure/Rhme-2016
烧录piece_of_scake固件
1
2
3# If you need to program - run this
fw_path = 'Rhme-2016/challenges/binaries/piece_of_scake/piece_of_scake.hex'
cw.program_target(scope, prog, fw_path)设置串口波特率,貌似piece_of_scake.hex固件中是按16mhz编译的,而实际在ChipWhisperer中,时钟频率为7384615,然后就导致串口波特率还有休眠函数delay变慢了,需要重新计算波特率,公式:7.38mhz/16mhz * orgin_baud
1
target.baud=7384615.384615385/16000000*19200
事实上实际波特率,还可以先烧一个不断往外发串口信息的固件,然后用逻辑分析仪,抓取波形,计算得到。下图为烧录一个以19200波特率不断往外发数据的arduino程序,实际波特率计算方式,寻找间距最小的峰,下图最小间距的峰为112.667us,所以频率为1 / 112.667 * 1e6 = 8875.7 bps
chipwhisperer官方中也有提到类似的问题 https://forum.newae.com/t/cw1173-errortarget-did-not-ack/1757/3
设置好正确波特率后,就可以与atmega328p正常通信了,按找'e'+16个字节数据的方式即可进行加密,这里可以用chipwhipserer的target.write函数来发送串口信息,不过有个问题是chipwhisperer的target.read函数返回的为str类型而不是bytes类型,导致乱码,直接用encode也不能得到正确的bytes数组,所以最后用一个usb转串口来配合串口通信
同样波特率需要重新计算,将usb转串口的Rx、Tx接到CW308T底板上的Rx和Tx即可
1
2
3from serial import Serial
import time
s=Serial('/dev/cu.wchusbserial1410', baudrate=7384615.384615385/16000000*19200)此时,可以正确获得加密后的结果,
这里之所以不用usb转串口发送数据,而是还是使用chipwhisperer发送数据的原因是,usb转串口发送的数据,atmega328p没有返回(原因未知),所以这里一个发一个接收正好互补(原因应该是Rx和Tx接反了,CW308T底板的Rx Tx是atmega328p的Rx Tx而不是cwlite攻击板的Rx Tx,所以正确接法是usb转串口的Rx接CW308T底板的Tx,Tx接Rx)接着设置采样samples数,还有触发信号引脚设为tio4,同时:chipwhisperer中用一根杜邦线将GPIO4(10)和SCK(12)连在一起,这样原因是SCK(12)连接的是atmega328p的19引脚,而之前的分析中,AES加密前会在19引脚有信号输出
1
2
3
4
5scope.adc.samples=24000
scope.clock.adc_src='clkgen_x4'
scope.adc.offset=0
scope.adc.presamples=0
scope.trigger.triggers = "tio4"动态画图
1
2%run "../../Helper_Scripts/plot.ipynb"
plot = real_time_plot(plot_len=24000)仿照官方例子,可以实现以下抓取波形代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36from tqdm import tnrange
import numpy as np
import time
ktp = cw.ktp.Basic()
key, text = ktp.next()
# key=bytes([0]*16)
key=bytes([0xaf, 0x23, 0xd5, 0x45, 0xa0, 0xea, 0xe6, 0xa0, 0x74, 0x65, 0x96, 0xca, 0xce, 0x51, 0xf0, 0xf7])
target.simpleserial_write('k', key)
trace_array = []
textin_array = []
textout_array=[]
proj = cw.create_project("projects/piece_of_scake", overwrite=True)
N = 50
for i in tnrange(N, desc='Capturing traces'):
scope.arm()
target.write(b"e"+bytes(text))
ret = scope.capture()
r=s.read_all()
while len(r)==0:
r=s.read_all()
if ret:
print("Target timed out!")
continue
trace = scope.get_last_trace()
if i % 10 == 0:
plot.send(trace)
print(r.hex())
trace_array.append(trace)
proj.traces.append(cw.common.traces.Trace(trace, text, r,key))
textin_array.append(text)
key, text = ktp.next()
proj.save()效果如下,这里波形中不能很明显看出AES的位置,可能还是时钟频率不一致导致的
按如下更改采样参数后,可以抓到如下的波形图,可以看到有几个差不多的峰形,推测就是AES的每轮循环
1
2scope.adc.samples=24000
scope.clock.adc_src='clkgen_x1'放大第一个峰形状的结果如下,看起来效果还不错
最后按如下修改参数,因为第一个峰结束位置差不多就在6-7000附近,当然保持24000也可,但计算会变慢,而且效果会变差些
1
scope.adc.samples=8000
最后,如下图,可以看到只需很少能量迹,实际上25条就可以恢复出AES密钥,比之前在面包板上的方式(500条)要少得多
总结
- 通过移植CW308T_AVR和使用CW308T_AVR解CTF题过程中,对侧信道攻击有了进一步了解,也对ChipWhisperer的用法有了进一步了解