libc2.23
0ctfbabyheap
首先打开程序就知道分成几个部分。
Allocate: 可以申请16个 由mmap分配了一个空间存储相应信息。
结构如下:一个chunk由一个24字节的区域记录 ,前8个字节记录是否没被free,8-16记录大小,16-24记录地址
并且每次申请都会用1初始化。
Fill: 写入字节,但是存在一个很明显的溢出,必须输入指定字节数 否则不停止
Free: 就是正常的free.
Dump: 输出内容, 前面有Content: 的提示
这里就用最简单的fastbindup篡改malloc_hook的思路。
这里需要需要控制出一个重叠来泄露libc地址。
fastbin在free时并不会设置后一块的prev_in_use位,也就是说,进入fastbin几乎没有检查,这让我们可以构造出一个fastbin和small的重叠
我们通过两个fastbin的部分覆盖能轻松实现这个要求.
先写好四个基础操作的函数
from pwn import * |
构思流程:
先申请4个0x30大小的fastbin 两个用来重叠(这里用的是部分覆盖 因此还需要多增加一个chunk用来溢出,否则fd会是0) 最后一个用来防止合并
然后申请一个0x90大小的 用来获得main_arena地址。
申请完调试一下,看smallchunk位置
Allocated chunk | PREV_INUSE |
我们先控制fastbindup 与 这个smallbin 重叠 .这里只需要相对覆盖,这里还需要注意的是需要先把size修改了。
Allocate(0x90-8) |
Allocated chunk | PREV_INUSE |
这就说明已经成功重叠了(这是新开了一次程序,看相对位置就行)
Allocated chunk | PREV_INUSE 0 |
然后就是修改回原来的大小 free掉4 这里要再malloc一个0x90大小的chunk先,防止free的时候和topchunk合并
Fill(3,p64(0)*5+p64(0x91)) |
输出检查一下
正常就继续
0x7d71b69c3b78 是给出的位置
算出libc位置
0x7d71b6600000
可计算出偏移0x3c3b78
mallochook的位置在3c3b10
我们分配到malloc_hook -8*2-3也就是-19的位置
这里要注意fastbin指针是从chunk起点开始的 我因为这个卡了很久。
SleepyHolder
程序初始化部分设置了一个60秒的限时 到时间自动退出
- 兼具申请和写入功能,申请大小无法自己设定,但是有largebin大小和fastbin大小,还有个非常大的大小。每个都只能申请一次,但largebin 和fastbin大小的可以free
- free功能,free没有检查是否已经free,导致有double free的可能性
- read功能,但是需要已经申请过。
可以double free 但是free后没法写 这就非常符合fastbin_dup_consolidate 技术的使用前提。
但是还有个问题,我们无法通过chunk泄露出libc,不过还好这题没开PIE,这样可以考虑修改got表 然后操作三个
还有个问题就是修改got以及后续利用的方法,我原本想的是用puts来泄露里libc,然后搞ROP 但我后来觉得既然都修改got了,不如直接再修改free成system 然后运行system(‘/bin/sh’)
先写对应的函数
from pwn import * |
为了防止一些读写方面字符的差距,这里写的比较复杂。
先构造重叠。
这里由于只能用三个块,所以可以1号用来构造重叠,2号用来隔离防止合并,3当作一个largebin大小的申请把1移入unsorted bin
这些都是正常的,但是程序由于没有对double free的检查,并且fastbin也没有(不检查后一个chunk的prev_inuse)
这就导致我们可以再free一次1,使得smallbin和fastbin都有1
smallbins |
这时候我们再申请 会先从fastbin里面取,然后就可以打unsortedbin attack 把这个堆块分配到1的指针位置
具体就不细说了.
add(1,b'a') |
这里就成功打了个fastbin_dup_consolidate + unlink
ptr1会被成功修改成ptr1-0x18,我们看bss的结构
.bss:00000000006020B8 byte_6020B8 db ? ; DATA XREF: sub_4008F0↑r |
我们目前可以控制三个的指针和两个free检查位 其中locked 和 buf3 没什么用 。
首先我们要泄露libc_base. buf1覆盖成free的got 这样再次覆盖的时候就可以修改free成其他的函数,我们稍后将它修改成puts.plt
再把buf2覆盖成任何一个got表,这里可以选择atoi
对于buf3,可以不用管。
这样覆盖一次后,再次覆盖就是在free的got覆盖了,把他覆盖成puts.plt 然后运行free(2) (其实是puts(2))
然后就会输出atoi的got里面的值,也就是atoi在glibc里面的实际位置。
然后我们再次覆盖 把free写成system的地址, 这里要注意新建2 写入’/bin/sh’ 然后运行即可.
free_got = 0x602018 |
stkof
有四个四个功能,申请部分直接暴露了存储堆ptr地址,写入部分存在溢出,free部分空置了指针,所以没有uaf,另外我们无法打印出指针。
我想到的方法其实和前面差不多? 搜到的也基本是这么做的。这样看来前面那题反而更难
我的想法大致是 unlink1用来控制指针,然后2用来控制free.got变成puts.plt然后再把2重新覆盖成atoi.got用来泄露libc,接着继续重复操作覆盖成system,最后我们直接覆盖ptr-0x18的位置为/bin/sh 来运行system(/bin/sh)
先写三个操作函数 //输出部分没什么用
p=process('./stkof') |
我先进行了unlink操作 结果测试的时候发现不对劲,我再malloc三次后检查了heap发现,程序由于输出主动申请了个缓冲区,所以我们的1号chunk没用了
unlink
new(0x88) |
为了方便后续操作 本来要在函数体内部处理的p.recvline()我都移到外面了。
free_got = 0x602018 |
这一块的操作可以参考一下上一题,就不细说了,流程稍微优化了一下 比刚才讲的简单点,利用到了ptr3
oreo
注意这一题是32位的
这个题题面比较复杂
Add new rifle:
一个rifle是一个结构体,大小56,如果加上head就是64
结构体大致如下
offset | label |
---|---|
0 | description |
… | … |
25 | name |
… | … |
52 | fd |
可以理解为一个链表,程序有一个变量记录表头 每次申请都往表头后插入 类似fastbin
还有个变量用于记录存储的所有rifles
但是很明显,Add new rifle过程存在堆溢出
每次读取都读取56个字符.
Show added rifles:
按单链表顺序,显示Name和Description
Order selected rifles:
free所有的rifles ,会把head归零。
Leave a Message with your Order:
在bss段一个128字节的空间里写入消息。
Show current stats:
显示所有rifles 和 orders 个数 ,显示notice .
这一题我们只能在malloc的时候写入,并且只能一次性free所有申请过的chunk,这让我们很难利用堆溢出构造重叠,因为我们几乎无法控制size部分,也没有写入free的堆块的手段。
但是一次性free所有指针+fd可以被控制,这可以让我们free任意地址,而notice存在指针。我们控制notice的指针,对后续必然非常有帮助,于是可以想到house of spirit
为了控制指针,我们需要利用到指针前面的totalrifles(记录rifles总数,之前说过的) ,这可以帮助我们构造出size部分。 然后我们控制了指针就可以进行任意写了。
但是我们还需要泄露libc地址,这也并不是很复杂,因为我们可以输出chunk中的内容,如果控制指针指向got表,即可输出got表中内容,进而获取libc地址
任意指的指针只能用一次,我们用它修改got内容,需要找到一个合适的调用位置,这里我是没注意到的,在readnum中有个位置可以正好让我们写入/bin/sh,并且马上可以作为函数的参数
call _fgets |
综合上述逻辑,就可以开始写脚本了。
先写对应的函数
def new(ptr): |
再泄露libc
#泄露libc |
house_of_spirit
#house_of_spirit |
这时候我们已经控制了指针,并且已经把指针调到了got
#覆盖got表 获得shell |
之后两题在原始的技术方面都挺简单,但是后面会跟着其他的比较难的技术,于是我直接跳过了
cookbook
这题的复杂程度简直离谱,我直接找了个writeup看,how2heap例题多少有点抽象。
涉及到的一些结构:
recipe:
0x0: ptr to linked list of ingredient counts
0x4: ptr to linked list of ingredient quantities
0x8: char array for recipe name
124: char array to recipe type
140: Char array for recipe instruction
ingredient:
0x0: calories
0x4: price
0x8: char array for ingredient name
0x140: ptr to itself
主程序是一个菜单,涉及三部分的操作,由于太复杂就不细说了,
漏洞出现在create recipe里,discard recipe功能有UAF漏洞。
case 'd': |
dword_804D0A0是一个指向当前正在操作的recipe的指针,其他的操作都需要用到这个指针,同样还有指向当前在操作的ingredient的指针
利用这个漏洞,再加上两个结构存储的数据,我们可以获取堆内存,先创建一个新的recipe free掉,输出recipe的时候就会输出heap地址。
然后就是泄露libc.很明显recipe的instruction是溢出的,那么我们就可以通过uaf漏洞,来覆盖之后链表中指向最新ingredient的指针,再输出这个ingredient信息,泄露libc,
我们可以利用之前已经free的recipe,先新建 ingredient 然后把他导入链表,然后再编辑recipe进行溢出,然后再输出ingredient信息即可。
然后就是利用house_of_force覆盖 free_hook 这里可以利用name自定义大小的功能。
脚本方面,这里操作太多了,写成很多函数的形式感觉并不容易,可以直接写输入输出。
from pwn import * |
zerostorage
这题漏洞的位置非常新颖。
后面的利用how2heap没怎么讲。
先讲一下每个部分的功能。
1: 申请并写入内容,最大不会超过0x1000 , 最小申请的大小是0x80 ,但是在记录的时候按输入大小计算
2: realloc 对某个序号对应的chunk realloc。其他和申请差不多。
3: merge 合并,输入第一个数是from 第二个是to from 的内容会接到to后面。如果两者记录的大小之和小于0x80会用沿用原来的chunk 但是序号会是新的。
4: 删除entry
5: 输出内容
6: 列出所有正在使用的chunk的大小。
漏洞在merge 部分,没有检测输入的两个序号是不是相同的,导致uaf漏洞。
后面的利用方式也是比较新,利用unlink把一个大数写入global_max_fast ,这样就能打fastbin dup,解决了unsorted bin 被打乱的问题。找到一个除了控制指针之外的新解法。
先写功能 我们要用到的 有12345
总体思路上就是先创建0 ,1 ,2 ,3 ,4
0:free一个chunk,用来泄露堆地址
1:写入binsh
2:unsortedbin attack
3:fastbindup
4:用来申请合适的size,便于fastbin dup控制指针
|
这里已经完成了泄露,如果我们把0update一下,bk位置填入global_max_fast - 0x10就能篡改它的值
但是后面就没法继续写了,因为内核的更新,通过libc算text就不行了。
heapstorm
沟槽的how2heap又挂羊头卖狗肉,这题说是largebin attack 没错,但其实主要是house_of_storm
这次四个功能都特别简单,要注意的是:
禁止了fastbin
用的alloc申请。
这题的漏洞是一个off-by-null 用这个漏洞可以实现overlap 然后我们要想的是怎么弄出largebin的重叠部分,还有怎么进行house_of_storm
复习一下构造重叠的操作:
alloc(0x18) 0
alloc(0x508) 1
alloc(0x18) 2
这时候要在第二个chunk 0x4f0的位置构造prev_size
free 1
然后利用off-one-byte 覆盖使得chunk1缩小
然后在中间申请一个chunk 再free 2 就重叠了.
后面的house of storm大致如下:
构造第二个如上的覆盖。
然后利用unsorted bin 的切割 构造出两个unsorted bin 并且使得其中进入large bin(利用整理机制 )
一个largebin 用来通过偏移的方式写入一个size ,同时覆盖出合理bk
然后用unsortedbin attack 获得获取对应的chunk
后面就是利用合理的覆盖满足view功能的限定条件泄露libc 同时覆盖__free_hook
先写一些利用函数
def Allocate(size): |
先提前申请好六个chunk用来构造重叠
Allocate(0x18) #0 |
Allocated chunk | PREV_INUSE |
构造第一个重叠
|
接下来构造第二个重叠的时候,要求最大的那个unsorted bin 大小比第一个略小,这样才能把他它放进largebin
Delete(4) |
由于先进先出 这里不申请 把2free了,然后再申请 才会整理到后面那个位置
Delete(2) |
然后就是利用覆盖伪造bk 和bk_nextsize 先largebin attack 然后分配
storage = 0x13370800 |
注意unsortedbin会先被并入largebin 然后切割.
tinypad
这一题基本就是我自己写的了
从这题流程来看 house_of_einherjar利用条件是:
能够泄露heap地址和控制目标指针的地址
目标指针附近能够伪造fakechunk
有off-one-bye
先看几个功能
ADD: 任意大小申请 申请的同时写入目标大小 add的过程有off_one_byte漏洞
Delete: free,会把size清零,但是这题仍然有uaf,因为无论什么时候程序都会尝试输出指针指向内容
Edit:重新编辑 长度不会超过strlen(ptr)
Quit:退出
首先我们需要泄露libc和heap地址,这很容易实现
Add(0x100,b'a'*0x100) |
这时候3的fd会是main_arena
1的fd会是3的地址
p.recvuntil(b'CONTENT: ') |
我们等会会off-one-byte到3来打house of einherjar
先做好提前准备
我们需要控制的位置内存布局如下
0x0 buffer: 256
0x100 ptr1_size: 8
0x108 ptr1_ptr: 8
0x110 ptr2_size: 8
……
0x130 ptr4_size: 8
0x138 ptr4_ptr: 8
前面我们先delete了3,先进后出规则 重新申请回来1的时候 ptr1_size会填入0x100 这使得我们能通过unlink对于下一个chunk,prev_size的检查
于是我们需要把fakechunk放在buffer+0x20的位置
tinypad = 0x602040 |
先计算好prev_size
prev_size = chunk3_addr - fakechunk |
把chunk先malloc回来方便后面利用
payload = b'a'*0x20+p64(0)+p64(0x101)+p64(fakechunk)*2 |
off_one_byte
Delete(2) |
伪造fakechunk
payload = b'a'*0x20+p64(0)+p64(0x101)+p64(fakechunk)*2 |
这里注意 因为fakechunk这时候进入了unsortedbin 我们还需要重新伪造一遍
Edit(4,payload) |
然后 我们需要控制 main的返回地址 这里不能控制malloc_hook或者 free_hook 因为他们原本都是0 这导致我们无法对其修改
获得main的栈帧地址 可以通过libc里面的__environ 它会指向main函数的environ参数
0x74359e5c24a0-0x74359e200000+libc 有stack 0x7ffd1f2631b8-0x7ffd1f245000
这里我用的libc版本稍微不同 具体需要按照自己的libc来
Add(0xf8,b'a'*(0x100-0x20-16)+p64(0x100)+p64(0x74359e5c24a0-0x74359e200000+libc_addr)+p64(0x100)+p64(0x602148)) |
然后退出即可获得shell
完整exp
from pwn import * |
house of orange
先看每个功能
Build:
新建一个house
按顺序输入 name长度,name ,price,color
使用一个结构体存储两个部分的指针
结构体:
0x0: ptr to description(price and color)
0x8: ptr to name
name 就是一个存储name的chunk 申请大小最大不超过4096
description 是calloc(1uLL, 8uLL)
前四个字节存储价格 后四个字节存储颜色
Upgrade:
更新当前的house
漏洞就在这里 更新的时候是重新指定大小的 所以可以有非常大的溢出
See:
输出 这里也有漏洞,因为申请没有初始化 所以可以泄露地址。
这题就是标准的house_of_orange 所以就不写思路了
直接写流程
from pwn import * |