总的来说题目难度并不大,有一半是古典密码,而且总体没用到啥公钥密码的知识,对新手很友好(当复健练手,很久没做题了,逃——

最后结果是密码AK了,但是奖励按总体排名(所有方向的题目),刚好差一名有奖,打白工。

更新:前面被抓py排名升到16了,冬令营也py属实离谱(

brute_vigenere·

题目文件:vigenere.zip

由于key是4字节重复,按题目简介提示穷举key即可,由于给的表是对称的所以可以用数学的方法查表(否则就要打字典了)。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
from tqdm import tqdm
import itertools

table = 'abcdefghijklmnopqrstuvwxyz{}'
c = '{mvjk}gbxyiutfchpm}ylm}a}amuxlmg'

for k in itertools.product(table, repeat=4):
key = ''.join(k)*8
print(key, end=': ')
m = ''
for i in range(32):
j = (table.index(key[i]) + table.index(c[i])) % len(table)
m += table[j]
print(m)
if 'hsnctf{' in m:
print(m)
break # jguv


另外,由于密钥是4字节重复,根据签到题(或其他题目)可以知道flag不是“HSNCTF”就是“hsnctf”做头部,有6个字节,结合密文可以恢复key的前4字节,进而进行解密。

但是我解出来的key和上面枚举出来的并不一样,估计是多解。

PS:由于上面是枚举,所以是index相加,而下面是解密,所以是index相减,找几个例子试一下就可以理解了。

1
2
3
4
5
6
7
8
9
10
11
12
13
from tqdm import tqdm
import itertools

table = 'abcdefghijklmnopqrstuvwxyz{}'
c = '{mvjk}gbxyiutfchpm}ylm}a}amuxlmg'

# hsnc + {mvj -> twih (fake intro.
key = 'twih' * 8
m = ''
for i in range(32):
j = (table.index(c[i]) - table.index(key[i])) % len(table)
m += table[j]
print(m)

daobudao·

题目文件:daobudao.zip

解Base64然后解ROT,签到题水平(

不过这里安利一个在线工具:https://www.dcode.fr/cipher-identifier

strange_chacha·

题目文件:strange_chacha.zip

仔细看加密调用,明文其实是 passphrase,口令其实是key,而passphrase = flag ^ key,根据其他题目可知flag头有可能是“hsnctf”,而知key等同于知chacha_stream(key),所以可以用r = flag ^ key ^ stream ^ enc(前两个异或即passphrase)的方法恢复rand的前几个比特,而rand是两个比特重复的弱随机数,所以即恢复了整个rand

最后只需要enc ^ stream ^ rand即可恢复passphrase,进而恢复完整flag。

只是不知道谁出的题,flag最后还做混淆的???(后来发现可能是flag头是大写的“HSNCTF”导致的

观察规律,{}变成了[],可以是+32或者^32,都试试就好了。

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
from struct import *
from os import *
import itertools

def chacha_stream(passphrase):
def mixer(u, v):
return ((u << v) & 0xffffffff) | u >> (32 - v)

def forge(w, a, b, c, d):
for i in range(2):
w[a] = (w[a] + w[b]) & 0xffffffff
w[d] = mixer(w[a] ^ w[d], 16 // (i + 1))
w[c] = (w[c] + w[d]) & 0xffffffff
w[b] = mixer(w[b] ^ w[c], (12 + 2*i) // (i + 1))

bring = [0] * 16
bring[:4] = [0x61707865, 0x3320646e, 0x79622d32, 0x6b206574]
bring[4:12] = unpack('<8L', passphrase)
bring[12] = bring[13] = 0x0
bring[14:] = [0] * 2

while True:
w = list(bring)
for _ in range(10):
forge(w, 0x0, 0x4, 0x8, 0xc)
forge(w, 0x1, 0x5, 0x9, 0xd)
forge(w, 0x2, 0x6, 0xa, 0xe)
forge(w, 0x3, 0x7, 0xb, 0xf)
forge(w, 0x0, 0x5, 0xa, 0xf)
forge(w, 0x1, 0x6, 0xb, 0xc)
forge(w, 0x2, 0x7, 0x8, 0xd)
forge(w, 0x3, 0x4, 0x9, 0xe)
for c in pack('<16L', *((w[_] + bring[_]) & 0xffffffff for _ in range(16))):
yield c
bring[12] = (bring[12] + 1) & 0xffffffff
if bring[12] == 0:
bring[13] = (bring[13] + 1) & 0xffffffff


key = '52f0907eca3ce05d8d0b6691bb8c8dbca19b63b7bcfcf033fc320f182b5ad610'
enc = '6d9b546c9f1f5e7116203933dabbf25e3a0e143122b20c27e5c83ea26b9d0dbb'

key = bytes.fromhex(key)
enc = bytes.fromhex(enc)

stk32 = bytes(b for a, b in zip('0'*32, chacha_stream(key)))
print(stk32)
r = bytes([a ^ b ^ c ^ d for a, b, c, d in zip(stk32[:2], b'hs', enc[:2], key[:2])])
rand = r*16
print(rand)
pas = bytes(a ^ b ^ d for a, b, d in zip(enc, stk32, rand))
flag = bytes(a ^ b for a, b in zip(pas, key))
print(flag)

#a = '[\x19\x11\x14\x10\x14A\x12\x10\x19E\x10F\x19AB\x17D\x12\x14\x15D\x15EE]'
#real_flag = ''.join([chr(ord(x)+32) for x in a]) # wtm
real_flag = ''.join([chr(x^32) for x in flag])
print(real_flag)

smooth_rsa·

题目文件:task.zip

素数生成有问题,导致生成了p1p-12192^{19}平滑的素数,Pollard’s p-1可以解(可以参考[HPS14]的3.7节)。

懒得自己再码代码了,顺着题目代码找到了picoCTF2022的Very Smooth的,然后找到了PollardRsaCracker这个工具,即分解n。

首先从上面已经可以获得m_1,而Smooth的DLP本身也是易解的,直接上Sage的discrete_log解之。

最后结合m_1m_2拿flag。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# Sage
n = 18063067344610339216153985692612281572817741664675607678652303435614007498381778351769875436146202656046376229230301546036936157387112023740191321525053376287669082764405582433625684806876916446972913665369214463085013163680561680547504669535234097704543181896102678806687855186991260097818170404933906747197883469031713141650074132393349905331106066834535590661982175788821272806789997889177223174440597557123215327802956604540266427629432102831417141857529533126730032545280982187575314304833926895362131842373431496451307025688884322822389454974409791286467835534879165048438720535286815275424674266921135402897929

# from PollarRsaCracker(https://github.com/ZeroBone/PollardRsaCracker)
q = 117486439452151013756875087780890246814564400691388361051671017362980892839542853238429910539305056241983874345637539194223655284177104469678700212956147349645867093623855297098201296729367179715901763674630438303543911790021803446119555075061116934105943593758927093241215951931328064556145541149845397321183
p = n // q
assert p*q == n
m1 = 412905115020036859448242054750143050792413836344

c2 = 889007651662506403203783493267282257215988729179620082971032093479384814992266823808192587257794931781622242482548202684315610166947125782056641835890542545830029903217817561269310184963519900268026434414254409767043528083064767018130469968738966212268513325090645430187706207887862845389628550171196492124556219364186293306935140349363837175430616647997752033400818089096772569695030947505437436792691260570218211502426593061096955991144063055944016658538765609416477033433728682889456364449301289731877176395077152830024245111711011732884071448942050549470148093802410710200714874231484059368826458031775976862475
e = 65537

G = Zmod(p)
m2 = discrete_log(G(c2), G(e))
print(m2)
# 567361900135895770360389450735093365969913930365

import libnum
flag = libnum.n2s(int(m1)) + libnum.n2s(int(m2))
print(flag)

Reference:[HPS14] Hoffstein, Jeffrey, et al. An introduction to mathematical cryptography. Vol. 1. New York: springer, 2008.

extract·

题目文件:Cloakify.txt.zip

附赠一道MISC题(

文件名叫“Cloakify”,github上找到同名工具,用其emoji模式(11)解之得到一个压缩包,观察可知是对一个文件(估计是flag)循环压缩,写py代码循环解压得flag。

代码逻辑大概是,遍历目录中的文件,解压,然后删除解压过的文件,循环执行,理论上整个循环过程文件夹中都只有一个文件(如果出题人没对多文件压缩的话)。

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

while True:
g = os.walk('./out/')
for path,dir_list,file_list in g:
for file_name in file_list:
zfn = os.path.join(path, file_name)
print(zfn)
if 'zip' not in zfn:
break
zf = zipfile.ZipFile(zfn)
for zfin in zf.namelist():
zf.extract(zfin, './out/')
zf.close()
os.remove(zfn)