newstarctf2025week5wp
newstarctf2025 week5 misc+crypto 详细wp
week5
misc
应急响应-把你mikumiku掉-1
题目内容:
城邦为世界第一公主殿下搭建了网站,突然受到了CVE组织的攻击,你能帮城邦对服务器进行排查吗
解压密码:d93e2cb85b2a51ef40e86e4bd6df0b14
账号:newstar 密码:newstar
请问攻击者使用的漏洞编号是?flag{漏洞编号}
【难度:中等】
漏洞编号这个挺难找的,先用tree -L 2查看目录

看到了tomcat,然后进入查看文件

这里直接省事把日志输出丢给ai分析得到


得到flag{CVE-2025-24813}
应急响应-把你mikumiku掉-2
题目内容:
flag{木马连接密码_恶意用户密码}
tips:用户密码是六位特定范围内的字母构成
先找到木马文件,tree -L 3

查看内容

通过request.getParameter("miiikuuu")获取参数,若存在则加载恶意类
所以密码应该就是miiikuuu,然后找恶意用户密码,先看用户cat /etc/passwd

看到了mikuu,查看密码sudo grep "mikuu" /etc/shadow

由于题目说密码只有六位,且特定范围,直接选择爆破,范围应该就是根miku有关大小写全部弄上一共也就八个字符,
import crypt
import itertools
from tqdm import tqdm
import threading
import time
# 核心配置
shadow_hash = "$y$j9T$gCRCetfmd6EZeGuAZkRfn0$uZ/dNiHtjvkJDNfwMoGkJYiOkVV4UW4K0uzNr5FBeO8"
chars = "mMkKiIuU" # 特定字符集(8个字符,8^6=262144组合)
password_length = 6
found_password = None # 存储结果
stop_flag = False # 停止信号
counter = 0 # 已验证密码数
counter_lock = threading.Lock() # 计数器锁(线程安全)
def verify_batch(batch):
"""验证一批密码,更新计数器和停止信号"""
global counter, found_password, stop_flag
for pwd in batch:
if stop_flag: # 已找到密码,直接退出
return
# 验证密码
if crypt.crypt(pwd, shadow_hash) == shadow_hash:
found_password = pwd
stop_flag = True
print(f"\n[!] 找到密码:{pwd}")
return
# 安全更新计数器(加锁避免冲突)
with counter_lock:
counter += 1
def main():
# 生成所有6位密码组合(一次性生成,避免重复计算)
all_passwords = ["".join(p) for p in itertools.product(chars, repeat=password_length)]
total = len(all_passwords)
print(f"[+] 总组合数:{total:,} 个 | 字符集:{chars}")
# 分割任务(8线程,每线程处理一批)
num_threads = 8
batch_size = total // num_threads
batches = [
all_passwords[i*batch_size : (i+1)*batch_size]
for i in range(num_threads)
]
# 最后一批可能多一点,补全剩余
batches[-1].extend(all_passwords[num_threads*batch_size:])
# 启动进度条(独立线程)
def show_progress():
start_time = time.time()
with tqdm(total=total, desc="[+] 爆破进度") as pbar:
while not stop_flag and counter < total:
# 读取当前计数器值并更新进度条
with counter_lock:
current = counter
pbar.update(current - pbar.n)
time.sleep(0.3) # 每0.3秒更新一次
# 最后补全进度
pbar.update(total - pbar.n)
print(f"[+] 总耗时:{time.time() - start_time:.2f} 秒")
progress_thread = threading.Thread(target=show_progress)
progress_thread.start()
# 启动验证线程
threads = []
for batch in batches:
t = threading.Thread(target=verify_batch, args=(batch,))
threads.append(t)
t.start()
# 等待所有线程结束
for t in threads:
t.join()
progress_thread.join()
# 输出结果
if found_password:
print(f"\n[✅] 密码:{found_password}")
else:
print("\n[❌] 未找到(字符集可能错误)")
if __name__ == "__main__":
main()
得到密码

flag{miiikuuu_miiiku}
应急响应-把你mikumiku掉-3
题目内容:
被加密文件里面的内容是什么?
首先要找到加密文件,进入之前的恶意用户的目录,然后用root用户查看二级目录

看到了flag.miku和一个可执行文件mikumikud,其他文件是做题创的,先直接看加密文件内容,这里不知到为什么看不了了,之前的截图

