2024BuildCTF公开赛-Misc&Crypto方向WriteUp详解
1623499358049469 发表于 湖北 CTF 518浏览 · 2024-10-26 01:22

MISC

FindYourWindows

题目拿到两个文件:密钥文件和磁盘镜像文件
使用磁盘解密的常用工具VeraCrypt进行镜像的挂载,并且使用指定keyfile进行解密,挂载到任意空闲的驱动器号中:

挂载成功后,就能像正常访问Windows文件管理器一样,进行简单的文件翻找,没有发现flag

使用WinHex打开磁盘驱动器,可以看到有关磁盘的相关信息

考虑到flag字符串的格式,直接在Winhex中搜索字符串,得到flag

题目分析:
这是一道很标准的磁盘取证题,关键就是要注意到文件的大小比较规整,为2048KB,结合另一个附件的文件名为key,就能知道这是一个被Keyfile密钥文件加密的文件,进而开始磁盘的取证分析,主要有以下做法:

  • 磁盘文件的解密和挂载
  • 磁盘文件格式(FAT32,NTFS)的分析
  • 磁盘文件扇区的恢复,找回删除的文件
  • 对磁盘内文件的分析

什么?来玩玩心算吧

题目交互环境是一个简单的python计算器


计算24点并不能获得flag,那么需要命令执行,确定题目类型为pyjail逃逸
注意到交互中的所有字母都被WAF,似乎是无字母RCE,简单Fuzz一下只有括号,数字,运算符可以通过。

想到使用Unicode字符绕过,使用如下的宽体字,可以成功通过检验得到执行:

eval(input())

题目分析:
python引入了对unicode的支持,这使得很多基于黑名单危险函数和字母的WAF均可以被简单绕过,总的来说,这个"无字母" pyjail并没有像PHP无字母RCE那样困难。
此外,如果使用八进制,如\160\162\151\156\164\50\70\51同样可以完成无字母绕过,但是此题限制了斜杠,不适用

四妹还是萍萍

使用十六进制编辑器打开附件图片,发现了异常的IDAT块:

在倒数第二个IDAT处发现了类似压缩包的文件结构,包内存在文件passwrod...

提取出压缩包,利用公众号得到的密码解压得到,文本文件,发现图片的BASE64标识 iVBOR
很容易还原图片:

不难发现这是宽度修改不当导致的色块分布错误,需要根据CRC32值爆破宽高进行修复。

  • 宽为: 00000311 高为: 00000616
    得到flagtu'pian

我太喜欢亢金星君了

附件得到gif,很显然是要分析帧图片,使用工具分离一共258帧图片:


根据题目提示,需要转成摩斯码,而黑色图片似乎间隔出现,不做译码
将白色图片作为分隔符,编写脚本转换摩斯码(使用md5哈希标识每张图片)

import os
import numpy as np
from PIL import Image
from hashlib import md5

files = os.listdir("./gif")
files = sorted(files, key=lambda x: int(x.lstrip("output.gif_").rstrip(".jpg")))
dic = {}
res = ""
tags = ["-", "", ".", "/"]
for img in files:
    img = Image.open("./gif/" + img)
    arrimg = np.array(img).flatten()
    imghash = md5(arrimg).hexdigest()
    if imghash not in dic:
        dic[imghash] = tags[len(dic)]
    res += dic[imghash]
print(res)

对得到的摩斯码进行解密就能得到flag.

Black&White

黑白图片已经给出,显然需要先将其转换为01的二进制信息:

import os
from PIL import Image
fl = [i for i in os.listdir(".") if i.endswith(".jpg")]
fl = sorted(fl, key=lambda x: int(x.split(".")[0]))
res = ""
for i in fl:
    im = Image.open(i).convert("L")
    color = im.getcolors(256)[0][1]
    if color == 255:
        res += "1"
    else:
        res += "0"
print(res)

得到了01串,发现大小恰好为33*33,将其转换黑白的二维码,扫码得到密文3I8XEDHUCJTARQFOEDX7D+08AC80T8N08Y6948DF2C43C9B6Z2,发现只有大写字符和数字,尝试Base45解密得到flag

题目分析:
本题和上一题一样,都需要手动编写脚本批量处理图片文件,不同于隐写题,图片处理需要对图片内容拥有敏锐的感知,如RGB色值转ASCII码等处理方式,都需要至少熟悉一种图像处理的库,才能做起来得心应手。

CRYPTO

Ju5t_d3c0de_1t!

进制、编码和RSA

