简介
使用前面学习了那么多侧信道攻击的内容,是时候实战一波了
本文这道题来自https://github.com/Riscure/Rhme-2016的piece_of_scake,下图为这题的提示信息
本文大部分内容基于这个视频中对这道题的解法,很不错的视频,值得一看:https://www.youtube.com/watch?v=FktI4qSjzaE&list=PLhixgUqwRTjwNaT40TqIIagv3b4_bfB7M&index=14
与上面提到的视频不同,这里为了方便起见使用的硬件为用面包板构造的Arduino最小系统,这样做的好处在于接电阻、接线和拆器件方便
最近更新了关于这道CTF题的另一篇文章,可供参考:CW308T-AVR使用笔记
开始
将上一篇中构造的Arduino直接拿过来用,但有个地方需要稍作调整,将一个20Ω的电阻与给Atmega328P 供电的GND串联,用于给ChipWhisperer-Lite测电压
为什么不连Vcc而连GND?因为实际使用中发现貌似无法避免ChipWhisperer-Lite GND与供电端的GND相连,这样相连的情况下把20Ω电阻接Vcc上,再用ChipWhisperer-Lite测电压,就会出现短路,这种情况貌似有个专业术语叫"Ground Loop"
如下图,把ChipWhisperer-Lite当作电压表,把Atmega328P和其它器件整体看成一个大电阻,如果把20Ω接在Vcc端,并且ChipWhisperer-Lite又与Atmega328P共地的情况下,就会使Atmega328P被短路了。实测按下图接法,芯片无法正常工作且20Ω电阻将发烫。
因为找不到好的解决方法,于是改为20Ω电阻接在电源的GND出,更改后的电路示意图,如下图所示,这样就避免了将Atmega328P芯片短路,电阻也不会发烫
下图为实际电路接线图,chipwhisperer-lite用一个sma转鳄鱼夹将一个20Ω来测电阻两端电压(试过用sma转bnc连接示波器的x1探头,但是效果不太行,可能是阻抗的问题)
下图为chipwhipserer-lite中引出来的线,这里只引出来了TIO4(chipwhipserer-lite正面朝上的情况下,20PIN下面的那排左数第3针脚即为TIO4),用于设置触发信号的探针
什么是TIO4?- 这得跟chipwhisperer的官方文档有关,chipwhisperer-lite主要有TIO1、TIO2、TIO3、TIO4这四个针脚可用于设置触发信号(其中TIO1和TIO2默认为Rx和Tx,TIO4默认为触发信号的输入),更多细节请看官方文档https://chipwhisperer.readthedocs.io/en/latest/scope-api.html
如下图所示,chipwhisperer-lite的背面有TIO1、TIO2、TIO3、TIO4的定义,所以只有用万用表量一下,看这4个针脚跟20PIN座子的哪几个针脚相连即可知道,哪个是Rx、哪个是Tx和哪个是触发信号的输入
接下来为Atmega328p刷入硬件CTF的题目,如果搭建的最新系统可以自动Reset并正常通过USB转UART上传程序,那么可以用下面的命令上传(只需修改USB转UART设备)
1
avrdude -v -c arduino -p atmega328p -P /dev/cu.wchusbserial1410 -b115200 -u -V -U flash:w:piece_of_scake.hex
如果不能通过USB转UART上传程序,那么可以用上一篇的Arduino Uno当作ISP编程器,为Atmega328p烧录固件
1
avrdude -p m328p -P /dev/cu.usbmodem14201 -c avrisp -b 19200 -U flash:w:piece_of_scake.hex
上传好固件后,就可以先来测试,这里使用python的pyserial包来使用串口
1
pip install pyserial
跟据题目提示,使用"e"+ 16 bytes即可加密,
如下图所示,设置好选用的设备名称和波特率即可用write和read方法来与芯片进行串口通信,可以看到使用"e"+16个0x01加密返回结果为"5cf473073f0a0940be290224ca495b90"。如果格式不对将没有任何响应
实际使用过程中可以发现连接在Atmega329P的19引脚和22引脚的二极管会在正确发送指令后亮一下,因此可以用chipwhipserer的TIO4与Atmega329P19引脚相连来作为触发信号的输入
设置好触发信号后,就可以开始写代码,用chipwhipserer-lite来抓取能量迹,正式开始之前,建议把面包板上的电容都去掉(因为电容有滤波效果,所以会影响抓取的能量迹波形)。而且建议给Arduino最小系统独立供电(串联的电阻较大时,靠笔记本供电,会电压不足),笔记本电脑拔掉充电头(电脑充电状态会有杂峰影响)
接下来照猫画虎,将之前侧信道攻击学习的前面部分的代码复制过来,并稍作修改,如下图所示,这里还是使用USB转UART作为串口通信(虽然chipwhisperer-lite有Rx和Tx,但是chipwhisperer-lite是3.3v的,而atmega328p是5v的,所以两者不能直接通信)
1
2
3
4
5
6
7
8
9
10
11
12import serial
SCOPE="OPENADC"
PLATFORM="CWLITEXMEGA"
CRYPTO_TARGET="TINYAES128C"
VERSION="HARDWARE"
%run "../../Setup_Scripts/Setup_Generic.ipynb"
s=serial.Serial('/dev/cu.wchusbserial1410', 19200, timeout=0.5)
proj = cw.create_project("test", overwrite=True)接着设置chipwhisperer-lite的触发信号输入引脚,触发模式,采样频率等,具体可查看官方文档,下面的设置为使用TIO4引脚作为触发信号,单次采样5000个点的数据,还有采样频率"clkgen_x4"(如果为"clkgen_x1",那么将会用更低的频率采样)
1
2
3
4
5
6
7
8
9
10
11# scope.adc.basic_mode = "falling_edge"
# scope.adc.basic_mode = "high"
scope.adc.samples = 5000
scope.clock.adc_src = "clkgen_x4"
scope.trigger.triggers = "tio4"
scope.adc.offset=0
scope.adc.presamples=0接着是,开始抓取能量迹,第一个key可以随意设置(主要用于后面画图高亮显示,如果一开始不知道真正key可以用16个00代替)
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
36
37
38
39import chipwhisperer
from tqdm import tnrange
import numpy as np
import time
reset_target(scope)
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=[]
N = 500
for i in tnrange(N, desc='Capturing traces'):
scope.arm()
s.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()
print(r.hex())
trace_array.append(trace)
proj.traces.append(chipwhisperer.common.traces.Trace(trace, text, r,key))
textin_array.append(text)
key, text = ktp.next()
proj.save()抓完能量迹后,可以话出一条能量迹,查看效果,如下图所示
1
2
3
4
5
6%matplotlib notebook
import matplotlib.pylab as plt
plt.figure()
plt.plot(trace_array[0])
plt.show()最后就是执行CPA攻击,可以看到最终结果为"0xaf, 0x23, 0xd5, 0x45, 0xa0, 0xea, 0xe6, 0xa0, 0x74, 0x65, 0x96, 0xca, 0xce, 0x51, 0xf0, 0xf7"
1
2
3
4
5
6
7import chipwhisperer as cw
import chipwhisperer.analyzer as cwa
leak_model = cwa.leakage_models.sbox_output
attack = cwa.cpa(proj, leak_model)
cb = cwa.get_jupyter_callback(attack)
results = attack.run(cb, 10)验证密钥是否正确,如下图所示,使用标准AES,用侧信道攻击得到的密钥,加密同样的数据[1]*16得到的结果同样为"5cf473073f0a0940be290224ca495b90",说明"0xaf, 0x23, 0xd5, 0x45, 0xa0, 0xea, 0xe6, 0xa0, 0x74, 0x65, 0x96, 0xca, 0xce, 0x51, 0xf0, 0xf7"是正确的AES密钥
1
2
3
4
5
6
7from Crypto.Cipher import AES
key=bytes([0xaf, 0x23, 0xd5, 0x45, 0xa0, 0xea, 0xe6, 0xa0, 0x74, 0x65, 0x96, 0xca, 0xce, 0x51, 0xf0, 0xf7])
aes=AES.new(key, AES.MODE_ECB)
data=bytes([1]*16)
aes.encrypt(data).hex()至此成功利用侧信道攻击解决了这道CTF
其他
如果不去掉电容会怎样?- 如下图,抓取的波形的振幅会减小,恢复正确的密钥需要的能量迹更多,去掉电容只需200条左右能量迹即可恢复正确密钥,但不去掉密钥则需要500条左右甚至更多
为什么串联20Ω大小的阻值,其它大小可以吗?- 理论上可以,试过1Ω的电阻(但效果不行),试过50Ω(分压太明显,需要输入更多的电压,大概6.8v输入才能稳定正常工作,20Ω则只需5.5v输入)
总结
- 利用侧信道攻击成功解了一道硬件CTF题
- 通过自己构建电路,才知道实施一次侧信道攻击不是一件容易的事