恃液 发表于 2026-1-21 13:55:07

记2025长城杯线上赛部分题目

0.前言

小比赛随便打,国赛教我做人....
1.AI安全

1.1The Silent Heist

题目内容: 目标银行部署了一套基于 Isolation Forest (孤立森林) 的反欺诈系统。该系统不依赖传统的黑名单,而是通过机器学习严密监控交易的 20 个统计学维度。系统学习了正常用户的行为模式(包括资金流向、设备指纹的协方差关系等),一旦发现提交的数据分布偏离了“正常模型”,就会立即触发警报。 我们成功截取了一份包含 1000 条正常交易记录的流量日志 (public_ledger.csv)。请你利用统计学方法分析这份数据,逆向推导其多维特征分布规律,并伪造一批新的交易记录 那基本上就能看出本题模拟了一个典型的对抗性机器学习场景。目标是骗过一个已经上线的异常检测系统
目标系统是基于孤立森林的实时风控引擎,输入数据是20 维浮点数特征
金额目标:
https://www.yijinglab.com/guide-img/d9634e2f-3b66-42e7-8279-c0877cdd70e5/2a4117ca-9254-4023-a1c9-d2127fe64219.png
孤立森林不同于传统的分类算法(如 SVM 或神经网络),它属于无监督学习
核心逻辑就是算法随机选择特征并随机选择切分点,构建二叉树
且异常点往往具有“少”且“异”的特点,在空间中,它们远离高密度区域
路径长度异常点和正常点也是不一样的,


[*] 异常点:只需要很少次数的随机切分就能被孤立出来,也就是处于树的浅层,路径短

[*] 正常点:位于数据簇的中心,需要密集的切分才能被隔绝,也就是说处于树的深层,路径长

[*] 判定公式:模型通过样本在多棵树中的平均路径长度计算异常评分。路径越长,评分越低,数据越正常

既然孤立森林难以孤立处于数据中心的点,那么我们的策略就是:制造大量极其平庸的数据
比如说,我们通过对截获的 1000 条日志进行统计:
计算每一列的平均值,计算每一列的标准差
只要生成的数据点无限趋近于各维度的均值 ,它们就会落在孤立森林最难切分的深处,逃过检测
由题目给的附件已知 feat_0的均值 μ0≈353
那么计算所需条数:2,000,000/353≈5,6662,000,000/353≈5,666条
也就是说,我们只要生成 6,000 条数据。这不仅能稳过 2M 金额线,还能通过大量的微小数据分摊风险,避免单笔大额交易触发阈值告警
但是如果 6000 条数据完全一样,会被去重算法拦截
所以应该在均值 μ的基础上,注入一个尺度极小的高斯白噪声
公式:
https://www.yijinglab.com/guide-img/d9634e2f-3b66-42e7-8279-c0877cdd70e5/e26cd51b-c395-4701-9b06-52676538b688.png
这里 ϵ(扰动系数)设为 0.01左右,这保证了:

[*] 每行数据在二进制层面都是唯一的

[*] 在统计学层面,数据分布依然极度向中心靠拢