19 19 81 01 14 51 40 ef fe dc ba 98 76 54 32 10
8c d5 12 23 48 13 16 62 0c 4f 23 b7 bb 0a 11 20
48 b0 f6 f8 38 9b be 31 b2 c2 46 48 79 04 75 2c#密文
运行一下可执行文件,看不懂说的啥,但是大概逻辑是接受flag.txt然后加密成flag.miku

执行ltrace ./mikumikud:跟踪程序

用gdb获取密钥

密钥:123456789ABCDEF01122334455667788
# 用AES-128-CBC,指定IV、密钥,解密后输出到 flag_dec.txt
echo -n "8CD51223481316620C4F23B7BB0A112048B0F6F8389BBE31B2C246487904752C" | xxd -r -p | openssl enc -d -aes-128-cbc -iv 19198101145140EFFEDCBA9876543210 -K 123456789ABCDEF01122334455667788 -out flag_dec.txt

flag{Miku_miku_oo_ee_oo}
TIME HACKER
题目内容:
我所害怕的,只有时间
【难度:困难】
看题目附件,时间都是未来时间,并且还都挺相近,再结合题目可以很容易联想到是跟时间戳有关的,并且还有一个密码检测器

用010打开flag.zip在末尾看到一处编码

用随波逐流解一下得到十位数字密码的提示

由于跟时间戳有关的,每个文件的时间戳都是2037年的,直接爆破密码固定前两位为21

得到密码2145768093,2145768093 对应的时间是 2038 年 01 月 10 日 14:41:33
跟文件时间很接近,按照正常的出题逻辑那个密码检测器如果用来单纯检验压缩包毫无意义,说明这个密码后续还有用处,不是单纯用来打开文件,这里尝试提取每个文件的时间戳来减去密码再转为字符得到aET_t_Y__HMrl4HR!e{}0ekI!acuegLr_f@e
import os
from datetime import datetime
def get_png_timestamps(folder_path, subtract_value=2145768093):
# 1. 筛选文件夹中所有PNG文件
png_files = []
for filename in os.listdir(folder_path):
if filename.lower().endswith('.png'):
# 提取文件名中的数字(如"33.png"→33),用于排序
try:
num = int(os.path.splitext(filename)[0]) # 分割扩展名,取前缀转数字
png_files.append((num, filename))
except ValueError:
print(f"跳过非数字命名的文件:{filename}")
if not png_files:
print("未找到PNG文件")
return
# 2. 按文件名中的数字升序排序(确保顺序正确:1→2→...→33→34...)
png_files.sort(key=lambda x: x[0])
sorted_filenames = [f[1] for f in png_files]
print(f"找到{len(sorted_filenames)}个PNG文件,按数字排序:{sorted_filenames}\n")
# 3. 处理每个文件的时间戳
result_chars = []
for filename in sorted_filenames:
file_path = os.path.join(folder_path, filename)
try:
# 获取文件修改时间戳(Unix时间戳,秒级)
# 注:os.path.getmtime()返回的是本地时间的时间戳,与系统时区一致
mtime = os.path.getmtime(file_path)
timestamp = int(mtime) # 转换为整数时间戳
# 计算:时间戳 - 目标值
calculated = timestamp - subtract_value
# 转换为字符(支持0-255范围,含扩展ASCII)
if 0 <= calculated <= 255:
char = chr(calculated)
# 标记不可打印字符(方便识别)
if not char.isprintable():
char = f"[不可打印:0x{calculated:02x}]"
else:
# 超出范围时取低8位(可能对应单字节编码)
calculated = calculated & 0xFF # 取低8位
char = f"[截断:0x{calculated:02x}→{chr(calculated)}]"
result_chars.append(char if char.isprintable() else chr(calculated))
print(f"{filename}:时间戳={timestamp} → {timestamp}-{subtract_value}={calculated} → {char}")
except Exception as e:
print(f"处理{filename}出错:{e}")
result_chars.append('[错误]')
# 4. 拼接最终结果(保留原始字符,忽略标记)
final_str = ''.join([c if c.isprintable() else chr(ord(c)) for c in result_chars])
print(f"\n最终字符拼接:{final_str}")
return final_str
if __name__ == "__main__":
# 替换为你的PNG文件夹路径
folder_path = r"E:\newstarctf2025\week5\misc\[Misc] TIME HACKER\flag" # 例如存放PNG的文件夹
get_png_timestamps(folder_path)
然后后面就卡住了至于为什么会想得到这个密文是因为,得到密码解压的文件除了一样的星际穿越台词的图片就毫无信息了。。,而且这个密文根据经验只考虑顺序就能得到flag了,到这一步就卡住了
后来用010打开解压的图片发现里面还有个时间

