LilCTF wp及复现
LilCTF wp及复现
wp:
1.ez_math
题目:
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)]
v2 = [getPrime(128), getPrime(128)]
A = matrix(GF(p), [v1, v2])
B = matrix(GF(p), [mul(v1,lambda1), mul(v2,lambda2)])
C = A.inverse() * B
print(f'p = {p}')
print(f'C = {str(C).replace(" ", ",").replace("\n", ",").replace("[,", "[")}')
# p = 9620154777088870694266521670168986508003314866222315790126552504304846236696183733266828489404860276326158191906907396234236947215466295418632056113826161
# C = [7062910478232783138765983170626687981202937184255408287607971780139482616525215270216675887321965798418829038273232695370210503086491228434856538620699645,7096268905956462643320137667780334763649635657732499491108171622164208662688609295607684620630301031789132814209784948222802930089030287484015336757787801],[7341430053606172329602911405905754386729224669425325419124733847060694853483825396200841609125574923525535532184467150746385826443392039086079562905059808,2557244298856087555500538499542298526800377681966907502518580724165363620170968463050152602083665991230143669519866828587671059318627542153367879596260872]
分析:
A是由两行向量v1,v2组成的2*2矩阵(元素在GF(p)上)
B的两行分别是lambda1 * v1和lambda2 * v2
因此B可以写成 B = S A,其中S = diag (λ1,λ2)(对角矩阵),相当于在A的基础上,第一行乘λ1,第二行乘λ2
C = A^(-1)B = A ^(-1) S A ,所以C和对角矩阵S相似,具有相同的特征值,迹,行列式值
因为λ1,λ2远小于p,所以将特征值在GF(p)中恢复为整数后,转换为字节序列,在拼接起来就是flag
exp:
from Crypto.Util.number import long_to_bytes
from sympy import sqrt_mod
p = 9620154777088870694266521670168986508003314866222315790126552504304846236696183733266828489404860276326158191906907396234236947215466295418632056113826161
C_rows = [[7062910478232783138765983170626687981202937184255408287607971780139482616525215270216675887321965798418829038273232695370210503086491228434856538620699645,7096268905956462643320137667780334763649635657732499491108171622164208662688609295607684620630301031789132814209784948222802930089030287484015336757787801],[7341430053606172329602911405905754386729224669425325419124733847060694853483825396200841609125574923525535532184467150746385826443392039086079562905059808,2557244298856087555500538499542298526800377681966907502518580724165363620170968463050152602083665991230143669519866828587671059318627542153367879596260872]]
#求迹
tr = (C_rows[0][0] + C_rows[1][1]) % p
#求行列式
det = (C_rows[0][0]*C_rows[1][1] - C_rows[0][1]*C_rows[1][0]) % p
#构造二次方程:λ2−(tr)λ+detC≡0(modp)
#求Δ
disc = (tr*tr - 4*det) % p
sqrt_list = sqrt_mod(disc, p, True) # 可能返回两个根 r 和 p-r
# 利用二次方程求根公式求根
cands = []
for r in sqrt_list:
lam1 = ((tr + r) // 2) % p
lam2 = ((tr - r) // 2) % p
cands.append((lam1, lam2))
# 结果拼接起来获得flag
for lam1, lam2 in cands:
b1 = long_to_bytes(lam1)
b2 = long_to_bytes(lam2)
print("candidate parts:", b1, b2)
print("candidate flags:", b1 + b2, b2 + b1)
结果截图:

运行出来有两个结果,提交第一个成功了
2.mid_math
题目:
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)
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
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"
分析:
D = C**key,作用于有限域GF(p),其中D,C,p已知,要求解key。这是一个典型的离散对数问题,直接用sagemath中的discrete_log()方法解出key。后续就是一个已知key的AES的ECB模式解密。
exp:
#sagemath求key
p = 14668080038311483271
F = GF(p)
# 构建矩阵 C 和 D
C_list = [
[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_list = [
[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]
]
C = matrix(F, C_list)
D = matrix(F, D_list)
# 计算特征值
eigC = C.eigenvalues()
eigD = D.eigenvalues()
# 过滤非零特征值
eigC_nonzero = [x for x in eigC if x != 0]
eigD_nonzero = [x for x in eigD if x != 0]
# 尝试所有特征值配对
found_key = None
for base in eigC_nonzero:
for target in eigD_nonzero:
try:
k = discrete_log(target, base) # 计算离散对数
# 验证 k 是否满足所有特征值
matched = [target]
valid = True
for x in eigC_nonzero:
if x == base:
continue
y_val = x**k # x^k mod p
found = False
for t in eigD_nonzero:
if t == y_val and t not in matched:
matched.append(t)
found = True
break
if not found:
valid = False
break
# 检查 k 在范围内
if valid and k >= 2**62 and k < p:
found_key = k
print(f"Found key: {found_key}")
break
except Exception as e:
continue
if found_key is not None:
break
if found_key is None:
print("未找到 key")
else:
print(f"Key = {found_key}")
from Crypto.Cipher import AES
from Crypto.Util.Padding import unpad
from Crypto.Util.number import long_to_bytes
from Crypto.Util.Padding import pad
key = 5273966641785501202 #从 SageMath 中获得的整数 key
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"
# 构造 AES 密钥
key_bytes = long_to_bytes(key)
key_bytes_padded = pad(key_bytes, 16) # PKCS#7 填充至 16 字节
# 解密
cipher = AES.new(key_bytes_padded, AES.MODE_ECB)
decrypted = cipher.decrypt(msg)
flag = unpad(decrypted, 64) # 移除填充(块大小 64 字节)
print(f"Flag: {flag.decode()}")
结果截图:

复现:
1.linear
题目:
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)
exp:
import pwn
import sage.all
import json
HOST = 'challenge.xinshi.fun'
PORT = 40464
r = pwn.remote(HOST, PORT)
data = r.recvuntil(b"Enter your solution: ")
data_str = data.decode()
#找到a的位置
start_A = data_str.find('[[')
end_A = data_str.find(']]', start_A) + 2
A_str = data_str[start_A:end_A]
A_data = json.loads(A_str)
# 找到b的位置
start_b = data_str.find('[', end_A)
end_b = data_str.find(']', start_b) + 1
b_str = data_str[start_b:end_b]
b_data = json.loads(b_str)
#
A = sage.all.matrix(sage.all.ZZ, A_data)
b = sage.all.vector(sage.all.ZZ, b_data)
M = A.augment(-b)
# 求 M 的整数核(kernel),找到格的基
kernel = M.right_kernel()
basis = kernel.basis()
#
Lattice = sage.all.matrix(basis)
print("格的基矩阵大小: {} x {}".format(Lattice.nrows(), Lattice.ncols()))
short_basis = Lattice.LLL()
v = short_basis[0]
# 归一化
y = v.change_ring(sage.all.QQ) / v[-1]
# 把 y 的前32个数取出来,转成整数,这就是我们的解
solution = sage.all.vector(sage.all.ZZ, y[:-1])
solution_str = " ".join(map(str, solution))
r.sendline(solution_str.encode())
r.interactive()
分析:
通过构造增广矩阵来进行求解。

将Ax = b这个非齐次方程,转换成 M v = 0这个齐次方程 。
几个函数的分析 :
1.json.loads():将字符串转换成python中的列表。
2.M.right_kernel():计算矩阵M的右核(“右核”:所有满足M x = 0的列向量x的集合),返回一个“子空间对象”(而非返回向量)。
区别:
M.left_kernel(): 满足y M = 0的行向量y。
3.kernel.basis():从右核子空间对象 kernel 中提取其基向量列表,基向量是构成右核的 “最小线性无关组”。
将kernel.basis()的结果用来构造格,再进行格基规约(LLL),取第一个向量,归一化处理(需转换到有理数域空间上)。
2.space_travel
题目:
from Crypto.Cipher import AES
from hashlib import md5
from os import urandom
from params import vecs
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()))
另有输出文件,由于数据量太大,就不粘过来了。
分析:
分析题目,知道key是由vecs数组中的50个元素拼接成的,共800bit。
🎁 部分:一个由600个元素组成的列表,每个元素由两部分组成:nonce和b(暂且用b表示一下,题目没有具体的名称)。nonce是随机生成的,800bit,b是由nonce和key按位与后再模2,也可以理解为nonce二进制表示的每一位和key二进制表示的对应比特位相乘之和再模2。

