1. 들어가며: 왜 포인터를 보호해야 하는가
메모리 취약점을 이용한 공격의 핵심은 거의 언제나 포인터 변조다. 스택 버퍼 오버플로우로 리턴 주소를 덮어쓰든(ROP), 함수 포인터를 변조하든(JOP), vtable을 조작하든 — 결국 공격자가 하는 일은 "프로그램이 의도하지 않은 주소로 제어 흐름을 바꾸는 것"이다.
소프트웨어 수준의 방어(ASLR, Stack Canary, CFI 등)는 효과적이지만 한계가 있다. ASLR은 정보 유출로 우회되고, Stack Canary는 비순차적 쓰기로 건너뛸 수 있다. 이 한계를 극복하기 위해 ARM은 하드웨어 수준에서 포인터 자체에 암호학적 서명을 삽입하는 기법을 도입했다.
이것이 PAC(Pointer Authentication Code)이다.
2. PAC의 핵심 개념
2.1 한 문장 요약
포인터의 미사용 상위 비트에 암호학적 해시(PAC)를 삽입하고, 포인터 사용 시 해시를 검증하여 변조 여부를 판별한다.
2.2 PAC가 들어가는 자리: 64비트 포인터의 빈 공간
현대 ARM64 시스템에서 64비트 포인터의 모든 비트가 실제 주소 지정에 사용되지는 않는다. 예를 들어 48비트 가상 주소 체계를 사용하는 Linux 시스템에서는:
63 48 47 0
┌──────── ─────────────────────────┬─────────────────────────────────────┐
│ 미사용 상위 비트 │ 가상 주소 (48비트) │
│ (기존: 부호 확장으로 채움) │ │
│ │ │
│ ← 여기에 PAC를 삽입 → │ │
└───────────────── ────────────────┴─────────────────────────────────────┘PAC 비트 폭 = 55 - 가상 주소 크기
| 가상 주소 크기 | PAC 비트 폭 | 가능한 PAC 값 수 |
|---|---|---|
| 48비트 | 7비트 | 128 |
| 42비트 | 13비트 | 8,192 |
| 39비트 | 16비트 | 65,536 |
PAC 비트 폭이 클수록 보안 강도가 높아진다. 7비트 PAC는 128개의 가능한 값 중 하나를 무작위로 맞춰야 하므로, 단순 브루트포스의 성공 확률은 약 1/128이다. 16비트라면 1/65,536이 된다.
2.3 PAC 계산의 세 가지 입력
PAC는 다음 세 가지 값을 입력으로 받아 계산된다:
┌──────────────┐
│ 포인터 값 │ (서명할 대상 — 예: 리턴 주소)
│ (64비트) │
└──────┬───────┘
│
▼
┌──────────────┐ ┌─────────── ──┐
│ 컨텍스트 │ │ 비밀 키 │
│ (64비트) │ │ (128비트) │
│ │ │ │
│ 예: 현재 SP │ │ 프로세스별 │
│ 또는 0 │ │ 랜덤 할당 │
└──────┬───────┘ └──────┬───────┘
│ │
└────────┬───────────┘
│
▼
┌──────────────┐
│ QARMA-64 │ (또는 구현체 선택 암호 알고리즘)
│ 암호 연산 │
└──────┬───────┘
│
▼
┌──────────── ─┐
│ PAC 값 │ → 포인터 상위 비트에 삽입
│ (7~16비트) │
└──────────────┘| 입력 | 설명 | 역할 |
|---|---|---|
| 포인터 값 | 서명할 64비트 포인터 (예: 함수 리턴 주소) | 서명 대상 |
| 컨텍스트(Modifier) | 64비트 보조 값 (예: 현재 스택 포인터, 0 등) | 동일 포인터라도 컨텍스트가 다르면 다른 PAC 생성 → 재사용 공격 방지 |
| 비밀 키 | 128비트 비밀 키 (시스템 레지스터에 저장) | 키를 모르면 PAC 위조 불가 |
컨텍스트의 중요성: 같은 함수의 리턴 주소라도, 호출 시점의 스택 포인터(SP)가 다르면 PAC 값이 달라진다. 이는 공격자가 한 곳에서 유출한 서명된 포인터를 다른 곳에서 재사용하는 것을 방지한다.
3. PAC의 5개 키 체계
ARMv8.3은 5개의 독립적인 128비트 비밀 키를 제공한다:
| 키 이름 | 용도 | 관련 명령어 | 사용 예시 |
|---|---|---|---|
| APIAKey | 명령어 포인터 서명 A | PACIA, AUTIA | 리턴 주소 보호 (주로 사용) |
| APIBKey | 명령어 포인터 서명 B | PACIB, AUTIB | 대체 키 (교차 서명 등) |
| APDAKey | 데이터 포인터 서명 A | PACDA, AUTDA | C++ vtable, 함수 포인터 보호 |
| APDBKey | 데이터 포인터 서명 B | PACDB, AUTDB | 대체 데이터 키 |
| APGAKey | 범용 인증 | PACGA | 임의 데이터 블록의 MAC 계산 |
왜 2개씩? 키를 분리하면 서로 다른 보안 도메인 간의 격리가 가능하다. 예를 들어 커널과 유저 공간이 다른 키를 사용하면, 유저 공간에서 유출된 서명이 커널 포인터 위조에 사용될 수 없다.
Apple의 확장: Apple Silicon(M1/M2)은 EXTRAKEY_EL1이라는 128비트 레지스터를 추가로 도입했다. 이 키는 ARM 표준 키와 XOR되어 실제 PAC 연산에 사용된다. 이를 통해 유저/커널 모드 간 PAC 키를 추가로 분리한다.
4. PAC 명령어 상세
4.1 명령어 명명 규칙
PAC + I/D + A/B + 수식어
│ │ │ │
│ │ │ └── SP: 스택 포인터를 컨텍스트로 사용
│ │ │ Z: 컨텍스트 = 0
│ │ │ (없음): 범용 레지스터를 컨텍스트로 사용
│ │ │
│ │ └── A: A키 사용, B: B키 사용
│ │
│ └── I: 명령어(Instruction) 포인터
│ D: 데이터(Data) 포인터
│
└── PAC: 서명 삽입
AUT: 서명 검증
XPAC: 서명 제거 (검증 없이)4.2 핵심 명령어 일람
서명 삽입 (PAC*)
| 명령어 | 동작 | 입력 | 컨텍스트 |
|---|---|---|---|
PACIA Xd, Xn |
A키로 Xd에 PAC 삽입 | Xd = 포인터 | Xn = 컨텍스트 |
PACIASP |
A키로 LR(x30)에 PAC 삽입 | LR = 리턴 주소 | SP = 컨텍스트 |
PACIAZ |
A키로 LR에 PAC 삽입 | LR | 0 |
PACIZA Xd |
A키로 Xd에 PAC 삽입 | Xd | 0 |
PACDA Xd, Xn |
A키로 데이터 포인터 서명 | Xd = 포인터 | Xn = 컨텍스트 |
PACIB, PACDB |
B키 버전 (동일 구조) | — | — |
PACGA Xd, Xn, Xm |
범용 인증 코드 생성 | Xn, Xm 입력 | 결과를 Xd에 저장 |
서명 검증 (AUT*)
| 명령어 | 동작 | 성공 시 | 실패 시 |
|---|---|---|---|
AUTIA Xd, Xn |
A키로 Xd의 PAC 검증 | PAC 제거, 원본 포인터 복원 | 포인터 변조 (역참조 시 폴트) |
AUTIASP |
A키로 LR 검증 (SP 컨텍스트) | LR 복원 | 폴트 유발 |
AUTDA Xd, Xn |
A키로 데이터 포인터 검증 | 원본 포인터 복원 | 포인터 변조 |
서명 제거 (XPAC*)
| 명령어 | 동작 | 용도 |
|---|---|---|
XPACI Xd |
PAC를 제거하고 원본 포인터 복원 | 디버깅, 포인터 비교 시 |
XPACD Xd |
데이터 포인터에서 PAC 제거 | — |
복합 명령어
| 명령어 | 동작 | 설명 |
|---|---|---|
RETAA |
AUTIASP + RET |
리턴 주소 검증 후 리턴 (원자적) |
RETAB |
AUTIBSP + RET |
B키 버전 |
BRAA Xn, Xm |
AUTIA + BR |
검증 후 간접 분기 |
BLRAA Xn, Xm |
AUTIA + BLR |
검증 후 간접 호출 |
4.3 인증 실패 처리
PAC 검증(AUT*)이 실패하면, 포인터가 의도적으로 변조(mangled)된다. 구체적으로:
인증 성공:
포인터 상위 비트에서 PAC 제거 → 원본 유효 주소 복원 → 정상 사용
인증 실패:
포인터 상위 비트를 고의적으로 오류 값으로 설정
→ 이 포인터를 역참조하면 가상 주소 범위 밖의 주소에 접근 시도
→ MMU가 페이지 폴트(Segmentation Fault) 발생
→ 프로그램 크래시핵심: PAC은 "공격을 막는 것"이 아니라, "변조된 포인터 사용 시 프로그램이 크래시하도록 보장"하는 것이다. 공격자가 포인터를 변조하면 해당 포인터로의 제어 흐름 전환이 폴트를 일으켜, 익스플로잇이 실패한다.
5. QARMA-64: PAC의 암호 엔진
5.1 왜 전용 암호 알고리즘이 필요한가
PAC 연산은 모든 함수 호출과 리턴에서 수행된다. 즉 프로그램 실행 중 매우 빈번하게 호출되 로, 암호 알고리즘이 다음 조건을 만족해야 한다:
- 극도로 낮은 지연 시간 (수 사이클 내 완료)
- 작은 하드웨어 면적 (모든 코어에 내장)
- 충분한 암호학적 강도 (PAC 비트 폭 내에서의 충돌 저항성)
AES나 SHA-256은 너무 무겁다. 그래서 Qualcomm이 ARM을 위해 QARMA-64를 설계했다.
5.2 QARMA-64 특성
| 항목 | 값 |
|---|---|
| 설계자 | Qualcomm (Roberto Avanzi) |
| 블록 크기 | 64비트 |
| 키 크기 | 128비트 |
| 구조 | Tweakable block cipher (조정 가능 블록 암호) |
| 라운드 수 | 5~7라운드 (QARMA-5, QARMA-7) |
| 특징 | 역변환이 거의 동일한 비용 → 서명과 검증 모두 빠름 |
"Tweakable"의 의미: 입력 블록(포인터)과 키 외에 트윅(tweak = 컨텍스트 값)을 추가 입력으로 받는다. 같은 포인터+같은 키여도 트윅(SP 등)이 다르면 른 출력을 생성한다. 이것이 PAC의 "컨텍스트" 역할을 하드웨어적으로 구현하는 방식이다.
참고: ARM은 QARMA-64를 권장하지만 구현체에 강제하지는 않는다. 칩 제조사는 다른 알고리즘을 사용할 수 있다. Apple Silicon은 자체 알고리즘을 사용하는 것으로 알려져 있다.
6. PAC의 실전: 함수 호출 보호
6.1 ROP 공격과 PAC 방어
ROP(Return-Oriented Programming) 공격의 핵심:
- 버퍼 오버플로우로 스택의 리턴 주소를 덮어쓴다
- 리턴 주소를 프로그램 내 기존 코드 조각(가젯)의 주소로 교체한다
- 함수가 리턴할 때 가젯으로 제어 흐름이 전환된다
PAC가 이를 막는 원리:
; ===== 함수 프롤로그 =====
my_function:
paciasp ; ① LR(리턴 주소)에 PAC 삽입
; 키 = APIAKey
; 컨텍스트 = 현재 SP 값
stp x29, x30, [sp, #-16]! ; ② PAC가 삽입된 LR을 스택에 저장
mov x29, sp
; ... 함수 본문 ...
; ===== 함수 에필로그 =====
ldp x29, x30, [sp], #16 ; ③ 스택에서 LR 복원
autiasp ; ④ LR의 PAC 검증
; 컨텍스트 = 현재 SP (프롤로그와 동일해야 함)
; 검증 실패 시 → LR 변조 → 폴트
ret ; ⑤ 검증 통과한 LR로 리턴
또는 복합 명령어를 사용하면:
; 에필로그 (간소화)
ldp x29, x30, [sp], #16
retaa ; AUTIASP + RET을 원자적으로 수행
공격자의 딜레마:
- 리턴 주소를 덮어쓰면 → PAC가 불일치 →
autiasp에서 폴트 - 유효한 PAC를 삽입하려면 → 128비트 비밀 키를 알아야 함 → 현실적으로 불가
6.2 JOP 공격과 PAC 방어
JOP(Jump-Oriented Programming)은 리턴 대신 간접 분기(BR Xn)를 악용한다. PAC는 간접 분기 전에도 포인터를 검증하는 명령어를 제공한다:
; 함수 포인터 호출 보호
ldr x8, [x0] ; 함수 포인터 로드
blraa x8, x9 ; x8의 PAC를 x9 컨텍스트로 검증 후 호출
; 검증 실패 시 → 폴트
6.3 데이터 포인터 보호
C++ vtable이나 연결 리스트의 next 포인터 등 데이터 포인터도 보호할 수 있다:
; 데이터 포인터 서명
pacda x0, x1 ; x0(데이터 포인터)에 PAC 삽입
; 키 = APDAKey, 컨텍스트 = x1
; 데이터 포인터 검증
autda x0, x1 ; PAC 검증 후 원본 포인터 복원
ldr x2, [x0] ; 검증된 포인터로 데이터 접근
7. 실제 구현: 컴파일러와 OS의 역할
7.1 컴파일러 지원
# GCC: PAC 활성화 컴파일
gcc -march=armv8.3-a -mbranch-protection=standard -o program program.c
# Clang/LLVM: PAC 활성화
clang -target aarch64-linux-gnu -march=armv8.3-a \
-msign-return-address=all -o program program.c
-mbranch-protection=standard는 다음을 활성화한다:
- 리턴 주소 서명: 모든 non-leaf 함수에
paciasp/autiasp삽입 - BTI(Branch Target Identification): 간접 분기 대상 검증
7.2 Linux 커널의 PAC 관리
커널 부팅
│
├── CONFIG_ARM64_PTR_AUTH 활성화 확인
│
├── 각 프로세스에 exec() 시 5개의 랜덤 키 할당
│ APIAKey, APIBKey, APDAKey, APDBKey, APGAKey
│
├── 동일 프로세스 내 모든 스레드는 키를 공유
│
├── fork() 시 부모의 키가 자식에게 상속
│
└── exec() 시 새로운 랜덤 키 할당커널 자체 보호: CONFIG_ARM64_PTR_AUTH_KERNEL이 활성화되면 커널 코드의 함수 리턴 주소도 PAC로 보호된다. 이때 HINT 공간 명령어를 사용하여 PAC 미지원 CPU에서도 NOP로 처리되어 호환성을 유지한다.
7.3 Apple XNU (iOS/macOS)
Apple의 PAC 구현은 ARM 표준보다 훨씬 광범위하다:
| 보호 대상 | 표준 ARM PAC | Apple PAC |
|---|---|---|
| 리턴 주소 | ✓ | ✓ |
| 함수 포인터 | 선택적 | 기본 적용 |
| Objective-C isa 포인터 | — | ✓ |
| C++ vtable | 선택적 | ✓ |
| 블록 invoke 포인터 | — | ✓ |
| 시스템 콜 핸들러 | — | ✓ |
Apple은 9가지 수식어(modifier) 유형을 정의하여 포인터 종류별로 다른 컨텍스트를 사용한다. 이는 한 종류의 서명된 포인터를 다른 종류로 재사용하는 타입 혼동 공격을 방지한다.
8. PAC 우회 공격 연구
PAC는 강력하지만 완벽하지 않다. 다음은 알려진 주요 공격 연구이다.
8.1 PACMAN (MIT, 2022)
논문: "PACMAN: Attacking ARM Pointer Authentication with Speculative Execution" (ISCA 2022)
핵심 아이디어: 투기적 실행(Speculative Execution)을 이용하여 PAC 값을 브루트포스 추측한다.
[PACMAN 공격 원리]
일반적인 PAC 브루트포스:
추측값이 틀림 → 프로그램 크래시 → 탐지됨 (불가능)
PACMAN의 우회:
① 추측값으로 포인터를 투기적으로(speculatively) 사용
② CPU가 투기적 실행 경로에서 해당 포인터로 메모리 접근
③ 추측이 맞으면 → 캐시에 흔적이 남음 (TLB/캐시 상태 변화)
추측이 틀리면 → 투기적 실행이 롤백되어 흔적 없음
④ 캐시 사이드 채널로 추측 성공 여부를 판별
⑤ 크래시 없이 PAC 값 확인 가능 → "PAC Oracle"영향:
- 7비트 PAC: 최대 128번 시도로 정확한 PAC 추측 가능
- Apple M1, ARM Cortex-A78, Neoverse V1/N2 등 광범위한 칩 영향
- 하드웨어 결함이므로 소프트웨어 패치 불가
제한 사항:
- PACMAN 자체만으로는 시스템을 침해할 수 없음
- 별도의 메모리 취약점(버퍼 오버플로우 등)과 결합해야 함
- Spectre와 유사하게 "익스플로잇 기법"이지 "독립적 취약점"이 아님
8.2 PAC 위조 (Signing Oracle) 공격
RET2 Systems의 연구에서 시연된 시나리오:
[Signing Oracle 공격 흐름]
① 프로그램 내에 "임의 포인터를 서명하는" 기능이 노출됨
(예: 사용자 입력을 PACIA로 서명하는 함수)
② 공격자가 원하는 목표 주소(예: system() 함수)를 입력
③ 프로그램이 해당 주소에 유효한 PAC를 삽입하여 반환
④ 공격자는 이 서명된 포인터를 버퍼 오버플로우로 리턴 주소에 배치
⑤ 함수 리턴 시 PAC 검증 통과 → 공격 성공교훈: PAC의 보안은 "서명 기능이 공격자에게 노출되지 않아야 한다"는 전제에 의존한다. 소프트웨어 설계에서 PACIA 등의 명령어 사용을 신중하게 제한해야 한다.
8.3 기타 연구
| 연구 | 연도 | 공격 방식 | 대상 |
|---|---|---|---|
| "Demystifying Pointer Authentication on Apple M1" | 2023 (USENIX Security) | Apple의 PAC 구현 역공학 + 키 관리 분석 | Apple M1 |
| "PAC it up" | 2019 (USENIX Security) | PAC의 보안 보장 범위 형식적 분석 | ARM PAC 일반 |
| TikTag | 2024 (Black Hat) | MTE(Memory Tagging Extension) 우회 — PAC와 관련 기법 | ARM MTE |
9. PAC의 한계와 보완 기술
9.1 PAC 단독의 한계
| 한계 | 설명 |
|---|---|
| 낮은 PAC 비트 폭 | 가상 주소 48비트 시 PAC은 7비트 = 128가지. 브루트포스에 취약 |
| 투기적 실행 사이드채널 | PACMAN 공격으로 크래시 없이 PAC 추측 가능 |
| Signing Oracle | 소프트웨어 버그로 서명 기능이 노출되면 무력화 |
| 데이터 전용 공격 | 제어 흐름이 아닌 데이터만 변조하는 DOP(Data-Oriented Programming)에는 무력 |
| 같은 컨텍스트 내 재사용 | 동일 SP에서 서명된 포인터는 해당 SP 값이 같은 시점에 재사용 가능 |
9.2 PAC + MTE (Memory Tagging Extension)
ARMv8.5에서 도입된 MTE는 PAC와 상호보완적이다:
PAC: 포인터의 "진위 여부"를 검증 (변조 탐지)
MTE: 포인터와 메모리의 "소유권"을 검증 (공간적 안전성)
┌─────────────────────────────────────────┐
│ PAC + MTE 병합 방어 │
│ │
│ [PAC] 리턴 주소 변조? → 크래시 │
│ [MTE] 범위 밖 메모리 접근? → 크래시 │
│ │
│ 두 기법 모두 우회해야 익스플로잇 성공 │
└────────────────────────────────────── ──┘9.3 PAC + BTI (Branch Target Identification)
ARMv8.5의 BTI는 간접 분기의 착지점(landing pad)을 제한한다:
| 방어 기법 | 보호 대상 | 위협 모델 |
|---|---|---|
| PAC | 리턴 주소, 함수 포인터 | ROP, JOP |
| BTI | 간접 분기 착지점 | JOP (가젯 체이닝 차단) |
| PAC + BTI | 리턴 + 분기 모두 | ROP + JOP 통합 방어 |
10. 정리: PAC가 바꾼 것과 바꾸지 못한 것
PAC가 바꾼 것
- 하드웨어 수준 CFI(Control Flow Integrity): 소프트웨어 CFI의 성능 오버헤드 없이 제어 흐름 보호
- ROP/JOP 공격 비용 극대화: PAC 없이는 스택 오버플로우 하나로 가능하던 공격이, PAC 하에서는 사이드 채널 + 메모리 취약점 + 정밀한 타이밍이 동시에 필요
- 산업 표준화: Apple, Google(Android), Linux 커널 모두 PAC를 채택하여 ARM 생태계 전반의 보안 수준 상승
PAC가 바꾸지 못한 것
- 메모리 안전성 자체: PAC는 포인터 변조를 탐지하지만, 버퍼 오버플로우나 Use-After-Free 자체를 방지하지는 않음
- 데이터 전용 공격: 제어 흐름을 변경하지 않고 데이터만 조작하는 공격(DOP)에는 무력
- 사이드 채널 내성: PACMAN이 증명했듯이, 투기적 실행이라는 현대 CPU의 근본 설계와 충돌
한 문장 요약
PAC는 "메모리 취약점이 존재해도 제어 흐름 탈취를 어렵게 만드는" 하드웨어 보호 기법이다. 완벽하지 않지만, 공격의 비용을 극적으로 높인다.
참고 자료
- ARM - Pointer Authentication Code Introduction
- ARM Learn - Understand Arm Pointer Authentication
- Qualcomm - Pointer Authentication on ARMv8.3 (Whitepaper)
- Linux Kernel - Pointer Authentication in AArch64 Linux
- LLVM - Pointer Auth Documentation
- RET2 Systems - Intro to PAC on ARM64
- Google Project Zero - Examining Pointer Authentication on the iPhone XS
- USENIX Security 2019 - PAC it up: Towards Pointer Integrity using ARM PAC
- USENIX Security 2023 - Demystifying Pointer Authentication on Apple M1
- ISCA 2022 - PACMAN: Attacking ARM Pointer Authentication with Speculative Execution
- MITRE D3FEND - Pointer Authentication
'Documentation & Blog' 카테고리의 다른 글
| 최신 보안사례 분석 (0) | 2026.04.06 |
|---|---|
| Red Team & AI 논문 정리 (0) | 2026.04.05 |
| 김수키(Kimsuky)의 termsrv.dll 패치 기반 내부 침투 전략 분석 (0) | 2026.04.05 |
| AV/EDR 다계층 탐지 메커니즘 종합 분석 (0) | 2026.04.05 |
| React2Shell 취약점 분석 (0) | 2026.03.20 |