IOS SPTM深度分析
原创 wzt 2026-01-02 17:57 浙江

本文深度分析了IOS SPTM代码实现细节
1 SPTM启动
2 GL Entry分析
3 Exception处理分析
4 Sptm页表保护
5 Sptm retype分类保护
6 TXM分析
1. sptm启动
MRS X0, #0, c4, c2, #2 ; CurrentELCMP X0, #8B.NE loc_FFFFFFF00706B074 ; != EL2MRS X0, #4, c1, c1, #0 ; HCR_EL2ORR X0, X0, #0x8000000 ; TGE, bit [27] All exceptions that would be routed to EL1 are routed to EL2.ORR X0, X0, #0x400000000 ; E2H, bit [34], The facilities to support a Host Operating System at EL2 are enabled.MSR #4, c1, c1, #0, X0 ;
首先判断如果在el2状态,则设置hcr_el2的TGE和E2H状态。 sptm由iboot加载,iboot跳转到sptm时处于el2状态。
ADRL X30, sptm_init_subsystemMOV X21, X20ADRL X0, unk_FFFFFFF0070B0000ADD X0, X0, X22SUB X0, X0, X23MSR #5, #0MOV SP, X0MOV X24, #1B init_hardware_registers ;
执行init_hardware_registers,后在跳转到sptm_init_subsystem继续执行。
init_hardware_registers的执行流程:
- 设置SPRR_PPERM_EL1寄存器为0x2020a52a302afaf5。
- 设置TCR_EL1为0x0800236511a511
- 设置TTBR0_EL1为0xFFFFFFF007010000
- 设置TTBR1_EL1为0xFFFFFFF0070A0000
- 重新设置APCTL_EL1。
- 用0xFEEDFACEFEEDFACF依次设置APIBKey_EL1、APDBKey_EL1、KERNKE_EL1(苹果新增)、 APIAKey_EL1、APDAKey_EL1、APGAKey_EL1。
- 设置SCTLR_EL1为0x01000fc14713d。
- 设置若干HID*寄存器(苹果新增)。
sptm_init_subsystem的执行流程:
- 调用copy_and_clear_random_seed生成若干随机种子。
- 调用init_xnu_ro_data初始化xnu_ro_pagetables_begin、xnu_ro_pagetables_end、
、xnu_exc_return_handler、xnu_ctrr_dispatch_table等全局变量。
- 调用validate_region_order检查txm的地址范围。
- 调用register_papt_range依次注册SPTM-rx、SPTM-rw、SPTM-le、TXM-ro、TXM-rx、TXM-bx、TXM-rw、BootKC-ro、BootKC-rs、BootKC-rx、BootKC-bx、BootKC-rw、BootKC-le、TrustCache、CL4-ro、CL4-rx、CL4-rw、CL4-le、RAMDisk、RTBuddySeg、SEPFW、SEPPatches、uStuff、preoslog、BootArgs、ExclaveOSIntegrityCatalog、ExclaveOSTrustCache、AVAILABLE、PHYS_SLIDE等地址范围信息。
register_papt_range(char *name, uint64_t type, uint64_t flag){init_get_image_region(name);bootstrap_register_papt_range();}
bootstrap_register_papt_range向papt_range_array数组注册一个地址范围信息:
struct papt_range_array {char *name;+0x8:uint64_t type;+0x10:uint64_t start_address;+0x18:uint64_t base;+0x20:uint64_t end_address;+0x24:char flag;+0x28:};
bootstrap_register_papt_range(char *name, uint64_t type, uint64_t start_addr, uint64_t end_addr, uint64_t flag){papt_range_array[papt_range_array_length].name = name;papt_range_array[papt_range_array_length].type = type;papt_range_array[papt_range_array_length].start_address = start_addr;papt_range_array[papt_range_array_length]end_address = end_addr;papt_range_array[papt_range_array_length].flag = flag;papt_range_array_length++
Name | Type | Flag |
DeviceTree | 0xb | 0x20 |
SPTM-ro | 0x2 | 0x28 |
SPTM-rx | 0x4 | 0x21 |
SPTM-rw | 0x2 | 0x21 |
SPTM-le | 0x2 | 0x21 |
TXM-ro | 0x20 | 0x48 |
TXM-rx | 0x20 | 0x41 |
TXM-bx | 0x5 | 0x41 |
TXM-rw | 0x20 | 0x41 |
TXM-le | 0x20 | 0x41 |
BootKC-ro | 0xb | 0x48 |
BootKC-rs | 0xb | 0x41 |
BootKC-rx | 0xb | 0x41 |
BootKC-bx | x | 0x41 |
BootKC-rw | 0xb | 0x51 |
BootKC-le | 0xb | 0x51 |
TrustCache | 0x0 | 0x40 |
CL4-ro | 0x32 | 0x6 |
CL4-rx | 0x32 | 0x6 |
CL4-rw | 0x32 | 0x6 |
CL4-le | 0x32 | 0x6 |
RAMDisk | 0xb | 0x2 |
RTBuddySeg | 0xb | 0x2 |
SEPFW | 0xb | 0x2 |
SEPPatches | 0xb | 0x2 |
uStuff | 0xb | 0x2 |
Preoslog | 0xb | 0x2 |
BootArgs | 0xb | 0x0 |
ExclaveOSIntegrityCatalog | 0x32 | 0x6 |
ExclaveOSTrustCache | 0x32 | 0x6 |
AVAILABLE | 0x0 | 0x0 |
PHYS_SLIDE | 0xb | 0x0 |
- 调用sptm_bootstrap_early
- 调用初始化sprr相关寄存器:
设置SPRR_PPERM_EL1 - sprr kernel permission寄存器值为0x2020a52a302afaf5。
设置SPRR_UPERM_EL0 - sprr user permission寄存器值为0x2010002030100000。
- 调用init_gxf_mode_prev
设置GXF_CONFIG_EL1寄存器为1。
设置GXF_PABENTRY_EL1。
设置GXF_ENTRY_EL1为sub_FFFFFFF00706C808。
执行0x201420指令进入Guard Level模式, GL的入口地址设置为sub_FFFFFFF00706C808,
__text:FFFFFFF00706C808 sub_FFFFFFF00706C808 ; DATA XREF:MSR #6, c15, c11, #1, X0 ; TPIDR_GL2 - Software Thread ID Register (GL2)MOVK X0, #0x2020,LSL#48MOVK X0, #0xA52A,LSL#32MOVK X0, #0x302A,LSL#16MOVK X0, #0xFAE6 ; 0x2020a52a302afae6MSR #6, c15, c1, #6, X0 ; SPRR_PPERM_EL1 - SPRR Kernel Permission Configuration Register (EL1)MOVK X0, #0,LSL#48MOVK X0, #0,LSL#32MOVK X0, #0,LSL#16MOVK X0, #0xFFMSR #6, c15, c1, #0, X0 ; SPRR_CONFIG_EL1 - SPRR Configuration Register (EL1)RET
首先设置TPIDR_GL2寄存器为init_gl2_thread。
gl2_thread {+0x0:char domain_type;+0x8:uint64_t tls_index;+0x18:uint64_t gl2_sp;+0x20:uint64_t unknown;+0x28:char sptm_state;+0x30:uint64_t hop_flag;+0x38:struct save_caller_reg {uint64_t x0,x1,x2,x3,x4,x5,x6,x7;};+0x78:struct save_exception_reg {uint64_t 0x0;...uint64_t 0xb0;};+0xf8:uint64_t gl1_sp;+0x130:struct save_exception_reg {uint64_t 0x0;...uint64_t 0xb0;};+0x1f0:uint64_t gl0_sp;+0x520:union {struct config_reg {uint64_t save_caller_arg_address;uint64_t sprr_user_permission;uint64_t id_isar2_el1;uint64_t unknown;uint64_t apctl_el1;uint64_t sctlr_el1;};struct config_reg1 {uint64_t tpidr_el0;uint64_t tpidrro_el0;uint64_t sprr_pperm_el12;uint64_t sprr_uperm_el02uint64_t gxf_config_el12;uint64_t sprr_config_el12;...uint64_t tpidr_gl1;uint64_t unknown;uint64_t vbar_gl12;uint64_t unknown;+0x88:uint64_t hcr_el2;};+0x5b0:strucct config_reg2 {uint64_t;+0x88:uint64_t;};+0x640:struct unknown_reg_store {uint64_t x0,x1,x2,x3,x4,x5,x6,x7;};+0x668:char el_level;+0x680:uint64_t current_cpu;+0x7f0:uint64_t value;+0x7f8:uint64_t unknown;+0x870:struct unknown_ctrr_struct *ctrr;};struct unknown_ctrr_struct {uint64_t value1;+0x800:uint64_t value2;};
重新设置SPRR_PPERM_EL1 - sprr kernel permission寄存器值为0x2020a52a302afae6。
重新设置SPRR_CONFIG_EL1为0xff。
注意随后使用的是ret指令,而不是0x201400,说明并没有推出GL模式,从而让sptm从普通的el2模式进入到GL2模式继续运行。
- 调用init_sptm_mode继续初始化sptm。
锁定VMSA_LOCK_EL1寄存器。
用CTRR_A_UPR/LOW_EL1寄存器锁定el1地址范围。
用CTRR_A_UPR/LOW_EL2寄存器锁定el2地址范围。
调用init_gxf_mode_final,它将GXF_ENTRY_EL1设置为gxf_entry_el1函数地址,将VBAR_GL1设置为syn_handler_sp0_g12地址,将GXF_CONFIG_EL1重新设置为0x2d。
- 分别设置elr_gl1_cl4、elr_gl1_txm、elr_gl1_kernelcache三个全局变量。
- 调用sptm_register_dispatch_table注册当前domain的三个dispatch_table函数。
table_id | Dispatch function | Table_permissions |
0 | sptm_dispatch | 2 |
1 | sptm_dispatch | 4 |
2 | sptm_dispatch | 8 |
sptm_register_dispatch_table(uint64_t table_id, uint64_t dispatch_entry_point, uint64_t table_permissions)当gl2_thread->domain_type < 2时选择xnu_ctrr_dispatch_table,否则选择domain_id_array。
domain_id_struct {table_id_struct {uint64_t entry_point;uint64_t table_permissions;uint64_t value2;}[16];}[16];
domain_type][table_id].entry_point = dispatch_entry_point;domain_type][table_id].table_permissions = table_permissions;
- 至此sptm初始化完毕。
2 GL Entry分析
2.1 gxf_entry_el1分析
Kernelcache和sptm启动阶段的代码会调用0x201420指令进入Guard Level模式,在sptm的启动阶段将gxf_entry_el1写入GXF_ENTRY_EL1寄存器,通过x16对其传递参数。
除了0x201420指令,在kernelcache代码中,还看到了其他三种指令也能进入GL模式:
com.apple.kernel:__text:FFFFFFF0284F63F0 sptm_txm_call ; CODE XREF: com.apple.kernel:__text:FFFFFFF028266D8C↑pPACIBSPMOV W16, W0MOVK X16, #2,LSL#48MOVK X16, #0,LSL#32 ; 0x20000w0000000MOV X10, X1LDP X0, X1,LDP X2, X3,LDP X4, X5,LDP X6, X7,DCD 0x201420RETABDCD 0x201421DCD 0x201422DCD 0x201423
在对gxf_entry_el1代码的追踪中,发现只有0x201420可以向sptm传递参数,其他三个只能调用特定的几个dispatch函数。
gxf_entry_el1(uint64_t event_metadata){uint64_t esr_gl1 = msr(ESR_GL1) & 0x1f; // genter argif (!(word_FFFFFFF007094018 & 0xf)) {if (word_FFFFFFF007094018 != gl2_thread->0x680) {dispatch_state_machine(0xb, 0);}else {if (esr_gl1 == 0) {if (gl2_thread->unkown_flag != 0)dead_loop();uint64_t domain_id = (metadata >> 48) & 0xffff;if (domain_id == 0)dispatch_state_machine(2, event_metadata);else if (domain_id == 3)dispatch_state_machine(4, event_metadata);else if (domain_id == 2)dispatch_state_machine(3, event_metadata);elsegenter_dispatch_entry_error();}else if (esr_gl1 == 1) {if (gl2_thread->unkown_flag != 0)dead_loop();dispatch_state_machine(0xa, 0);}else {genter_panic();}}}else {if (esr_gl1 == 0) {if (gl2_thread->unkown_flag != 0)dead_loop();uint64_t domain_id = (metadata >> 48) & 0xffff;if (domain_id == 0)dispatch_state_machine(2, event_metadata);else if (domain_id == 3)dispatch_state_machine(4, event_metadata);else if (domain_id == 2)dispatch_state_machine(3, event_metadata);elsegenter_dispatch_entry_error();}else if (esr_gl1 == 1) {if (gl2_thread->unkown_flag != 0)dead_loop();dispatch_state_machine(8, 0);}else if (esr_gl1 == 2) {if (gl2_thread->unkown_flag != 0)dead_loop();dispatch_state_machine(3, 0x200000000000);}else if (esr_gl1 == 3) {if (gl2_thread->unkown_flag != 0)dead_loop();dispatch_state_machine(4, 0x3000000000000);}else {dead_loop();}}}
0x201420可以触发如下调用路径,可以传递参数:
dispatch_state_machine(2, event_metadata);dispatch_state_machine(4, event_metadata);dispatch_state_machine(3, event_metadata);
0x201421可以触发如下调用路径,不能传递参数:
dispatch_state_machine(0xa, 0);dispatch_state_machine(8, 0);
0x201422可以触发如下调用路径,不能传递参数:
dispatch_state_machine(3, 0x200000000000);
0x201423可以触发如下调用路径,不能传递参数:
dispatch_state_machine(4, 0x3000000000000);
2.2 dispatch_state_machine分析
dispatch_state_machine根据sptm的状态数据库sptm_state_array进行不同的操作。
sptm_state_array是一个二维数组:
sptm_state_struct {dispatch_table_struct {char next_state;void *func_addr;char domain_type;+0x18:uint64_t unknown_value;+0x20:}[12];+0x180:}[16];
通过gl2_thread->sptm_state定位一维数组索引,通过第一个参数event_type定位二维数组索引。

Func_addr为不同sptm state状态的驱动函数。
![]()
在对sptm_state_array这个庞大的状态数组进行分析后,得到了所有的状态驱动函数:
(gdb) p/x 0x598E0+0x180$3 = 0x59a60(gdb) p/x 0x5D548+0x4000$4 = 0x61548(gdb) p/x 0x5C370+0x4000$5 = 0x60370(gdb) p/x 0x5D04C+0x4000$6 = 0x6104c(gdb) p/x 0x5D098+0x4000$7 = 0x61098(gdb) p/x 0x5CCFC+0x4000$8 = 0x60cfc(gdb) p/x 0x5CD8C+0x4000$9 = 0x60d8c(gdb) p/x 0x5C0E4+0x4000$10 = 0x600e4(gdb) p/x 0x5D57C+0x4000$11 = 0x6157c(gdb) p/x 0x5C47C+0x4000$12 = 0x6047c(gdb) p/x 0x5C294+0x4000$13 = 0x60294(gdb) p/x 0x5D44C+0x4000$14 = 0x6144c(gdb) p/x 0x85DA0+0x4000$15 = 0x89da0(gdb) p/x 0x5CEE0+0x4000$16 = 0x60ee0(gdb) p/x 0x5C8E0+0x4000$17 = 0x608e0(gdb) p/x 0x5D32C+0x4000$18 = 0x6132c(gdb) p/x 0x5D364+0x4000$19 = 0x61364
对其重新命名:

接着对参数event_metadata进行提取domain_type和table_id,event_metadata的48-53位保存domain_type, 32-39保存table_id。

然后测试提取出的domain_type有没有这个table_id的对应权限。

最终提取的是table_id_struct里的entry_point函数,接着调用sptm state的驱动函数,将这个entry_point作为回调参数:

Entry_point在sptm启动阶段通过sptm_register_dispatch_table进行了注册:

IOMMU子系统也进行了对应的注册:

在iommu_register_dispatch_table函数内部也是将sptm_dispatch作为entry_point进行注册:

在分析Sptm_dispatch之前,还需要弄清sptm state驱动函数在何时调用sptm_dispatch。
Sptm在不同的状态将会调用不同的驱动函数,sptm_state_func1函数只会将elr_gl1设置为elr_gl1_cl4,返回gl0运行,不会调用sptm_dispatch。sptm_state_func2函数同上,在gl0运行elr_gl1_txm。而sptm_state_func3函数才会在gl2调用sptm_dispatch:
![]()
X1就是驱动函数的第2个参数,即sptm_dispatch。
而sptm_state_func8比较有意思,它将sptm_dispatch放到了gl0运行:

完整的dispatch_state_machine函数如下:
voiddispatch_state_machine(char event_type, uint64_t event_metadata){check_GXF_STATUS_EL1();if (gl2_thread->stpm_state > 0x11)invalid_state_branch();if (event_type > 0xc)invalid_event_type_branch();struct sptm_state_struct *sptm_state = sptm_state_array[gl2_thread->sptm_state * 0x180];struct dispatch_table_struct *target_elem = sptm_state_array[gl2_thread->sptm_state * 0x180][event_type * 32];if (target_elem->next_state > 0x11)invalid_next_state_branch();if (!target_elem->func_addr)invalid_state_transition_branch();gl2_thread->domain_type = target_elem->domain_type;if (!(target_elem->unknown_value & 0x1)) {if (!(target_elem->unknown_value & 0x2)) {gl2_thread->sptm_state = target_elem->next_state;target_elem->func_addr(event_metadata, NULL);}else {if (gl2_thread->hop_flag != 1)invalid_hop_detected_branch();dispatch_err_log();}}else {if (event_metadata & 0xFC000000000000)domain_id_branch();if (event_metadata & 0xF000000000)table_id_branch();uint64_t domain_id_index = (event_metadata >> 48) & 0xff - 1;uint64_t table_id_index = (event_metadata >> 32) & 0xffif (domain_id > 1) {struct domain_id_struct *domain_id = domain_id_array[domain_id_index];struct table_id_struct *table_id = domain_id_array[domain_id_index][table_id_index * 0x18];}else {struct domain_id_struct *domain_id = domain_id_array[domain_id_index];struct table_id_struct *table_id = domain_id_array[domain_id_index][table_id_index * 0x18];}if (!(table_id->table_permissions >> gl2_thread->caller_domain) & 0x1))table_permissions_branch();if (table_id->entry_point == 0)illegal_dispatch_branch();if (!(target_elem->unknown_value & 0x2)) {gl2_thread->sptm_state = target_elem->next_state;target_elem->func_addr(event_metadata, table_id->entry_point);}else {if (gl2_thread->hop_flag != 1)invalid_hop_detected_branch();dispatch_err_log();}}}
2.3 sptm_dispatch
sptm注册了7个子系统,每个子系统都有一个dispatch table。
编号 | 子系统名称 | Dispatch table函数表数目 |
0 | Xnu | 22 |
1 | txm | 5 |
2 | unknown | 3 |
3 | dart | 17 |
4 | unknown | 2 |
5 | sart | 3 |
6 | uat | 13 |
函数原型:sptm_dispatch(uint64_t event_metadata)
event_metadata的32-39位保存的是dispatch table表中的索引。
使用以下脚本将table表中的值转为对应的函数地址:
awk '{print $3}' c.txt|sed -e 's/8[0-9][0-9]/0/' |awk '{a=strtonum($1)+0x4000+0x7000000; printf("%d 0xfffffff0%08x\n", NR, a)}'XNU | 函数地址 | 函数名 |
1 | 0xfffffff007072b9c | unknown |
2 | 0xfffffff00708a2ec | sptm_retype |
3 | 0xfffffff00708a824 | sptm_map_page |
4 | 0xfffffff00708b2d8 | sptm_map_table |
5 | 0xfffffff00708ba34 | sptm_unmap_table |
6 | 0xfffffff00708cd58 | sptm_update_region |
7 | 0xfffffff00708d350 | sptm_update_disjoint |
8 | 0xfffffff00708c95c | sptm_unmap_region |
9 | 0xfffffff00708d3f0 | sptm_unmap_disjoint |
10 | 0xfffffff00708d830 | sptm_shared_region_configure |
11 | 0xfffffff00708db74 | sptm_nest_region |
12 | 0xfffffff00708e2bc | sptm_unnest_region |
13 | 0xfffffff00708e6f8 | sptm_validate_root_config |
14 | 0xfffffff00708e7d4 | sptm_generate_rtlbi_param |
15 | 0xfffffff0070730ac | sptm_register_cpu |
16 | 0xfffffff007075114 | sptm_register_type |
17 | 0xfffffff00708ec94 | sptm_sign_user_pointer |
18 | 0xfffffff00708ed54 | sptm_auth_user_pointer |
19 | 0xfffffff00708a020 | sptm_register_xnu_exception_return |
20 | 0xfffffff007073814 | sptm_cpu_unknown_func |
21 | 0xfffffff007073530 | sptm_slide_region |
22 | 0xfffffff00708d428 | sptm_update_papt |
txm | 函数地址 | 函数名称 |
1 | 0xfffffff0070751cc | |
2 | 0xfffffff007089e60 | sptm_register_dispatch_table |
3 | 0xfffffff00708a2ec | sptm_retype |
4 | 0xfffffff00708ee28 |
Unknown | 函数地址 | 函数名称 |
1 | 0xfffffff007089e60 | sptm_register_dispatch_table |
2 | 0xfffffff00708a2ec | sptm_retype |
3 | 0xfffffff0070878f8 |
dart | 函数地址 | 函数名称 |
1 | 0xfffffff00707dee0 | |
2 | 0xfffffff00707d498 | |
3 | 0xfffffff00707cdec | |
4 | 0xfffffff00707c1bc | |
5 | 0xfffffff00707bdf4 | |
6 | 0xfffffff00707aeb4 | |
7 | 0xfffffff007079e0c | |
8 | 0xfffffff007079b54 | |
9 | 0xfffffff0070798d8 | |
10 | 0xfffffff00707962c | |
11 | 0xfffffff0070794f4 | |
12 | 0xfffffff007079358 | |
13 | 0xfffffff007079068 | |
14 | 0xfffffff007078e8c | |
15 | 0xfffffff007078ca8 | |
16 | 0xfffffff007078a60 | |
17 | 0xfffffff007078044 |
Unknown | 函数地址 | 函数名称 |
1 | 0xfffffff00707e928 | |
2 | 0xfffffff00707e800 |
sart | 函数地址 | 函数名称 |
1 | 0xfffffff00707791c | |
2 | 0xfffffff0070774dc | |
3 | 0xfffffff007077000 |
nvme | 函数地址 | 函数名称 |
1 | 0xfffffff007076078 | |
2 | 0xfffffff0070759e4 | |
3 | 0xfffffff0070756fc | |
4 | 0xfffffff0070754c0 | |
5 | 0xfffffff007075000 |
uat | 函数地址 | 函数名称 |
1 | 0xfffffff0070718ec | |
2 | 0xfffffff0070716e8 | |
3 | 0xfffffff00707135c | |
4 | 0xfffffff0070710c8 | |
5 | 0xfffffff007070fdc | |
6 | 0xfffffff007070978 | |
7 | 0xfffffff0070707c0 | |
8 | 0xfffffff0070704d4 | |
9 | 0xfffffff007070190 | |
10 | 0xfffffff00706f628 | |
11 | 0xfffffff00706f340 | |
12 | 0xfffffff00706eb14 | |
13 | 0xfffffff00706e97c |

