作者: wzt
本文為作者投稿,Seebug Paper 期待你的分享,凡經采用即有禮品相送! 投稿郵箱:paper@seebug.org

內核pac key初始化

common_start

mrs   x0, S3_4_C15_C0_4

and   x1, x0, #0x2

cbz   x1, 0xfffffff0081384d0

orr   x0, x0, #0x1

orr   x0, x0, #0x4

msr   S3_4_C15_C0_4, x0

Isb

首先探測下S3_4_C15_C0_4寄存器的第2個bit是否為0, 如果為0, 則一直等待下去,否則把其第1個和第3個比特位置1,結合后面對_ml_set_kernelkey_enabled的分析,可以推測出S3_4_C15_C0_4寄存器是蘋果公司為增強pac而加入的控制寄存器。

LDR       X0, =0xFEEDFACEFEEDFACF

MSR       #0, c2, c1, #2, X0 ; APIBKeyLo_EL1

ADD       X0, X0, #1

MSR       #0, c2, c1, #3, X0 ; APIBKeyHi_EL1

ADD       X0, X0, #1

MSR       #0, c2, c2, #2, X0 ; APDBKeyLo_EL1

ADD       X0, X0, #1

MSR       #0, c2, c2, #3, X0 ; APDBKeyHi_EL1

ADD       X0, X0, #1

MSR       #4, c15, c1, #0, X0; ???

ADD       X0, X0, #1

MSR       #4, c15, c1, #1, X0; ???

ADD       X0, X0, #1

MSR       #0, c2, c1, #0, X0 ; APIAKeyLo_EL1

ADD       X0, X0, #1

MSR       #0, c2, c1, #1, X0 ; APIAKeyHi_EL1

ADD       X0, X0, #1

MSR       #0, c2, c2, #0, X0 ; APDAKeyLo_EL1

ADD       X0, X0, #1

MSR       #0, c2, c2, #1, X0 ; APDAKeyHi_EL1

ADD       X0, X0, #1

MSR       #0, c2, c3, #0, X0 ; APGAKeyLo_EL1

ADD       X0, X0, #1

MSR       #0, c2, c3, #1, X0 ; APGAKeyHi_EL1

Iphone12使用固定值0xFEEDFACEFEEDFACF依次初始化APIAKey_EL1APIBKey_EL1APDAKey_EL1APDBKey_EL1APGAKey_EL1寄存器。高版本的Iphone內核是否也使用固定值有待確認。

MOVK       X0, #0,LSL#48

MOVK       X0, #0,LSL#32

MOVK       X0, #0x3454,LSL#16

MOVK       X0, #0x593D ; 0x3454593d

ORR       X0, X0, #0x40000000 ; 0x7454593d

MSR       #0, c1, c0, #0, X0 ; SCTLR_EL1

很奇怪這里sctlr_el1只設置了EnIB位(筆者翻遍了代碼,也沒發現對sctlr_el1其他pac比特位的使用)。

用戶進程pac key初始化

通過execve執行一個binary時,XNU內核會進行新進程pac key的初始化,XNU將每個進程的pac key保存在一個叫做shared region的進程內存區域,這個區域可以被其他進程共享代碼和數據,用來加快進程的啟動速度,通常情況下一個進程只有一個shared region區域,但是在支持pac的情況下,shared region根據不同的shared_region_id會有不同的shared region區域,通過一個隊列_shared_region_jop_key_queue鏈接起來,每個節點是一個struct shared_region_jop_key_map類型的結構體,xnu source code有如下定義:

typedef struct shared_region_jop_key_map {

    queue_chain_t  srk_queue;

   char      *srk_shared_region_id;

   uint64_t    srk_jop_key;

    os_refcnt_t   srk_ref_count;     /* count of tasks active with this shared_region_id */

} *shared_region_jop_key_map_t;

成員srk_jop_key保存的是這個region所使用的pac key。下面我們來看下pac key是如何被初始化的。

/*

void

shared_region_key_alloc(char *shared_region_id, bool inherit, uint64_t inherited_key)

 */

exec_mach_imgact->_shared_region_key_alloc

LDR       X8, [X24] ; _shared_region_jop_key_queue

