文章总结: 本文深度解析iOSSPTM机制,详述启动、GL模式切换及状态机调度。核心发现是SPTM接管页表映射,利用SPRR索引实现内存retype分类与隔离以防止非法访问。文章还分析异常处理及TXM运行机制,揭示了苹果内核安全的深层防护逻辑,为研究提供参考。
综合评分: 90
文章分类: 逆向分析,二进制安全,移动安全,漏洞分析
IOS SPTM深度分析
原创
wzt
kernsec
2026年1月2日 17:57
浙江
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_el02 uint64_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_id_array[gl2_thread->domain_type][table_id].entry_point = dispatch_entry_point;domain_id_array[gl2_thread->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, [X10]LDP X2, X3, [X10,#0x10]LDP X4, X5, [X10,#0x20]LDP X6, X7, [X10,#0x30]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 arg if (!(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); else genter_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); else genter_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函数如下:
void dispatch_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) & 0xff if (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,0×13,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
免责声明:
本文所载程序、技术方法仅面向合法合规的安全研究与教学场景,旨在提升网络安全防护能力,具有明确的技术研究属性。
任何单位或个人未经授权,将本文内容用于攻击、破坏等非法用途的,由此引发的全部法律责任、民事赔偿及连带责任,均由行为人独立承担,本站不承担任何连带责任。
本站内容均为技术交流与知识分享目的发布,若存在版权侵权或其他异议,请通过邮件联系处理,具体联系方式可点击页面上方的联系我。
本文转载自:kernsec wzt《IOS SPTM深度分析》