다음 사이트를 참고하여 공부하고 작성한 글입니다.
Documentation – Arm Developer
developer.arm.com
시스템 보안의 핵심 기술 중 하나인 PAC에 대해 알아보겠습니다. 한 마디로 요약하면 "함수의 반환 주소가 변조되었는지 검사하는 디지털 서명" 기술입니다.
1. 왜 PAC가 필요할까?
함수가 실행될 때 CPU는 Return Address를 메모리의 스택 영역에 저장합니다.
- 정상 흐름 : foo() 실행 → 0x1000 주소 저장 → 종료 후 0x1000으로 복귀
- 공격 상황 : 공격자가 스택의 값을 덮어써서 주소를 0xDEAD(악성 코드)로 변경
- 결과 : 함수 종료 시 공격자가 의도한 코드가 실행됨 (ROP 공격)
2. PAC의 핵심 원리
PAC는 반환 주소가 진짜인지 확인하기 위해 암호학적 서명을 생성합니다.
PAC = HASH(LR, SP, SECRET_KEY)
- 비밀키 (Secret Key) : CPU 내부 하드웨어에 숨겨져 있어 공격자가 알 수 없습니다.
- 변조 방지 : 주소를 1비트만 바꿔도 서명이 완전히 달라집니다.
3. PAC의 작동 프로세스
1) 함수 시작
- 함수가 시작될 때, 현재의 반환 주소와 스택 포인터, 그리고 비밀키를 조합하여 PAC 값을 만듭니다.
- 이 값은 포인터의 남는 비트 공간이나 별도의 레지스터(R12 등)에 저장됩니다.
2) 함수 실행
- 프로그램이 원래 의도된 기능을 수행합니다.
3) 함수 종료
- 함수가 끝나기 직전, CPU는 다시 한번 PAC를 계산합니다.
- 계산 값 : 현재 스택에 있는 LR, SP, Key를 이용
- 비교 : 처음에 저장해둔 PAC 값과 방금 계산한 값이 일치하는지 확인
4. 왜 공격자는 PAC를 뚫지 못할까?
- 공격자가 메모리에서 return address, PAC 값을 읽을 수는 있지만, PAC 생성에 사용된 비밀 키를 모르기 때문에 위조된 return address에 맞는 새로운 PAC를 만들 수 없습니다.
- 만약 공격자가 주소를 위조했다면, CPU가 계산한 PAC 값이 저장된 값과 달라집니다. 이때 함수 종료 시 인증에 실패하고 CPU가 fault exception을 발생시킵니다.
5. PAC 기능의 세 가지 핵심 요소
1) Cryptographic algorithm
Armv8.1-M이 제공하는 두 가지 알고리즘 선택지
① QARMA 알고리즘 (표준 경량 암호)
특징 : 하드웨어 구현에 최적화된 경량 블록 암호 알고리즘입니다.
장점 : 매우 낮은 지연 시간으로 암호화 값을 계산할 수 있어, 성능 저하를 최소화해야 하는 실시간 임베디드 시스템에 적합합니다.
② IMPLEMENTATION DEFINED (사용자 정의)
특징 : 아키텍처에서 고정된 알고리즘을 강제하지 않고, 제조사가 독자적인 알고리즘을 적용할 수 있도록 열어둔 옵션입니다.
장점 : 특정 보안 요구 사양에 맞춘 특수 알고리즘이나, 하드웨어 제조사만의 고유한 보안 설계를 반영할 수 있는 유연성을 제공합니다.
2) Key management
PAC의 보안성은 결국 키를 얼마나 안전하게 관리하느냐에 달려 있습니다.
1. PAC 키의 구조와 접근
- Armv8.1-M은 보안 상태와 권한 상태에 따라 총 4개의 128비트 키를 제공합니다.
- 키 구성 : 각 128비트 키는 4개의 32비트 내부 시스템 레지스터가 연결되어 형성됩니다.
- 접근 방법 : 하드웨어 내부 레지스터이므로 MRS(읽기) 및 MSR(쓰기) 명령어를 통해 접근할 수 있습니다.
2. PAC 도입 목적
- 보안 강화 (ROP 공격 탐지 등)
- 키는 절대적인 기밀 정보입니다.
- 키가 유출되면 공격자가 가짜 PAC를 생성해 보호 기능을 무력화할 수 있습니다.
- 단순 장애 탐지 (시스템 견고성)
- 키의 기밀성보다는 데이터 무결성이 중요합니다.
- 키를 하드코딩하거나 여러 소프트웨어가 공유하는 등 비교적 단순하게 관리할 수 있습니다.
3. 보안을 위한 PAC 키 관리 권장 사항
- 하드코딩 금지 : 프로그램 이미지에 키 값을 직접 넣으면, 펌웨어 분석으로 키가 노출될 수 있어 장비가 위험함.
- 예측 불가능성 : 시리얼 번호처럼 추측하기 쉬운 값을 키로 쓰지 말고, 난수 생성기(TRNG/PRNG)를 활용
- 권한 분리 : 키 값은 반드시 권한이 있는 상태에서만 접근 가능한 메모리에 저장해야 함.
- 디버그 차단 : 메모리에 대한 디버그 접근을 비활성화하여 공격자가 스택이나 키를 훔쳐보지 못하게 해야 함.
4. 키 개수
키를 많이 쓸수록 보안은 강력해지지만, 시스템 비용은 증가합니다.
시스템 환경 권장 키 구성
| Baremetal / 단일 권한 도메인 | 하나의 PAC 키 사용 가능 |
| Secure + Non-secure 환경 | Secure world와 Non-secure world가 서로 다른 키 사용 |
| RTOS + 프로세스 격리 지원 | privileged / unprivileged 영역별 키 분리 |
| 높은 보안 수준 요구 | 각 unprivileged 컨텍스트(프로세스/스레드)별 독립 키 사용 |
*성능 팁 : 모든 키를 TRNG로 생성하면 시간 문제, 성능 문제가 발생할 수 있기 때문에, TRNG로 시드를 만들고 이를 PRNG에 넣어 여러 개의 키를 빠르게 생성하는 방식이 있습니다.
5. 추가 고려 사항
- 컨텍스트 스위칭 : 스레드마다 다른 키를 쓴다면, OS가 스레드를 바꿀 때마다 PAC 키 레지스터도 함께 갱신해야 해서 컨텍스트 스위칭 오버헤드를 증가시킴
- 슬립 모드 : 프로세서 전원이 꺼지는 깊은 슬립 모드에서도 스택 데이터가 유지된다면, PAC 키 역시 안전한 영역에 보존되어야 합니다.
- 부팅 속도 최적화 : 백그라운드에서 미리 난수를 생성해 비휘발성 메모리에 저장해두면, 다음 부팅 시 키 생성 시간을 단축할 수 있습니다.
3) Instructions
PAC은 단순히 암호 알고리즘만 있다고 완성되지 않고, 이를 실행하는 전용 명령어 세트와 엄격한 검증 메커니즘이 조화를 이루어야 합니다.
1. PAC의 핵심: 서명과 인증
PAC는 두 종류의 명령어가 '쌍'으로 동작하며 포인터의 무결성을 보호합니다.
- 서명 과정 [PAC 명령어] : 암호학적 알고리즘을 사용해 PAC 값을 생성합니다.
- 인증 과정 [AUT 명령어] : 생성된 PAC 값을 검증하여 포인터가 변조되지 않았는지 확인합니다.
2. PAC 계산을 위한 3가지 입력 요소
PAC* 명령어가 32비트 PAC 값을 계산할 때 참조하는 데이터는 다음과 같습니다.
- 암호학적 키 레지스터 : 보안 상태 및 권한 수준에 따라 선택되는 비밀 키
- Modifier 레지스터 : 컨텍스트 값(예: SP)을 넣어 같은 키로도 다른 결과값을 유도
- 포인터 레지스터 : 인증하고자 하는 실제 주소 값
3. Armv8.1-M에서 제공하는 주요 명령어
명령어 설명
| PAC R12, LR, SP | LR을 address(address), SP를 modifier, 그리고 키를 사용하여 PAC를 계산한다. 계산된 PAC는 R12에 기록된다. |
| PACG Rd, Rn, Rn | 두 개의 입력 레지스터와 키를 사용하여 PAC를 계산한다. 계산된 PAC는 지정된 레지스터 Rd에 기록된다. |
| PACBTI R12, LR, SP | LR을 주소, SP를 modifier, 그리고 키를 사용하여 PAC를 계산하며, 추가로 검증용 BTI landing pad도 제공한다. |
| AUT R12, LR, SP | SP를 modifier로 사용하여 LR을 인증한다. |
| AUT Ra, Rn, Rm | Rm을 modifier로 사용하여 Rn을 인증한다. |
| BXAUT Ra, Rn, Rm | Rm을 modifier로 사용하여 Rn을 인증한 뒤 Branch and Exchange를 수행한다. |
4. 알아두어야 할 메커니즘
예외 처리 (INVSTATE Usage Fault)
인증 과정에서 다음과 같은 상황이 발생하면 즉시 Usage Fault가 발생하여 시스템을 보호합니다.
- PAC 검증 결과가 일치하지 않을 때
- 입력 인수가 손상되었을 때
- PAC 생성 시점과 인증 시점 사이에서 보안 상태나 권한 수준이 변경되었을 때
하위 호환성 (NOP-compatible)
- PAC, PACBTI, AUT 명령어는 NOP 호환 인코딩 공간에 위치합니다.
- 즉, PAC 기능을 지원하지 않는 구형 프로세서에서는 이 명령어들이 NOP(No Operation)로 처리되어 코드가 멈추지 않고 그대로 실행됩니다.
부채널 공격 방지 (Side-channel Protection)
- 보안을 위해 PAC*, AUT* 등의 명령어는 키 값에 따라 실행 시간이 달라지지 않습니다.
- 이는 실행 시간 차이를 이용해 키를 유추하는 부채널 공격을 방지하기 위함입니다.
6. 포인터 인증 활성화
PACBTI 확장이 구현된 시스템에서 PAC 기능은 항상 켜져 있는 것이 아닙니다. Armv8.1-M은 보안 상태와 권한 수준에 따라 PAC를 세밀하게 제어할 수 있는 Banked 레지스터 구조를 제공합니다.
1) 권한별 / 보안 상태별 개별 제어
- PAC 활성화 비트는 Secure와 Non-secure 상태로 각각 분리되어 있으며, 다시 Privileged(권한)와 Unprivileged(비권한) 모드로 세분화됩니다.
- 이를 통해 시스템 설계자는 특정 모드에서만 PAC를 선택적으로 활성화할 수 있습니다.
모드 (Privilege) Non-secure 상태 제어 Secure 상태 제어
| 모드 (Privilege) | Non-secure 상태 재 | Secure 상태 제어 |
| Privileged | CONTROL_NS.PAC_EN | CONTROL_S.PAC_EN |
| Unprivileged | CONTROL_NS.UPAC_EN | CONTROL_S.UPAC_EN |
2) 핵심 제어 비트 상세
1. CONTROL.PAC_EN (Privileged Mode)
- 역할 : 권한 접근 시 포인터 인증 기능을 활성화합니다.
- 작동 : 이 비트가 1일 때만 권한 모드에서 PAC* 및 AUT* 명령어가 실제 계산과 검증을 수행합니다.
- 특징 : 보안 상태별로 별도 관리됩니다.
2. CONTROL.UPAC_EN (Unprivileged Mode)
- 역할 : 비권한 사용자 애플리케이션 레벨에서 포인터 인증을 활성화합니다.
- 작동 : 1로 설정되면 사용자 모드에서도 PAC를 생성하고 검증할 수 있습니다.
- 특징 : 마찬가지로 보안 상태별로 별도 관리되어, Secure 유저 모드와 Non-secure 유저 모드를 다르게 설정할 수 있습니다.
3) 제어 비트 설정 값의 의미
- 0 (Disabled) : 포인터 인증 비활성화, 해당 모드에서 PAC 관련 명령어가 무효화되거나 NOP처럼 동작할 수 있습니다.
- 1 (Enabled) : 포인터 인증 활성화, 명령어가 정상적으로 PAC 값을 계산하고 검증 프로세스를 수행합니다.
7. 결론
PAC는 함수의 반환 주소를 암호학적으로 보호하여 ROP 공격을 하드웨어 수준에서 차단하는 핵심 기술입니다. 성능 저하를 최소화하면서도 강력한 보안성을 제공하여 임베디드 및 모바일 시스템의 무결성을 보장합니다.
8. 느낀점
단순히 주소를 숨기는 것이 아니라, SP와 키를 조합한 가변적인 서명 방식을 통해 보안을 완성하는 설계가 인상적이었습니다. 하위 호환성까지 고려한 Arm 아키텍처의 정교함을 배울 수 있었습니다.
'프로젝트' 카테고리의 다른 글
| react2shell 취약점 분석 (0) | 2026.05.12 |
|---|---|
| Push & Pull -- Git 원격 저장소! (0) | 2026.05.06 |
| 고급 문제 (0) | 2026.05.06 |
| 종합선물세트 (0) | 2026.05.06 |
| 코드 이리저리 옮기기 (0) | 2026.05.06 |