不同的switch case最后统一调用sptm_dispatch_entry_point函数:
函数原型:sptm_dispatch_entry_point(uint64_t table_entry_point)

首先从gl2_thread中恢复sptm_dispatch需要的各种参数,这些参数在gxf_entry_el1开始处进行了保存。
![]()
然后就可以调用具体的entry_point了。
3 Exception处理分析
sptm在启动阶段,将syn_handler_sp0_g12地址设置为vbar_gl1的异常处理地址。

以syn_handler_sp0_g12+0x400的异常处理函数为例,追踪sptm如何处理来自gl0的异常,逻辑还原如下:
syn_handler_low_gl2(uint64_t event_metadata){uint64_t ec_value = (msr(ESR_GL1) >> 26) & 3f;uint64_t svc_number = msr(ESR_GL1) & 0xffff;if (ec_value == 0x15) {if (svc_number == 0) {uint64_t value = (event_metadata >> 32) & 0xffff;if (value != 0xfd) {if (value != 0xfe) {if (value != 0xff) {dispatch_state_machine(2, event_metadata);}else {dispatch_state_machine(7, 1);}}else {dispatch_state_machine(0xb, 0);}}else {dispatch_state_machine(5, 0);}}else if (svc_number == 37) {uint64_t value = mrs(SPSR_GL1);msr(value & ~0x1C0);}else if (svc_number == 38) {uint64_t value = mrs(SPSR_GL1);msr(value | 0x1C0);}else {dead_loop();}}else if (ec_value == 0x16) {}else {handle_unkdown_exception_gl12();}}
syn_handler_low_gl2提供了3个系统调用:
Svc 0Svc 37Svc 38
后两个系统调用用于关闭中断。
Svc 0有如下触发路径:
dispatch_state_machine(2, event_metadata);dispatch_state_machine(7, 1);dispatch_state_machine(0xb, 0);dispatch_state_machine(5, 0);
同时可以看到,异常处理中并没有对关键寄存器进行保护。
4 sptm页表保护
sptm是secure page table monitor的缩写,ios将原本xnu内核中的页表映射代码转移到了sptm,也就是gl2模式进行处理。
页表映射的函数原型:sptm_map_page(uint64_t paddr, uint64_t vaddr, uint64_t new_pte)
首先调用acquire_root_pt获取root页目录表的地址。