所以exp.py
import socket import numpy as np import pandas as pd import io ​ # 1. 题目提供的部分日志数据(基于你提供的片段进行统计建模) # 在实际环境中,如果能下载完整csv,分析结果会更精确。 def generate_payload():   # 统计特征 (均值 mu 和 标准差 sigma)   # 基于样本计算的近似值   means = np.array([       353.45, 27.56, 93.67, 82.78, 45.12, 4.23, 13.45, 51.67, 11.23, 30.56,          39.12, 84.78, 10.34, 82.12, 73.67, 18.89, 30.56, 41.89, 13.12, 27.56 ])   stds = np.array([       25.0, 2.5, 3.0, 3.0, 2.0, 2.5, 2.5, 2.0, 2.5, 3.0,       3.0, 3.0, 2.5, 3.0, 3.0, 3.0, 3.0, 3.0, 3.0, 2.5 ]) ​   # 设定生成 6000 条记录以确保总金额 > 2,000,000   num_samples = 6000      print(f"
[*] 正在生成 {num_samples} 条伪造交易记录...")      # 生成数据:均值 + 极小的随机扰动 (0.01倍标准差)   # 这样可以确保数据唯一(躲避去重检测)且极度接近中心(躲避异常检测)   generated_data = []   for _ in range(num_samples):       noise = np.random.normal(0, 0.01, size=20) * stds       row = means + noise       generated_data.append(row)      # 转换为 CSV 格式   df = pd.DataFrame(generated_data)   df.columns =       csv_buffer = io.StringIO()   df.to_csv(csv_buffer, index=False, float_format='%.6f')      payload = csv_buffer.getvalue()   return payload ​ def pwn_bank():   host = '182.92.11.65'   port = 30799      payload = generate_payload()      try:       # 2. 建立连接       print(f"
[*] 正在连接到 {host}:{port}...")       s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)       s.connect((host, port))              # 接收服务器欢迎语       # s.recv(1024)                 # 3. 发送数据       print("
[*] 正在传输数据流并注入金额...")       s.sendall(payload.encode())              # 4. 发送结束标志       s.sendall(b"EOF\n")              # 5. 接收返回结果(Flag通常在这里)       print("
[*] 等待银行系统响应...")       response = b""       while True:           data = s.recv(4096)           if not data:               break           response += data           # 如果收到 flag 格式,提前停止打印(假设格式为 flag{...})           if b"flag" in response.lower():               break                      print("\n[+] 服务器响应结果:")       print(response.decode(errors='ignore'))              s.close()   except Exception as e:       print(f"[-] 错误: {e}") ​ if __name__ == "__main__":   pwn_bank() https://www.yijinglab.com/guide-img/d9634e2f-3b66-42e7-8279-c0877cdd70e5/e88eeed0-1aa5-473e-9e1b-932ad485dbc0.png
2.Cry

2.1 ECDSA

题目给了三个东西


[*] task.py:生成密钥和签名的程序

[*] signatures.txt:使用弱私钥生成的 60 个签名样本

[*] public.pem:与私钥对应的公钥

看它task.py的代码就知道这个私钥生成有问题
from ecdsa import SigningKey, NIST521p from hashlib import sha512 from Crypto.Util.number import long_to_bytes ​ # 计算固定字符串的SHA512哈希 digest_int = int.from_bytes(sha512(b"Welcome to this challenge!").digest(), "big") ​ # 获取曲线阶数 curve_order = NIST521p.order ​ # 对曲线阶数取模得到私钥 priv_int = digest_int % curve_order ​ # 转换为字节格式 priv_bytes = long_to_bytes(priv_int, 66) ​ # 创建私钥对象 sk = SigningKey.from_string(priv_bytes, curve=NIST521p) 首先它私钥种子固定不变
私钥的生成依赖于固定字符串 "Welcome to this challenge!",这个字符串在代码中硬编码,任何人都可以访问源代码并计算出完全相同的私钥
接着算法也有问题,仅使用 SHA512 哈希运算就生成私钥,哈希函数是确定性的,给定相同输入必然产生相同输出
【----帮助网安学习,以下所有学习资料免费领!加vx:YJ-2021-1,备注 “博客园” 获取!】
 ① 网安学习成长路径思维导图
 ② 60+网安经典常用工具包
 ③ 100+SRC漏洞分析报告
 ④ 150+网安攻防实战技术电子书
 ⑤ 最权威CISSP 认证考试指南+题库
 ⑥ 超1800页CTF实战技巧手册
 ⑦ 最新网安大厂面试题合集(含答案)
 ⑧ APP客户端安全检测指南(安卓+IOS)
