1. 密码学
1.1 BabyRSA:高精度浮点数泄露与密钥恢复
这是一道典型的RSA密钥恢复题目,其核心利用点在于高精度浮点数泄露来还原私钥参数。题目给出了一个计算得到的leak值,其公式为:
leak = (p-1)/q
该漏洞的成因在于,给出的十进制小数精度(1024位)远大于还原分数(p-1)/q所需的信息量(约617位)。这多出的400多位精度,保证了我们可以唯一地将这个小数还原为原始的分数,进而恢复出p和q。
解题脚本 (Python):
import decimal
from Crypto.Util.number import long_to_bytes
# --- 题目数据 ---
leak_str = "1.396995694831414203476063690838730308815841662737318558906107823553922718340982125801595368449608188770051881765292978548960520326036779130167518285237817101541807766017642530065080930654694948943506714268685400709580398894902693407016988670394423892586264077247263710263220932577837642377245651448838665854362532801659965471421937839336237670710012298796758992931116659292915200628873553198226185187089027680973673618973869464164460226697625936493428822424637497370197316811245879504779934098600596822159243994319583651080005054538419168988020562590543262648544970376255020489363894055887067948343768399654357738592577280906555896933717091837896978973488220368081406433117367524537063718421897982643644320078600517763936883820416362057895941185749296170109172249907094176821124345672294602380784325702476105763209165109703429326132417850746805701054961710623030742187505484821961670922386999933202645522248608323217011522889282323071281405301772218220381951540118124201599862330377374571641729649420917168701463539034702411"
d = 16306054997613721520756151430779642117683661431522665108784419231044104572118893098180652730976905729602478591047033305251624752030036736271198006715513694904231940253554804069707679445942892410812386221633728427239116007373836662495075237456279818311659331982404534490546781763464409713789636372508503902598331950861474527128323735250673137355260113147338636761737748874105625008482750923429512271416511835596944209137554445130949731646669691366003832655082535985891463876904334888009751956386994969339847254470145428608062575606120441725590059524749595027078238962391188809496875025237129899849787699468205026040721
c = 7908369000608075306226552240713890041649799894903074579356627811865842237315201153498579205223600526520994811661608630888045462921547166872107507948062717836952855804806976414887413729060431265217539895710936669089248515746191716161194996469977577048602427553584286064475300979649416171469313168995504717602670924606819204605601860560767900702512753735554900344201907921239415885901489708576066483012272256175573658509614344875077232108364134161997767814675830320630271209201503987787921279932886374846298269125068817280777403718279754392091441050281244934594776307137448975055247018414699621410668188864774860026941
# --- 求解脚本 ---
decimal.getcontext().prec = 5000
L = decimal.Decimal(leak_str)
e_list = [65537, 3, 5, 17, 257]
print("开始寻找 flag ...")
for e in e_list:
for k in range(1, e):
if (e * d - 1) % k == 0:
phi = (e * d - 1) // k
term1 = (L + 1) ** 2
term2 = 4 * L * (decimal.Decimal(phi) - 1)
delta = term1 + term2
if delta < 0:
continue
sqrt_delta = delta.sqrt()
q_approx = (L + 1 + sqrt_delta) / (2 * L)
q_int = int(q_approx)
for q_cand in range(q_int - 2, q_int + 3):
if q_cand < 2: continue
if phi % (q_cand - 1) == 0:
p_cand = phi // (q_cand - 1) + 1
n = p_cand * q_cand
try:
m_int = pow(c, d, n)
m_bytes = long_to_bytes(m_int)
if b'ISCTF' in m_bytes or b'flag' in m_bytes:
print(f"\n[+] 成功找到 Flag (e={e}, k={k})")
print(f"[+] Flag: {m_bytes.decode()}")
exit()
except Exception:
pass
print("[-] 未找到 Flag,请检查输入数据或参数。")
成功解密即可获得Flag。
1.2 Peco:复合型数论与格密码攻击
本题是一道复合型密码学题目,融合了RSA密钥恢复、不定方程求解与格密码攻击技术。这道题涉及从流量包分析到二进制逆向的完整安全/渗透链条。解题主要分为以下几步:
- 解不定方程:通过佩尔方程求解得到
x和y。
- 分解RSA模数
n:利用Hensel Lifting(亨泽尔引理)恢复p的低位,再结合Coppersmith攻击恢复完整的p,从而分解n。
- RSA解密:获得中间消息
m。
- 构造格并求解:利用
x、y和m构造一个格(Lattice),通过LLL规约求解出最终的f0和f1,拼接后得到Flag。
解题脚本 (SageMath/Python):
import sys
def long_to_bytes(val, endianness='big'):
# ... (省略实现,同原exp)
pass
# --- 题目数据 ---
n = 18443962106578943927922829208562388331564422618353954662348987125496135728205879853444693999188714508145409575298801277623433658530589571956301880815632542860363148763704636874275223979061507756787642735086825973011622866458454405794279633717255674221895468734500735123736684346340314680683830866884050311047424068122453972745273167956795195575475691048908906061023817574695902603984554911326264947716547564759877947888574515784489778380086664649338093680740990860192640619047071160362288611331225632270531304525264824445326394068892806774552310748255977040249822464839809344521107040968321810533993659358229305320413
c = 8176283809770578639445916571748890916863681496488338436815389781344271720445865752568007651231910205530735296305471880971422173915403956857863330698931559658909826642456860761540607878553228782799635976463090037022164739976302533892173751687781100980039065722082091714141141136171701360981540040678479802206949078162548124224838019262997441233919136963696523351831737708850863538007579105976954619102728135600542584651031405327214877358323388674864043740117718200022790892542634633918493245432384562983429810936975869853596007429259749282607844407676244954057886824475948603911174707176467261179324130051317766768127
gift1_A = 1293023064232431070902426583269468463
gift1_B = 105279230912868770223946474836383391725923
gift2 = 26161714402997656593966327522661504448812191236385246127313450633226841096347099194721417620572738092514050785292503472019045698167235604357096118735431692892202119807587271344465029467089266358735895706496467947787464475365718387614
e = 65537
# --- 全局变量 ---
val_x = None
val_y = None
p_found = None
q_found = None
m_dec = None
print("=== 步骤 1: 求解佩尔方程 x, y ===")
# 求解过程 (代码较长,逻辑同原exp,此处省略)
# ... (假设已成功求解得到 val_x, val_y)
if val_x is not None:
print("\n=== 步骤 2: Hensel Lifting 恢复 p 低位 ===")
# Hensel Lifting 过程
# ... (代码逻辑同原exp)
print("\n=== 步骤 3: Coppersmith 恢复完整 p ===")
# Coppersmith 攻击
P_poly.<x_poly> = PolynomialRing(Zmod(n))
for idx, p0 in enumerate(p_cands):
f = p0 + x_poly * (1 << mod_limit_bits)
f = f.monic()
try:
roots = f.small_roots(X=2**250, beta=0.4)
if roots:
p_high = int(roots[0])
p_check = p0 + p_high * (1 << mod_limit_bits)
if n % p_check == 0:
p_found = p_check
q_found = n // p_check
print(f"[+] 成功分解 n !")
break
except Exception as e:
continue
if p_found:
print("\n=== 步骤 4: RSA 解密 m ===")
phi = (p_found - 1) * (q_found - 1)
d_rsa = inverse_mod(e, phi)
m_dec = pow(c, d_rsa, n)
print(f"[+] m = {m_dec}")
print("\n=== 步骤 5: LLL 求解 Flag ===")
M = Matrix(ZZ, [
[1, 0, val_x],
[0, 1, val_y],
[0, 0, m_dec]
])
L = M.LLL()
for row in L:
f0_cand = abs(row[0])
f1_cand = abs(row[1])
r_cand = abs(row[2])
if r_cand < 2**110:
s0 = long_to_bytes(int(f0_cand))
s1 = long_to_bytes(int(f1_cand))
cands = [s0 + s1, s1 + s0]
for flag_bytes in cands:
if b"flag{" in flag_bytes or b"ISCTF" in flag_bytes:
print(f"\n[SUCCESS] Flag: {flag_bytes.decode(errors='ignore')}")
sys.exit(0)
print("[-] 未能自动识别 Flag,请手动检查LLL结果向量。")
else:
print("[-] 未能分解 n")
2. 杂项(Misc)
2.1 Blue:多层图片隐写
题目给出一张纯色图片blue.png,视觉上无任何信息。
解题步骤:
- 提取像素值:发现每个像素蓝色通道(B)值的高4位可以组成一个ZIP文件的十六进制数据。
from PIL import Image
from tqdm import *
img = Image.open('blue.png')
width, height = img.size
s = ''
for i in trange(height):
for j in range(width):
tmp = img.getpixel((j,i))
s += hex(tmp[2]>>4)[2:]
open('oo.zip','wb').write(bytes.fromhex(s))
- ZIP明文攻击:得到的
oo.zip中xor.png被加密,利用已知的PNG文件头进行明文攻击,获取密钥。
bkcrack.exe -C oo.zip -c xor.png -x 0 89504e470d0a1a0a0000000d4948445200
# 得到key: 68cc45ab 864060ce ac958caa
- 解密并提取:使用密钥解压出
xor.png,发现其文件末尾附加了另一张图片Untitled1.png,将其分离。
- 图片异或:根据文件名提示,将
xor.png与Untitled1.png进行像素级异或,得到xor1.png。
from PIL import Image
import numpy as np
img1 = Image.open("xor.png")
img2 = Image.open("Untitled1.png")
assert img1.size == img2.size
arr1 = np.array(img1)
arr2 = np.array(img2)
xor_arr = arr1 ^ arr2
Image.fromarray(xor_arr).save("xor1.png")
- 盲水印提取:对
xor1.png使用盲水印工具(如blindwatermark)提取,最终获得Flag。
2.2 Hidden:多重信息隐藏
题目给出一个treasure.bmp图片文件。
解题步骤:
- LSB隐写分析:使用
zsteg工具检查最低有效位,发现存在隐藏数据。
zsteg -a treasure.bmp
- Steghide隐写:尝试使用
steghide提取,密码为PixelWhisper(可从上下文或常见密码字典尝试)。
steghide extract -sf treasure.bmp
# 输入密码: PixelWhisper
- 提取成功后得到
flag.txt,打开即获得Flag:flag{a9a3c2872e428b6d859a0e63458a43f8}。
2.3 The_Rogue_Beacon:CAN总线流量分析
题目提供一个车辆CAN总线流量包,要求找到车速信号的峰值。
解题步骤:
- 协议分析:使用Wireshark打开流量包,识别出CAN协议帧。在车载网络/系统中,车速信号通常有特定的ID。
- 筛选有效信号:观察发现ID
0x039的数据跳变无规律,为干扰信号;ID 0x244的数据呈现平滑的加速趋势,符合真实车速特征。
注意:数据包中的CAN-ID为大端存储,0x244对应的Hex为00 00 02 44。
- 过滤与定位:在Wireshark过滤器中输入
frame[0:4] contains 00:00:02:44,仅显示真实车速数据包。
- 寻找峰值:浏览过滤后的数据,对比相邻帧的数值。当发现某帧数据(如帧号12149,数据
35e4)大于其前一帧(35d1)和后一帧(35d1)时,即可确定该帧为峰值点。
- 生成Flag:将峰值帧号
12149进行SHA-256哈希计算,得到的哈希值即为Flag。
flag{9db878fd06dd7587a91c0fb600e0e9f7c3ea310e75f36253ef57ac2d92dd8c29}
2.4 SMB:流量分析与简单逆向
题目提供一个网络流量包,其中包含通过SMB协议传输的文件。
解题步骤:
- 流量分析:使用Wireshark打开,在SMB协议流量中追踪文件传输流(
TCP Follow Stream),发现一个名为letter.exe的可执行文件被传输,将其导出保存。
- 逆向分析:使用IDA Pro打开
letter.exe,发现其为Rust语言编写。定位到主函数letter::main。
- 关键数据定位:在代码中观察到内存分配和拷贝操作,地址
0x1400A22A8处有19字节的数据被复制。
v2 = __rustc::__rust_alloc(a1, a2, 1LL, 19LL);
*(_OWORD *)v2 = xmmword_1400A22A8;
*(_DWORD *)(v2 + 15) = 1060843565;
- 数据提取与解密:在Hex View中查看地址
0x1400A22A8处的19字节原始数据。怀疑是简单加密(如XOR),尝试用单字节密钥0x42进行解密,成功得到Flag。
2.5 Zipcracker:无线电信号处理与明文攻击
题目提供三个文件:一个.grc文件(GNU Radio流程图)、一张图片和一个加密ZIP。
解题步骤:
- 理解.grc文件:该文件描述了一个NBFM(窄带调频)解调流程,将I/Q(同相/正交)信号的实部、虚部分别写入文件。
- 提取I/Q数据:从图片文件
something in it.jpg末尾分离出一个ZIP,解压得到flag1.txt和flag2.txt,即为I和Q数据。
- 信号解调:读取I/Q数据,重构复数信号,计算相位并差分得到FM信号,最后进行低通滤波和降采样,输出为WAV音频文件。
import numpy as np
from scipy.signal import decimate
from scipy.io.wavfile import write
I = np.fromfile("flag1.txt", dtype=np.float32)
Q = np.fromfile("flag2.txt", dtype=np.float32)
iq = I + 1j * Q
phase = np.unwrap(np.angle(iq))
fm = np.diff(phase)
audio = decimate(fm, 4)
write("out.wav", 48000, audio / np.max(np.abs(audio)))
- 摩斯密码解码:收听生成的
out.wav,为摩斯电码。解码后得到数字串:114514350234114514。
- ZIP明文攻击:另一个
flag.zip中包含加密的flag.txt。已知Flag格式为flag{...!!!},因此可以构造部分明文(头flag{Y和尾!!!})对ZIP进行明文攻击。
bkcrack.exe -C flag.zip -c flag.txt -x 0 666c61677b593075 -x 25 2121217d
- 破解密码:获得密钥后,使用工具(如ARCHPR)或
bkcrack的-k参数直接解密ZIP,或利用密钥生成一个已知密码的新ZIP包,最终解压获得完整Flag。
3. 逆向工程
3.1 More_More_Flower:自定义VM逆向
本题为一个32位Windows控制台程序,实现了一个自定义的虚拟机(VM)来验证输入的Flag。
分析过程:
- 程序行为:输入长度为24字节(0x18),程序通过自定义的字节码解释器进行处理,最后与
.data段中的常量数组比对。
- VM结构分析:
- 存在全局寄存器(如R0C, R10, R14, R18, R28)。
- 从
.data段读取opcode,通过分发跳转表(dispatch jump table)执行对应的handler。
- 在
.data段开辟栈空间,有SP指针。
- 算法识别:逆向VM字节码handler后,识别出其核心是一个类似TEA的ARX(加法-循环移位-异或)加密算法,每组4字节(共6组)独立加密。
sum初始为0,每轮增加一个固定delta(0x23251156)。
- 轮数为30轮(0x1e)。
- 每轮更新公式:
v += ((v << 5) ^ sum ^ (v >> 4))。
- 验证逻辑:6组数据加密后,将每组的32位结果按小端序拆分为4字节压入VM栈。最后从栈顶依次弹出24字节,与
.data中的常量数组进行比对。常量数组为:21 7a 01 1c 33 d3 3e f7 03 78 25 5e 2f b8 8b 3b 93 84 ae 5b de a5 d6 e9。
- 由于是弹出比较,实际比较顺序是生成顺序的逆序。因此,将常量数组反序后,每4字节以小端序解释,得到6个目标密文块:
0xDEA5D6E9, 0x9384AE5B, 0x2FB88B3B, 0x0378255E, 0x33D33EF7, 0x217A011C
- 暴力破解:问题转化为,对于每个32位密文,寻找一个由4个可打印字符(ASCII 32-126)组成的明文,经过上述30轮加密后恰好等于目标密文。由于每组仅需遍历约95^4 ≈ 81M种可能,可以暴力破解。需考虑输入字节序(大端或小端)。
破解脚本 (C++):
#pragma GCC optimize("O3,unroll-loops")
#include<iostream>
#include<cstdint>
const uint32_t CFG_DELTA = 0x23251156;
const int CFG_ROUNDS = 30;
const uint32_t TARGETS[6] = {0xDEA5D6E9, 0x9384AE5B, 0x2FB88B3B, 0x0378255E, 0x33D33EF7, 0x217A011C};
uint32_t ACC_TABLE[CFG_ROUNDS];
void precompute_acc() {
uint32_t acc = 0;
for(int i = 0; i < CFG_ROUNDS; i++) {
acc += CFG_DELTA;
ACC_TABLE[i] = acc;
}
}
inline uint32_t encrypt_core(uint32_t v) {
for (int i = 0; i < CFG_ROUNDS; i++) {
v += ((v << 5) ^ ACC_TABLE[i] ^ (v >> 4));
}
return v;
}
int main() {
precompute_acc();
std::string results[6] = {""};
#pragma omp parallel for collapse(2)
for (int c0 = 32; c0 <= 126; c0++) {
for (int c1 = 32; c1 <= 126; c1++) {
for (int c2 = 32; c2 <= 126; c2++) {
for (int c3 = 32; c3 <= 126; c3++) {
uint32_t val_be = (c0 << 24) | (c1 << 16) | (c2 << 8) | c3;
uint32_t val_le = (c3 << 24) | (c2 << 16) | (c1 << 8) | c0;
uint32_t enc_be = encrypt_core(val_be);
uint32_t enc_le = encrypt_core(val_le);
for (int i = 0; i < 6; i++) {
if (enc_le == TARGETS[i] || enc_be == TARGETS[i]) {
char buf[5] = {(char)c0, (char)c1, (char)c2, (char)c3, 0};
#pragma omp critical
{
results[i] = buf;
}
}
}
}
}
}
}
std::cout << "Flag: ";
for(int i=0; i<6; i++) std::cout << results[i];
std::cout << std::endl;
return 0;
}
运行脚本后得到Flag:flag{Fl0weRTeAVM15E3}。