这个时间一看就是人为添加上去的,并且每个文件都不一样,仔细一看f对应文件时间恰好是最小的那么很明显就是按照这个时间顺序来转换顺序排列,写个脚本提取一下,再转换之前的密文
import os
import datetime
import re
def extract_target_string(file_path, offset=81, length=19):
"""从文件固定偏移量提取日期时间字符串(YYYY-MM-DD HH:MM:SS)"""
try:
with open(file_path, 'rb') as f:
f.seek(offset) # 跳到目标偏移量(默认81字节,对应之前的圈出位置)
target_bytes = f.read(length) # 读取19字节(日期时间长度)
return target_bytes.decode('utf-8', errors='ignore').strip()
except Exception as e:
raise IOError(f"提取文件{file_path}的日期字符串失败:{str(e)}")
def get_original_index(filename):
"""从文件名(如34.png)中提取原始序号(34)"""
match = re.search(r'(\d+)\.png', filename, re.IGNORECASE)
if not match:
raise ValueError(f"文件名{filename}格式错误,无法提取原始序号(应为数字.png)")
return int(match.group(1))
def sort_and_convert_ciphertext(img_dir, original_ciphertext, expected_count=36, offset=81, length=19):
# 1. 校验密文长度
if len(original_ciphertext) != expected_count:
raise ValueError(f"密文长度不符:预期{expected_count}字符,实际{len(original_ciphertext)}字符")
# 2. 读取PNG文件,提取日期和原始序号
png_files = [f for f in os.listdir(img_dir) if f.lower().endswith('.png')]
if len(png_files) != expected_count:
raise ValueError(f"图片数量不符:预期{expected_count}张,实际{len(png_files)}张")
file_info = [] # 存储 (日期时间对象, 原始序号, 文件名)
for filename in png_files:
file_path = os.path.join(img_dir, filename)
try:
# 提取原始序号(如34.png -> 34)
original_idx = get_original_index(filename)
# 提取日期字符串并转换为datetime
date_str = extract_target_string(file_path, offset, length)
date_time = datetime.datetime.strptime(date_str, '%Y-%m-%d %H:%M:%S')
file_info.append( (date_time, original_idx, filename) )
except Exception as e:
raise ValueError(f"处理文件{filename}失败:{str(e)}")
# 3. 按日期时间排序(升序)
file_info.sort(key=lambda x: x[0])
# 4. 按排序后的顺序映射密文(排序后第n位 = 原始序号对应的密文字符)
sorted_cipher = []
mapping = [] # 记录排序位置、原始序号、密文字符的对应关系
for sort_idx, (date_time, original_idx, filename) in enumerate(file_info, start=1):
# 原始序号从1开始,密文索引=原始序号-1
cipher_char = original_ciphertext[original_idx - 1]
sorted_cipher.append(cipher_char)
mapping.append({
"排序后位置": sort_idx,
"原始图片序号": original_idx,
"原始文件名": filename,
"对应密文字符": cipher_char,
"日期时间": date_time.strftime('%Y-%m-%d %H:%M:%S')
})
# 5. 输出结果
print("="*50)
print("排序与密文转换结果:")
print("-"*50)
print("排序后位置 -> 原始图片序号(文件名) -> 密文字符(日期时间)")
for item in mapping:
print(f"第{item['排序后位置']}位 -> 原始{item['原始图片序号']}号({item['原始文件名']}) -> '{item['对应密文字符']}'({item['日期时间']})")
print("-"*50)
print(f"最终转换后的密文:{''.join(sorted_cipher)}")
print("="*50)
return ''.join(sorted_cipher)
# ---------------------- 使用示例 ----------------------
if __name__ == "__main__":
# 替换为你的PNG图片目录(绝对路径或相对路径)
png_directory = r"E:\newstarctf2025\week5\misc\[Misc] TIME HACKER\flag"
# 原始密文(1-36字符对应原始图片1-36)
original_cipher = "aET_t_Y__HMrl4HR!e{}0ekI!acuegLr_f@e"
try:
# 若日期字符串位置不同,可调整offset和length(默认81字节,19字符)
result = sort_and_convert_ciphertext(
img_dir=png_directory,
original_ciphertext=original_cipher,
offset=81,
length=19
)
except Exception as e:
print(f"错误:{str(e)}")
得到flag{Y0u_4re_tHe_ReaL_TIME_H@cker!!},痛失一血
区块链-INTbug
看合约代码,可以先通过addPoints向用户添加足够积分(如 1001),确保userPoints满足花费需求;再调用usePoints(1001):此时userSpentPoints(1000)减去 1001,因unchecked不检查下溢,uint256类型会下溢为2^256 - 1(一个远大于 1000 的数),满足userSpentPoints > 1000,使unlocked[msg.sender]置为true;最后调用getFlag即可获取 flag。