所以,种子字符串是公开的,计算过程是确定性的,无需任何额外信息即可恢复私钥
from ecdsa import SigningKey, NIST521p, VerifyingKey from hashlib import sha512 from Crypto.Util.number import long_to_bytes, bytes_to_long import binascii ​ def recover_private_key():   """ 通过计算固定字符串的SHA512哈希值恢复私钥 """   message = b"Welcome to this challenge!"   digest = sha512(message).digest()   digest_int = int.from_bytes(digest, "big")   curve_order = NIST521p.order   priv_int = digest_int % curve_order      priv_bytes = long_to_bytes(priv_int, 66)   sk = SigningKey.from_string(priv_bytes, curve=NIST521p)      return sk ​ def generate_nonce(index):   """ 生成指定索引的nonce值 """   seed = sha512(b"bias" + bytes()).digest()   k = int.from_bytes(seed, "big")   return k ​ def load_public_key(pem_file="public.pem"):   """ 从PEM文件加载公钥 """   with open(pem_file, "rb") as f:       pem_data = f.read()   vk = VerifyingKey.from_pem(pem_data)   return vk ​ def extract_rs_from_der(sig_bytes):   """ 从DER编码的签名中提取r和s值 """   if len(sig_bytes) < 8:       return None, None      pos = 0   if sig_bytes != 0x30:       return None, None   pos += 1      length_bytes = sig_bytes   pos += 1      if sig_bytes != 0x02:       return None, None   pos += 1      r_length = sig_bytes   pos += 1   r_value = sig_bytes   pos += r_length      if sig_bytes != 0x02:       return None, None   pos += 1      s_length = sig_bytes   pos += 1   s_value = sig_bytes      r_int = bytes_to_long(r_value)   s_int = bytes_to_long(s_value)      return r_int, s_int ​ def verify_signature_ecdsa(vk, message, signature):   """ 使用公钥验证签名 """   try:       return vk.verify(signature, message)   except:       return manual_verify(vk, message, signature) ​ def manual_verify(vk, message, signature):   """ 手动验证ECDSA签名 """   try:       r, s = extract_rs_from_der(signature)       if r is None or s is None:           return False              msg_hash = sha512(message).digest()       msg_hash_int = bytes_to_long(msg_hash)              point = vk.pubkey.point       curve_order = NIST521p.order              # 计算 w = s^(-1) mod n       def modinv(a, m):           if a < 0:               a = a % m           for i in range(1, m):               if (a * i) % m == 1:                   return i           return 1              w = modinv(s, curve_order)       u1 = (msg_hash_int * w) % curve_order       u2 = (r * w) % curve_order              G = NIST521p.generator              point1 = G * u1       point2 = point * u2       result_point = point1 + point2              return (result_point.x() % curve_order) == r   except:       return False ​ def sign_message_with_nonce(sk, message, nonce_index):   """ 使用指定索引的nonce签名消息 """   k = generate_nonce(nonce_index)   signature = sk.sign(message, k=k)   return signature ​ def main():   print("=" * 70)   print("ECDSA 私钥恢复和签名工具")   print("=" * 70)      # 1. 恢复私钥   print("\n 恢复私钥...")   sk = recover_private_key()   print(f"[✓] 私钥已恢复")   print(f"  私钥值: {sk.privkey.secret_multiplier}")   print(f"  私钥字节: {binascii.hexlify(sk.to_string()).decode()}")      # 2. 加载公钥   print("\n 加载公钥...")   vk = load_public_key()   print("[✓] 公钥已加载")      # 3. 验证私钥正确性   print("\n 验证私钥...")      # 使用一个已有的签名验证   with open("signatures.txt", "r") as f:       first_line = f.readline().strip()       msg_hex, sig_hex = first_line.split(":")       test_msg = bytes.fromhex(msg_hex)       test_sig = bytes.fromhex(sig_hex)      if verify_signature_ecdsa(vk, test_msg, test_sig):       print("[✓] 私钥验证成功!恢复的私钥与公钥匹配")   else:       print("[✗] 私钥验证失败")       return      # 4. 尝试签名获取flag   print("\n 尝试生成签名...")      # 尝试使用不同的nonce索引   flag_messages = [       b"flag",       b"getflag",          b"submit flag",       b"give me the flag",       b"CTF{", ]      for msg in flag_messages:       print(f"\n尝试签名消息: {msg}")              # 尝试使用不同的nonce索引 (0-59)       for i in range(60):           try:               sig = sign_message_with_nonce(sk, msg, i)                              # 验证签名               if verify_signature_ecdsa(vk, msg, sig):                   print(f"[✓] 成功!")                   print(f"  Nonce索引: {i}")                   print(f"  签名: {binascii.hexlify(sig).decode()}")                                      # 保存签名到文件                   with open("flag_signature.txt", "w") as f:                       f.write(f"Message: {msg.decode()}\n")                       f.write(f"Nonce Index: {i}\n")                       f.write(f"Signature: {binascii.hexlify(sig).decode()}\n")                                      print(f"\n[+] 签名已保存到 flag_signature.txt")                                      # 5. 展示如何使用                   print("\n" + "=" * 70)                   print("解题步骤:")                   print("=" * 70)                   print(f""" 1. 私钥已成功恢复  私钥值: {sk.privkey.secret_multiplier} ​ 2. 使用恢复的私钥,可以:  - 验证任何使用该密钥签名的消息  - 为新消息生成有效签名  - 在CTF服务器上提交签名获取flag ​ 3. 生成的签名:  消息: {msg.decode()}  签名: {binascii.hexlify(sig).decode()} ​ 4. 将此签名提交给题目服务器即可获取flag                 """)                                      return                              except Exception as e:               continue              print(f"[-] 使用所有nonce索引签名失败")      print("\n[!] 尝试其他方法...")      # 如果上面的方法失败,输出更多信息   print("\n 输出私钥信息供手动使用...")   print(f"\n私钥值 (十进制):")   print(sk.privkey.secret_multiplier)   print(f"\n私钥值 (十六进制):")   print(binascii.hexlify(sk.to_string()).decode()) ​ if __name__ == "__main__":   main() https://www.yijinglab.com/guide-img/d9634e2f-3b66-42e7-8279-c0877cdd70e5/06210901-d72e-418b-83f1-6383afbd69b0.png
2.2 Ezflag

