2025LilCTF -- Crypto -- WriteUp

2025LilCTF -- Crypto -- WriteUp

[WARM UP] 对称!Just Decrypt

task

"""Just Decrypt - A simple LilCTF warmup challenge."""

from random import Random
from Crypto.Cipher import AES
from Crypto.Util.Padding import pad


FLAG = b""
key = Random(2025).randbytes(16)

print(AES.new(key, AES.MODE_CBC, iv=FLAG[9:25]).encrypt(pad(FLAG, 16)).hex())

# ae39cfab1ba8d38fc3761216c393caf16e3c3f13fe57e2dedd52f1b13072fa93df405c7e731a193cabe5fd88ee3241f79aded62d139faba8c767a3b8efc5a855

analysis

  • AESkey不变,我们只需要根据AES - CBC前后存在异或的性质进行求解即可。
    1. 已知flag前七个字节为LILCTF{ --> iv的前七个字节为flag[9:16] --> flag[9:16] = decrypt(ciphertext[:7]) ^ b'LILCTF{'
    2. 因此flag的第一段我们只缺少flag[7]flag[8]两个可视化字符,我们进行一个爆破。
    3. iv = flag[:16] ^ decrypt(ciphertext[:16]),最后根据iv == flag[9:25]判定是否爆破成功。

exp

from Crypto.Cipher import AES
from Crypto.Util.Padding import unpad
from pwn import xor

ciphertext = bytes.fromhex("ae39cfab1ba8d38fc3761216c393caf16e3c3f13fe57e2dedd52f1b13072fa93df405c7e731a193cabe5fd88ee3241f79aded62d139faba8c767a3b8efc5a855")
key = b'\xbc\xd8\xc8\x8e\x81\xf8+\x15\x82\x17V\xa5\xa3\x87h\xd6'
# print(len(ciphertext)) 64
aes1 = AES.new(key, AES.MODE_ECB)
# print(my_aes.decrypt(ciphertext))
temp = aes1.decrypt(ciphertext[:16])
for i in range(32, 127):
    for j in range(32, 127):
        mask = b'LILCTF{' + bytes([i]) + bytes([j])
        message0 = mask + xor(temp[:7], b'LILCTF{')
        iv = xor(temp, message0)
        aes2 = AES.new(key, AES.MODE_CBC, iv)
        flag = unpad(aes2.decrypt(ciphertext), 16)
        if flag[9:25] == iv:
            print(flag)
# b'LILCTF{b1a6labl4@#$%_H4v3_fUn_W1tH_y0Ur_sYmM3try_3NcRyPt10n!}'

ez_math

task

from sage.all import *
from Crypto.Util.number import *

flag = b'LILCTF{test_flag}'[7:-1]
lambda1 = bytes_to_long(flag[:len(flag)//2])
lambda2 = bytes_to_long(flag[len(flag)//2:])
p = getPrime(512)
def mul(vector, c):
    return [vector[0]*c, vector[1]*c]

v1 = [getPrime(128), getPrime(128)] # v1 = [a, b]
v2 = [getPrime(128), getPrime(128)] # v2 = [c, d]

A = matrix(GF(p), [v1, v2])
B = matrix(GF(p), [mul(v1, lambda1), mul(v2, lambda2)])

"""
A = [
    [a, b],
    [c, d]
]

B = [
    [a * flag1, b * flag1],
    [c * flag2, d * flag2]
]

    [flag1, 0]      [a, b]
B =             * 
    [0, flag2]      [c, d]

B = diag(flag1, flag2) * A --> C = A ^ -1 * B = A ^ -1 * diag(flag1, flag2) * A

diag(flag1, flag2) ~ C --> diag(flag1, flag2)的迹和行列式与C对应相等
"""
C = A.inverse() * B

print(f'p = {p}')
print(f'C = {str(C).replace(" ", ",").replace("\n", ",").replace("[,", "[")}')

# p = 9620154777088870694266521670168986508003314866222315790126552504304846236696183733266828489404860276326158191906907396234236947215466295418632056113826161
# C = [7062910478232783138765983170626687981202937184255408287607971780139482616525215270216675887321965798418829038273232695370210503086491228434856538620699645,7096268905956462643320137667780334763649635657732499491108171622164208662688609295607684620630301031789132814209784948222802930089030287484015336757787801],[7341430053606172329602911405905754386729224669425325419124733847060694853483825396200841609125574923525535532184467150746385826443392039086079562905059808,2557244298856087555500538499542298526800377681966907502518580724165363620170968463050152602083665991230143669519866828587671059318627542153367879596260872]

analysis

一道简单的线性代数的问题,相关的分析在task的注释中。主要应用的就是相似矩阵的特性。

exp

from Crypto.Util.number import *
from gmpy2 import iroot

p = 9620154777088870694266521670168986508003314866222315790126552504304846236696183733266828489404860276326158191906907396234236947215466295418632056113826161
C = [
    [7062910478232783138765983170626687981202937184255408287607971780139482616525215270216675887321965798418829038273232695370210503086491228434856538620699645,7096268905956462643320137667780334763649635657732499491108171622164208662688609295607684620630301031789132814209784948222802930089030287484015336757787801],
    [7341430053606172329602911405905754386729224669425325419124733847060694853483825396200841609125574923525535532184467150746385826443392039086079562905059808,2557244298856087555500538499542298526800377681966907502518580724165363620170968463050152602083665991230143669519866828587671059318627542153367879596260872]
]

a, b, c, d = C[0][0], C[0][1], C[1][0], C[1][1]
# tr = flag1 + flag2
tr = (a + d) % p
# det = flag1 * flag2
det = (a * d - b * c) % p

temp = iroot(tr ** 2 - 4 * det, 2)[0]
flag1 = (tr + temp) // 2
flag2 = tr - flag1

flag = b'LILCTF{' + long_to_bytes(flag1) + long_to_bytes(flag2) + b'}'
print(flag)
# b'LILCTF{It_w4s_the_be5t_of_times_1t_wa5_the_w0rst_of_t1me5}'

mid_math

task

from sage.all import *
from Crypto.Util.number import *
from tqdm import tqdm
from random import randint
from Crypto.Cipher import AES
from Crypto.Util.Padding import pad

flag = b'LILCTF{test_flag}'

p = getPrime(64)
P = GF(p)

key = randint(2 ** 62, p)
print(f"key = {key}")
def mul(vector, c):
    return [vector[0] * c, vector[1] * c, vector[2] * c, vector[3] * c, vector[4] * c]

v1 = [getPrime(64), getPrime(64), getPrime(64), getPrime(64), getPrime(64)]
v2 = [getPrime(64), getPrime(64), getPrime(64), getPrime(64), getPrime(64)]
v3 = [getPrime(64), getPrime(64), getPrime(64), getPrime(64), getPrime(64)]
v4 = [getPrime(64), getPrime(64), getPrime(64), getPrime(64), getPrime(64)]
v5 = [getPrime(64), getPrime(64), getPrime(64), getPrime(64), getPrime(64)]
a, b, c, d, e = getPrime(64), getPrime(64), getPrime(64), getPrime(64),  0

A = matrix(P, [v1, v2, v3, v4, v5])
B = matrix(P, [mul(v1, a), mul(v2, b), mul(v3, c), mul(v4, d), mul(v5, e)])
C = A.inverse() * B
D = C ** key

"""
B = [
    [a * v1[0], a * v1[1], a * v1[2], a * v1[3], a * v1[4]],
    [b * v2[0], b * v2[1], b * v2[2], b * v2[3], b * v2[4]],
    [c * v3[0], c * v3[1], c * v3[2], c * v3[3], c * v3[4]],
    [d * v4[0], d * v4[1], d * v4[2], d * v4[3], d * v4[4]],
    [0, 0, 0, 0, 0]
]

    [a, 0, 0, 0, 0]
    [0, b, 0, 0, 0]
B = [0, 0, c, 0, 0] * A
    [0, 0, 0. d, 0]
    [0, 0, 0, 0, 0]

    
C = A ^ -1 * B = A ^ -1 * diag(a, b, c, d, 0) * A --> C ~ diag(a, b, c, d, 0) -->
D = C ** key = A ^ -1 * diag(a, b, c, d, 0) ** key * A --> D ~ diag(a, b, c, d, 0) ** key --> D ~ diag(a ** key % p, b ** key % p, c ** key % p, d ** key % p, 0)
D.eigenvalue = C.eigenvalue ** key

"""
key = pad(long_to_bytes(key), 16)
aes = AES.new(key, AES.MODE_ECB)
msg = aes.encrypt(pad(flag, 64))

print(f"p = {p}")
print(f'C = {[i for i in C]}'.replace('(', '[').replace(')', ']'))
print(f'D = {[i for i in D]}'.replace('(', '[').replace(')', ']'))
print(f"msg = {msg}")

# p = 14668080038311483271
# C = [[11315841881544731102, 2283439871732792326, 6800685968958241983, 6426158106328779372, 9681186993951502212], [4729583429936371197, 9934441408437898498, 12454838789798706101, 1137624354220162514, 8961427323294527914], [12212265161975165517, 8264257544674837561, 10531819068765930248, 4088354401871232602, 14653951889442072670], [6045978019175462652, 11202714988272207073, 13562937263226951112, 6648446245634067896, 13902820281072641413], [1046075193917103481, 3617988773170202613, 3590111338369894405, 2646640112163975771, 5966864698750134707]]
# D = [[1785348659555163021, 3612773974290420260, 8587341808081935796, 4393730037042586815, 10490463205723658044], [10457678631610076741, 1645527195687648140, 13013316081830726847, 12925223531522879912, 5478687620744215372], [9878636900393157276, 13274969755872629366, 3231582918568068174, 7045188483430589163, 5126509884591016427], [4914941908205759200, 7480989013464904670, 5860406622199128154, 8016615177615097542, 13266674393818320551], [3005316032591310201, 6624508725257625760, 7972954954270186094, 5331046349070112118, 6127026494304272395]]
# msg = b"\xcc]B:\xe8\xbc\x91\xe2\x93\xaa\x88\x17\xc4\xe5\x97\x87@\x0fd\xb5p\x81\x1e\x98,Z\xe1n`\xaf\xe0%:\xb7\x8aD\x03\xd2Wu5\xcd\xc4#m'\xa7\xa4\x80\x0b\xf7\xda8\x1b\x82k#\xc1gP\xbd/\xb5j"

analysis

CD的特征值之间的离散对数关系可以求解key进行求解flag,证明在解题分析的时候也写在了task的注释中。

这道题相当于是ez_math的升级版,因为在ez_math中我利用的是相似方阵的行列式相等。这里使用的是相似矩阵的特征值是相等的。两者都可以进行简单的证明,或许在线性代数的课程中我们都以结论的形式进行记忆。注意,因为在求解特征值的过程中,我们是经过特征多项式求解的,所以在顺序的对应关系上可能存在乱序的关系,所以exp中使用简单的组合避免了这一情况。

exp

from sage.all import *
from Crypto.Util.number import *
from Crypto.Cipher import AES
from Crypto.Util.Padding import unpad, pad
import itertools

p = 14668080038311483271
C = [
    [11315841881544731102, 2283439871732792326, 6800685968958241983, 6426158106328779372, 9681186993951502212],
    [4729583429936371197, 9934441408437898498, 12454838789798706101, 1137624354220162514, 8961427323294527914],
    [12212265161975165517, 8264257544674837561, 10531819068765930248, 4088354401871232602, 14653951889442072670],
    [6045978019175462652, 11202714988272207073, 13562937263226951112, 6648446245634067896, 13902820281072641413],
    [1046075193917103481, 3617988773170202613, 3590111338369894405, 2646640112163975771, 5966864698750134707]
]
D = [
    [1785348659555163021, 3612773974290420260, 8587341808081935796, 4393730037042586815, 10490463205723658044],
    [10457678631610076741, 1645527195687648140, 13013316081830726847, 12925223531522879912, 5478687620744215372],
    [9878636900393157276, 13274969755872629366, 3231582918568068174, 7045188483430589163, 5126509884591016427],
    [4914941908205759200, 7480989013464904670, 5860406622199128154, 8016615177615097542, 13266674393818320551],
    [3005316032591310201, 6624508725257625760, 7972954954270186094, 5331046349070112118, 6127026494304272395]
]

msg = b"\xcc]B:\xe8\xbc\x91\xe2\x93\xaa\x88\x17\xc4\xe5\x97\x87@\x0fd\xb5p\x81\x1e\x98,Z\xe1n`\xaf\xe0%:\xb7\x8aD\x03\xd2Wu5\xcd\xc4#m'\xa7\xa4\x80\x0b\xf7\xda8\x1b\x82k#\xc1gP\xbd/\xb5j"

P = GF(p)

C = matrix(P, C)
D = matrix(P, D)

eigC = C.eigenvalues()
eigD = D.eigenvalues()

eigC = [lam for lam in eigC if lam != P(0)]
eigD = [mu for mu in eigD if mu != P(0)]

for lc, ld in itertools.product(eigC, eigD):
    try:
        key = discrete_log(ld, lc)
        if C ** key == D:
            print(f"key = {key}")
            break
    except (ValueError, ArithmeticError):
        continue

key = pad(long_to_bytes(int(key)), 16)
my_aes = AES.new(key, mode = AES.MODE_ECB)
flag = unpad(my_aes.decrypt(msg), 64)
print(flag)

"""
key = 5273966641785501202
b'LILCTF{Are_y0u_5till_4wake_que5t1on_m4ker!}'
"""

baaaaaag

task

from Crypto.Util.number import *
import random
from Crypto.Cipher import AES
import hashlib
from Crypto.Util.Padding import pad
from secret import flag

p = random.getrandbits(72)
assert len(bin(p)[2:]) == 72

a = [getPrime(90) for _ in range(72)]
b = 0
t = p
for i in a:
    temp = t % 2
    b += temp * i
    t = t >> 1

key = hashlib.sha256(str(p).encode()).digest()
cipher = AES.new(key, AES.MODE_ECB)
flag = pad(flag,16)
ciphertext = cipher.encrypt(flag)

print(f'a = {a}')
print(f'b = {b}')
print(f"ciphertext = {ciphertext}")

'''
a = [965032030645819473226880279, 699680391768891665598556373, 1022177754214744901247677527, 680767714574395595448529297, 1051144590442830830160656147, 1168660688736302219798380151, 796387349856554292443995049, 740579849809188939723024937, 940772121362440582976978071, 787438752754751885229607747, 1057710371763143522769262019, 792170184324681833710987771, 912844392679297386754386581, 906787506373115208506221831, 1073356067972226734803331711, 1230248891920689478236428803, 713426848479513005774497331, 979527247256538239116435051, 979496765566798546828265437, 836939515442243300252499479, 1185281999050646451167583269, 673490198827213717568519179, 776378201435505605316348517, 809920773352200236442451667, 1032450692535471534282750757, 1116346000400545215913754039, 1147788846283552769049123803, 994439464049503065517009393, 825645323767262265006257537, 1076742721724413264636318241, 731782018659142904179016783, 656162889354758353371699131, 1045520414263498704019552571, 1213714972395170583781976983, 949950729999198576080781001, 1150032993579134750099465519, 975992662970919388672800773, 1129148699796142943831843099, 898871798141537568624106939, 997718314505250470787513281, 631543452089232890507925619, 831335899173370929279633943, 1186748765521175593031174791, 884252194903912680865071301, 1016020417916761281986717467, 896205582917201847609656147, 959440423632738884107086307, 993368100536690520995612807, 702602277993849887546504851, 1102807438605649402749034481, 629539427333081638691538089, 887663258680338594196147387, 1001965883259152684661493409, 1043811683483962480162133633, 938713759383186904819771339, 1023699641268310599371568653, 784025822858960757703945309, 986182634512707587971047731, 1064739425741411525721437119, 1209428051066908071290286953, 667510673843333963641751177, 642828919542760339851273551, 1086628537309368288204342599, 1084848944960506663668298859, 667827295200373631038775959, 752634137348312783761723507, 707994297795744761368888949, 747998982630688589828284363, 710184791175333909291593189, 651183930154725716807946709, 724836607223400074343868079, 1118993538091590299721647899]
b = 34962396275078207988771864327
ciphertext = b'Lo~G\xf46>\xd609\x8e\x8e\xf5\xf83\xb5\xf0\x8f\x9f6&\xea\x02\xfa\xb1_L\x85\x93\x93\xf7,`|\xc6\xbe\x05&\x85\x8bC\xcd\xe6?TV4q'
'''

analysis

  • 显而易见,我们拿到的数据有ab以及flag经过AES加密之后的密文,解决问题的关键就是获得p以得到keyciphertext进行AES的解密获取flag
  • 根据题目名称以及b的计算过程,可以知晓这就是一个背包问题。针对于这个背包问题,其实就是根据ab去还原p的二进制位,我们构造方程式的格求解即可。
  • 注意,针对于GF(2)中的格,我们采用2x - 1令其定义域为{-1, 1}提高规约效率。这里我先使用了LLL进行规约,发现并不能准确地规约出我们想要地结果;之后BKZ进行规约结果优化,记得适当调整block_size的大小,不断地进行增大以增加规约的准确性。如果前者没有针对于2x-1的变化,block_size增大到一定量的时候也是可以准确规约出p的。
  • 针对于BKZ规约之后的结果,我们进行p的检验。
    1. 最后一列必须为0 --> (sum(x_i*a_i*C) - b*C = 0 → sum(x_i*a_i) = b)
    2. 前n列元素绝对值≤1(对应2x_i - 1 ∈ {-1,1},即x_i = (元素 + 1) // 2)

exp

from sage.all import Matrix, ZZ
from Crypto.Util.number import *
from Crypto.Cipher import AES
import hashlib
from Crypto.Util.Padding import unpad

a = [965032030645819473226880279, 699680391768891665598556373, 1022177754214744901247677527, 680767714574395595448529297, 1051144590442830830160656147, 1168660688736302219798380151, 796387349856554292443995049, 740579849809188939723024937, 940772121362440582976978071, 787438752754751885229607747, 1057710371763143522769262019, 792170184324681833710987771, 912844392679297386754386581, 906787506373115208506221831, 1073356067972226734803331711, 1230248891920689478236428803, 713426848479513005774497331, 979527247256538239116435051, 979496765566798546828265437, 836939515442243300252499479, 1185281999050646451167583269, 673490198827213717568519179, 776378201435505605316348517, 809920773352200236442451667, 1032450692535471534282750757, 1116346000400545215913754039, 1147788846283552769049123803, 994439464049503065517009393, 825645323767262265006257537, 1076742721724413264636318241, 731782018659142904179016783, 656162889354758353371699131, 1045520414263498704019552571, 1213714972395170583781976983, 949950729999198576080781001, 1150032993579134750099465519, 975992662970919388672800773, 1129148699796142943831843099, 898871798141537568624106939, 997718314505250470787513281, 631543452089232890507925619, 831335899173370929279633943, 1186748765521175593031174791, 884252194903912680865071301, 1016020417916761281986717467, 896205582917201847609656147, 959440423632738884107086307, 993368100536690520995612807, 702602277993849887546504851, 1102807438605649402749034481, 629539427333081638691538089, 887663258680338594196147387, 1001965883259152684661493409, 1043811683483962480162133633, 938713759383186904819771339, 1023699641268310599371568653, 784025822858960757703945309, 986182634512707587971047731, 1064739425741411525721437119, 1209428051066908071290286953, 667510673843333963641751177, 642828919542760339851273551, 1086628537309368288204342599, 1084848944960506663668298859, 667827295200373631038775959, 752634137348312783761723507, 707994297795744761368888949, 747998982630688589828284363, 710184791175333909291593189, 651183930154725716807946709, 724836607223400074343868079, 1118993538091590299721647899]
b = 34962396275078207988771864327
ciphertext = b'Lo~G\xf46>\xd609\x8e\x8e\xf5\xf83\xb5\xf0\x8f\x9f6&\xea\x02\xfa\xb1_L\x85\x93\x93\xf7,`|\xc6\xbe\x05&\x85\x8bC\xcd\xe6?TV4q'

n = 72
C = 2 ** 100   # C > sum(a) 2 ** (90 + 6) --> 2 ** 100
# print(C > sum(a))

L = Matrix(ZZ, n + 1, n + 1)

for i in range(n):
    L[i, i] = 2
    L[i, -1] = a[i] * C

for i in range(n):
    L[-1, i] = -1
L[-1, -1] = -b * C


# L = L.LLL()
# print(L)
L = L.BKZ(block_size = 26)
# print(L)

for idx, row in enumerate(L):
    # 1. 最后一列必须为0 --> (sum(x_i*a_i*C) - b*C = 0 → sum(x_i*a_i) = b)
    # 2. 前n列元素绝对值≤1(对应2x_i - 1 ∈ {-1,1},即x_i = (元素 + 1) // 2)
    if row[-1] != 0:
        continue
    if not all(abs(x) <= 1 for x in row[:-1]):
        continue
    
    p_bits = [(x + 1) // 2 for x in row[:-1]]

    p_bin = ''.join(map(str, reversed(p_bits)))

    p = int(p_bin, 2)
    print(f"p = {p}")
    if len(bin(p)[2:]) != 72:
        continue

key = hashlib.sha256(str(p).encode()).digest()
cipher = AES.new(key, AES.MODE_ECB)
flag = cipher.decrypt(ciphertext)
flag = unpad(flag, 16)
print(flag)
# p = 4208626653103825685156
# b'LILCTF{M4ybe_7he_brut3_f0rce_1s_be5t}'

Linear

task

import os
import random
import signal

signal.alarm(10)

flag = os.getenv("LILCTF_FLAG", "LILCTF{default}")

nrows = 16
ncols = 32

A = [[random.randint(1, 1919810) for _ in range(ncols)] for _ in range(nrows)]
x = [random.randint(1, 114514) for _ in range(ncols)]

b = [sum(A[i][j] * x[j] for j in range(ncols)) for i in range(nrows)]
print(A)
print(b)

xx = list(map(int, input("Enter your solution: ").strip().split()))
if xx != x:
    print("Oh, your linear algebra needs to be practiced.")
else:
    print("Bravo! Here is your flag:")
    print(flag)

analysis

  • 针对于这道题,一道挑战应答式的题目,要求我们根据A * X = b,已知Ab还原X。针对于格的学习中最基本的A * X - b = 0的题型,我们修改好格的参数直接进行规约即可,但是这道题目,我们并不能像原来那样直接进行规约,因为这里的A矩阵32 * 16的,b是长度为16的结果矩阵,是欠定的,但是我们可以注意到xi都是small integers(在 1 到 114514 之间),因此我们可以采用相关数据填充的方法进行填充之后再进行规约。
  • 因此,这个问题也很简单了。我们只需将简单的A * x - b = 0的格问题转化到其右盒上进行LLL规约即可。其实感觉也是可以在向量L的左边进行小数据的填充的,只要我们保证目标向量也是small integers即可。
  • 这里是先拿到一组数据进行测试恢复X,之后让AI修改接收数据并进行发送的整个过程,因为出题人这边要求10s内完成正确的响应。

exp_test

# 为了方便核对,我们也可以自己本地跑一遍这个程序,输出x再进行结果核对
from sage.all import *

A = [[725363, 1862610, 477364, 1664162, 813736, 1289445, 1711858, 1812783, 269783, 1293792, 1570001, 1772463, 871374, 1412902, 363455, 196320, 49793, 224547, 937412, 217063, 93485, 1678778, 1351488, 420567, 287560, 314317, 1053564, 1322555, 393926, 1675639, 110186, 713192], [1530281, 47741, 1655007, 722362, 48075, 220831, 1728558, 1890733, 311698, 1884424, 819174, 613340, 262949, 729848, 1214418, 168382, 1081541, 861352, 827760, 1005854, 1861602, 1353915, 1272604, 1867012, 747768, 914319, 72636, 1547501, 46989, 623111, 1418856, 720165], [1764994, 236559, 1522840, 1733136, 1757271, 313163, 1423870, 167680, 1822510, 1510098, 152347, 1140652, 343203, 879399, 665811, 683332, 70834, 1632414, 1544901, 1680401, 792639, 1164160, 1289262, 463378, 548477, 511217, 232793, 1618852, 801198, 901460, 1388987, 1762199], [431688, 545494, 130922, 284061, 1711443, 1482795, 1617959, 593411, 534890, 1308734, 1067994, 1850637, 636709, 262139, 1874345, 1522756, 1013838, 99628, 1042148, 1154589, 1190485, 715182, 891891, 400400, 940323, 456661, 488659, 1119913, 769493, 1297690, 508803, 1752010], [1202533, 1053398, 1368790, 689502, 1341592, 1297401, 491876, 210611, 1158699, 1504912, 1135993, 991358, 945619, 1605834, 1499105, 1617150, 288032, 1731777, 1435276, 1408825, 1615251, 1730395, 1296462, 1843419, 1381333, 1577875, 579016, 1016296, 828257, 1573410, 1745372, 1883607], [587276, 122652, 48727, 792647, 367214, 1066464, 110550, 1405504, 752007, 1886320, 1900693, 376556, 1348789, 982845, 1280856, 1196188, 968498, 531354, 1195332, 754388, 419965, 94221, 1059117, 101565, 57537, 1593630, 1436531, 22849, 593328, 570535, 1753825, 1793841], [524834, 301679, 472164, 109112, 1634984, 1412690, 1122, 1449544, 1636047, 119627, 1189142, 65706, 1318841, 679721, 1194724, 1709645, 1821540, 728958, 549816, 872350, 1789838, 1690306, 969198, 1341485, 1560610, 991081, 1679252, 626846, 388354, 499415, 1137383, 981694], [1437845, 1400852, 1004308, 825488, 752383, 1707994, 999304, 180543, 1341961, 290943, 673330, 981205, 299165, 1618718, 1082471, 1538682, 611246, 391991, 1234035, 1655508, 67829, 1356213, 466267, 1159158, 1380899, 910870, 904397, 1298107, 1097507, 987700, 172865, 1455446], [1731794, 1486929, 459206, 1708522, 1652848, 152739, 1166682, 1385280, 1174298, 780008, 1379369, 1890431, 224863, 888735, 668569, 410239, 1173793, 433290, 1171849, 1777508, 161216, 90575, 810930, 679540, 19772, 1345184, 414186, 1086061, 87040, 486665, 750282, 180285], [1395655, 1686667, 1916935, 1049376, 1165814, 115250, 503064, 68164, 790169, 942096, 1731378, 200189, 1724025, 1567138, 148384, 937532, 1596424, 332679, 543237, 1701435, 157751, 10537, 1353996, 1277799, 1847479, 653014, 358450, 1795914, 733971, 1648938, 1593477, 50007], [1104477, 129462, 245745, 1856127, 1128448, 550430, 1918174, 202891, 413085, 1751214, 1325477, 914988, 1906511, 1100602, 1560487, 658945, 660046, 970797, 408867, 737119, 109363, 1170192, 543622, 1228905, 1858255, 1128615, 1207833, 1328766, 1028417, 753005, 486200, 666929], [1549640, 1162378, 654085, 235224, 1835491, 870036, 145818, 1409593, 331054, 1053291, 414051, 1850833, 1500715, 1703273, 481732, 1532315, 905239, 1300866, 388738, 171732, 1660284, 378382, 806059, 1367428, 242786, 1635853, 423761, 1892721, 343326, 409194, 1200606, 484669], [1512991, 1364013, 762841, 633497, 579756, 1290872, 885093, 1550846, 187379, 1180623, 480100, 56964, 762538, 338163, 859905, 543159, 489996, 664428, 853238, 639278, 104608, 266010, 597973, 1083456, 66711, 366055, 1548250, 865985, 1167194, 1739248, 1782841, 200838], [242469, 1663720, 1845418, 239760, 1461290, 809041, 816627, 630820, 1465576, 791903, 710332, 83807, 997047, 1379855, 427759, 1916568, 1004934, 13374, 288099, 639082, 1509850, 1185097, 1864298, 421326, 1287065, 1405519, 1792645, 496414, 500000, 581405, 130725, 485726], [1795568, 1750028, 1015841, 1388344, 1188316, 1853487, 1598891, 913961, 670592, 377377, 241164, 162697, 1340948, 1554384, 674263, 1556794, 909201, 1733081, 330961, 374183, 1664410, 1451643, 134532, 618276, 324671, 1828077, 670680, 1444801, 1596126, 1813894, 684737, 296776], [217632, 462503, 457195, 720437, 1586137, 1070806, 1877656, 862757, 1818046, 1320478, 646455, 1548091, 36616, 514981, 1758202, 1795036, 53132, 32553, 1241695, 949993, 640815, 1056331, 939950, 367563, 273869, 1521708, 1415923, 914997, 1631675, 1800323, 1417638, 29959]]
b = [1553885419780, 1488638792655, 1627613044912, 1552852900670, 2245442493678, 1634803086510, 1770270806160, 1774708647399, 1626413279299, 1869639262653, 1660187833435, 1560857359314, 1467296949047, 1551115596723, 1741721795766, 1568516461016]

nrows = 16
ncols = 32

A_mat = matrix(ZZ, A)
b_vec = vector(ZZ, b)

M = A_mat.augment(-b_vec)

kernel = M.right_kernel()
basis = kernel.basis_matrix()

L = basis.LLL()

for v in L:
    if v[-1] == 1:
        x_candidate = v[:ncols]
        if all(1 <= xi <= 114514 for xi in x_candidate):
            print(" ".join(str(xi) for xi in x_candidate))
            break
    elif v[-1] == -1:
        x_candidate = -v[:ncols]
        if all(1 <= xi <= 114514 for xi in x_candidate):
            print(" ".join(str(xi) for xi in x_candidate))
            break

exp

from pwn import *
import ast
from sage.all import *

def get_remote_A_b(io):
    A_raw = io.recvuntil(b']]', timeout=5).decode().strip()
    A_raw = A_raw.rstrip('\n').rstrip(' ')
    A = ast.literal_eval(A_raw)
    
    b_raw = io.recvuntil(b']', timeout=5).decode().strip()
    b_raw = b_raw.rstrip('\n').rstrip(' ')
    b = ast.literal_eval(b_raw)
    
    assert len(A) == 16 and len(A[0]) == 32
    assert len(b) == 16
    return A, b

def solve_x(A, b):
    ncols = 32
    A_mat = matrix(ZZ, A)
    b_vec = vector(ZZ, b)
    M = A_mat.augment(-b_vec)

    kernel = M.right_kernel()
    basis = kernel.basis_matrix()
    L = basis.LLL()
    
    for v in L:
        last = v[-1]
        if last == 1:
            x_candidate = v[:ncols]
        elif last == -1:
            x_candidate = -v[:ncols]
        else:
            continue

        if all(1 <= xi <= 114514 for xi in x_candidate):
            return x_candidate
    raise Exception("未找到符合条件的x")

def main():
    io = remote('******', *****)
    print("[+] 已连接远程服务器")
    
    A, b = get_remote_A_b(io)
    print("[+] 成功接收A矩阵和b向量")
    print(f"[*] A矩阵维度: {len(A)}x{len(A[0])}")
    print(f"[*] b向量长度: {len(b)}")
    
    x = solve_x(A, b)
    print(f"[+] 找到解x: {x}")
    
    x_str = ' '.join(str(xi) for xi in x)
    io.sendline(x_str.encode())
    print("[+] 已发送x给服务器")
    
    response = io.recvall().decode()
    print("[+] 服务器响应:")
    print(response)
    
    io.close()

if __name__ == "__main__":
    main()
# LILCTF{483b5dff-50db-47f2-bd0a-97d8e030b18c}

Space Travel

task.py

from Crypto.Cipher import AES
from hashlib import md5
from params import vecs
from os import urandom

key = int("".join([vecs[int.from_bytes(urandom(2)) & 0xfff] for _ in range(50)]), 2)

print("🎁 :", [[nonce := int(urandom(50*2).hex(), 16), (bin(nonce & key).count("1")) % 2] for _ in range(600)])
print("🚩 :", AES.new(key=md5(str(key).encode()).digest(), nonce=b"Tiffany", mode=AES.MODE_CTR).encrypt(open("flag.txt", "rb").read()))

params.py

vecs = [......]

output.txt

🎁 :[[...]...]
🚩 :...

analysis

  • 这道题目的task.py的内容很少,params.py虽然内容很多,但是可以看成一个字典,最重要的加密思路还是在task.py中。首先vesc是一个长度为0xfff的列表,其中每一个位置上是16bit01序列。我们随机挑选出50个组成50 * 16 = 800bitskey。之后针对于flag进行CTR模式的加密。

  • ....原本的思路,还想从计数器模式上找找流密码方面的解决方案。头大......

  • 后面还是要回归到对称加密的基础上,找到key进行求解flag。首先我们先判断一下直接求解key肯定是不显示的,根据CTF赛事的习惯🎁一定是这道题解题的关键。

    1. nonce是一个800bits的数字。🎁每个位置的列表位置上的第一个元素为随机生成的nonce.
    2. nonce & key之后针对于该结果判断其中的二进制位上的1的个数是奇数还是偶数,在这里命名为:奇偶校验结果。相当于是在GF(2)上的noncekey的点积。
  • 刚开始看起来这还是一个格的问题,根据原nonce与点积结果去规约key。但是如果我们不做处理的话,600组的点积去恢复800bits的一个数据,先不讨论能不能规约出准确的这600bit01,就算我们成功规约出,还剩余200bit等待着我们去进行爆破。因此我们只能改换思路。

  • 这里思路已经枯竭了,把题目和目前已经考虑到的问题交给deepseek,有以下提示内容:vecs实际上是一个12维的线性子空间,但有一个平移向量v0(即vecs[0]),因此每个向量可表示为v = uG + v0,其中u是12位信息向量,G是12×16的生成矩阵。

  • 根据deepseek的提示,利用vecs的线性结构,我们可以将问题转化为求解线性方程组。具体地,将key映射到600维向量K',满足key = K' * G50 + v50,其中G50是由50个G组成的块对角矩阵(600×800),v50是由50个v0组成的800维向量。给定nonce矩阵N(800×600)和奇偶校验向量H(600维),有方程:

    \[(K^′∗G50+v50)∗N=H\rightarrow K^′∗(G50∗N)=H−v50∗N \]

  • A = G50 * N(600×600矩阵),B = H - v50 * N(600维向量),求解K'满足K' * A = B即可恢复key

exp

from sage.all import *
from Crypto.Cipher import AES
from hashlib import md5
import ast

def parse_output(filename):
    with open(filename, 'r', encoding='utf-8') as f:
        lines = f.readlines()
    data_line = None
    enc_line = None
    for line in lines:
        if line.startswith('🎁'):
            data_line = line.strip().split(':', 1)[1].strip()
        elif line.startswith('🚩'):
            enc_line = line.strip().split(':', 1)[1].strip()
    if data_line is None or enc_line is None:
        raise ValueError("Invalid output file")
    data = ast.literal_eval(data_line)
    enc = ast.literal_eval(enc_line)
    return data, enc

def main():
    from params import vecs
    F = GF(2)
    
    vecs_vec = [vector(F, [int(bit) for bit in s]) for s in vecs]
    v0 = vecs_vec[0]
    vecs_minus_v0 = [v - v0 for v in vecs_vec]
    V = span(vecs_minus_v0)
    dim = V.dimension()
    print(f"Dimension of the subspace: {dim}")
    assert dim == 12, "Subspace dimension is not 12"
    
    basis = V.basis()
    G = matrix(F, basis)
    
    data, enc = parse_output('output.txt')
    n_blocks = 50
    total_bits = n_blocks * 16
    
    N_list = []
    H_list = []
    for d in data:
        nonce_int = d[0]
        parity = d[1]
        nonce_bin = bin(nonce_int)[2:].zfill(total_bits)
        nonce_vec = [int(bit) for bit in nonce_bin]
        N_list.append(nonce_vec)
        H_list.append(parity)
    
    N = matrix(F, N_list).T
    H = vector(F, H_list)
    
    G50 = block_diagonal_matrix([G] * n_blocks)
    v50 = vector(F, list(v0) * n_blocks)
    
    A = G50 * N
    B = H - v50 * N
    
    try:
        K_prime = A.solve_left(B)
    except ValueError:
        print("No solution found")
        return
    
    key_vec = K_prime * G50 + v50
    key_bin = ''.join(str(bit) for bit in key_vec)
    key_int = int(key_bin, 2)
    
    key_md5 = md5(str(key_int).encode()).digest()
    cipher = AES.new(key=key_md5, nonce=b"Tiffany", mode=AES.MODE_CTR)
    flag = cipher.decrypt(enc)
    print(flag.decode())

if __name__ == '__main__':
    main()
# Dimension of the subspace: 12
# LILCTF{Un1qUe_s0luti0n_1N_sUbSp4C3!}

写在最后

  • 题目质量很高,基本都在围绕着代数与格展开。
  • 针对于格的相关操作得到了不小的了解,也更加体会到LLLBKZ算法的强大之处。
  • ez_mathmid_math都是针对于线性代数的矩阵的相关性质的利用进行的解题,后续看到别的师傅的WriteUp,好多师傅都是直接利用特征值进行的求解,以及后者的DLP问题,先前还没有了解过,我只是觉得后者有点像学习线代时利用相似矩阵的B = P ^ {-1} * A * P不断消解,达到求解B ^ e这种形式的题目。还得xue......
  • baaaaaag的题目其实就是一道比较简单的规约,如果我们不进行格的构造上的优化,那么我们只能采取更大的规约空间去进行求解。
  • Linear赛题是给出了我们由A * X = B的格基规约的问题,但是我们需要注意的就是这涉及到一个欠定矩阵的问题,不过因为small integers出现了我们可以利用的漏洞点。
  • 最后一道Space Travel,比赛期间以及后续在思考解决这道题的时候,思路已经枯竭了。本人觉得好难......要不是给deepseek提示到了题目名称可能涉及到线性空间以及赛事方平台的Jump up!提示,可能也考虑不到线性空间的变换,后续也看了一些师傅的WriteUp,其中的线性空间的变换,平移等解释,我理解的是这都是针对于该线性空间的基向量的一些线性变换,解题的关键点在于我们需要get到我们直接格是格不出来key的,我们需要将800bits的求解转化到600组的gift上,这是需要我们找到vesc中的元素是锁定在12维的空间上的,16bits一个元素的内容是没有利用完的,之后我们找到上述提及的方程就能利用600组的点积数据恢复800bits的key
  • 还得xue......
posted @ 2025-08-31 01:25  chen_xing  阅读(64)  评论(0)    收藏  举报