Pointer authentication에 이어 PAC의 보안 및 취약점에 관련된 내용입니다.
모든 설명은 ARM manualDDI 0487 G.b를 기준으로 합니다.
PAC의 security는 1차적으로 pointer address의 MAC을 계산하는 알고리즘과, key size 그리고 어떻게 key management를 할 것인지에 의존합니다.
Pointer address의 인증코드를 생성하는 Key는 아래와 같이 5가지가 있었습니다.
여기서 A key는 user mode, B key는 kernel mode에서 사용됩니다.
128bit key를 정의하고 있고, context/tweak는 64bit로 한정됩니다.
entropy를 계산한다면, 128bit randomness에 64bit combination만큼 PAC가 다양하게 생성될 수 있습니다, 단 key가 매번 바뀐다면 말이죠.
ID_AA64ISAR1_EL1.API
bit열에 따라 algorithm을 상세할 수 있게 정의하였습니다.
D5.1.5 Pointer authentication in AArch64 state
For the Pointer authentication instructions, it is IMPLEMENTATION DEFINED whether PACs are generated using:
• The QARMA algorithm, see The QARMA Block Cipher Family. When this is the case, the value of ID_AA64ISAR1_EL1.APA is non-zero.
• An IMPLEMENTATION DEFINED algorithm. When this is the case, the value of ID_AA64ISAR1_EL1.API is non-zero.
QARMA 라고 하는 알고리즘은, Qualcomm에서 디자인한 symmetric block cipher 알고리즘인데요, S-box에 keyed hash를 차용하여 tweak을 추가할 수 있도록 만들어졌습니다: 관련 whitepaper
QARMA를 사용하거나, 혹은 Hardware vendor에서 구현하기 위해서는 ARM reference에 있는 pseudo code를 사용한 알고리즘을 구현해야 합니다.
AP vendor마다 구현이 다를 수 밖에 없는데요, 보통은 Hardware crypto block을 이용하게 됩니다.
PAC와 연관되는 system register들은 4가지 정도가 있습니다.
각 5개의 key마다, low:high bit로 구분되어 각각의 register가 존재합니다.
예를 들면 APIAKey는 apiakeylo_el1
, apiakeyhi_el1
가 대응하는 식입니다.
PAC key들은 memory가 아니라 system register로 관리하고, 해당 system register는 EL0에서는 접근할 수 없습니다.
SCTLR_EL1.EnIA
, bit [31]에서는 apiakeylo_el1
, apiakeyhi_el1
를 실제로 사용할지 여부를 enable하는 용도로 사용됩니다.
D13.2.116 SCTLR_EL1, System Control Register (EL1)
EnIA Controls instructions that apply to PACs for instruction addresses that are generated using the APIAKey_EL1 key.
같은 방식으로 EnIA
bit [31], EnIB
bit [30], EnDA
bit [27], EnDB
bit [13] 가 존재합니다.
D13.2.115 SCR_EL3, Secure Configuration Register
API: Controls the use of the following instructions related to Pointer Authentication.
APK: Trap registers holding "key" values for Pointer Authentication.
EL3에서만 접근 가능한 API
bit [17]과 APK
bit [16]이 추가되었습니다.
API: PAC instruction fault 발생 시, EL3 trap 이 되고 ESR_ELx.EC
bit에 해당 fault가 기록됩니다.
APK: PAC key register 접근 시, EL3 trap됩니다. 마찬가지로 ESR_ELx.EC
bit에 기록됩니다.
PAC와 관련된 fault들은 모두 ESR을 통해 기록되는데요,
EC
(Exception class) bit [31:26]을 보시면 됩니다.
EC == 0b011100
로 기록되면 해당 failure는 PAC와 관련된 에러가 발생된 것입니다.
PAC size에 따라 성공확률은 다르겠지만 brute force 시도가 가능합니다. PACIASP
와 같이 sp
를 context input으로 사용한다면 더욱 복잡도는 올라가겠지만, context를 사용하지 않는 PACIAZ
와 같은 것들은 더 취약할 수 있습니다.
위에 언급한 system register들도 취약점이 될 수 있습니다. 알려진 kernel ACE (arbitrary code execution)를 통해 EL1 권한을 얻어 system register들을 조작할 수 있죠, SCR은 PAC enable에 critical bit이기 때문에 가장 취약합니다.
EL1 권한을 얻으면 무엇인들 못 하리
추가적으로 kernel에서 구현방식에 따라서, system register접근이 아닌 memory 접근을 통해 key를 접근할 수도 있습니다.
실제로 OS 구현에 따라, 종종 memory load를 해서 PAC를 사용하고 있는 경우도 있습니다. 그러나 대부분의 OS에서는 memory load를 부팅 극 초반에만 하고, (ARM ATF가 그러합니다) 그 후에는 register를 통해 접근하도록 key를 memory에서 지워버립니다.
EL1에서 사용하는 B key는 user process와 관계없이 동일한 key가 사용되기 때문에, 상황에 따라 forgery가 쉬울 수 있습니다.
ROP는 ROP gadget을 모아서 하는 방식이죠, 반대로 PAC 공격은 PAC signing gadget을 모아서 공격자가 원하는 어떤 pointer든 signing을 받으면 됩니다.
마지막으로 Project Zero팀이 Examining Pointer Authentication on the iPhone XS에서 소개한 내용과 공격기법을 소개합니다.
Reading the PAC keys from memory
PAC key를 register -> kernel memory로 load시에 read/write 가능 하지 않을까?
Signing kernel pointers in user-space
kernel pointer에 대한 PAC를 user space에서 forge 가능하지 않을까?
A12 boot 코드 분석을 보면, common_start
에서 EL1/EL0 process간에 PAC key 구분 없이 고정된 값인 0xFEEDFACEFEEDFACF으로 시작을 하고 있습니다. MSR
은 ARM co-processor 명령어와 유사한데요, c2, c1, #?, X0
은 APIBKey를 세팅하는데 사용됩니다.
common_start+A8
LDR X0, =0xFEEDFACEFEEDFACF ;; x0 = pac_key
MSR #0, c2, c1, #2, X0 ;; APIBKeyLo_EL1
MSR #0, c2, c1, #3, X0 ;; APIBKeyHi_EL1
...
단순 초기값일 수 있으니 부팅을 반복하면서, KASLR offset에 따른 kernel function pointer (PACIZA
-ed)를 보면 iBoot에서 random value로 변경하는 것이 아닌가 추정하고 있습니다.
여전히 memory 값은 0xFEEDFACEFEEDFACF인 걸 보면, 이 시점부터 register로 이미 PAC key를 세팅해놓고 memory 값은 dummy인 것으로 예상됩니다
switch_context
에서 offset 0x458위치가 pac_key_seed
로 사용하는 것으로 추정하여, dump를 떠보면
pid 0 thread ffffffe00092c000 pac_seed feedfacefeedfacf
pid 0 thread ffffffe00092c550 pac_seed feedfacefeedfacf
pid 0 thread ffffffe00092caa0 pac_seed feedfacefeedfacf
...
pid 258 thread ffffffe003597520 pac_seed 51c6b449d9c6e7a3
pid 258 thread ffffffe003764aa0 pac_seed 51c6b449d9c6e7a3
kernel thread (pid 0)의 pac_key는 동일하다고 읽혀지지만, 실제로 PAC의 포인터는 부팅 시마다 다른 것으로 보입니다.
이 때문에 여러가지 실험을 해 보게 되는데요,
kernel과 user 간 PACIZA, 또는 kernel에서 생성된 PACIZA'd pointer를, user space에서의 PACIZB pointer로 접근하는 시도 모두 실패하게 됩니다.
당연하겠지만 user/kernel PAC는 다릅니다.
gettime = fffffff0161f2050
kPACIZA = faef2270161f2050
uPACIZA = 138a8670161f2050
uPACIZB forge = d7fd0ff0161f2050
결국 bypass experiments는 모두 실패하고, 추정해보는 것은 두가지인데요
Secure monitor에서 EL transition마다 PAC key를 re-write 하거나
iBoot에서 PAC key에 직접 접근하거나 (A12 내에 PAC를 옮겨두는 것도 가정하고 있습니다) 하는 것이죠.
한가지 흥미로운 것은, iBoot/kernel에서 user space poiter를 signing 하고 있는 흔적이 있는 것인데요,
음 아래와 같이 특정 co-processor register를 masking 한 채로 user sapce의 PAC를 생성해주고 있습니다. (아마도 SCTLR
을 건드려서 특정 PAC key를 사용하지 않도록 조정하는 것 같습니다)
MRS X8, #4, c15, c0, #4 ; S3_4_C15_C0_4
AND X8, X8, #0xFFFFFFFFFFFFFFFB
MSR #4, c15, c0, #4, X8 ; S3_4_C15_C0_4
ISB
... ;; PAC stuff for userspace
MRS X8, #4, c15, c0, #4 ; S3_4_C15_C0_4
ORR X8, X8, #4
MSR #4, c15, c0, #4, X8 ; S3_4_C15_C0_4
ISB
마지막으로는 PACIZA
'd pointer를 활용한 signing gadget 찾기인데요.
전역함수 pointer로 PACIZA
가 참조되는 부분을 찾는 형태로 sysctl_unregister_oid
함수를 분석하고 있습니다.
사실 그보다는 PACIA/PACDA gadget을 stack frame이 설정되기 전에 찾아냈다는 점이 더 의미있는 취약점이 될 것 같습니다. JOP를 활용하여 해당 signing gadget으로 직접 jump하면 공격자가 원하는 pointer를 signing 해 주는 gadget을 구성하게 됩니다.
사용자 입장에서는 compiler option을 사용하는 것만으로도 ROP/JOP를 방어할 수 있지만, 간단하게는 user space단에서 key management 취약점 하나만 나와도 PAC를 무력화시킬 수 있습니다. 그 점을 제일 먼저 파고 든 연구가 HackPac: Hacking Pointer Authentication in iOS User Space, DEFCON 27 이었죠,
A12 취약점, Examining Pointer Authentication on the iPhone XS, 은 kernel 단에서 user space까지 포괄하는 거의 모든 취약점을 다루었다고 생각합니다. 아직 A12 외에 PAC가 상용화된 AP가 없다보니 취약점 발표가 정체되어 있는 느낌인데, 2022년을 기점으로 ARMv9 반도체가 많이 상용화 되면서 취약점도 다시 발표되길 기대해봅니다 :)
번외로 최근에는 code pointer 뿐만 아니라, llvm compiler를 수정하여 data pointer에도 PAC를 적용하는 연구도 진행되고 있습니다. Apple이 llvm에 patch submission도 했습니다!