분기 예측을 노리는 공격들 — Spectre부터 ret2spec까지
컴퓨터 구조(파이프라인·투기 실행)와 시스템 해킹(메모리 누출)이 만나는 지점, 마이크로아키텍처 취약점 정리. 분기 예측·투기 실행을 악용하는 대표 공격들을 다룬다.
배경 — 왜 분기 예측이 공격 대상이 되는가
CPU는 파이프라인을 놀게하지 않으려고, 분기(if, 함수 호출, ret) 결과가 확정되기 전에 ㅇ마 이쪽으로 갈 것이라 예측하고 그 방향 명령어를 미리 실행한다. 이것이 투기 실행(speculative execution) 이다. 예측이 틀리면 그 결과를 되돌린다(rollback).
문제는 되돌림이 불완전하다는 데 있다.
- 되돌려지는 것: 레지스터·연산 결과 등 구조적 상태(architectural state)
- 되돌려지지 않는 것: 투기 실행 중 캐시에 올라간 데이터
이 지우다 만 흔적이 핵심이다. 투기 실행이 비밀 값에 접근해 그 값에 따라 특정 메모리를 캐시에 올렸다면, 예측이 틀려 되돌려져도 캐시 흔적은 남는다. 공격자는 그 흔적을 캐시 접근 시간 측정(side channel) 으로 읽어내 비밀을 복원한다.
투기 실행으로 비밀에 접근 → 캐시에 흔적을 남김 → 되돌림 후에도 남은 흔적을 시간으로 추론.
분기 예측기는 한 종류가 아니다
CPU 안에는 예측 대상별로 여러 장치가 있다. 공격 분류의 기준이 된다.
| 조건 분기 예측기 | if가 참인지 거짓인지 | Spectre v1 |
| 간접 분기 예측기 (BTB) | 함수 포인터·가상 함수의 점프 목적지 | Spectre v2 |
| 반환 스택 버퍼 (RSB) | ret의 리턴 주소 | ret2spec |
1. Spectre v1 — Bounds Check Bypass (CVE-2017-5753)
노리는 것: 조건 분기 예측기
가장 기본적인 형태. 경계 검사 분기를 악용한다.
if (x < array1_size) // ← 이 분기를 길들인다
y = array2[array1[x] * 256]; // 투기 실행되는 가젯
공격 흐름
- 길들이기 — x에 정상 값을 여러 번 넣어, 예측기가 "이 if는 항상 참"이라고 학습하게 만든다.
- 경계 밖 호출 — x에 배열 범위를 벗어난 값을 넣는다. 정상이라면 if에서 걸러져야 한다.
- 투기 실행 — 예측기가 "참"이라 학습돼 있어, 검사가 확정되기 전에 if 안쪽을 투기 실행한다. 여기서 경계 밖 비밀 값 array1[x]를 읽는다.
- 흔적 남기기 — 그 비밀 값을 인덱스로 array2에 접근 → array2[secret * 256]가 캐시에 올라간다.
- 누출 — 예측이 틀렸음이 밝혀져 되돌려지지만, 캐시 흔적은 남는다. 공격자가 array2의 각 원소 접근 시간을 재서 캐시에 올라간 원소를 찾고, 거꾸로 secret을 복원한다.
이름은 "경계 검사 우회"지만 사용자 입력에만 국한되지 않는다.
2. Spectre v2 — Branch Target Injection / BTI (CVE-2017-5715)
노리는 것: 간접 분기 예측기 (BTB)
v1보다 강력하다. 간접 분기(목적지가 변수인 점프 — 함수 포인터, 가상 함수 호출)의 목적지 예측을 조작한다.
왜 더 위험한가
v1은 "경계 밖을 읽게" 하는 정도지만, v2는 공격자가 투기 실행의 점프 목적지를 원하는 곳으로 유도할 수 있다. 공격자는 BTB(Branch Target Buffer)를 오염시켜, 피해자의 간접 분기가 공격자가 고른 가젯 코드로 투기적으로 튀게 만든다.
공격 흐름
- 공격자가 자신의 주소 공간에서 간접 분기를 반복해, 피해자 주소 공간의 특정 주소(가젯 위치)로 BTB를 오염시킨다.
- 피해자가 간접 분기를 실행할 때, 오염된 예측으로 그 가젯이 투기 실행된다.
- 가젯이 비밀을 읽어 캐시에 흔적을 남긴다. → 이후는 v1과 동일(사이드 채널로 복원).
ROP가 기존 코드 가젯을 재활용하듯ㅇ, Spectre v2는 기존 코드 가젯을 투기적으로 실행시킨다는 점에서 발상이 닮았다. 차이는 실제 실행이 아니라 되돌려질 투기 실행이라는 것.
3. Meltdown — 대비용 사촌 (CVE-2017-5754)
노리는 것: 분기 예측이 아님 (비순차 실행 + 예외 타이밍)
Spectre와 자주 함께 언급되지만 메커니즘이 다르다. 분기 예측을 쓰지 않는다.
차이의 핵심
- Spectre: 분기 예측을 길들여 프로그램 자신의 코드가 잘못된 걸 투기 실행하게 함. 예외 없음.
- Meltdown: 커널 메모리를 직접 읽으려 시도한다. 권한 위반이라 예외가 발생하지만, CPU가 예외를 처리하기 전의 짧은 비순차 실행 창에서 이미 그 값을 읽어 캐시에 흔적을 남긴다.
Meltdown은 유저 프로세스가 커널 메모리를 읽는 격리 붕괴라 충격이 컸고, KPTI(커널/유저 페이지 테이블 분리)로 대응됐다. 분기 예측 공격은 아니지만, "투기/비순차 실행의 되돌림이 캐시엔 안 남는다"는 같은 토대 위에 있어 대비용으로 넣는다.
4. ret2spec / Spectre-RSB — ret을 노리는 변종
노리는 것: 반환 스택 버퍼 (RSB)
배경
CPU는 ret의 목적지(리턴 주소)도 예측한다. call이 일어날 때마다 리턴 주소를 RSB(Return Stack Buffer) 라는 작은 하드웨어 스택에 밀어넣고, ret 때 거기서 꺼내 목적지를 예측한다. (실제 메모리 스택과 별개인 예측 전용 버퍼)
공격 발상
공격자가 이 RSB를 오염시켜, ret이 공격자가 고른 주소로 투기적으로 점프하게 만든다. 그 주소의 가젯이 투기 실행되어 비밀을 캐시에 남긴다.
BOF의 ret 조작과 무엇이 다른가
고전 BOF / ROP ret2spec
| 조작 대상 | 메모리 스택의 실제 리턴 주소 | RSB(예측 전용 버퍼) |
| 실행 성격 | 실제 실행 (아키텍처 상태 변경) | 투기 실행 (되돌려짐) |
| 결과 | 실행 흐름 탈취 | 캐시 사이드 채널로 정보 누출 |
발상은 같다 — "ret의 목적지를 조작한다." 하지만 하나는 실제 스택을, 하나는 예측기를 건드린다. 컴퓨터 구조에서 배운 ret·리턴 주소가 마이크로아키텍처 예측 층에서 다시 등장하는 셈이다.
정리 — 계보 한눈에
투기 실행 = 성능 최적화 (버그 아님)
│ 되돌림이 캐시엔 안 남는다는 틈
▼
┌──────────────────────────────────────────────┐
│ Spectre v1 → 조건 분기 예측기 (경계 검사 우회) │
│ Spectre v2 → 간접 분기 예측기 (BTB, 목적지 조작)│
│ ret2spec → 반환 스택 버퍼 (ret 목적지 조작) │
└──────────────────────────────────────────────┘
Meltdown → 분기 예측 아님 (비순차 실행 + 예외 타이밍)
공통 마무리: 캐시에 남은 흔적을 접근 시간 측정으로 복원 (side channel)
왜 끝나지 않는가
하나의 완화책으로 모든 변종을 막을 수 없고, 대응은 프로세서마다 다르다. 그래서 2018년 이후로도 Retbleed, Inception(SRSO), BHI/Spectre-BHB, Indirector, iLeakage 등 변종과 패치가 계속 반복되고 있다. 투기 실행이라는 성능 최적화를 포기하지 않는 한 근본 해결이 어려운, 전형적인 성능↔보안 트레이드오프다.