flag{Good_NewStar2025_Byeeeee!}
Crypto
Poly
c1≡m119+*m*118+4m117mod*p*、*c*2≡5*m*219+m218+4*m*217modp、s≡m17*m*22+m2modp,c1只含m1,c2只含m2,s含有m1,m2可以通过结式消去含m2的两方程中的m2,得到m1的多项式并求解验证;再将有效m1代入第三式,得二次方程Am22+*m*2−*s*≡0(*A*=*m*17),再用求根公式求解并验证
from sympy import symbols, Poly
from sympy.polys.polytools import resultant
from sympy.ntheory.residue_ntheory import sqrt_mod
# 题目给出的数值
p = 30784558756838163538710632027143185397437897603217673077150297305544071001199
c1 = 2909317260219356685336632301474678396728564531244632916913671591997406996972
c2 = 4294738619365099885640900866122577092111906369664055461700321556058254607968
s = 8215705534787817006092091346252328321484153279277254569529867991109185617083
# 符号
m1, m2 = symbols("m1 m2")
# 多项式(在 GF(p) 中)
eq1_expr = m1**19 + m1**18 + 4*m1**17 - c1
eq2_expr = 5*m2**19 + m2**18 + 4*m2**17 - c2
eq3_expr = (m1**7)*m2**2 + m2 - s
# 将表达式包装为 Poly,指定主元为 m2 以便对 m2 求消元
poly_eq2 = Poly(eq2_expr, m2, m1, modulus=p)
poly_eq3 = Poly(eq3_expr, m2, m1, modulus=p)
# 对 m2 求消元,得到关于 m1 的一元多项式(模 p)
Res_expr = resultant(poly_eq2.as_expr(), poly_eq3.as_expr(), m2)
Res_poly = Poly(Res_expr, m1, modulus=p)
# 分解 Res_poly,提取一次因子得到 m1 的候选根
unit, factors = Res_poly.factor_list()
m1_candidates = []
for fac, mult in factors:
if fac.degree() == 1:
# fac = a*m1 + b (mod p) 的根是 r = -b * a^{-1} mod p
a, b = fac.all_coeffs() # [a, b]
a_mod = int(a) % p
b_mod = int(b) % p
inv_a = pow(a_mod, -1, p)
r = (-b_mod * inv_a) % p
m1_candidates.append(r)
def check_eq1(m1_val):
return (pow(m1_val, 19, p) + pow(m1_val, 18, p) + (4 * pow(m1_val, 17, p))) % p == c1
def check_eq2(m2_val):
return ( (5 * pow(m2_val, 19, p)) + pow(m2_val, 18, p) + (4 * pow(m2_val, 17, p)) ) % p == c2
def solve_m2(m1_val):
# A m2^2 + m2 - s = 0, A = m1^7
A = pow(m1_val, 7, p)
if A == 0:
return [] # 不太可能
D = (1 + (4 * A * s) % p) % p
roots = sqrt_mod(D, p, all_roots=True)
if roots is None:
return []
sols = []
denom = (2 * A) % p
inv_denom = pow(denom, -1, p)
for t in roots:
m2_val = ((-1 + t) % p) * inv_denom % p
sols.append(m2_val)
return sols
def int_to_bytes(x: int) -> bytes:
if x == 0:
return b"\x00"
return x.to_bytes((x.bit_length() + 7) // 8, "big")
solutions = []
for m1_val in m1_candidates:
# 先校验 eq1
if not check_eq1(m1_val):
continue
# 解 m2
for m2_val in solve_m2(m1_val):
if check_eq2(m2_val):
solutions.append((m1_val, m2_val))
print("Solutions count:", len(solutions))
for m1_val, m2_val in solutions:
x1 = int_to_bytes(m1_val)
x2 = int_to_bytes(m2_val)
data = x1 + x2
try:
print("Recovered flag:", data.decode())
except Exception:
print("Bytes (not UTF-8):", data)
flag{3bdb2424-591a-4a92-b587-74951c8ad192}
Smile盒
核心思路是:先分析加密流程(明文经 k0 异或、S 盒、P 置换得中间值 s,再经 k1 异或、S 盒、k2 异或得密文),然后向服务器查询多组明文 - 密文对,通过正向遍历 k0 计算中间值 s 的相对差分向量、逆向遍历 k2 计算逆 S 盒后中间值 y 的相对差分向量,匹配向量关联 k0 与 k2 并确定 k1,验证后得到密钥 k2
import socket
import time
import re
from collections import defaultdict
HOST = "8.147.132.32"
PORT = 22975
# Cipher spec from server
S = (13, 11, 6, 5, 2, 7, 3, 4, 9, 0, 10, 1, 12, 15, 8, 14)
P = (0,4,8,12,1,5,9,13,2,6,10,14,3,7,11,15)
INV_S = [0]*16
for i, v in enumerate(S):
INV_S[v] = i
INV_S = tuple(INV_S)
def deblock(m):
return [(m >> 12) & 0xF, (m >> 8) & 0xF, (m >> 4) & 0xF, m & 0xF]
def block(nibs):
return (nibs[0] << 12) | (nibs[1] << 8) | (nibs[2] << 4) | nibs[3]
def permute_bits(x):
bits = [(x >> (15 - i)) & 1 for i in range(16)]
new_bits = [0] * 16
for i in range(16):
new_bits[P[i]] = bits[i]
result = 0
for i in range(16):
result = (result << 1) | new_bits[i]
return result
def s1(m, k0):
p = m ^ k0
p = block([S[i] for i in deblock(p)])
p = permute_bits(p)
return p
def invS_word(w):
return block([INV_S[i] for i in deblock(w)])
def recv_until(sock, marker, timeout=8.0):
end = time.time() + timeout
buf = ""
sock.settimeout(0.5)
while time.time() < end:
try:
chunk = sock.recv(4096)
if not chunk:
time.sleep(0.05)
continue
s = chunk.decode(errors="ignore")
buf += s
if marker in buf:
break
except socket.timeout:
continue
return buf
def query(sock, m):
sock.sendall((f"0x{m:04x}\n").encode())
# 一般上一条 Output 与下一次提示同缓冲返回
buf = recv_until(sock, "Your input:", timeout=10.0)
matches = re.findall(r"Output:\s*0[xX]([0-9a-fA-F]{1,4})", buf)
if not matches:
# 兜底:提示与输出分开发送时再补一轮
buf += recv_until(sock, "Output:", timeout=2.0)
matches = re.findall(r"Output:\s*0[xX]([0-9a-fA-F]{1,4})", buf)
if not matches:
raise RuntimeError("ciphertext not found:\n" + buf[-400:])
return int(matches[-1], 16) & 0xFFFF
def encrypt_offline(m, k0, k1, k2):
# Round 1: ^k0 → S → P
p = s1(m, k0)
# Round 2: ^k1 → S(无 P)
p ^= k1
p = block([S[i] for i in deblock(p)])
# Output: ^k2
return (p ^ k2) & 0xFFFF
def recover_key_mitm3(m_list, c_list, m4, c4):
# 三明文差分 MITM:用 m0,m1,m2 的三元差分键对齐 k0 与 k2, subsequent求 k1 并用 m4 验证
from collections import defaultdict
m0, m1v, m2 = m_list
c0, c1, c2 = c_list
d_s = defaultdict(list)
for k0 in range(0x10000):
s0 = s1(m0, k0)
s1w = s1(m1v, k0)
s2w = s1(m2, k0)
key_t = (s0 ^ s1w, s1w ^ s2w, s2w ^ s0)
d_s[key_t].append((k0, (s0, s1w, s2w)))
def invS_word(w):
return block([INV_S[i] for i in deblock(w)])
d_x = defaultdict(list)
for k2 in range(0x10000):
y0 = invS_word(c0 ^ k2)
y1 = invS_word(c1 ^ k2)
y2 = invS_word(c2 ^ k2)
key_t = (y0 ^ y1, y1 ^ y2, y2 ^ y0)
d_x[key_t].append((k2, (y0, y1, y2)))
candidates = []
for key_t in set(d_s.keys()) & set(d_x.keys()):
for (k0, s_tuple) in d_s[key_t]:
s0, s1w, s2w = s_tuple
for (k2, y_tuple) in d_x[key_t]:
y0, y1, y2 = y_tuple
# 一致性检查:同一个 k1 必须满足三条明文
k1 = s0 ^ y0
if (s1w ^ y1) != k1 or (s2w ^ y2) != k1:
continue
# 严格验证:三条明文与密文完整匹配
if encrypt_offline(m0, k0, k1, k2) != c0:
continue
if encrypt_offline(m1v, k0, k1, k2) != c1:
continue
if encrypt_offline(m2, k0, k1, k2) != c2:
continue
# 再验证第 4 条
if encrypt_offline(m4, k0, k1, k2) != c4:
continue
candidates.append((k0, k1, k2))
return candidates
def recover_k2_mitm_relative(m_list, c_list):
# 以 m_list[0] 为锚,构建 k0 的 s1 相对差分向量表;再对每个 k2 构建 invS(c⊕k2) 的相对差分向量,做哈希相交
base_m = m_list[0]
s_diff_map = defaultdict(list)
for k0 in range(0x10000):
s0 = s1(base_m, k0)
vec = []
for i in range(1, len(m_list)):
si = s1(m_list[i], k0)
vec.append(si ^ s0)
s_diff_map[tuple(vec)].append((k0, s0))
candidates = []
for k2 in range(0x10000):
y0 = invS_word(c_list[0] ^ k2)
vecY = []
for i in range(1, len(c_list)):
yi = invS_word(c_list[i] ^ k2)
vecY.append(yi ^ y0)
key = tuple(vecY)
if key in s_diff_map:
for (k0, s0) in s_diff_map[key]:
k1 = s0 ^ y0
ok = True
# 用所有已查询的明文严格验证
for mi, ci in zip(m_list, c_list):
if encrypt_offline(mi, k0, k1, k2) != ci:
ok = False
break
if ok:
candidates.append((k0, k1, k2))
return candidates
def main():
sock = socket.create_connection((HOST, PORT), timeout=10)
banner = recv_until(sock, "Your input:", 5.0)
if banner:
print(banner, end="")
# 选用更“强扩散”的明文集,增强相对差分约束
m_list = [0x0000, 0xffff, 0xaaaa, 0x5555, 0xf0f0, 0x0f0f, 0x00ff, 0xff00]
c_list = []
for i, m in enumerate(m_list, 1):
c = query(sock, m)
print(f"Query {i}: 0x{m:04x} -> 0x{c:04x}")
c_list.append(c)
# 相对差分 MITM,通常能将候选压到 1
cand_list = recover_k2_mitm_relative(m_list, c_list)
print(f"Candidates after relative MITM: {len(cand_list)}")
if not cand_list:
print("No key candidates found.")
sock.close()
return
# 若仍有多个候选,追加一条查询做最终筛选
chosen = None
if len(cand_list) == 1:
chosen = cand_list[0]
else:
m_extra = 0x1357
c_extra = query(sock, m_extra)
print(f"Extra Query: 0x{m_extra:04x} -> 0x{c_extra:04x}")
for (k0, k1, k2) in cand_list:
if encrypt_offline(m_extra, k0, k1, k2) == c_extra:
chosen = (k0, k1, k2)
break
if not chosen:
print("No valid candidate after extra check.")
sock.close()
return
k0, k1, k2 = chosen
print(f"Chosen k2: 0x{k2:04x}")
sock.sendall(b"k\n")
recv_until(sock, "Your key:")
sock.sendall((f"0x{k2:04x}\n").encode())
final = recv_until(sock, "You can smile:")
if "You can smile:" not in final:
final += recv_until(sock, "Wrong. Bye.")
print(final)
sock.close()
if __name__ == "__main__":
main()
flag{Ur_awesome_4_being_able_2_successfully_find_1_high-probability_delta(?)_path!}
不给你看喵
题目生成 16 个随机数a和 16 个 0/1 随机变量x,计算t = sum(a[i]*x[i])并发送a和t。可以通过暴力搜索反推x(因x是 0/1 变量,遍历所有可能组合找到满足sum(a[i]*x[i])=t的x)。
接着,构造C列表:每个C[i] = (g^x[i] * h²) mod p(利用x[i]的 0/1 特性)。服务器收到C后发送随机数s列表,客户端计算S = sum(s[i]*x[i]),并设R = 2*sum(s)(确保R > S,因x[i]为 0/1,sum(s[i]*x[i]) ≤ sum(s) < 2*sum(s))。
最终验左侧prod(C[i]^s[i]) mod p经代数变换等于g^S * h^R mod p,与右侧一致,所有条件满足即可获得flag
import socket
import ast
HOST = "8.147.132.32"
PORT = 38529
p = 225791639467198034995070527100776477487
g = 3
h = 5
def recover_x(a, t):
n = len(a)
for mask in range(1 << n):
ssum = 0
for i in range(n):
if (mask >> i) & 1:
ssum += a[i]
if ssum == t:
return [(mask >> i) & 1 for i in range(n)]
raise RuntimeError("Failed to recover x")
def recv_lines(sock, timeout=0.2):
sock.settimeout(timeout)
try:
data = sock.recv(4096)
if not data:
return []
text = data.decode(errors="ignore")
return text.splitlines()
except socket.timeout:
return []
def send_line(sock, s):
sock.sendall((str(s) + "\n").encode())
def parse_list(line):
try:
if "[" in line and "]" in line:
return ast.literal_eval(line[line.find("["):line.rfind("]")+1])
except Exception:
pass
return None
def parse_int(line):
try:
return int(line.strip())
except Exception:
return None
def main():
with socket.create_connection((HOST, PORT), timeout=5) as sock:
# 读取 a 与 t
a = None
t = None
while a is None or t is None:
for line in recv_lines(sock, timeout=1.0):
if a is None:
lst = parse_list(line)
if isinstance(lst, list) and all(isinstance(x, int) for x in lst):
a = lst
continue
if t is None:
ti = parse_int(line)
if isinstance(ti, int):
t = ti
continue
if a is not None and t is not None:
break
if a is None or t is None:
raise RuntimeError("Failed to read a or t from server")
# 恢复 x
x = recover_x(a, t)
# 发送 16 个 C[i]:C[i] = g^{x[i]} * h^2 (mod p)
h2 = pow(h, 2, p)
for xi in x:
Ci = (pow(g, xi, p) * h2) % p
send_line(sock, Ci)
# 读取 s 列表
s = None
while s is None:
for line in recv_lines(sock, timeout=1.0):
lst = parse_list(line)
if isinstance(lst, list) and all(isinstance(v, int) for v in lst):
s = lst
break
if s is None:
raise RuntimeError("Failed to read s from server")
# 计算并发送 S 与 R
S = sum(s[i] * x[i] for i in range(len(s)))
R = 2 * sum(s)
send_line(sock, S)
send_line(sock, R)
# 读取最终输出(FLAG)
flag = None
# 多读几轮,直到出现 flag 字样
for _ in range(20):
lines = recv_lines(sock, timeout=1.0)
for line in lines:
if "flag{" in line or "FLAG{" in line or "flag" in line.lower():
flag = line.strip()
break
if flag:
break
print("FLAG output:", flag if flag else "(not found, check logs)")
# 打印所有剩余输出方便排查
for line in recv_lines(sock, timeout=0.5):
print(line)
if __name__ == "__main__":
main()
flag{Do_u_r3a1ly_n33d_me_2_5h0w_u_2_pr0v3_7h4t_1_h4v3_it?}
BLS多重签名:零的裂变
服务器要求获取 flag 需满足两个关键条件:一是提交的 3 个公钥(前两个为用户注册公钥,第三个为服务器公钥)的聚合结果必须等于服务器公钥;二是对应的聚合签名能通过该聚合公钥对消息 “get_flag” 的验证。
椭圆曲线群中,公钥是私钥与生成元的乘积(pk = sk・G),聚合公钥为各公钥之和(pk_agg = pk1 + pk2 + … + pkn),且聚合签名为各私钥签名之和(sig_agg = sig1 + sig2 + … + sign)。
可以先在客户端生成两个私钥 sk1 和 sk2,其中 sk2 = -sk1 mod 曲线阶数(确保 sk1 + sk2 = 0)。对应的公钥 pk1 = sk1・G、pk2 = sk2・G,二者之和为 0(pk1 + pk2 = (sk1 + sk2)・G = 0)。将这两个公钥注册后,与服务器公钥 pk_server 组成的 3 个公钥聚合结果为:pk1 + pk2 + pk_server = 0 + pk_server = pk_server,满足服务器对聚合公钥的要求。
随后,客户端获取服务器对 “get_flag” 的签名(即 sk_server 对消息的签名)。由于前两个私钥的签名之和为 sk1・sig + sk2・sig = (sk1 + sk2)・sig = 0,因此总聚合签名即为服务器的签名。该签名能通过聚合公钥(即 pk_server)的验证
import socket
import json
import secrets
from py_ecc.bls import G2ProofOfPossession as bls
HOST = "8.147.132.32"
PORT = 24710
# BLS12-381 curve order (same as server)
ORDER = 52435875175126190479447740508185965837690552500527637822603658699938581184513
def recv_line(sock, timeout=10.0):
sock.settimeout(timeout)
buf = b""
while b"\n" not in buf:
chunk = sock.recv(4096)
if not chunk:
break
buf += chunk
line, _, _ = buf.partition(b"\n")
return line.decode()
def send_cmd(sock, obj):
data = json.dumps(obj).encode() + b"\n"
sock.sendall(data)
resp = recv_line(sock)
if not resp:
raise RuntimeError("Empty response")
return json.loads(resp)
def main():
# 1) 连接服务器
sock = socket.create_connection((HOST, PORT), timeout=10)
# 2) 获取服务器信息(公钥、POP)
info = send_cmd(sock, {"type": "get_info"})
assert info.get("success"), f"get_info failed: {info}"
server_pk_hex = info["message"]["server_pk"]
print(f"[+] Server PK: {server_pk_hex[:16]}...")
# 3) 生成两把“零和”私钥与公钥
sk1 = secrets.randbelow(ORDER - 1) + 1
sk2 = (ORDER - sk1) % ORDER # -sk1 mod ORDER, 非零
if sk2 == 0:
# 极低概率(sk1=0);重选
sk1 = secrets.randbelow(ORDER - 1) + 1
sk2 = (ORDER - sk1) % ORDER
pk1 = bls.SkToPk(sk1)
pk2 = bls.SkToPk(sk2)
pk1_hex = pk1.hex()
pk2_hex = pk2.hex()
print(f"[+] PK1: {pk1_hex[:16]}..., PK2: {pk2_hex[:16]}... (PK1 + PK2 == 0)")
# 4) 为两把公钥生成并提交 PoP(对固定消息 POP 的 BLS 签名)
pop1_hex = bls.Sign(sk1, b"POP").hex()
pop2_hex = bls.Sign(sk2, b"POP").hex()
r1 = send_cmd(sock, {"type": "register", "pk": pk1_hex, "pop": pop1_hex})
assert r1.get("success"), f"register pk1 failed: {r1}"
print("[+] Registered PK1")
r2 = send_cmd(sock, {"type": "register", "pk": pk2_hex, "pop": pop2_hex})
assert r2.get("success"), f"register pk2 failed: {r2}"
print("[+] Registered PK2")
# 5) 取得服务器对 get_flag 的签名
sign_resp = send_cmd(sock, {"type": "sign", "msg": "get_flag"})
assert sign_resp.get("success"), f"server sign failed: {sign_resp}"
server_sig_hex = sign_resp["message"]
print("[+] Got server signature for 'get_flag'")
# 6) 提交 get_flag:三公钥为 [PK1, PK2, ServerPK],签名为服务器返回的签名
pks_hex = [pk1_hex, pk2_hex, server_pk_hex]
flag_resp = send_cmd(sock, {"type": "get_flag", "pks": pks_hex, "sig": server_sig_hex})
print(flag_resp)
if flag_resp.get("success"):
print("[+] Flag:", flag_resp["message"])
else:
print("[-] Failed:", flag_resp.get("message"))
sock.close()
if __name__ == "__main__":
main()
flag{2c4898b8-6f0b-4343-a3c6-50d99b8669aa}
更多推荐


所有评论(0)