graf
Fault2.1 Introduction to Voltage Glitching 본문
1. 개요
그동안은 모두 클럭 글리칭을 사용했다.
이는 결함을 정밀하게 발생시킬 수 있다는 장점이 있긴 하지만
최근 나오는 대부분의 장비들은 pll이 고속 클럭을 다 씹는다고 알고 있다.
클럭 글리칭은 외부 클럭을 주입할 수 있을 때 활용 가능하기 때문에
대상 장치가 내부 클럭을 사용하거나 pll을 쓴다면 활용이 어려워진다.

이번에 시도할 것은 볼트 글리칭이다.
클럭 방식만큼 정밀하진 않지만 훨씬 범용적으로 활용 가능하다.
내가 계획 중인 것도 이 쪽이다.
scope.io.glitch_hp = True #enable HP glitch
scope.io.glitch_hp = False #disable LP glitch
scope.io.glitch_lp = True #enable LP glitch
scope.io.glitch_lp = False #disable LP glitch
볼트 글리칭은 hp와 lp 두가지를 사용할 수 있다.
강한 글리치가 필요할 경우 두가지를 모두 활성화해야한다.

glitch_only - 클럭 글리칭과 유사하게 width와 offset으로 글리칭을 정밀하게 조절 가능하다.
enable_only - 하나의 클럭 사이클 전체를 글리치로 넣는다. 대부분의 경우 글리치가 너무 강하게 작용한다. 대상 장치가 내부 클럭을 사용하거나 장치의 클럭과 비동기적으로 글리치를 만들어야 하는 경우 유용하다.
enable_only로 할 경우 repeat이 width의 역할을 하고, ext_offset이 offset 역할을 한다.
enable_only를 쓰더라도 허스키의 클럭을 가속해서 충분히 정밀한 글리치를 넣을 수 있다는 내용이다.
가능하다면 glitch_only가 훨씬 정밀하겠지만
트리거만 잡을 수 있고 클럭 동기화가 어려운 상황이라면 enable_only가 맞겠다.
대신 지터가 커지고 재현성이 떨어지는 건 감수를 해야할 거다.
나중에 프로젝트할 때는 클럭을 동기화할 방법이 있는지 먼저 알아봐야겠다.

클럭 글리칭과 볼트 글리칭의 차이점을 이야기한다.
디커플링 캐패시터같이 전압의 변화를 방해하는 외부 요소들이 많아서
파라미터 뿐만 아니라 회로 설계에 따라서도 결과가 변할 수 있다는 내용이다.
예전에 공부했던 내용에서는 글리치를 넣는 케이블 길이에 따라서도
임피던스가 변해서 결과가 달라질 수 있다고 했던 것으로 기억한다.
2. 통신 테스트
펌웨어는 Fault1.1에서 쓴 것과 동일하다.
이번에도 글리칭을 테스트해볼 목적으로 간단한 반복문을 돌린다.
먼저 통신 테스트를 해보자.
reboot_flush()
scope.arm()
target.simpleserial_write("g", bytearray([]))
scope.capture()
val = target.simpleserial_read_witherrors('r', 4, glitch_timeout=10)#For loop check
valid = val['valid']
if valid:
response = val['payload']
raw_serial = val['full_response']
error_code = val['rv']
print(val)

valid도 True로 떴고 결과도 c4 09이다.
제대로 넘어왔다.
scope.clock.clkgen_src = "system"
scope.clock.adc_mul = 1
scope.adc.samples = 1000 * scope.clock.adc_mul
scope.adc.offset = 0 * scope.clock.adc_mul
print(scope.adc.trig_count)
cw.plot(scope.get_last_trace()[300:500])
트레이스도 캡쳐를 해봤다.

이게 펌웨어 루프에 대한 전력 캡쳐다.
9개의 루프를 도는 동안 189 클럭이 들어갔다.
루프 하나에 21 클럭 정도로 생각하고 있으면 되겠다.