CMP       X8, X24

B. EQ       loc_FFFFFFF007B33338 ;

遍歷_shared_region_jop_key_queue,如果隊列為空,跳轉到后面去申請一個新的shared_region節點。

MOV       X9, X8

LDR       X10, [X9,#0x10]

MOV       X11, X22

LDRB       W12, [X10]

LDRB       W13, [X11]

CMP       W12, W13

B.NE       loc_FFFFFFF007B3332C

ADD       X11, X11, #1

ADD       X10, X10, #1

CBNZ       W12, loc_FFFFFFF007B3330C

B        loc_FFFFFFF007B33450 ; srk_ref_count

LDR       X9, [X9]

CMP       X9, X24

C. NE       loc_FFFFFFF007B33304

循環遍歷每個shared_region節點,如果節點名與第一個參數相同,證明存在一個要匹配的節點。

__TEXT_EXEC:__text:FFFFFFF007B33450

E ADD       X0, X9, #0x20 ; ' ' ; srk_ref_count

MOV       W8, #1__TEXT_EXEC:__text:FFFFFFF007B33458         LDADD      W8, W8, [X0] ; srk_ref_count++

將引用計數srk_ref_count加1.

CBZ       W8, loc_FFFFFFF007B33554

MOV       W10, #0xFFFFFFF

CMP       W8, W10

B. CS       loc_FFFFFFF007B33558

判斷引用計數是否溢出

MOV       X22, X21 ; x21 == 0

MOV       X21, X9

CBZ       W20, loc_FFFFFFF007B33488 ; inherit == 0

LDR       X8, [X21,#0x18] ; inherit == 1 -> srk_jop_key

LDR       X9, [SP,#0x70+var_60]

CMP       X8, X9  ; arg3: inherited_key

C. NE       loc_FFFFFFF007B3356C

如果第二個參數inherit為1,并且此shared_region保存的pac key與第三個參數不相同,則panic,否則直接返回,不需要更新pac key。

下面看下隊列如果為空,或者沒有找到要匹配到的shared_region_id的后續處理流程。

ADRL       X0, _KHEAP_DEFAULT

MOV       W1, #0x28 ; '('

MOV       W2, #0

ADRL       X3, _shared_region_key_alloc.site

BL        _kalloc_ext

分配一個新的struct shared_region_jop_key_map結構體

MOV       X21, X0 ; new struct shared_region_jop_key_map

MOV       X0, X22 ; __s

BL        _strlen ; shared_region_id

計算參數1shared_region_id的長度

ADD       W28, W0, #1

ADRL       X0, _KHEAP_DATA_BUFFERS

MOV       X1, X28

MOV       W2, #0

ADRL       X3, _shared_region_key_alloc.site.3

BL        _kalloc_ext ; alloc shared_region_id buffer

分配shared_region_id內存

STR       X0, [X21,#0x10] ; set srk_shared_region_id

保存shared_region_idstruct shared_region_jop_key_map對應成員。

MOV       X1, X22 ; __source

MOV       X2, X28 ; __size

LDR       X23, [SP,#0x70+var_60]

BL        _strlcpy ; strlcpy(srk_shared_region_id, shared_region_id, size);

拷貝shared_region_id

MOV       W8, #1

STR       W8, [X21,#0x20] ; srk_ref_count = 1

引用計數srk_ref_count初始化為1。

ADRP       X8, #_diversify_user_jop@PAGE

LDR       W9, [X8,#_diversify_user_jop@PAGEOFF]

CMP       W9, #0

CSET       W8, NE

TST       W8, W20 ; arg2: inherit

MOV       X8, #0xFEEDFACEFEEDFAD5

CSEL       X8, X23, X8, NE ; x23: arg3 inherit

如果diversify_user_jop為1并且第2個參數inherit也為1,x8被設置為第三個參數inherit,否則設置為0xFEEDFACEFEEDFAD5。

ADRL       X24, _shared_region_jop_key_queue

CBZ       W9, loc_FFFFFFF007B33444

TBNZ       W20, #0, loc_FFFFFFF007B33444

MOV       X0, X22 ; __s

BL        _strlen

CBZ       X0, loc_FFFFFFF007B33434 ; strlen(shared_region_id) == 0

如果shared_region_id長度為0, x8設置為0xFEEDFACEFEEDFAD5。我們看到用戶進程的pac key居然和內核的pac key0xFEEDFACEFEEDFACF值很接近!

__TEXT_EXEC:__text:FFFFFFF007B333FC

LDR       X8, [X19]

LDR       X0, [X26,#_prng_ctx@PAGEOFF]

BLRAA      X8, X28

LDR       X8, [X19,#(qword_FFFFFFF0076E7760 - 0xFFFFFFF0076E7758)]

LDR       X0, [X26,#_prng_ctx@PAGEOFF]

MRS       X9, #0, c13, c0, #4 ; TPIDR_EL1

LDR       X9, [X9,#0x4F8]

LDRH       W1, [X9]

ADD       X3, SP, #0x70+var_58

MOV       W2, #8

BLRAA      X8, X25

LDR       X8, [SP,#0x70+var_58]

CBZ       X8, loc_FFFFFFF007B333FC

獲取一個隨機數,賦值給x8。

STR       X8, [X21,#0x18]

X8為要保存的pac key值。

總結以下,一個進程可以繼承父進程的pac key,可以是個隨機值,還可以是固定值0xFEEDFACEFEEDFAD5。

這里還存在另一個安全問題,雖然把生成隨機數的兩個函數指針都使用pac簽名過,但是pac計算過程中使用的context值居然是個固定的4個字節值:0x2ABE和0x9BF6。這會弱化隨機數生成函數的安全性。

進程之間切換pac

由于每個進程的pac key可能不同,所以在進程切換的時候,也需要切換pac key。

_Switch_context

STR       XZR, [X3,#0x78] ; SS64_KERNEL_PC

MOV       W4, #0x100004 ; PSR64_KERNEL_POISON

STR       W4, [X3,#0x80] ; SS64_KERNEL_CPSR

STP       X0, X1, [SP,#var_10]!

STP       X2, X3, [SP,#0x10+var_20]!

STP       X4, X5, [SP,#0x20+var_30]!

MOV       X0, X3

MOV       X1, #0

MOV       W2, W4

MOV       X3, X30

MOV       X4, X16

MOV       X5, X17

BL        _ml_sign_kernel_thread_state ; compute old thread pac code.

在進程切換前,首先計算處當前進程的thread state pac值。linux內核也提供了pac服務,但是沒有對thread狀態做校驗。

__TEXT_EXEC:__text:FFFFFFF008139A8C _ml_sign_kernel_thread_state       ; CODE PACGA      X1, X1, X0

AND       X2, X2, #0xFFFFFFFFDFFFFFFF

PACGA      X1, X2, X1

PACGA      X1, X3, X1

PACGA      X1, X4, X1

PACGA      X1, X5, X1

STR       X1, [X0,#0x88] ; struct arm_kernel_saved_state->jophash

RET

Pacga指令用于計算大塊的內存區域,可以看到XNU使用pacga指令依次對x0: The ARM context pointer、x1: PC value to sign、x2: CPSR value to sign、x3: LR to sign、x16、x17做計算生成pac。

X0是一個struct arm_kernel_save_state的數據結構:

 struct arm_kernel_saved_state {

uint64_t x[12];   /* General purpose registers x16-x28 */

uint64_t fp;     /* Frame pointer x29 */

uint64_t lr;     /* Link register x30 */

uint64_t sp;     /* Stack pointer x31 */

uint64_t pc;     /* Program counter */

uint32_t cpsr;    /* Current program status register */

uint64_t jophash;

} __attribute__((aligned(16)));

在生成當前進程的thread state pac后,開始校驗下要切換的進程thread state狀態,如果校驗不過,證明要切換的進程狀態被篡改過,系統直接panic。

LDR       X3, [X2,#0x498] ; new TH_KSTACKPTR

MOV       X20, X0

MOV       X21, X1

MOV       X22, X2

MOV       X0, X3

LDR       W2, [X0,#0x80] ; new SS64_KERNEL_CPSR

DMB       LD

LDR       X1, [X0,#0x78] ; new SS64_KERNEL_PC

LDP       X16, X17, [X0]

MOV       X25, X3

MOV       X26, X4

MOV       X27, X5

MOV       X23, X1

MOV       X24, X2

LDR       X3, [X0,#0x68]

MOV       X4, X16

MOV       X5, X17

BL        _ml_check_kernel_signed_state ; check new thread pac code.

__TEXT_EXEC:__text:FFFFFFF008139AEC _ml_check_kernel_signed_state      ; CODE PACGA      X1, X1, X0

AND       X2, X2, #0xFFFFFFFFDFFFFFFF

PACGA      X1, X2, X1

PACGA      X1, X3, X1

PACGA      X1, X4, X1

PACGA      X1, X5, X1

LDR       X2, [X0,#0x88]

struct arm_kernel_saved_state結構體提取pac code。

CMP       X1, X2

B.NE       loc_FFFFFFF008139B14

RET

相同返回上層函數。

__TEXT_EXEC:__text:FFFFFFF008139B14 ;

PACIBSP

STP       X29, X30, [SP,#var_10]!

MOV       X29, SP

BL        _panic

不相同則panic系統。

LDR       X5, [X2,#0x4F8]

MOV       W6, #0

LDR       X3, [X2,#0x508]

LDR       X4, [X5,#0x200]

CMP       X3, X4

B.EQ       loc_FFFFFFF008138B48

STR       X3, [X5,#0x200]

MSR       #0, c2, c1, #2, X3 ; APIBKeyLo_EL1

ADD       X3, X3, #1

MSR       #0, c2, c1, #3, X3 ; APIBKeyHi_EL1

ADD       X3, X3, #1

MSR       #0, c2, c2, #2, X3 ; APDBKeyLo_EL1

ADD       X3, X3, #1

MSR       #0, c2, c2, #3, X3 ; APDBKeyHi_EL1

加載新進程的pac key到對應的系統寄存器。

內核pac與用戶進程pac切換

進入內核

進程每次通過系統調用進入內核或者發生錯誤進入內核異常處理流程時,需要把進程的pac key切換為內核的pac key。

__TEXT_EXEC:__text:FFFFFFF008131410 fleh_dispatch64

MOVK       X2, #0xFEED,LSL#48

MOVK       X2, #0xFACE,LSL#32

MOVK       X2, #0xFEED,LSL#16

MOVK       X2, #0xFAD5 ; 0xFEEDFACEFEEDFAD5

MRS       X3, #0, c13, c0, #4 ; TPIDR_EL1

LDR       X3, [X3,#0x4F8]

LDR       X4, [X3,#0x208]

CMP       X2, X4

B.EQ       loc_FFFFFFF0081314F0

MSR       #0, c2, c1, #0, X2 ; APIAKeyLo_EL1

ADD       X4, X2, #1

MSR       #0, c2, c1, #1, X4 ; APIAKeyHi_EL1

ADD       X4, X4, #1

MSR       #0, c2, c2, #0, X4 ; APDAKeyLo_EL1

ADD       X4, X4, #1

MSR       #0, c2, c2, #1, X4 ; APDAKeyHi_EL1

STR       X2, [X3,#0x208]

在進入內核時,首先要更新APIAKey_EL1APDAKey_EL1為固定值0xFEEDFACEFEEDFAD5極其增值。

MOV       X21, X1

MOV       X20, X30

MOV       X1, X22

MOV       W2, W23

MOV       X3, X20

MOV       X4, X16

MOV       X5, X17

BL        _ml_sign_thread_state

更新完pac key值,馬上對當前進程的thread state進行簽名。

退出內核

__TEXT_EXEC:__text:FFFFFFF00813197C exception_return_unint_tpidr_x3_dont_trash_x18

MOV       X0, SP

LDR       W2, [X0,#arg_110]

LDR       X1, [X0,#arg_108]

LDP       X16, X17, [X0,#arg_88]

MOV       X22, X3

MOV       X23, X4

MOV       X24, X5

MOV       X20, X1

MOV       X21, X2

LDR       X3, [X0,#arg_F8]

MOV       X4, X16

MOV       X5, X17

BL        _ml_check_signed_state

在退出內核到用戶態前,需要再次校驗下當前進程的thread state,如果發生錯誤,則panic系統。

LDR       X1, [X2,#0x510]

LDR       X2, [X2,#0x4F8]

LDR       X3, [X2,#0x208]

CMP       X1, X3

B.EQ       loc_FFFFFFF008131A3C

MSR       #0, c2, c1, #0, X1 ; APIAKeyLo_EL1

ADD       X3, X1, #1

MSR       #0, c2, c1, #1, X3 ; APIAKeyHi_EL1

ADD       X3, X3, #1

MSR       #0, c2, c2, #0, X3 ; APDAKeyLo_EL1

ADD       X3, X3, #1

MSR       #0, c2, c2, #1, X3 ; APDAKeyHi_EL1

STR       X1, [X2,#0x208]

恢復進程使用的pac key。

進程thread state pac的初始化

前面提到在進程切換時,需要驗證進程的thread state pac,那么它是在何時初始化的呢?

答案在進程初始化stack時進行thread state的pac計算。

_machine_stack_attach

STR       X1, [X0,#0x78] ; struct arm_kernel_saved_state->pc

MOV       X2, #0x100004

STR       W2, [X0,#0x80] ; struct arm_kernel_saved_state->cpsr

ADRL       X3, _thread_continue

STR       X3, [X0,#0x68] ; struct arm_kernel_saved_state->lr

MOV       X4, XZR

MOV       X5, XZR

STP       X4, X5, [X0]

MOV       X6, X30

BL        _ml_sign_kernel_thread_state ; sign thread state.

Ppl相關

Ppl提供了兩個服務用來給用戶進程數據進行簽名與驗簽。

_pmap_sign_user_ptr

__TEXT_EXEC:__text:FFFFFFF007B609A0 _pmap_sign_user_ptr

ADD       X29, SP, #0x30

MOV       X19, X0

MRS       X23, #3, c4, c2, #1 ; DAIFSet

TBNZ       W23, #7, loc_FFFFFFF007B609D0 ; TPIDR_EL1

MSR       #6, #7

關閉DAIF中斷

MRS       X8, #0, c13, c0, #4 ; TPIDR_EL1

LDR       X8, [X8,#0x4F8]

LDR       X20, [X8,#0x208]

MOV       W0, #0

MOV       X1, X3

BL        _ml_set_kernelkey_enabled

將0作為參數傳遞給_ml_set_kernelkey_enabled

__TEXT_EXEC:__text:FFFFFFF008139374 _ml_set_kernelkey_enabled        

MRS       X2, #0, c13, c0, #4 ; TPIDR_EL1

LDR       X2, [X2,#0x4F8]

LDR       X3, [X2,#0x208]

CMP       X1, X3

B. EQ       loc_FFFFFFF0081393A8 ; S3_4_C15_C0_4

如果當前進程的pac key和用戶傳遞進來的pac key相等,則直接跳轉到后面。

MSR       #0, c2, c1, #0, X1 ; APIAKeyLo_EL1

ADD       X3, X1, #1

MSR       #0, c2, c1, #1, X3 ; APIAKeyHi_EL1

ADD       X3, X3, #1

MSR       #0, c2, c2, #0, X3 ; APDAKeyLo_EL1

ADD       X3, X3, #1

MSR       #0, c2, c2, #1, X3 ; APDAKeyHi_EL1

STR       X1, [X2,#0x208]

依次切換系統寄存器APIAKey_EL1APDAKey_EL1,可以看到ppl只用key A來簽名用戶代碼或數據。

MRS       X1, #4, c15, c0, #4 ; S3_4_C15_C0_4

ORR       X3, X1, #4

AND       X2, X1, #0xFFFFFFFFFFFFFFFB

CMP       W0, #0

CSEL       X1, X2, X3, EQ

MSR       #4, c15, c0, #4, X1 ; S3_4_C15_C0_4

ISB

RET

如果傳遞給_ml_set_kernelkey_enabled函數的第一個參數為0,那么將S3_4_C15_C0_4的第3個bit置0,否則置1。

回到_pmap_sign_user_ptr

CMP       W22, #2

B.EQ       loc_FFFFFFF007B609FC

CBNZ       W22, loc_FFFFFFF007B60A34

PACIA      X19, X21

B        loc_FFFFFFF007B60A00

PACDA      X19, X21

如果第2個參數為2,則使用pacda對第1個參數簽名,如果為0,使用pacia進行簽名,如果不是0,也不是2, 則panic系統。

MOV       W0, #1

MOV       X1, X20

BL        _ml_set_kernelkey_enabled

將1作為參數傳遞給_ml_set_kernelkey_enabled

_ml_set_kernelkey_enabled(0, pac_key)

...

Pacia/pacib

...

_ml_set_kernelkey_enabled(1, pac_key)

因此我們可以推測出S3_4_C15_C0_4的第2個bit為上鎖功能, 0代表上鎖,1代表解鎖。

_pmap_auth_user_ptr

AUTIA      X19, X21

B        loc_FFFFFFF007B60AD4

AUTDA      X19, X21

B        loc_FFFFFFF007B60AD4

AUTDB      X19, X21

B        loc_FFFFFFF007B60AD4

AUTIB      X19, X21

這里我們看到_pmap_auth_user_ptr在驗簽的時候還使用了key b,而_ml_set_kernelkey_enabled只設置了key a, 說明了key a是進程相關的,而key b是進程不相關的, XNU源碼里定義了以下幾種類型的pac key:

EXTERNAL_HEADERS/ptrauth.h

typedef enum {

 ptrauth_key_asia = 0,

 ptrauth_key_asib = 1,

 ptrauth_key_asda = 2,

 ptrauth_key_asdb = 3,



 /* A process-independent key which can be used to sign code pointers.

   Signing and authenticating with this key is a no-op in processes

   which disable ABI pointer authentication. */

 ptrauth_key_process_independent_code = ptrauth_key_asia,



 /* A process-specific key which can be used to sign code pointers.

   Signing and authenticating with this key is enforced even in processes

   which disable ABI pointer authentication. */

 ptrauth_key_process_dependent_code = ptrauth_key_asib,



 /* A process-independent key which can be used to sign data pointers.

   Signing and authenticating with this key is a no-op in processes

   which disable ABI pointer authentication. */

 ptrauth_key_process_independent_data = ptrauth_key_asda,



 /* A process-specific key which can be used to sign data pointers.

   Signing and authenticating with this key is a no-op in processes

   which disable ABI pointer authentication. */

 ptrauth_key_process_dependent_data = ptrauth_key_asdb,



 /* The key used to sign C function pointers.

   The extra data is always 0. */

 ptrauth_key_function_pointer = ptrauth_key_process_independent_code,



 /* The key used to sign return addresses on the stack.

   The extra data is based on the storage address of the return address.

   On ARM64, that is always the storage address of the return address plus 8

   (or, in other words, the value of the stack pointer on function entry) */

 ptrauth_key_return_address = ptrauth_key_process_dependent_code,



 /* The key used to sign frame pointers on the stack.

   The extra data is based on the storage address of the frame pointer.

   On ARM64, that is always the storage address of the frame pointer plus 16

   (or, in other words, the value of the stack pointer on function entry) */

 ptrauth_key_frame_pointer = ptrauth_key_process_dependent_data,


 /* The key used to sign block function pointers, including:

    invocation functions,

    block object copy functions,

    block object destroy functions,

    __block variable copy functions, and

    __block variable destroy functions.

   The extra data is always the address at which the function pointer

   is stored.



   Note that block object pointers themselves (i.e. the direct

   representations of values of block-pointer type) are not signed. */

 ptrauth_key_block_function = ptrauth_key_asia,



 /* The key used to sign C++ v-table pointers.

   The extra data is always 0. */

 ptrauth_key_cxx_vtable_pointer = ptrauth_key_asda,



 /* Other pointers signed under the ABI use private ABI rules. */



} ptrauth_key;

Paper 本文由 Seebug Paper 發布,如需轉載請注明來源。本文地址:http://www.bjnorthway.com/1777/