제 개인 블로그에 올린 글을 제가 링크를 걸어놓았는데,
아무래도 클리앙에 직접 글을 올리는게 더 좋을 것 같아 핵심글을 여기서 직접 다시 작성했습니다.
Spectre
스펙터 공격의 핵심 코드는 다음과 같습니다.
악명높은 스펙터 공격의 핵심 코드는 생각보다 단순합니다. 각 요소의 설명은 다음과 같습니다.

위 코드를 포함한 전체 스펙터 공격이 어떻게 동작하는지 요약하자면 다음과 같습니다.
A. 워밍업: if문 안의 조건이 참이 되도록 x를 세팅한다. 코드를 여러번 반복시켜 CPU가 조건문이 참이 될 것이라 예상하도록 만든다.
B. 공격시작: if문 안의 조건문이 거짓이 되면서 array1[x]가 타겟 프로그램의 메모리 주소를 향하도록 x값을 설정한다.
C. 프로그램의 논리대로라면, if문 안의 조건이 거짓이기 때문에 CPU가 두번째 줄 명령어를 실행하지 않아야 한다.
D. 그러나 여기서 CPU는 내부적으로는 두번째 줄 명령어를 미리 실행한다 (Branch Prediction 기법 때문에). 다만 조건문이 거짓이므로 결과를 실제로 y에 저장하지는 않는다.
E. D에서의 명령어 실행이 array1[x] 값을 읽어왔기 때문에 array1[x]의 값이 캐쉬 메모리에 올라가게 된다 (후에 다시 설명). 이 때문에 Flush+Reload 기법을 사용하면 해당 값을 알 수 있게 된다.
아래에서는 스펙터 공격을 가능케 하는 Branch prediction과 Flush+Reload 기법에 대한 보충설명입니다.
Branch Prediction
스펙터 공격은 Branch Prediction의 허점을 이용합니다. 해당 기법은 현대 CPU에서는 성능향상을 위해 대부분의 고성능 CPU에서 사용하는 기법입니다.
배경을 잠시 설명하자면, 1900년대 후반에 CPU성능이 발전함에 따라 CPU들은 한 사이클 (CPU 시간의 단위) 에 여러개의 명령어를 처리할 수 있게 되었습니다. 그런데 한 가지 한계점이 발생했습니다.

위 코드에서 if 문 안의 조건은 참 혹은 거짓이 될 수 있는데, 문제는 CPU가 더 많은 명령어들을 처리할 수 있음에도 if 문 조건이 참인지 거짓인지 밝혀질때까지는 다음 명령어가 무엇이 될지 알 수 없다는 것입니다.
예를들면, 위 프로그램에서는 y가 15보다 작으면 z에 20을 넣고, 아니면 50을 넣습니다. 그러나 y와 z의 값은 실행을 해봐야지만 알 수 있습니다.
이를 극복하기 위해 엔지니어들은 Branch prediction (분기예측) 기법을 도입했습니다. 조건문이 참일지 거짓일지는 모르지만, 미리 하나를 찍어서 정하고 다음 명령어들을 미리 실행해 놓습니다. 만약 맞춘다면 미리 명령어들을 실행해 놓을 수 있으므로 성능이 향상됩니다. 최근의 Branch prediction은 평균 약 95% 이상의 정확도를 보여준다고 보고되며, 본 기법을 통해 현대 CPU의 성능은 크게 향상되었습니다.
CPU는 조건문이 나오면 결과를 예측해서 미리 다음 명령어들을 실행한다.
만약 Branch prediction 예측이 틀리면, CPU는 미리 실행해 두었던 명령어들을 취소합니다. 때문에 예측되어 실행된 명령어들의 실행은 취소됩니다.
그러나, 실행취소된 명령어에서 읽은 데이터가 캐쉬 메모리에 올라간다는 사실이 밝혀졌습니다. 그리고 이 때문에 Flush+Reload 기법을 사용하면 해당 데이터를 읽어올 수 있습니다.
Flush+Reload
Flush+Reload는 캐쉬 메모리 (Cache Memory) 를 이용해 메모리 데이터를 간접적으로 읽어오는 부채널 공격 (Side Channel Attack) 의 일종입니다. 여기서 잠시 캐쉬 메모리에 대해 간단히 설명하고자 합니다.

캐쉬 메모리는 CPU안의 작고 빠른 메모리입니다. CPU가 메인 메모리에서 데이터를 읽거나 쓸때마다 해당 데이터는 캐쉬 메모리 안에 임시로 저장됩니다. 모든 메모리 데이터의 접근 순서는 다음과 같습니다.
- CPU는 캐쉬 메모리에서 데이터를 먼저 찾는다. 데이터가 있다면 메인 메모리에 접근하지 않는다.
- 캐쉬 메모리에 데이터가 없으면 메인 메모리에서 데이터를 가져온다.
Flush+Reload를 이해하기 위한 중요 포인트 둘은 다음과 같습니다.
#모든 메모리 접근 시 해당 데이터는 캐쉬 메모리에 새로이 저장된다.
#캐쉬 메모리로의 접근시간이 메인 메모리로의 접근시간보다 매우 짧다.
다시 처음으로 돌아가서 스펙터의 코드를 살펴보겠습니다.

해당 코드를 실행시키면, Branch prediction에 의해 타 프로그램의 비밀인 array1[x]의 값이 캐쉬 메모리에 올라가게 됩니다. 여기서는 array1[x]가 75라고 가정해보겠습니다. 256은 여기서 메모리 페이지의 사이즈인데, 책으로 비유하자면 array1[x] 한 문장을 읽기위해 책의 한 페이지 일부를 캐쉬 메모리에 올린다고 생각하면 됩니다.
위 코드를 실행시키면 array2의 75페이지'만' 캐쉬 메모리에 올릴 수 있습니다. 몇번째 페이지가 캐쉬메모리에 있는지 읽기 위해서는 위에서 언급한 포인트 중 두번째 성질을 이용합니다.
캐쉬 메모리로의 접근시간이 메인 메모리로의 접근시간보다 매우 짧다.
여기서 array2안에 256개의 메모리 페이지가 있다고 가정해보겠습니다. 만약 우리가 0페이지부터 255페이지까지 한번씩 읽어본다면, 75페이지를 읽는 속도만 유난히 빠를 것입니다. 읽는 속도는 대략 다음과 같습니다.

이렇게 접근 시간을 측정함으로써 array1[x]의 값을 알아낼 수 있습니다. 이 작업을 원하는 만큼 반복해서 어떤 메모리 영역의 데이터도 알아낼 수 있습니다.
스펙터 공격의 가장 무서운 점은 현대 CPU의 핵심 성능향상 기술인 Branch Prediction과 캐시 메모리의 취약점을 이용한다는 것입니다.
근 20년 간의 CPU 속도 향상에 크게 기여한 기술이 치명점 보안약점을 야기한다는 것이 참으로 아이러니합니다.
- 본 글은 https://www.meltdownattack.com 에 있는 논문과 자료들을 토대로 작성되었습니다.
좋은 글 잘 봤습니다!