트리거가 올라가있는 동안 샘플은 5만 3천개가 모였다.
adc_mul이 1인데도 5만이니 정말 널널한 셈이다.
21클럭으로 2500번 도니까 5만 3천이면 거의 정확하다.
3. 글리칭 테스트
gc.set_range("offset", 1000, scope.glitch.phase_shift_steps)
gc.set_range("width", 1000, scope.glitch.phase_shift_steps//2)
gc.set_range("ext_offset", 11000, 11000)
gc.set_range("tries", 1, 1)
gc.set_global_step([1])
gc.set_step("offset", 100)
gc.set_step("width", 100)
gc.set_step("ext_offset", 1)
gc.set_step("tries", 1)
scope.glitch.repeat = 1
파라미터는 무난하게 잡았다.
ext_offset은 고정해놨다.
지금은 ext를 탐색할 때가 아니다.
먼저 적절한 수준의 결함이 발생하는 width와 offset을 찾아야한다.
내가 이해한게 맞다면 이번엔 클럭 때랑 다르게 width가 훨씬 영향력이 클 것이다.
먼저 순회하도록 width를 두번째에 넣었다.
scope.clock.clkgen_src = "system"
scope.clock.adc_mul = 1
scope.adc.samples = 2000 * scope.clock.adc_mul
scope.adc.offset = 10000 * scope.clock.adc_mul
11000 클럭에 글리치를 넣을 예정이라
1만 클럭 이후부터 2천 클럭 동안 캡쳐를 진행했다.

파형을 확인해봤다.
1000 언저리에서 전압이 크게 튄다.
이제 파라미터를 좀 더 순회해보자.
gc.set_range("offset", 1000, scope.glitch.phase_shift_steps)
gc.set_range("width", 1000, scope.glitch.phase_shift_steps//2)
gc.set_range("ext_offset", 11000, 11100)
gc.set_range("tries", 1, 1)
gc.set_global_step([1])
gc.set_step("offset", 100)
gc.set_step("width", 100)
gc.set_step("ext_offset", 1)
gc.set_step("tries", 1)
scope.glitch.repeat = 1
보드 손상 방지를 위해 width는 최대값의 절반까지만 간다.

리셋은 width가 2000일 때부터 뜨기 시작했다.

근데 이건 너무 강한 글리치가 들어간 것 같다.

width를 좁히고 다시 돌려본 결과
1933에서부터 리셋이 발생했다.

근데 결과는 이번에도 전부 다 invalid로 넘어왔다.

확인해보니 그냥 타겟 보드가 죽은 모양이다.

계속 다시 확인해보고 있는데
이런 식으로 한번 글리치가 들어가고 나면

이렇게 정상 요청에도 타겟 보드가 응답을 제대로 못 한다.
def reset_target(scope):
scope.io.nrst = 'low'
time.sleep(0.5)
scope.io.nrst = 'high_z'
time.sleep(0.5)
리셋이 제대로 안 들어가는 것 같아서 함수를 수정했다.
대기 시간을 조금 더 길게 잡아줬다.
4. 글리칭 주입
미치는 줄 알았다.
글에서는 티가 안 나겠지만 이미 하루를 통째로 날렸다.
위에 언급한 것처럼 리셋이 안 들어가는 것도 문제였는데
글리치를 반복하다보면 허스키랑 통신이 끊어지기도 하고
normal만 떠서 트레이스를 확인해보면 글리치 자체가 생성이 안되고 있기도 하고
어쩔 땐 글리치가 멈추지 않아서 연결을 몇번이나 다시 해야했다.
단순한 글리치 하나만 넣으면 되는 건데 예상 못한 오류가 너무 많이 발생했다.
하드웨어를 다룰 때마다 느끼는 거지만 뭐 하나 쉽게 가는 법이 없다.
그래도 일단 성공은 했다.
결과를 정리해보자.
scope.glitch.enabled = True
scope.glitch.clk_src = "pll"
scope.io.glitch_hp = True
scope.io.glitch_lp = False
scope.glitch.output = "glitch_only" # glitch_out = clk ^ glitch
scope.glitch.trigger_src = "ext_single" # glitch only after scope.arm() called
처음에 hp와 lp를 모두 올리고 했었는데
글리치가 너무 강하게 들어가는 것 같아 lp는 false로 설정했다.
gc.set_range("offset", 2600, 2600)
gc.set_range("width", 1800, 2500)
gc.set_range("ext_offset", 11000, 11100)
gc.set_range("tries", 1, 1)
gc.set_global_step([1])
gc.set_step("offset", 500)
gc.set_step("width", 100)
gc.set_step("ext_offset", 1)
gc.set_step("tries", 1)
scope.glitch.repeat = 2
파라미터는 계속 조금씩 돌려보면서 하나씩 맞췄다.
무조건 이 값으로 고정할 필요는 없다.
어차피 똑같은 루프를 반복하니 ext는 100 정도만 돌게 했다.
import struct
import numpy as np
#disable logging
cw.set_all_log_levels(cw.logging.CRITICAL)
scope.adc.timeout = 0.5
for glitch_setting in gc.glitch_values():
#reboot_flush()
scope.glitch.offset = glitch_setting[1]
scope.glitch.width = glitch_setting[0]
scope.glitch.ext_offset = glitch_setting[2]
if scope.adc.state:
# can detect crash here (fast) before timing out (slow)
print("Trigger still high!")
gc.add("reset", (scope.glitch.width, scope.glitch.offset, scope.glitch.ext_offset, 1))
#Device is slow to boot?
reboot_flush()
scope.arm()
#Do glitch loop
target.simpleserial_write("g", bytearray([]))
ret = scope.capture()
# 최소 피크 출력. 너무 높게 나오면 글리치가 안 들어가고 있는 상황.
trace = scope.get_last_trace()
peak_value = np.min(trace)
if peak_value > -0.12 :
print(peak_value)
print("glitch error!")
scope.io.vglitch_reset()
if ret:
print('Timeout - no trigger')
gc.add("reset", (scope.glitch.width, scope.glitch.offset, scope.glitch.ext_offset, 1))
reboot_flush()
else:
val = target.simpleserial_read_witherrors('r', 4, glitch_timeout=10, timeout=50)#For loop check
if val['valid'] is False:
gc.add("reset", (scope.glitch.width, scope.glitch.offset, scope.glitch.ext_offset, 1))
reboot_flush()
print(val)
else:
gcnt = struct.unpack("<I", val['payload'])[0]
if gcnt != 2500: #for loop check
gc.add("success", (scope.glitch.width, scope.glitch.offset, scope.glitch.ext_offset, 1))
print((scope.glitch.width, scope.glitch.offset, scope.glitch.ext_offset))
reboot_flush()
else:
#print("normal", end="\r")
gc.add("normal", (scope.glitch.width, scope.glitch.offset, scope.glitch.ext_offset, 1))
#enable logging
cw.set_all_log_levels(cw.logging.WARNING)
이번에도 기록용 코드 전문이다.
trace = scope.get_last_trace()
peak_value = np.min(trace)
if peak_value > -0.12 :
print(peak_value)
print("glitch error!")
가장 중요한 부분은 이거다.
클럭 글리칭을 시도할 때는 고려할 필요가 없던 오류다.
루프를 여러번 하다보면 허스키에 문제가 생기는 건지
글리치가 아예 안 만들어질 때가 있어서 상태을 확인 가능하도록 추가했다.
scope.clock.clkgen_src = "system"
scope.clock.adc_mul = 1
scope.adc.samples = 2000 * scope.clock.adc_mul
scope.adc.offset = 10000 * scope.clock.adc_mul

파형 캡쳐 기준이다.
글리치는 잘 확인된다.
gc = cw.GlitchController(groups=["success", "reset", "normal"], parameters=["offset", "width", "ext_offset", "tries"])
gc.display_stats()
gc.glitch_plot(plotdots={"success":"+g", "reset":"xr", "normal":None}, x_index="width", y_index="ext_offset")

위 코드대로 시도한 글리칭 결과다.
성공한 파라미터들을 보면 ext_offset의 간격이 대부분 21클럭으로
앞서 확인했던 루프의 간격과 거의 일치한다.
gc.set_range("offset", 2600, 2600)
gc.set_range("width", 2300, 3000)
gc.set_range("ext_offset", 11000, 11100)
gc.set_range("tries", 1, 1)
gc.set_global_step([1])
gc.set_step("offset", 500)
gc.set_step("width", 10)
gc.set_step("ext_offset", 1)
gc.set_step("tries", 1)
scope.glitch.repeat = 2


success가 더 잘 잡히는 쪽으로 파라미터를 바꿔봤다.
width를 3000까지 넣었는데 2700 이후부터는 성공이 거의 안 떴다.
여기서 반대로 width가 작아지면 리셋이 뜬다.
주입되는 길이에 따라 발생시키는 오류의 종류가 달라지기 때문으로 추정한다.
클럭 글리칭 때랑 동일한 펌웨어라서 설정만 좀 바꾸면 될 줄 알았는데
생각보다 힘들었다..
'ChipWhisperer > GlitchingTutorial' 카테고리의 다른 글
| Fault2.3 Voltage Glitching to Memory Dump (0) | 2026.04.12 |
|---|---|
| Fault2.2 Voltage Glitching to Bypass Password (0) | 2026.04.11 |
| Fault1.4 Authenticated AES Bootloader-2 (0) | 2026.03.29 |
| Fault1.4 Authenticated AES Bootloader-1 (0) | 2026.03.23 |
| Fault1.3 Clock Glitching to Memory Dump (0) | 2026.03.22 |