본문 바로가기
연구

Introduction to Glitching Attack, 글리칭 어택이란?

by jskimm 2022. 1. 10.
728x90

Introduction to Glitching Attack

이번에 소개해드릴 기법은 하드웨어 해킹 기법 중 물리적인 전기 신호를 주입하여 오류를 발생시키고 이를 통해 Side Effect를 유도하는 공격 기법인 Glitching Attack 입니다. 이 문서에서는 Glitching Attack 이라는 기법의 종류와 이 공격이 어떻게 가능하고, 또 어떤 효과를 불러일으키는지를 중점으로 설명하겠습니다.

Blackhat 2015에서 Implementing Practical Electrical Glitching Attacks 를 발표한 NCC group의 Brett Giller는 Glitching attack 의 종류를 다음과 같이 정의했습니다.

Giller는 Glitching attack을 크게 2가지, 비침습성(Noninvasive), 침습성(Invasive) 로 나누었는데요. 이 둘 사이의 차이는 침습성, 즉 공격을 위해 IC 칩을 떼는 등의 '물리적인 힘을 가해야 하는가, 혹은 그럴 필요가 없는가'에 있습니다. 침습적인 공격은 IC 칩에 대한 수정, 혹은 칩을 물리적으로 분리하는 작업이 필요하여 비교적 난이도가 높고 시간도 더 오래 걸릴 뿐더러 값 비싼 장비를 요구합니다. 반면에 비침습적인 Glitching attack은 말 그대로 IC 칩에 최소한의 데미지를 줄 수 있고 구현이 비교적 간단합니다.

따라서 오늘은 좀 더 쉽고 유용하게 사용할 수 있는 비침습성을 가진 공격 중 가장 대중적인 Clock Glitching 공격에 대해 알아보고 원리를 이해하는 시간을 가져봅시다 😉

First Draft of a Report on the EDVAC

에드박의 보고서 초안에 최초로 서술된 폰 노이만 구조(Von Neumann architecture)는 현재와 같은 CPU, Memory, 프로그램 구조를 갖는 범용 컴퓨터 구조를 확립했습니다. 폰 노이만 구조는 데이터 메모리와 프로그램 메모리가 구분되어 있지 않고 하나의 버스만을 가지는, CPU와 한 개의 메모리 유닛만을 사용하여 명령어를 처리하는 구조입니다. 그 이후부터 현재까지 나온 컴퓨터가 모두 폰 노이만 구조를 채택하여 디자인되고 있습니다.

폰 노이만 구조를 따른 머신은 Fetch, Decode, Execute, Store로 총 4개의 명령 주기를 갖습니다. 부팅 후 시동이 꺼질 때까지 CPU가 메모리로부터 한 개의 명령어를 가져와(Fecth) 어떠한 동작을 요구하는지를 결정하고(Decode) 명령어가 요구하는 동작들을 수행(Execute)하는 작업을 반복하기 때문에 이를 CPU 사이클 이라고 부르기도 합니다.

기계어 명령어 하나를 실행하기 위해서는 최대 4개의 단계가 필요합니다. 각 단계는 CPU의 클럭 신호에 동기되어 동작합니다. 각 단계는 각각 몇개의 클럭 펄스가 필요하며, 필요한 펄스는 각 마이크로프로세서마다 다릅니다. 하나의 명령어를 실행하기 위해 단계적으로 나뉜 과정이 한 사이클이 됩니다.

여기서 잠깐, 저희는 Glitching을 이해하기 앞서 '각 단계는 CPU의 클럭 신호에 동기되어 동작된다는 말'에 주의를 기울일 필요가 있습니다.

Into the Clock Pulse

클럭 신호(Clock Pulse)는 논리 상태 H(high, logically 1)와 L(low, logically 0)이 주기적으로 나타나는 방형파(square wave) 신호를 말합니다. 중요한 것은, 앞서 말씀드린대로 명령어 실행 주기의 각 단계가 CPU의 클럭 신호에 동기되어 동작한다는 점입니다. 클럭 자체가 각 단계의 주기를 뜻하기 때문에 클럭을 CPU의 산술 속도를 나타내는 단위로(Hz) 사용하곤 합니다.