e = 'VTJGc2RHVmtYMThUWGplSkN2YzJwazJ2KzJieQ=='
c = 10110011010010110101101101011110100001010100101110111011101101111101101001000010111010010011101111001111111000000001111000000010101
p = 1100010000110101100011011010101011111101001101010011001010110111100011110110101111000101100001011011001100110010010111111100110000001111010111111101111001100111100011100110110011101011111011100111010011000100011001101101111011010110000011011100101010010101110001011011010111001010101101100101011111101000110010111000011010111001101001
q = 11000010010010011110101110100011011100101101100100011011100010111000011110111000000011111100010100100000101101010101000101101101101011011100001101111010001010010011001010110010110101001100100011010110100011110011101110101110111110100000011110101010011011111010111100011000011111001000010101100100011110010000010001110010101100100111001
key = 592924741013363689040199750462798275514934297277010275281372369969899775117892551575873706970423924419480394766364097497072075403342004187895966953143489192628648965081601335846012859223829286606349019
# use m minus key to get the final flag!

题目分析:
RSA本身并不难,但是需要进行解码,其中,e需要在base64之后进行Rabbit解密得到65537
已知p,q,解密的过程并不难,根据提示需要最后减去key再转为字符串。

ezzzzz_RSA

题目涉及两个关于RSA因子的问题:

n = p * q
    n1 = q * r
    n2 = p * s
    c = pow(m, e, n)
    h0 = pow(2023 * p + 2024, s, n2)
    h1 = pow(2024 * r + 2023 * q, 113, n1)
    h2 = pow(2023 * r + 2024 * q, 629, n1)

题目分析:

  • 首先是利用h0分解n2,在(2023 * p + 2024)^s中只有一个不含p的常数项2024^s,利用2024^n2 % n2做差就能消除,然后和 n2 就拥有了共同的因子p
  • 然后利用h1,h2分解n1,在模n1下,r和q的共同项都会被消去,根据二项式定理就知道,h1和h2只含r和q的最高次项。
e = 65537
n1 = 19957426023169626195602761840035904096149402534966487535713447987366768645542881124782551268978342063458430846877824210659778126281705984711061190351636497944943321988950188171159903717348936556346198638311950016136865425015037098270040031872702873264144372191898253134939805153141701819590164140250130420280491966786900651186941317959556066730959744279963976065565436153399679475410040773637142677936926894677919242351610457296203864806991539480593546084449323017670431590012312526757477514457145686070196978477495658962519391041011847512041022828710693830661412217389320600888361578917153088073678587422269955710471
n2 = 11933661747067216317642315621042074566046499785197709817779978157416906347669444374234313329064859622960743743511735672614999566264025648698589886185056758071718319964262619819143757922916624196354313322456534266520150543008117888101349920396737532937616502689667208207329048979872222563877933742673021891249520999021187404065706388700711208445628041386956459398271230236018476964839399245143666534359113777846535151773174701732284280083586580489995666306373839417946648196140879978268472361473557375951972193618245984950374326806423407152520541682571610372434453778172497925696535270204943842467472100237854318244291
c = 20080676122944896238797522372441559951736929534371084097400233944319893926800196694449564534150770085554349952433141815637324753386484549616573636001763815852095984830828952020047938406909274311785306299061021662484544371813739713520361343350959698642021322243662988875917088108399877176033404097457939417134483333264562602633853694382014472747500159100723626314928476484037666519857604568300967071868151508142784271042600815406853978696857309760951105852288354603503207383899902135741426285551161292195639862478256231538619968275273876467583013024899054710124331145912185471501398910765579441956531091561893256832468
h0 = 2996726009726260695732821166504040344731102637047682432884058857493935625094258046641569918904978173116793673563730117949606727933902262668880339210084101176866383602543966179840353633735507442926707342258391362245904850297416642271123328980812931025677857373199540129280097315832907023777052101133649877194495480543646472133854655383755313968952550827443970931104462445312146328606862802196901953935238972759852435882720786570965542286278549107402918041194008845717507735786897968734831064393337773557817839343449001368565856921138408039931608804233595980497557733714560035682416265029819340316734845279080134432704
h1 = 19843160604742228074331688651361052208481287636527838615063387670722213224954610448720065937378201545177278841575633697012434074186046556843292068835752113384756149944114298949115412819730843598288637259467085268861201775723817790428386595559040938133481222229290199923979132846871398172318539492741755408720073350962388138453341677009547616238262211176727424067946020683742262782319735286357465817786446238528187722959357444676512705451504136333336415880020502524009647940182721264953084120705872870651891290569527156804993340563927419561415555818468261824287933683736509372616293569615247228388443284457740072850735
h2 = 15147052684674827267989051566164167603473413362261253296001082161136918959833294463185335416662127368473980239667918561600741667513285708843081475074688239507330230558331408877583246661862040918410036936505307437329914363201630212163952357444441705663871720438955166472073576526814546767805314463827075388036712200327696168965762177567346966479399896578190111819130000991594490932388132188241726654756368698998232826340969288082645860324404980143489489946490266439447342461483490582149239131554246756547000945718737195930407251232848166108751122870333559461452459416252942341423373918245090162970624108991537972775066

