前言
苹果自A6开始采用定制CPU核心,A7是全球第一款使用64位ARM的处理器。苹果CPU性能一直领先高通,必有一定的原因。通过读取ARM64 ID registers,或许能侧面揭晓一些答案。
PongoOS
自2019年checkm8漏洞公开以来,很快有大神们利用该漏洞制作了checkra1n越狱工具,为了方便地支持A7-A11多个硬件平台,多个iOS版本,checkra1n实现了一个Preboot environment,名为PongoOS,一个简易的操作系统。
checkra1n运行时利用checkm8将PongoOS注入iBoot,PongoOS通过内存Hook劫持iBoot,利用iBoot初始化平台,并在iBoot执行末尾启动自身,Pongo启动后加载KPF(Kernel Patch Finder)内核模块,KPF会patch掉内存中的iOS内核。
PongoOS目前已经开源,可以当作苹果硬件的实验环境使用。
编译启动PongoOS
在修改PongoOS之前,需要能够编译PongoOS,能够使用checkra1n启动iPhone。
PongoOS源码:
https://github.com/checkra1n/pongoOS
支持macOS和Linux编译,根据官方readme,macOS下编译会简单一些:
- Install Xcode + command-line utilities
- Run
make all
需要安装Xcode及其命令行工具。(命令行工具只需要在终端里运行git等命令就会自动弹出来)
先跑一遍make all,build下应该生成Pongo.bin,安装checkra1n后可通过以下命令启动:
/Applications/checkra1n.app/Contents/MacOS/checkra1n -c -p -k ./build/Pongo.bin
接下来
cd scripts/
make
会生成pongoterm工具,通过它就可以通过USB和iPhone上的PongoOS shell交互了。
一切准备就绪以后,可以修改Pongo源码,在pongo shell里添加读取ARM64寄存器的命令了。
读取ARM64功能支持寄存器
打开 src/shell/main.c, 在shell_main函数以前加入读寄存器的命令:
void read_aa64_ids_cmd(const char* cmd, char* args) {
//PongoOS有一个command handler,用户输入的命令和参数会以char*传递给相应命令处理函数
//也就是说,需要自己解析args(不过这里并不需要)
uint64_t id; //要读的寄存器是64位的
__asm__("mrs %0, ID_AA64ISAR0_EL1" : "=r" (id) : : );//通过内联asm读取寄存器
iprintf("ID_AA64ISAR0_EL1 is %llx\n", id);
__asm__("mrs %0, ID_AA64ISAR1_EL1" : "=r" (id) : : );
iprintf("ID_AA64ISAR1_EL1 is %llx\n", id);
__asm__("mrs %0, ID_AA64MMFR0_EL1" : "=r" (id) : : );
iprintf("ID_AA64MMFR0_EL1 is %llx\n", id);
__asm__("mrs %0, ID_AA64MMFR1_EL1" : "=r" (id) : : );
iprintf("ID_AA64MMFR1_EL1 is %llx\n", id);
__asm__("mrs %0, ID_AA64MMFR2_EL1" : "=r" (id) : : );
iprintf("ID_AA64MMFR2_EL1 is %llx\n", id);
__asm__("mrs %0, ID_AA64PFR0_EL1" : "=r" (id) : : );
iprintf("ID_AA64PFR0_EL1 is %llx\n", id);
}
然后在shell_main函数里注册刚添加的命令:
void shell_main() {
/*
Load command handler
*/
extern void task_list(const char *, char*);
//注册read_aa64_ids_cmd函数,命令名aa64id
command_register("aa64id", "Read AArch64 ID registers", read_aa64_ids_cmd);
command_register("panic", "calls panic()", panic_cmd);
保存main.c,再次make all 生成Pongo.bin,再次通过checkra1n命令启动。
结果
在PongoOS shell中执行我们新添加的命令,即可读取苹果CPU的支持状态了。
我在A7(iPhone 5S)和A10(iPhone 7)上测试了读取:
A7:
pongoOS> aa64id
ID_AA64ISAR0_EL1 is 1110 //支持基本AES, SHA2, SHA1
ID_AA64ISAR1_EL1 is 0 //无支持
ID_AA64MMFR0_EL1 is 11021 //36 bits, 64GB物理地址 16 bits.ASID , EL3/1不支持BigEnd, EL0支持BigEnd, 支持SNSMem, 支持4KB和64KB内存页表
ID_AA64MMFR1_EL1 is 0 //无支持
ID_AA64MMFR2_EL1 is 0 //无支持
ID_AA64PFR0_EL1 is 1012 //EL3,EL1仅AArch64, EL0可AArch64或AArch32, 未实现EL2
A10:
pongoOS> aa64id
ID_AA64ISAR0_EL1 is 10011120 //支持AES(PMULL/PMULL2), SHA2, SHA1, CRC32, FEAT_RDM , 不支持原子操作指令(违反Armv8.1规范)
ID_AA64ISAR1_EL1 is 0 //无支持
ID_AA64MMFR0_EL1 is f0100001//36 bits, 64GB物理地址 8 bits.ASID, 不支持SNSMem, 不支持BigEnd,支持16KB和64KB内存页表
ID_AA64MMFR1_EL1 is 111000 //支持Hierarchical Permission Disables,LORegions,Privileged Access Never(均Armv8.1强制要求)
ID_AA64MMFR2_EL1 is 0 //无支持
ID_AA64PFR0_EL1 is 12 //EL1仅AArch64, EL0可AArch64或AArch32, 未实现EL3,EL2
其中A7兼容Armv8.0-A,A10 Armv8.1-A。(真的完全兼容吗?)
通过上面的读取结果,可以看到:
1.苹果的ARM64处理器A7开始就不支持EL2(虚拟化),最近的M1/A14X因为要在mac上用才开始支持(且仅支持VHE模式,因为macOS只用这个模式)
2.苹果不使用ARM TrustZone实现加密敏感的操作,而是通过一个独立的SEP实现。但是在A7-A9上,为了实现KPP(Kernel Patch Protection),苹果使用了EL3。
而自A10开始,苹果使用了更加可靠的KTRR及其演进版本APRR代替KPP,于是苹果在A10里立刻砍掉了EL3支持。
3.A7和A10都使用了36bits物理地址。(已经完全够用了)
4.在A7中部分支持的大端模式已经在A10彻底砍掉。
5.因为不用EL3(TrustZone),A10没有SNSMem安全内存标识。
6.A7中的16bits ASID在A10变成了8bits(也够用了)
7.A10中砍掉了4KB内存页表支持,添加了16KB页表支持
这是iPhone7上很难运行安卓,(目前)不能运行Win10的原因之一。有很多人讨论4KB和16KB的优劣,苹果应该是测试发现16KB在iPhone场景效率更高一些。
8.A10的结果可以看到,其中有明显违反ARM规范的地方。事实上1中仅支持VHE也是违反ARM specs的。
9.A7开始就不支持32bit EL1(仅支持64位操作系统)
综合来看,苹果因为掌握App Store的控制权,很容易要求开发者按照相应的规范适配,更不用考虑运行macOS/iOS体系以外的软件,因此也就没有安卓/Windows那么重的历史包袱,想要放弃一些CPU功能很容易(例如32位支持)。
因此苹果的原则就是,仅保留需要用的CPU功能,多余的使劲砍,需要用的不断优化,自然开销更低,效率也会高一些。
还有一个疑问:从A10返回结果来看,EL0支持AArch32模式(32位),但是不知为何在Project Sandcastle的readme里写道iPhone 7上的安卓不支持32位apk。
参考
Arm Armv8-A Architecture Registers
https://developer.arm.com/documentation/ddi0595/2021-06/AArch64-Registers