저희가 주로 사용하는 PC의 CPU 뿐 만 아니라, 임베디드 시스템에서 주로 사용하는 MPU(Micro Processing Unit), MCU(Micro Controller Unit) 역시 이 폰 노이만 구조를 기반으로 디자인 되었기 때문에 클럭이라는 단위를 기준으로 명령을 실행합니다. 클럭이 High 일 때 명령어 실행 주기의 각 단계를 실행하는 방식으로 말이죠.

조금만 더 자세히 들어가 봅시다. 아래 보이는 그림은 ATmega 아키텍쳐의 AVR ATMega328P 칩의 Datasheet입니다.

http://ww1.microchip.com/downloads/en/DeviceDoc/Atmel-7810-Automotive-Microcontrollers-ATmega328P_Datasheet.pdf

마찬가지로 명령어를 Memory 로부터 읽고(fetch) 어떠한 동작을 요구하는지 결정하고(Decode) 실행(execute)하는 순서를 따릅니다. 그러나 모든 명령어를 한 번에 fetch 하여 실행하는 것이 아니라, 하나의 파이프라인을 만들어 명령어를 하나씩 차례로 읽고 실행합니다. (초기의 마이크로프로세서는 한개의 기계어 명령어의 명령 주기가 완료될 때까지 다음 명령을 읽지 않았으나 발전과정에서 속도를 높이기 위해 한개의 명령어가 완료되기 전에 다음 명령어를 읽는 prefetch를 진행함)

따라서 프로그램을 실행하면, 명령어가 Memory 로부터 차례대로 fetch된 후, 이 명령어가 디코딩되고 실행됨과 동시에 다음에 실행할 명령어를 미리 읽어들이는 식(prefecth)으로 동작하게 됩니다. 그리고 이것은 CPU의 클럭 단위로 이루어집니다.

How Glitching works?

폰 노이만 구조를 채택한 일반적인 마이크로프로세서의 동작 과정은 위와 같습니다. 클럭이 H일 때, 명령어 실행주기가 반복되며 프로그램을 실행합니다.

만약 여기서 저희가 칩셋의 클럭을 임의로 조작할 수 있게 된다면 어떻게 될까요?

예를 들어, Load #1과 Load #2 사이에 특정한 전압을 주어 클럭을 조작했다고 가정해봅시다. CPU는 기존의 #1과 #2 사이에 새로이 생긴 클럭에서 파이프라인을 실행시키려 할 것입니다. 하지만 두 번째 파이프라인에서 명령어가 디코딩되고 실행될 수 있는 충분한 시간이 보장되지 않는다면? 바로 'Load #2, Execute #1' 부분을 건너 뛰는, 즉 #1 번 째 명령어의 실행을 효과적으로 건너뛸 수 있게 되겠죠.

Go Deeper

과연 몇 개의 명령어를 건너뛰는 행위로 무슨 일을 할 수 있을까요? 좀 더 자세한 예를 들어보겠습니다.

대부분의 부트로더는 펌웨어 변조를 막기위한 여러 수단을 제공하는데, 예를 들어 다음과 같이 부트로더가 구현되어 있다고 가정해봅시다.

intialize_device()
bool isValid = verify_firmware()
if(!isValid) {
    exit()
}
bootm()

initialize_device()를 통해 주변 장치를 초기화하고 verify_firmware() 함수를 통해 펌웨어가 변조되었는지 검사하는 코드가 존재합니다. 펌웨어가 변조되어 있다면 아마도 아래의 조건문을 통해 exit() 가 실행되어 정상적으로 부팅이 되지 않겠죠?

만약 조건 분기 명령어가 실행될 때, glitching 을 통해 verify_firmware 함수가 실행된 직후의 클럭을 의도적으로 짧게 추가할 수 있다면 과연 어떤 일이 일어날까요?

