0%

CW308T-AVR使用笔记

简介

  • 最近入手了ChipWhisperer的CW308 的底板,方便进一步学习侧信道攻击,详见官网介绍:https://rtfm.newae.com/Targets/CW308 UFO/

    image-20230416125221810

  • 有了底板之后就可以自从官方的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

  • 在移植CW308T_AVR过程中,才发现原来atmega328p 3.3v供电也可以正常运行,只不过时钟频率需要变为8mhz,而16mhz对应的为5v电压

  • 在反复查看官方原理图过程中,也终于明白,在给Vcc串联一个电阻后是怎么测电压的,实际上也并没有直接测串联的小电阻两端的电压,而是相当于测atmega328p两端的电压,这样也就避免了因为共地直接测电阻两端电压会导致短路的问题,官方原理图如下

    image-20230416140330629

    image-20230416140359733

  • 接下来以官方的这个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
    4
    SCOPETYPE = '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的配置,如下图

    image-20230416141803733

  • 实际上Setup_Notduino.ipynb中主要起作用的就是下图部分

    image-20230416141904996

  • 接着添加以下代码(也可添加在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)

    image-20230416142618454

  • 接着修改,samples数和偏移如下

    1
    2
    scope.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
    23
    import 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()
  • 如下图,可以看到,官方的例子,抓取的波形还是很清晰明了的

    image-20230416143436456

  • 最后也成功恢复出了AES密钥,实际上这里只需抓取25条能量迹就可以恢复出AES密钥而不用250条

    1
    2
    3
    4
    5
    import 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))

    image-20230416143610401

  • 用官方例子验证完目标板没有问题后,就尝试用来解之前硬件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)

    image-20230416145625000

  • 设置串口波特率,貌似piece_of_scake.hex固件中是按16mhz编译的,而实际在ChipWhisperer中,时钟频率为7384615,然后就导致串口波特率还有休眠函数delay变慢了,需要重新计算波特率,公式:7.38mhz/16mhz * orgin_baud

    1
    target.baud=7384615.384615385/16000000*19200

    image-20230416145654325

  • 事实上实际波特率,还可以先烧一个不断往外发串口信息的固件,然后用逻辑分析仪,抓取波形,计算得到。下图为烧录一个以19200波特率不断往外发数据的arduino程序,实际波特率计算方式,寻找间距最小的峰,下图最小间距的峰为112.667us,所以频率为1 / 112.667 * 1e6 = 8875.7 bps

    image-20230416150445490

  • chipwhisperer官方中也有提到类似的问题 https://forum.newae.com/t/cw1173-errortarget-did-not-ack/1757/3

    image-20230416151249144

  • 设置好正确波特率后,就可以与atmega328p正常通信了,按找'e'+16个字节数据的方式即可进行加密,这里可以用chipwhipserer的target.write函数来发送串口信息,不过有个问题是chipwhisperer的target.read函数返回的为str类型而不是bytes类型,导致乱码,直接用encode也不能得到正确的bytes数组,所以最后用一个usb转串口来配合串口通信

    image-20230416151734753

  • 同样波特率需要重新计算,将usb转串口的Rx、Tx接到CW308T底板上的Rx和Tx即可

    1
    2
    3
    from serial import Serial
    import time
    s=Serial('/dev/cu.wchusbserial1410', baudrate=7384615.384615385/16000000*19200)

    image-20230416152210307

  • 此时,可以正确获得加密后的结果,这里之所以不用usb转串口发送数据,而是还是使用chipwhisperer发送数据的原因是,usb转串口发送的数据,atmega328p没有返回(原因未知),所以这里一个发一个接收正好互补(原因应该是Rx和Tx接反了,CW308T底板的Rx Tx是atmega328p的Rx Tx而不是cwlite攻击板的Rx Tx,所以正确接法是usb转串口的Rx接CW308T底板的Tx,Tx接Rx)

    image-20230416152742451

  • 接着设置采样samples数,还有触发信号引脚设为tio4,同时:chipwhisperer中用一根杜邦线将GPIO4(10)和SCK(12)连在一起,这样原因是SCK(12)连接的是atmega328p的19引脚,而之前的分析中,AES加密前会在19引脚有信号输出

    1
    2
    3
    4
    5
    scope.adc.samples=24000
    scope.clock.adc_src='clkgen_x4'
    scope.adc.offset=0
    scope.adc.presamples=0
    scope.trigger.triggers = "tio4"

    image-20230416153251978

  • 动态画图

    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
    36
    from 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的位置,可能还是时钟频率不一致导致的

    image-20230416153741880

  • 按如下更改采样参数后,可以抓到如下的波形图,可以看到有几个差不多的峰形,推测就是AES的每轮循环

    1
    2
    scope.adc.samples=24000
    scope.clock.adc_src='clkgen_x1'

    image-20230416155013977

  • 放大第一个峰形状的结果如下,看起来效果还不错

    image-20230416155052561

  • 最后按如下修改参数,因为第一个峰结束位置差不多就在6-7000附近,当然保持24000也可,但计算会变慢,而且效果会变差些

    1
    scope.adc.samples=8000

    image-20230416155700555

  • 最后,如下图,可以看到只需很少能量迹,实际上25条就可以恢复出AES密钥,比之前在面包板上的方式(500条)要少得多

    image-20230416155631724

总结

  • 通过移植CW308T_AVR和使用CW308T_AVR解CTF题过程中,对侧信道攻击有了进一步了解,也对ChipWhisperer的用法有了进一步了解