看雪论坛
发新帖

【OSG】iOS9开始引入的内核完整性保护(KPP)功能是如何实现的

ksmokee 2017-9-5 12:16 609

原文链接:https://xerub.github.io/ios/kpp/2017/04/13/tick-tock.html

Tick (FPU) Tock (IRQ)

与iOS9一起推出的内核完整性保护又称为“KPP”,这对于arm64越狱带来了新的问题。 在修补内核代码(通常在较旧的越狱中)之后,一般要不了多久,设备就会panic。 很明显,有些东西会检查内核代码。 这个东西在内核之外,不断地管理和监视着内核。

到目前为止,我仍然感到惊讶的是,没人对KPP写点什么。 至少到现在我都没看到。 所以,我将尝试解释虚拟管理程序如何工作。 具体的实施细节可能会在稍后的时间提供,只要我有时间。现在,不浪费时间了我们开始吧。

第1部分(设置)

KPP位于一个Mach-O可执行文件,附加在压缩的内核块之后,在kernelcache img4内。 iBoot加载KPP的镜像,将其加载到0x4100000000,并在EL3中运行。

EL3

_start(monitor_boot_args *mba)

这结构体有如下几项

struct monitor_boot_args {
    uint64_t version;
    uint64_t virtBase;
    uint64_t physBase;
    uint64_t memSize;
    struct kernel_boot_args *kernArgs;
    uint64_t kernEntry;
    uint64_t kernPhysBase;
    uint64_t kernPhysSlide;
    uint64_t kernVirtSlide;
};

KPP调用_start(NULL)在0x4100000000来覆盖自己的Mach-O头,并安装了两个异常处理程序sync_handler和irq_handler。 回想一下AArch64异常表:

VBAR_ELN 异常类型 描述
+0x000 Synchronous Current EL with SP0
+0x080 IRQ/vIRQ
+0x100 FIQ/vFIQ
+0x180 SError/vSError
+0x200 Synchronous Current EL with SPx
+0x280 IRQ/vIRQ
+0x300 FIQ/vFIQ
+0x380 SError/vSError
+0x400 Synchronous Lower EL using AArch64
+0x480 IRQ/vIRQ
+0x500 FIQ/vFIQ
+0x580 SError/vSError
+0x600 Synchronous Lower EL using AArch32
+0x680 IRQ/vIRQ
+0x700 FIQ/vFIQ
+0x780 SError/vSError

ExceptionVector具有两个处理程序的位置