接着调用check_vaddr_range检查虚拟地址vaddr是否在root页目标表所能翻译的地址范围内,注意每层目录表都有一个能翻译的地址范围。

接着检查new_pte合法性。
![]()
然后通过root_pt对应的attr_idx,在attr_struct_array中找到对应的结构体,判断物理地址paddr是否合法。

接着获取中间目录表和最后的页表结构体。
重点来了,sptm_type_params_database数组中隐藏着巨大秘密。
首先通过页表page_fte->type在sptm_type_params_database数组中找到对应的struct current_type_params结构体,它保存着此种类型页表对应的sprr_index,然后在new_pte中提取出新的页表对应的sprr_index,将两者对比,如果不相等则中止此次页表映射。也就是说sptm维护了一组sprr_index数据库,只有类型匹配才能有权限进行映射。
5 sptm retype分类保护
Sptm将不同的数据段或代码段进行分类保护,每种不同的类型都有对应的地址范围,不同的类型不能互相映射,用分类概念完成了地址隔离保护。
在sptm启动阶段,调用sptm_bootstrap_early对若干地址区域进行了分类注册:
对papt_range_array中的所有地址范围进行注册, retype类型为0x9。这些地址范围包括:SPTM-rx、SPTM-rw、SPTM-le、TXM-ro、TXM-rx、TXM-bx、TXM-rw、BootKC-ro、BootKC-rs、BootKC-rx、BootKC-bx、BootKC-rw、BootKC-le、TrustCache、CL4-ro、CL4-rx、CL4-rw、CL4-le、RAMDisk、RTBuddySeg、SEPFW、SEPPatches、uStuff、preoslog、BootArgs、ExclaveOSIntegrityCatalog、ExclaveOSTrustCache、AVAILABLE、PHYS_SLIDE等。
还有其他的地址范围注册了以下retype类型:0xb、0x13、0x36。
__text:FFFFFFF0070866A8 MOV X0, #0xFFFFFFF000000000__text:FFFFFFF0070866AC MOV W1, #1__text:FFFFFFF0070866B0 MOV W2, #0__text:FFFFFFF0070866B4 MOV W3, #0x13__text:FFFFFFF0070866B8 MOV W4, #3__text:FFFFFFF0070866BC MOV W5, #1__text:FFFFFFF0070866C0 BL retype_handlerretype_handler(0xFFFFFFF000000000,1,0,0x13,3,1)__text:FFFFFFF0070866D8 ADD X22, X22, #4,LSL#12__text:FFFFFFF0070866DC MOV X0, X22__text:FFFFFFF0070866E0 MOV W1, #2__text:FFFFFFF0070866E4 MOV W2, #0__text:FFFFFFF0070866E8 MOV W3, #0x13__text:FFFFFFF0070866EC MOV W4, #3__text:FFFFFFF0070866F0 MOV W5, #1__text:FFFFFFF0070866F4 BL retype_handler
retype_handler(x22,2,0,0x13,3,1)
retype_handler原型:
retype_handler(uint64_t base, uint64_t length, x2, uint64_t frame_type, uint64_t max_num, bool flag);首先根据参数base在retype_range_array数组中找到其对应的retype_array数组地址。
接着调用bootstrap_alloc_frames分配一个retype帧结构体。
然后调用bootstrap_retype_frames将刚分配到的retype帧结构进行下一步操作。
bootstrap_retype_frames函数原型:
bootstrap_retype_frames(uint64_t frame_start_address, uint64_t frame_num, uint64_t old_type, uint64_t new_type);bootstrap_retype_frames调用set_retype_by_sprr完成类型编码的存储操作。
set_retype_by_sprr原型:
set_retype_by_sprr(uint64_t *retype_address, uint64_t sprr_index)最终看到是将sprr_index编码进了retype_array数组。
6 txm
txm为trust execution monitor的缩写,运行在gl0,通过svc请求sptm服务:
__bootcode:FFFFFFF017054084 loc_FFFFFFF017054084 ; CODE XREF: sub_FFFFFFF017021C44+291BC↑j__bootcode:FFFFFFF017054084 ; sub_FFFFFFF01704AE04+10↑j ...__bootcode:FFFFFFF017054084 PACIBSP__bootcode:FFFFFFF017054088 SVC 0__bootcode:FFFFFFF01705408C RETAB__bootcode:FFFFFFF01705408C ; END OF FUNCTION CHUNK FOR sub_FFFFFFF017021C44__bootcode:FFFFFFF017054090 sub_FFFFFFF017054090 ; CODE XREF: start+48↑p__bootcode:FFFFFFF017054090__bootcode:FFFFFFF017054090 ; FUNCTION CHUNK AT __bootcode:FFFFFFF017054084 SIZE 0000000C BYTES__bootcode:FFFFFFF017054090__bootcode:FFFFFFF017054090 MOVK X16, #0,LSL#48__bootcode:FFFFFFF017054094 MOVK X16, #1,LSL#32__bootcode:FFFFFFF017054098 MOVK X16, #0,LSL#16__bootcode:FFFFFFF01705409C MOVK X16, #1__bootcode:FFFFFFF0170540A0 B loc_FFFFFFF017054084