🚩部分:一个CRT模式的AES加密。
所以核心是找key。
按照bin(nonce & key).count("1")) % 2的逻辑,可以得到600个方程,但是key有800位,难道又是一个欠定线性方程组求解?不合理,不太会有两道题相同的考点。
分析vecs列表 ,一共4096(2 ^ 12)个元素,每个元素是一个16bit的二进制数字符串。联想到二进制线性码(或其仿射码)。具体细节如下:

exp:
from Crypto.Cipher import AES
from hashlib import md5
vecs =
gift =
enc =
vecs_bin = [list(map(int, i)) for i in vecs]
vecs_bin = [vector(GF(2), i) for i in vecs_bin]
nonce_bin = [list(map(int, bin(i[0])[2:].zfill(800))) for i in gift]
nonce_bin = [vector(GF(2), i) for i in nonce_bin]
A = Matrix(GF(2), nonce_bin).T
b = vector(GF(2), [i[1] for i in gift])
v0 = vecs_bin[0]
L = Matrix(GF(2), [i - v0 for i in vecs_bin[1:]])
L1 = L.echelon_form()[:12]
v0_ = vector(GF(2), v0.list()*50)
L1_ = block_diagonal_matrix([L1 for _ in range(50)])
x = (L1_*A).solve_left(b - v0_*A)
x_ = [(vector(GF(2), x.list()[12*j:12*j+12])*L1+v0).list() for j in range(50)]
key = ""
for i in x_:
for j in i:
key += str(j)
key = int(key, 2)
flag = AES.new(key=md5(str(key).encode()).digest(), nonce=b"Tiffany", mode=AES.MODE_CTR).decrypt(enc)
print(flag)
#LILCTF{Un1qUe_s0luti0n_1N_sUbSp4C3!}
3.baaaaaag
题目:
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'
'''
分析:
要想找出flag,就要解出p,由题意可知p的求解是一个背包问题,通过构造格来求解。
exp:
from Crypto.Cipher import AES
import hashlib
def BKZ_search(a,b,size):
n = len(a)
M = Matrix(ZZ,n+1,n+1)
for i in range(n):
M[i,i] = 2
M[i,-1] = a[i]
M[-1,i] = -1
M[-1,-1] = -b
L = M.BKZ(block_size=size)
for res in L:
kk = [(i+1)//2 for i in res[:-1]]
if all(x in [0,1] for x in kk):
secret = [str(bit) for bit in kk]
return secret
a = ...
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'
secret = BKZ_search(a,b,26)
p = str(int("".join(secret[::-1]),2))
key = hashlib.sha256(p.encode()).digest()
cipher = AES.new(key, AES.MODE_ECB)
flag = cipher.decrypt(ciphertext)
print(flag)
# LILCTF{M4ybe_7he_brut3_f0rce_1s_be5t}

浙公网安备 33010602011771号