바로 isValid 의 값을 체크하는 분기 명령어를 건너 뛸 수 있게 되어, 저희는 펌웨어의 무결성을 체크하는 verify_firmware 함수를 성공적으로 우회하고 변조된 펌웨어로 부팅할 수 있겠죠!

Case study #1: Breaking ARM Trustzone for Cortex-M

또 다른 예제를 들어보겠습니다. ARM 사에서 제공하는 Trustzone은 Non-Secure world와 Secure world 개념을 도입하고, 각각의 레지스터를 독립적으로 구분해 운영체제의 무결성을 유지할 수 있도록 돕는 기술입니다. 그 중 Cortex-M 칩셋은 기존 Trustzone 의 한 단계 심플한 버전인 Trustzone for Cortex-M을 제공하는데, 그 루틴은 대략 다음과 같습니다.

intialize_device()
enable_trustzone()
bootm()

TrustZone이 활성화 되면 암호화에 이용되는 키나 로직들을 Secure world에서 구동시킬 수 있는데요. 해당 영역에 존재하는 값들은 Non-Secure world에서 접근하려고 시도하면 exception이 발생하게 됩니다.

하지만 여기서 키포인트는 다음과 같습니다.

ARM Trustzone에서 해당 영역이 Secure 영역인지, Non-Secure (Normal) 영역인지는 ARM processor의 내부 유닛인 IDAU(Implementation Defined Attribution Unit)와 SAU(Secure Attribution Unit) 를 통해 정해집니다. IDAU는 DAU(Device Attribution Unit)와 연결하여 향상된 세부 정보를 제공하며, SAU와 함께 특정 주소의 보안 특성을 결정합니다.

모든 칩셋마다 Trustzone을 구현한 방식이 다를 수 있어, nuvoton numicro m2351 라는 칩셋을 예로 들어보겠습니다. 해당 칩셋도 마찬가지로 SAU와 IDAU를 통해 Secure와 Non-secure 영역을 결정하는데, 해당 칩셋의 enable_trustzone 로직 내부에 다음과 같은 부분이 존재합니다.

line 308 을 보시면 SAU→CTRL = 1 라는 코드가 존재합니다. 만약 우리가 IF 조건분기문을 Glitch해서 건너 뛰게 할 수 있다면, SAU가 ENABLE 될 것이고, 그로 인해 Application Memory가 secure / non-secure 영역으로 나뉘는 게 아닌 모든 영역이 Secure 영역으로 존재하게 될 수 있게 됩니다. 따라서 저희가 작성한 코드가 Secure world에서 동작할 수 있게 되는 것이죠. 결과적으로 저희는 Glitching attack을 통해 secure world 에 있는 암호화 키에 접근하거나, 운영체제 혹은 펌웨어를 변조할 수 있게 됩니다.

Thomas Roth는 이 방법을 통해 성공적으로 Trustzone을 우회하는데 성공했고, 더 자세한 내용은 2019년 12월에 열린 36c3 Conference 에서 진행된 발표를 참고해주세요.

https://media.ccc.de/v/36c3-10859-trustzone-m_eh_breaking_armv8-m_s_security

So... are every chipsets glitchable?

그래서 모든 칩셋은 glitching attack 으로부터 자유로울 수 없을까요? 아닙니다. 해당 공격을 성공시키기 위해서는 상당히 많은 노력과 시간이 필요한 것은 물론, 굉장히 구체적인 공격 시나리오가 필요합니다. 칩셋 또한 코드를 커스텀하여 사용하는 경우도 많고, 예외 처리만 잘만 한다면 glitching이 발생해도 정상적으로 해당 분기를 처리할 수 있습니다. 또한 직접 클럭이나 전압을 바꿔가며 glitching 하다보면 보드가 금방 죽기도 해서 직접 해보면 난이도가 꽤 높았습니다.. 😢

물론 하드웨어를 개발하는 개발자 분들은 필히 본인이 개발 중인 보드가 glitchable한지 반드시 확인하고, 그에 따른 대응을 해야 하겠지만요..!

728x90

'연구' 카테고리의 다른 글

[CVE-2017-5638] Apache Struts2 RCE 분석  (2) 2022.01.10

댓글