先ida进行一个逆向找到main函数
https://www.yijinglab.com/guide-img/d9634e2f-3b66-42e7-8279-c0877cdd70e5/18e82cf1-4331-431d-891c-c5230a873858.png
只有当输入的密码完全等于 V3ryStr0ngp@ssw0rd 时,程序才会进入 else 分支生成 Flag
std::operator str:       """核心编码逻辑:自定义位流映射"""       out = []       val, bits = 0, 0       for byte in data:           val = (val = 6:               bits -= 6               out.append(self._alphabet[(val >> bits) & 0x3F])              if bits > 0:           out.append(self._alphabet[(valstr:       """计算特定时间戳下的认证指纹"""       u, p = self._user_info       # 预处理密码编码       p_enc = self._transform(p.encode('latin-1'))              # 构造原始载荷       payload = '{"username":"%s","password":"%s"}' % (u, p_enc)       raw_msg = payload.encode('utf-8') ​       # 密钥派生 (Key Derivation)       seed = str(tick).encode()       key_block = hashlib.sha256(seed).digest() if len(seed) > 64 else seed       key_block = key_block.ljust(64, b'\x00') ​       # 这里的 118(0x76) 和 60(0x3C) 是原始逻辑的特征常数       p1 = bytes()       p2 = bytes() ​       # 嵌套哈希架构 (注意:这是非标准的哈希顺序 inner + opad)       mid_hash = hashlib.sha256(p1 + raw_msg).digest()       final_sig = self._transform(hashlib.sha256(mid_hash + p2).digest()) ​       # 生成最终校验体       full_body = '{"username":"%s","password":"%s","signature":"%s"}' % (u, p_enc, final_sig)       return hashlib.md5(full_body.encode()).hexdigest() ​   def run_audit(self):       """执行扫描任务"""       # 时间范围定义       tz = timezone(timedelta(hours=8))       t_start = int(datetime(2025, 12, 22, 0, 0, tzinfo=tz).timestamp() * 1000)       t_end = int(datetime(2025, 12, 22, 6, 0, tzinfo=tz).timestamp() * 1000) ​       print(f"
[*] Task started: scanning range {t_start} -> {t_end}")              total = t_end - t_start       for current_ts in range(t_start, t_end + 1):           token = self.check_sequence(current_ts)                      if token.startswith(self._goal_prefix):               print(f"\n[+] Match discovered at index: {current_ts}")               print(f"[+] Final Flag: flag{{{token}}}")               return ​           if current_ts % 100000 == 0:               progress = (current_ts - t_start) / total * 100               print(f"
[*] Processing... {progress:.1f}%", end='\r') ​ if __name__ == "__main__":   engine = CryptoEngine()   engine.run_audit() https://www.yijinglab.com/guide-img/d9634e2f-3b66-42e7-8279-c0877cdd70e5/3faf5036-64bc-4a14-adca-4c9fe807931b.png
3.2 babygame

一道Godot逆向题,得有专门的工具
https://www.yijinglab.com/guide-img/d9634e2f-3b66-42e7-8279-c0877cdd70e5/42440355-8f53-4c4a-b55f-f45beb2827f6.png
extends CenterContainer ​ @onready var flagTextEdit: Node = $PanelContainer / VBoxContainer / FlagTextEdit @onready var label2: Node = $PanelContainer / VBoxContainer / Label2 ​ static var key = "FanAglFanAglOoO!" var data = "" ​ func _on_ready() -> void :   Flag.hide() ​ func get_key() -> String:   return key ​ func submit() -> void :   data = flagTextEdit.text ​   var aes = AESContext.new()   aes.start(AESContext.MODE_ECB_ENCRYPT, key.to_utf8_buffer())   var encrypted = aes.update(data.to_utf8_buffer())   aes.finish() ​   if encrypted.hex_encode() == "d458af702a680ae4d089ce32fc39945d":       label2.show()   else:       label2.hide() ​ func back() -> void :   get_tree().change_scene_to_file("res://scenes/menu.tscn") 可以看到


[*] 初始key:FanAglFanAglOoO!

[*] 目标密文hex:d458af702a680ae4d089ce32fc39945d

[*] 算法 是 AES ,代码中明确调用了 AESContext.new()

[*] 模式是 ECB 代码中使用了 AESContext.MODE_ECB_ENCRYPT

[*] 密钥 FanAglFanAglOoO!

[*] 该字符串长度为 16 个字符。

[*] 在 UTF-8 编码下,16 个字符等于 16 字节(128位),因此,这是 AES-128


照理说直接写个脚本逆向就可以得到flag了,可是一直不对
然后看了题目内容
题目内容: 请找出隐藏的Flag。请注意只有收集了所有的金币,才能验证flag。 意思就是金币,也就是分数得达到一个设定好的数才能验证flag,回去逆向看看那里关于分数的函数
https://www.yijinglab.com/guide-img/d9634e2f-3b66-42e7-8279-c0877cdd70e5/d1d30c7f-1bb4-4505-898e-7b3ab3b85f85.png
可以看到分数这里的代码是说当分数+1的时候,密钥中的A替换成B
所以正确的密钥应该是
FanBglFanBglOoO! 所以套上脚本就是
from Crypto.Cipher import AES key = b"FanBglFanBglOoO!" ciphertext = bytes.fromhex("d458af702a680ae4d089ce32fc39945d") cipher = AES.new(key, AES.MODE_ECB) result = cipher.decrypt(ciphertext) print(result) https://www.yijinglab.com/guide-img/d9634e2f-3b66-42e7-8279-c0877cdd70e5/c7253845-9b9d-4df2-8134-5679ec822aae.png
更多网安技能的在线实操练习,请点击这里>>
  

来源:程序园用户自行投稿发布,如果侵权,请联系站长删除
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!

簑威龙 发表于 2026-2-2 22:00:57

前排留名,哈哈哈

纪晴丽 发表于 2026-2-3 09:49:48

yyds。多谢分享

鄂缮输 发表于 2026-2-3 10:38:12

懂技术并乐意极积无私分享的人越来越少。珍惜

廖雯华 发表于 2026-2-5 18:00:49

感谢发布原创作品,程序园因你更精彩

替攀浮 发表于 2026-2-6 04:24:54

谢谢分享,辛苦了

林鱼 发表于 2026-2-7 04:26:40

yyds。多谢分享

抑卞枯 发表于 2026-2-8 11:07:51

热心回复!

铵滔 发表于 2026-2-8 12:38:10

这个好,看起来很实用

鞭氅 发表于 2026-2-9 07:10:46

谢谢分享,试用一下

忿惺噱 发表于 2026-2-10 08:41:49

感谢分享,学习下。

高小雨 发表于 2026-2-10 11:50:05

感谢发布原创作品,程序园因你更精彩

辈霖利 发表于 2026-2-10 14:13:11

前排留名,哈哈哈

客臂渐 发表于 2026-2-11 20:09:10

收藏一下   不知道什么时候能用到

晦险忿 发表于 2026-2-13 22:53:01

谢谢分享,辛苦了

殷罗绮 发表于 2026-2-21 07:39:57

这个好,看起来很实用

恙髡 发表于 2026-2-26 05:54:35

这个好,看起来很实用

撵延兵 发表于 2026-3-11 12:39:40

收藏一下   不知道什么时候能用到
页: [1]
查看完整版本: 记2025长城杯线上赛部分题目