接下来,它解析内核及其kexts(来自__PRELINK_INFO

  • save TEXT, DATA segments to map list
  • save TEXT, DATA::__const zones to hash list

最后,如果被启用 它始终设置下面的寄存器

CPACR_EL1 = 0x100000;  // CPACR_EL1.FPEN=1, causes instructions in EL0 that use the Floating Point execution to be trapped
CPTR_EL3 = 0x80000000; // CPTR_EL3.TCPAC=1, accesses to CPACR_EL1 will trap from EL2 and EL1 to EL3
SCR_EL3 = 0x631;       // SCR_EL3.IRQ=0, When executing at any Exception level, physical IRQ interrupts are NOT taken to EL3
                       // SCR_EL3.SMD=0, SMC instructions are ENABLED at EL1 and above
                       // SCR_EL3.SIF=1, Secure state instruction fetches from Non-secure memory are NOT permitted

EL1

内核在EL1中开始执行

_start() => start_first_cpu() => arm_init():
    => cpu_machine_idle_init() => monitor_call(0x800)
    => machine_startup() => kernel_bootstrap() => kernel_bootstrap_thread() => monitor_call(0x801)
_start_cpu() => arm_init_cpu() => cpu_machine_idle_init() => monitor_call(0x800)

monitor_call()将升级到EL3到管理程序的sync_handler

EL3

sync_handler:

if (ESR_EL3 == 0x5E000011) { // ESR_EL3.EC==0x17 && ESR_EL3.IL==1 && ESR_EL3.ISS==0x11 aka "SMC #0x11" aka monitor_call() inside the kernel
    switch (arg0) {
        case 0x800: // called by cpu_machine_idle_init()
            /* save kernel entrypoint */
            return ok;
        case 0x801: // called by kernel_bootstrap_thread()
            if (enabled) {
                if (locked) {
                    FAIL(4);
                }
                /* do lockdown:
                 * hash all regions from hash list
                 * initialize some vars
                 * save SCTLR_EL1, TCR_EL1, TTBR1_EL1, VBAR_EL1
                 */
                ...
                SCR_EL3.SMD=1;
                locked = 1;
            }
            return OK;
        case 0x802: // wtf is this shit?
            FAIL(5);
    }
}
(to be continued)

当出现问题时,FAIL(代码)设置一个全局变量,并通过以下方式向内核发出信号:

ESR_EL1 = 0xBF575400 | code // ESR_EL1.EC=0x2F, ESR_EL1.ISV=1, ESR_EL1.IS=0x575400|code

代码含义

  1. violation in frame
  2. bad syscall
  3. not locked
  4. already locked
  5. software request
  6. invalid TTE/PTE
  7. violation in mapping
  8. violation in system register

然后往下调用SError返回到内核的ExceptionTable中:

SError => fleh_serror() => sleh_serror() => kernel_integrity_error_handler() => panic()

否则,如果一切是正常的,就在monitor_call()之后,在内核中执行恢复操作。
这是设置阶段,为了让内核设置一次写入内存位置所需的。 接下来,进入中心阶段

第二部分 (the ticking)

同时在用户空间运行代码,当FPU指令被执行后,CPACR_EL1.FPEN==1会产生一个内核陷阱。

EL1

在内核中,fleh_synchronous() fleh_irq() fleh_fiq()fleh_serror() 所有都会这样结束
exception_return_dispatch() => check_user_asts() => MSR CPACR_EL1, X0
一旦来自于EL3内核陷阱,CPACR_EL1就会执行。当CPTR_EL3.TCPAC==1时,执行权就转交到EL3 sync_handler

EL3

sync_handler:

(continued)
else if (ESR_EL3 == 0x62340400) { // ESR_EL3.EC==0x18 && ESR_EL3.IL==1 && ESR_EL3.ISS==0x340400 aka trapped by "MSR CPACR_EL1, X0"
    if (violated) {
        FAIL(1);
    }
    if (!locked) {
        FAIL(3);
    }
    if (!(++number_of_hits & watchtower_throttle)) {
        if (!(++flip_flop & 1)) {
            if (hash_is_ready) {
                blake2b_final(&hash, digest);
                if (memcmp(cur->digest, digest, 32)) {
                    FAIL(1);
                }
                cur = get_next_region();
                if (!cur) {
                    cur = get_first_region();
                }
                cur_data_ptr = cur->base;
                cur_data_left = cur->size;
                blake2b_init(&hash);
                hash_is_ready = 0;
            } else {
                chunk = min(cur_data_left, 128);
                blake2b_update(&hash, cur_data_ptr, chunk);
                cur_data_ptr += chunk;
                cur_data_left -= chunk;
                if (!cur_data_left) {
                    hash_is_ready = 1;
                }
            }
        } else {
            /* walk and check TTE/PTE
             * verify map list
             * check system registers SCTLR_EL1, TCR_EL1, TTBR1_EL1, VBAR_EL1
             */
            ...
        }
    }
    ELR_EL3 += 4;         // skip insn
    CPTR_EL3 = 0;         // CPTR_EL3.TCPAC=0, accesses to CPACR_EL1 will not trap from EL2 and EL1 to EL3
    CPACR_EL1 = 0x300000; // CPACR_EL1.FPEN=3, does not cause any FPU instruction to be trapped
    SCR_EL3 = 0x6B3;      // SCR_EL3.IRQ=1, When executing at any Exception level, physical IRQ interrupts are taken to EL3
                          // SCR_EL3.SMD=1, SMC instructions are UNDEFINED at EL1 and above
                          // SCR_EL3.SIF=1, Secure state instruction fetches from Non-secure memory are NOT permitted
    return OK;
}

如果所有的检查都通过的话,管理程序就会禁用FPU陷阱(允许FPU最终执行)使得IRQ执行到EL3(确保再次命中),在CPACR_EL1命中后恢复内核然后等待。

EL1

内核和用户空间愉快的运行着。当下一次IRQ触发时,就由EL3 IRQ的管理程序接管。

EL3

irq_handler:

CPACR_EL1 = 0x100000;  // CPACR_EL1.FPEN=1, causes instructions in EL0 that use the Floating Point execution to be trapped
CPTR_EL3 = 0x80000000; // CPTR_EL3.TCPAC=1, accesses to CPACR_EL1 will trap from EL2 and EL1 to EL3
SCR_EL3 = 0x431;       // SCR_EL3.IRQ=0, When executing at any Exception level, physical IRQ interrupts are NOT taken to EL3
                       // SCR_EL3.SMD=0, SMC instructions are ENABLED at EL1 and above
                       // SCR_EL3.SIF=0, Secure state instruction fetches from Non-secure memory are permitted

也就是说:重置IRQs到EL1,重新启动FPU陷阱,重新启动CPACR_EL1访问的陷阱,这些是最后的4步,然后会一直重复。

*

总结:KPP总是确保FPU陷阱且不会被禁用。当FPU命中,内核就会尝试禁用陷阱但同时也会由KPP接管。然后KPP运行检查,释放FPU,运行IRQs本身。只要任何IRQ触发,就会使FPU进入内核陷阱并结束IRQs

这是保持虚拟管理程序跳动的引擎。 如果你修改触发器,即CPACR_EL1访问,则FPU无法执行。 但是,有一个catch。 我们可以“窃取”CPACR_EL1单独访问:

  1. 取消patch
  2. 触发CPACR_EL1,管理程序然后运行恢复执行权
  3. 再次patch
  4. profit
    这种绕过方式在@qwertyoruiop的yalu102中使用过。
本主题帖已收到 0 次赞赏,累计¥0.00
最新回复 (1)
聖blue 2017-9-6 21:10
2
good!!!!!!!!
返回



©2000-2017 看雪学院 | Based on Xiuno BBS | 域名 加速乐 保护 | SSL证书 又拍云 提供 | 微信公众号:ikanxue
Time: 0.012, SQL: 9 / 京ICP备10040895号-17