题目描述

你能突破九大关卡修成神仙吗?

hint1:压缩包密码为比赛名称+8位什么来着?忘了。哈哈哈!

hint2:flag格式:XYCTF{md5(flag)}

hint3:第三层非夏多,看看交点

hint4:第六层键盘画图,狼蛛键盘最新版你值得拥有!

开头

由提示猜测压缩包密码XYCTF20240401

炼气

第一层是天书加密,用随波逐流就可以解出压缩包密码。

第二层是一张图片,修改高就可以看到flag的第一部分:

XYCTF{T3e_c0mb1nation_

筑基

第一层是BubbleBabble编码,在线解码就能解出压缩包密码。

第二层是一张图片,010看不出什么东西,用StegSolve通过LSB可以找到一串Base64编码,解码可得flag第二部分:

0f_crypt0_and_

结丹(全复现)

给出一张图片

hint3

hint说看交点,可能跟Begin CTF 2023的下一站上岸差不多,四个交点为”-”,三个交点为空格,一个交点为”.”,可以转化为:

- .... . ..--.- - .... .. .-. -..

摩斯密码解码就可以得到压缩包的密码

打开压缩包发现还有一层压缩包和一个txt,文本文件里面没有有用信息,但是压缩包带密码,用010打开看看,看到末尾有一段附加信息:

image-20240428173027310

分析特征,显然是Base64,解码之后可以得到压缩包密码,解压发现里面有一个文本文件,由特征可知是Base32,解码就可以得到flag的第三部分。

misc_1s_re6lly_fun!!

元婴

hint4.txt打开一看发现是base64,解密一看salted,用这个网站解密:在线Triple DES加密 | Triple DES解密- 在线工具 (sojson.com)(一个个试出来的),密码是2024.

拿密码打开压缩包,有个hint.txt和一个db文件,hint内容如下:

wqk:1m813onn17o040358p772q37rm137qpnqppqpn38nr704m56n2m9q22po7r05r77

进行一个凯撒解密就可以得到一个key,

下面是在52上面找到的一个解密微信聊天数据库的脚本:

# -*- coding: utf-8 -*-
from Crypto.Cipher import AES
import hashlib, hmac, ctypes

SQLITE_FILE_HEADER = bytes("SQLite format 3",encoding='ASCII') + bytes(1)#文件头
IV_SIZE = 16
HMAC_SHA1_SIZE = 20
KEY_SIZE = 32
DEFAULT_PAGESIZE = 4096 #4048数据 + 16IV + 20 HMAC + 12
DEFAULT_ITER = 64000
#yourkey
password = bytes.fromhex("...".replace(' ',''))#这里填入上面求出的key

with open(r'...\\MSG0.db', 'rb') as f:#这里填入MSG0.db的文件路径
blist = f.read()
print(len(blist))

salt = blist[:16]#微信将文件头换成了盐
key = hashlib.pbkdf2_hmac('sha1', password, salt, DEFAULT_ITER, KEY_SIZE)#获得Key

first = blist[16:DEFAULT_PAGESIZE]#丢掉salt

# import struct
mac_salt = bytes([x^0x3a for x in salt])
mac_key = hashlib.pbkdf2_hmac('sha1', key, mac_salt, 2, KEY_SIZE)

hash_mac = hmac.new(mac_key ,digestmod = 'sha1')#用第一页的Hash测试一下
hash_mac.update(first[:-32])
hash_mac.update(bytes(ctypes.c_int(1)))
# hash_mac.update(struct.pack('=I',1))
if (hash_mac.digest() == first[-32:-12]):
print('Correct Password')
else:
raise RuntimeError('Wrong Password')

blist = [blist[i:i+DEFAULT_PAGESIZE] for i in range(DEFAULT_PAGESIZE,len(blist),DEFAULT_PAGESIZE)]
with open(r'...\\ChatMsg.db', 'wb') as f:#解密后的路径
f.write(SQLITE_FILE_HEADER)#写入文件头
t = AES.new(key ,AES.MODE_CBC ,first[-48:-32])
f.write(t.decrypt(first[:-48]))
f.write(first[-48:])
for i in blist:
t = AES.new(key ,AES.MODE_CBC ,i[-48:-32])
f.write(t.decrypt(i[:-48]))
f.write(i[-48:])

用打开数据库的软件打开解密之后的数据库(我用的Dbeaver),在MSG表里面就可以看到flag的第四部分:

L1u_and_K1cky_Mu

化神(第二部分复现):

hint5给出了一串字符和md5后的结果,进行爆破,爆破出压缩包密码,解压第五层.zip 看到flag.txt里面没有有用的东西.

serpent.txt文件里有东西,考虑serpent加密,用这个网站Serpent Encryption – Easily encrypt or decrypt strings or files (online-domain-tools.com)

通过文件输入,从头到尾没有密钥,猜测密钥是前面爆破得到的key,解密之后下载下来塞进Cyberchef可以看到

image-20240428172659005

导出为txt并转换编码为UTF-8后可以得到:

image-20240428172642676

字数比可以看到的多很多,考虑零宽字符隐写:

将文本转化为unicode编码后:

image-20240428172606982

筛选发现,里面包含零宽unicode字符\u200A,\u200B,\u200C,\u202C,\uFEFF

Unicode Steganography with Zero-Width Characters (mzy0.com)解码即可得出flag的第五部分:

_3re_so_sm4rt!

炼虚(第二部分复现):

第一层是键盘画图,是一个句子,不确定的字母可以掩码爆破。

用上面得到的密码打开压缩包,可以看到一个压缩包,一个word文档,一个Excel文档,一个PPT,一个文本文件还有一张图片。除了图片之外,都没有看到有什么有用的,直接用steghide提取一下,发现要密码,发现上面几个文件名称都是数字,倒着来输一遍(98641)就是密码(纯猜谜),提取出来就可以得到flag的第六部分:

In_just_a_few_m1nutes_

合体

第一层UTF-7加维吉尼亚,密钥是字母表。

解开压缩包可以看到一个图片:

flag

就一个八进制,按颜色填数字就行,八进制转换之后就可以得到flag的第七部分:

they_were_thr0ugh!

大乘

这一层的第一部分是一个RSA,显然是p^q泄露,加密代码如下:

from Crypto.Util.number import bytes_to_long, getPrime
flag=b"password{xxxxx}"
p,q= getPrime(1024),getPrime(1024)
n = p * q
e = 65537
m = bytes_to_long(flag)
c = pow(m,e,n)
print("n=",n)
print("c=",c)
print("p^q=",p^q)
'''
n= 22424440693845876425615937206198156323192795003070970628372481545586519202571910046980039629473774728476050491743579624370862986329470409383215065075468386728605063051384392059021805296376762048386684738577913496611584935475550170449080780985441748228151762285167935803792462411864086270975057853459586240221348062704390114311522517740143545536818552136953678289681001385078524272694492488102171313792451138757064749512439313085491407348218882642272660890999334401392575446781843989380319126813905093532399127420355004498205266928383926087604741654126388033455359539622294050073378816939934733818043482668348065680837
c= 1400352566791488780854702404852039753325619504473339742914805493533574607301173055448281490457563376553281260278100479121782031070315232001332230779334468566201536035181472803067591454149095220119515161298278124497692743905005479573688449824603383089039072209462765482969641079166139699160100136497464058040846052349544891194379290091798130028083276644655547583102199460785652743545251337786190066747533476942276409135056971294148569617631848420232571946187374514662386697268226357583074917784091311138900598559834589862248068547368710833454912188762107418000225680256109921244000920682515199518256094121217521229357
p^q= 14488395911544314494659792279988617621083872597458677678553917360723653686158125387612368501147137292689124338045780574752580504090309537035378931155582239359121394194060934595413606438219407712650089234943575201545638736710994468670843068909623985863559465903999731253771522724352015712347585155359405585892

'''

解密代码如下:

from Crypto.Util.number import *
import sys
sys.setrecursionlimit(100000)#随便设置,但是不能不设置,增大递归深度限制

def pq_high_xor(p="", q=""):
lp, lq = len(p), len(q)
tp0 = int(p + (1024-lp) * "0", 2)
tq0 = int(q + (1024-lq) * "0", 2)
tp1 = int(p + (1024-lp) * "1", 2)
tq1 = int(q + (1024-lq) * "1", 2)

if tp0 * tq0 > n or tp1 * tq1 < n:
return
if lp == leak_bits:
pq.append(tp0)
return

if xor[lp] == "1":
pq_high_xor(p + "0", q + "1")
pq_high_xor(p + "1", q + "0")
else:
pq_high_xor(p + "0", q + "0")
pq_high_xor(p + "1", q + "1")


def pq_low_xor(p="", q=""):
lp, lq = len(p), len(q)
tp = int(p, 2) if p else 0
tq = int(q, 2) if q else 0

if tp * tq % 2**lp != n % 2**lp:
return
if lp == leak_bits:
pq.append(tp)
return

if xor[-lp-1] == "1":
pq_low_xor("0" + p, "1" + q)
pq_low_xor("1" + p, "0" + q)
else:
pq_low_xor("0" + p, "0" + q)
pq_low_xor("1" + p, "1" + q)

n = ...
c = ...
mask = (1<<1024)-1

leak_bits = 1024

leak = ...
xor = bin(leak)[2:].zfill(1024)
pq = []
pq_low_xor()
for p in pq:
if n % p == 0:
q = n // p
phi = (p-1)*(q-1)
d = inverse(65537,phi)
m = pow(c,d,n)
print(long_to_bytes(m))

得到密码解开压缩包,可以见到一个txt,打开看到一堆no和yes,将no替换为0,yes替换为1,通过下述脚本转换为图片:

from PIL import Image
with open("temp.txt",'r') as f:
s = f.read()
pic = Image.new("L",(548,72))
for h in range(72):
for w in range(548):
if s[h * 548 + w] == '0':
pic.putpixel((w,h),0)
else:
pic.putpixel((w,h),255)
pic.save("out.png")

out

是须弥沙漠文,对字母表换就行,下附字母表

fcd8e8310bde689198d4be3e6b749f15_9129134670435119053

换完就可以得到flag的第八部分:

sm3rty0ucando

渡劫(第二部分复现)

这一层的第一部分是一个RSA,很简单,加密代码如下:

from Crypto.Util.number import *
from random import randint

p = getPrime(512)
q = getPrime(512)
n = p * q
e = 65537

list = []
for _ in range(2):
a, b = randint(0, 2**8), randint(0, 2**256)
list.append(a * p + b * q)

password = b"xxxxx"
c = pow(bytes_to_long(password), e, n)
print(f'{n = }')
print(f'{c = }')
print(f'{list = }')

#n = 107803636687595025440095910573280948384697923215825513033516157995095253288310988256293799364485832711216571624134612864784507225218094554935994320702026646158448403364145094359869184307003058983513345331145072159626461394056174457238947423145341933245269070758238088257304595154590196901297344034819899810707
#c = 46049806990305232971805282370284531486321903483742293808967054648259532257631501152897799977808185874856877556594402112019213760718833619399554484154753952558768344177069029855164888168964855258336393700323750075374097545884636097653040887100646089615759824303775925046536172147174890161732423364823557122495
#list = [618066045261118017236724048165995810304806699407382457834629201971935031874166645665428046346008581253113148818423751222038794950891638828062215121477677796219952174556774639587782398862778383552199558783726207179240239699423569318, 837886528803727830369459274997823880355524566513794765789322773791217165398250857696201246137309238047085760918029291423500746473773732826702098327609006678602561582473375349618889789179195207461163372699768855398243724052333950197]

解密代码如下:

from Crypto.Util.number import *
import gmpy2

n = ...
c = ...
list = [..., ...]
e = 65537

for a1 in range(2**8):
for a2 in range(2**8):
if(gmpy2.gcd(list[0]*a1-list[1]*a2,n)!=1 and gmpy2.gcd(list[0]*a1-list[1]*a2,n)!=n):
print(gmpy2.gcd(list[0]*a1-list[1]*a2,n))#这里求出来是q
#--------part2--------
q = ...
p = n // q
assert p*q == n
phi = (p-1)*(q-1)
d = gmpy2.invert(e,phi)
m = pow(c,d,n)
print(long_to_bytes(m))

解开压缩包就是一个带图片的压缩包和一个txt,txt内容如下:

压缩包里的图片真的有东西吗?不如看向外面

图片里面确实没有东西,由压缩包名字“我们的小秘密嘿嘿”猜测是oursecret隐写,猜测密码是上面解出来的,尝试发现是正确的,就可以得到flag的第九部分:

_nine_turns?}

汇总

组合之后:

XYCTF{T3e_c0mb1nation_0f_crypt0_and_misc_1s_re6lly_fun!!L1u_and_K1cky_Mu_3re_so_sm4rt!In_just_a_few_m1nutes_they_were_thr0ugh!Sm3rt_y0u_can_do_nine_turns?}

注意,第八段开头大写,然后要按词加下划线得到flag之后要整体用md5加密之后再包上XYCTF{}才是flag。