a = pow(h1, 629, n1)
b = pow(h2, 113, n1)
t = 629 * 113
A = pow(2024, t, n1) * a % n1
B = pow(2023, t, n1) * b % n1
r = GCD(n1, A - B)
q = n1 // r
p = GCD(pow(2024, n2, n2) - h0, n2)
phi = (p - 1) * (q - 1)
d = inverse(e, phi)
m = pow(c, d, p * q)
print(libnum.n2s(m))

gift

题目涉及p+q高位已知的问题:

def get_gift(p, q):
        noise = getPrime(40)
        p, q = p + 2 * noise + 1, q - pow(noise, 2)
        gift = 2024 * (p + q)
        return gift
    p = getPrime(512)
    q = getPrime(512)
    n = p * q
    e = 0x10001
    m = bytes_to_long(flag)

题目分析:
先可以把式子变形为(p+q)-(r-1)**2+2,p,q为512位,noise的位数为40,显然gift泄露了p+q的高位
可以多次尝试发现,除去低86位,gift高位等于p+q的高位
造Copper解决:

#sage
import gmpy2
from Crypto.Util.number import *
gift = 46635322848619790584491725916282901439691751328335921415278638528896063068132242718070261114525516272650970256270551306096774004921902972838212903368063625872
c=101383046356447336426623798470530695448361708798731382238747567108067236241251384089401506320741815081024352908156466877907424203888923965647318146770258139921360377246187637085549628797640957048672797430217647039035455011311505942632107576730906489223641894279483592789523228409885925263914621255862261546919
n=131097719698687108485813302886652389604731026998272796315024695395496199386497660846418712521921387496051077394308820230360184411431376692252923609505060476542577219656866593501271690536991944882324175509626138475159461332403161471880082192150081456601522403673111515117219716055561941951891570977025178643791
gift=(gift//2024)-2
x1=(gift>>86)<<86
e = 0x10001
PR.<x> = PolynomialRing(RealField(1000))
f = x*(x1-x) - n
phigh = int(f.roots()[0][0])
print(phigh,phigh.bit_length())
PR.<x> = PolynomialRing(Zmod(n))
f = phigh + x
res = f.small_roots(X=2**90, beta=0.4,epsilon=0.01)[0]
p=int(res+phigh)
q=n//p
assert p*q==n
d=gmpy2.invert(e,(p-1)*(q-1))
m=int(pow(c,d,n))
print(long_to_bytes(m))

girls_band_cry_pto

def getprime(kbit, FLAG):
    a = getPrime(kbit)
    b = getPrime(kbit)
    N = getPrime(kbit + 5)
    seed = getPrime(kbit)
    t = seed
    list_t = []
    for i in range(10):
        t = (a * t + b) % N
        list_t.append(t)
    if FLAG:
        print(list_t)
    return seed

p = getprime(512, 1)
q = getprime(512, 0)
flag = b"..."
flag = bytes_to_long(flag)
n = p * q
e = 1384626
assert flag.bit_length() < n.bit_length() // 2
c = pow(flag, e, n)
print("c=", c)

关键在于利用LCG的多个连续项,还原LCG的a,b和seed参数从而得到p.

  • 同时,题目给出了提示flag.bit_length() < n.bit_length() // 2,这意味者我们可以只用p解RSA
    X=[]
    c = 51846448616255629242918159354807752786692784645460532308823434086479848425723111371477823327980874708898952566998637230358105087254392989515438172155717708590176244736140994735777168368143405720703501031813936741444894000217727880068767785957507824708838189619286341612305393812568642372035793481458142583420
    diffs = T = [x1 - x0 for x0, x1 in zip(X[:-1], X[1:])]
    zeros = [t2 * t0 - t1 * t1 for t0, t1, t2 in zip(diffs, diffs[1:], diffs[2:])]
    N = abs(GCD(*zeros))
    print(N, N.bit_length())
    n = 2
    A = (T[n] * pow(T[n - 1], -1, N)) % N
    B = (X[2] - X[1] * A) % N
    print(A.bit_length(), B.bit_length())
    p = (X[0] - B) * inverse(A, N) % N
    pphi = p - 1
    e = 1384626
    c = c % p
    print(GCD(e // 6, pphi))
    d = inverse(e // 6, pphi)
    m = pow(c, d, p)
    print(f"{p=}")
    print(f"{m=}")
    
    当发现e和phi不互质时,可以先把看成一个关于m**6的RSA,此时e//6和p-1互质
    最后利用模下开方得到m:
    #sage
    from Crypto.Util.number import *
    p=11406146503880823399297963629845559494461007497449751823413253129053497454943898251983630826960583790125622063400069720120655389759003474122064810099132057
    c=4627418782344342494068293700017423506328601823965912183563375693738732971994620371812616987659520185367722780188594554060692021622964832980886716122288540
    R.<x> = Zmod(p)[]
    f=x^6-c
    res=f.roots()
    print([long_to_bytes(int(i[0])) for i in f.roots()])
    

mitm

顾名思义为中间相遇攻击,题目使用4个随机的密钥进行了4次AES加密

flag = b""
note = b"Crypt_AES*42$@" * 2
r = 4
keys = []

def enc():
    for i in range(r):
        key = bytes(choices(note, k=3))
        print(key)
        print(sha256(key).digest())
        keys.append(sha256(key).digest())
    print(keys)
    leak = b"Hello_BuildCTF!!"
    cipher = leak
    for i in range(r):
        cipher = AES.new(keys[i], AES.MODE_ECB).encrypt(cipher)
    enc_key = sha256(b"".join(keys)).digest()
    enc_flag = AES.new(enc_key, AES.MODE_ECB).encrypt(pad(flag, AES.block_size))
    print(f"cipher = {cipher}")
    print(f"enc_flag = {enc_flag}")
  • 爆破四个密钥进行四层加密/解密,复杂度为(A3/14) ^ 4 无法穷举
  • 进行中间相遇攻击,使用空间换时间,分别进行两层爆破解密和两层爆破加密,对中间的密文结果取交集
from Crypto.Util.number import *
from Crypto.Util.Padding import *
from hashlib import sha256
from Crypto.Cipher import AES
from random import *
from itertools import permutations
from tqdm import tqdm
note = b"Crypt_AES*42$@"
r = 4
A, B = set(), set()
leak = b"Hello_BuildCTF!!"
dic = set()
for i in permutations(note * 3, 3):
    dic.add(bytes(i))
for a in tqdm(dic):
    a = sha256(bytes(a)).digest()
    c1 = AES.new(a, AES.MODE_ECB).encrypt(leak)
    for b in dic:
        b = sha256(bytes(b)).digest()
        c2 = AES.new(b, AES.MODE_ECB).encrypt(c1)
        A.add(c2)

cipher = b"\xb9q\x04\xa3<\xf0\x11-\xe9\xfbo:\x9aQn\x81"
for d in tqdm(dic):
    d = sha256(bytes(d)).digest()
    c3 = AES.new(d, AES.MODE_ECB).decrypt(cipher)
    for c in dic:
        c = sha256(bytes(c)).digest()
        c2 = AES.new(c, AES.MODE_ECB).decrypt(c3)
        B.add(c2)
print(A & B)

发现加密过程的中间密文为b"\xd0\xe8]\x1dIQ\x93S\x7f\xe1\xda\x90\xe5\x19\xe6\xb8"
由此可以解得flag

ominous

from Crypto.Util.number import *
from secret import flag
import random
import string

Ominous_dic = ['啊', '米', '诺', '斯']
flag_word = (string.ascii_letters + string.digits + '{}@_!').encode()
assert all(char in flag_word for char in flag)
msg = bytes_to_long(flag)
random.shuffle(Ominous_dic)
def Ominous_enc(msg):
    res = 0
    for idx, word in enumerate(Ominous_dic):
        res += random.randint(0, 200) * ord(word) * (2 ** (50 * (idx + 1))) 
    return res + msg

cipher = Ominous_enc(msg)
print(f'cipher = {cipher}')
# cipher = 11174132013050242293373893046306047184706656363469879247040688497021

200以内的随机数乘word并不能超过2**50,这意味着可以每50个比特去穷举而不用连续穷举4层深度:

from Crypto.Util.number import *
import string

cipher = 11174132013050242293373893046306047184706656363469879247040688497021
print(cipher.bit_length())
dic = ["诺", "斯"]
for idx, word in enumerate(dic):
    print(idx, word, ord(word))
    assert 200 * ord(word) < 2**44
flag_word = string.ascii_letters + string.digits + "{}@_!"
c1 = cipher % (2**48)
print(long_to_bytes(c1).decode())
c2 = cipher % (2**96)
for i in dic:
    for j in range(201):
        x = j * ord(i) * 2**50
        c11 = (c2 - x) >> 48
        try:
            m = long_to_bytes(c11).decode()
            assert all(char in flag_word for char in m) and "}" not in m
            print(m, len(m), i)
            # break
        except:
            pass
print()
c3 = cipher % (2**144)
for i in dic:
    for j in range(201):
        x = j * ord(i) * 2**100
        c11 = (c3 - x) >> 96
        try:
            m = long_to_bytes(c11).decode()
            assert all(char in flag_word for char in m) and "}" not in m
            print(m, len(m), i)
            # break
        except:
            pass
print()
# BuildCTF{Wh@t_i5_0m1nous!!!}

where is my n

flag = "..."
    e = 65537
    p = getPrime(512)
    q = gmpy2.next_prime(p)
    n = p * q
    phi = (p - 1) * (q - 1)
    d = inverse(e, phi)
    c = pow(flag, e, n)
    print("c=", c)
    print("e=", e)
    print("d=", d)

给出了e和d,要求还原n,需要先还原phi,可以先简单计算一下,phi在1023~1024个比特位
e*d=k*phi+1 在1039个bit,所以可以穷举得到phi,然后分解p,q:

from Crypto.Util.number import *
from gmpy2 import *
from sympy import nextprime, prevprime
c = 107973408658512316248795675829719026556281556876279221462095299771897472835817102507431099132436173117611783572607408542140665445616624626408781699266046553444252772105867617770124779786841928535661872891635303381758336724610931502145965143374870804147444436791292512235485326451051756451904673491759905663466
e = 65537
d = 62036379179617188220635702722848631787124203142048526951004487465970915306760341332025319712290841316288636152355908585406155087541334717529113872233640624205650204907669681116401961584897042519881342485819364897891612540596760113597723865477121348794797592568686540283535491492936074500143092361821406613969
kphi = e * d - 1
print(kphi.bit_length())
for i in range(2**14, 2**16):
    # print((kphi // i).bit_length())
    if kphi % i == 0:
        phi = kphi // i
        temp = isqrt(phi)
        p = nextprime(temp)
        q = prevprime(temp)
        n = p * q
        if d == inverse(e, (p - 1) * (q - 1)):
            m = pow(c, d, p * q)
            print(long_to_bytes(m))
            break

我这辈子就是被古典给害了

先进行类似凯撒的换表加密,然后是AES加密,编写相应解密函数即可,由于flag头已知,密钥的长度为8,就能直接得到key

from Crypto.Util.Padding import pad, unpad
from Crypto.Random import get_random_bytes
from Crypto.Cipher import AES
def minus(a, b):
    assert len(a) == len(b)
    res = ""
    for i in range(len(a)):
        x = (dict1[a[i]] - dict1[b[i]]) % 26
        res += dict2[x]
    return res
def AES_dec(key, value):
    key = (key * 2).encode()
    cipher = AES.new(key, AES.MODE_ECB)
    decrypted_text = cipher.decrypt(value)
    decrypted_text = unpad(decrypted_text, AES.block_size)
    print("AES Decrypted Text =", decrypted_text.decode())

Text = "HLMPWKGLYSWFACEWBYRSUKYFAXZFXDKOTZHHSLFCXNICAHPGRIFUF"
AESText = b'\x92T{\x1f\x0f"\xbd\xbb\xfa|O\x11\x83\xa0\xec.\x15]\x9f\x9a\xe5\x85Z\x9f@yUm\xbb\xdc\x93\x08\xe5\x8b\xd5\x98\x84\xfa\x91\xe8\xde\x1b}\xcd\x9056\xa3\xbf\xdb\x85J\xcc\xec\x812T\x11\xa7Tl\x15\xf6"'
print(len(Text))
a = "BUILDCTF"
b = "HLMPWKGL"
key_ = minus(b, a)
print(key_, len(key_))
print(AES_dec(key_, AESText))
0 条评论
某人
表情
可输入 255