找回密码
 立即注册
首页 业界区 安全 记2025长城杯线上赛部分题目

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

卢莹洁 2026-1-21 13:55:06
0.前言

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

1.1The Silent Heist

  1. 题目内容: 目标银行部署了一套基于 Isolation Forest (孤立森林) 的反欺诈系统。该系统不依赖传统的黑名单,而是通过机器学习严密监控交易的 20 个统计学维度。系统学习了正常用户的行为模式(包括资金流向、设备指纹的协方差关系等),一旦发现提交的数据分布偏离了“正常模型”,就会立即触发警报。 我们成功截取了一份包含 1000 条正常交易记录的流量日志 (public_ledger.csv)。请你利用统计学方法分析这份数据,逆向推导其多维特征分布规律,并伪造一批新的交易记录
复制代码
那基本上就能看出本题模拟了一个典型的对抗性机器学习场景。目标是骗过一个已经上线的异常检测系统
目标系统是基于孤立森林的实时风控引擎,输入数据是20 维浮点数特征
金额目标:

孤立森林不同于传统的分类算法(如 SVM 或神经网络),它属于无监督学习
核心逻辑就是算法随机选择特征并随机选择切分点,构建二叉树
且异常点往往具有“少”且“异”的特点,在空间中,它们远离高密度区域
路径长度异常点和正常点也是不一样的,


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

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

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

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

这里 ϵ(扰动系数)设为 0.01左右,这保证了:

  • 每行数据在二进制层面都是唯一的

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

所以exp.py
  1. 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"
  2. [*] 正在生成 {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 = [f'feat_{i}' for i in range(20)]          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"
  3. [*] 正在连接到 {host}:{port}...")         s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)         s.connect((host, port))                  # 接收服务器欢迎语         # s.recv(1024)                   # 3. 发送数据         print("
  4. [*] 正在传输数据流并注入金额...")         s.sendall(payload.encode())                  # 4. 发送结束标志         s.sendall(b"EOF\n")                  # 5. 接收返回结果(Flag通常在这里)         print("
  5. [*] 等待银行系统响应...")         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()
复制代码
[img=720,163.29896907216494]https://www.yijinglab.com/guide-img/d9634e2f-3b66-42e7-8279-c0877cdd70e5/e88eeed0-1aa5-473e-9e1b-932ad485dbc0.png[/img]

2.Cry

2.1 ECDSA

题目给了三个东西


  • task.py:生成密钥和签名的程序

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

  • public.pem:与私钥对应的公钥

看它task.py的代码就知道这个私钥生成有问题
  1. 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)
所以,种子字符串是公开的,计算过程是确定性的,无需任何额外信息即可恢复私钥
  1. 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([index])).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[pos] != 0x30:         return None, None     pos += 1          length_bytes = sig_bytes[pos]     pos += 1          if sig_bytes[pos] != 0x02:         return None, None     pos += 1          r_length = sig_bytes[pos]     pos += 1     r_value = sig_bytes[pos:pos + r_length]     pos += r_length          if sig_bytes[pos] != 0x02:         return None, None     pos += 1          s_length = sig_bytes[pos]     pos += 1     s_value = sig_bytes[pos:pos + s_length]          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[1] 恢复私钥...")     sk = recover_private_key()     print(f"[✓] 私钥已恢复")     print(f"    私钥值: {sk.privkey.secret_multiplier}")     print(f"    私钥字节: {binascii.hexlify(sk.to_string()).decode()}")          # 2. 加载公钥     print("\n[2] 加载公钥...")     vk = load_public_key()     print("[✓] 公钥已加载")          # 3. 验证私钥正确性     print("\n[3] 验证私钥...")          # 使用一个已有的签名验证     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[4] 尝试生成签名...")          # 尝试使用不同的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[5] 输出私钥信息供手动使用...")     print(f"\n私钥值 (十进制):")     print(sk.privkey.secret_multiplier)     print(f"\n私钥值 (十六进制):")     print(binascii.hexlify(sk.to_string()).decode()) ​ if __name__ == "__main__":     main()
复制代码

2.2 Ezflag

先ida进行一个逆向找到main函数

只有当输入的密码完全等于 V3ryStr0ngp@ssw0rd 时,程序才会进入 else 分支生成 Flag
  1. 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[(val  str:         """计算特定时间戳下的认证指纹"""         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([b ^ 118 for b in key_block])         p2 = bytes([b ^ 60 for b in key_block]) ​         # 嵌套哈希架构 (注意:这是非标准的哈希顺序 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"
  2. [*] 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"
  3. [*] Processing... {progress:.1f}%", end='\r') ​ if __name__ == "__main__":     engine = CryptoEngine()     engine.run_audit()
复制代码

3.2 babygame

一道Godot逆向题,得有专门的工具
[img=720,376.07142857142856]https://www.yijinglab.com/guide-img/d9634e2f-3b66-42e7-8279-c0877cdd70e5/42440355-8f53-4c4a-b55f-f45beb2827f6.png[/img]

  1. 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了,可是一直不对
然后看了题目内容
  1. 题目内容: 请找出隐藏的Flag。请注意只有收集了所有的金币,才能验证flag。
复制代码
意思就是金币,也就是分数得达到一个设定好的数才能验证flag,回去逆向看看那里关于分数的函数
[img=720,323.35714285714283]https://www.yijinglab.com/guide-img/d9634e2f-3b66-42e7-8279-c0877cdd70e5/d1d30c7f-1bb4-4505-898e-7b3ab3b85f85.png[/img]

可以看到分数这里的代码是说当分数+1的时候,密钥中的A替换成B
所以正确的密钥应该是
  1. FanBglFanBglOoO!
复制代码
所以套上脚本就是
  1. 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)
复制代码

更多网安技能的在线实操练习,请点击这里>>
  

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

相关推荐

您需要登录后才可以回帖 登录 | 立即注册