E_S_P - Writeup by AI

一、题目信息

  • 题目来源: BugKu CTF
  • 题目名称: E_S_P (Extra Sensory Perception)
  • 题目类别: Crypto / RSA
  • 考点: RSA 小指数攻击、Coppersmith 方法、SageMath 在线应用

二、题目描述

2.1 加密代码 (esp.py)

from Crypto.Util.number import *
from secret import flag, yukko
import re

assert re.match(r"^KosenCTF{.+}$", flag)

Nbits = 1024
p = getPrime(Nbits)
q = getPrime(Nbits)
n = p * q
e = 5
c = pow(bytes_to_long((yukko + flag).encode()), e, n)

print("N = {}".format(n))
print("e = {}".format(e))

print("Wow Yukko the ESPer helps you!")
print(yukko + "the length of the flag = {}".format(len(flag)))
print("c = {}".format(c))

2.2 输出数据 (out.txt)

N = 11854673881335985163635072085250462726008700043680492953159905880499045049107244300920837378010293967634187346804588819510452454716310449345364124188546434429828696164683059829613371961906369413632824692460386596396440796094037982036847106649198539914928384344336740248673132551761630930934635177708846275801812766262866211038764067901005598991645254669383536667044207899696798812651232711727007656913524974796752223388636251060509176811628992340395409667867485276506854748446486284884567941298744325375140225629065871881284670017042580911891049944582878712176067643299536863795670582466013430445062571854275812914317
e = 5
Wow Yukko the ESPer helps you!
Yukko the ESPer: My amazing ESP can help you to get the flag! -----> the length of the flag = 39
c = 4463634440284027456262787412050107955746015405738173339169842084094411947848024686618605435207920428398544523395749856128886621999609050969517923590260498735658605434612437570340238503179473934990935761387562516430309061482070214173153260521746487974982738771243619694317033056927553253615957773428298050465636465111581387005937843088303377810901324355859871291148445415087062981636966504953157489531400811741347386262410364012023870718810153108997879632008454853198551879739602978644245278315624539189505388294856981934616914835545783613517326663771942178964492093094767168721842335827464550361019195804098479315147

三、考点分析

3.1 知识点权重

知识点 重要性 难度 说明
RSA 基础原理 ⭐⭐⭐ 简单 理解加密过程 c = m^e mod n
小指数攻击 ⭐⭐⭐⭐⭐ 中等 e=5 很小,但需要满足条件才能直接开方
Coppersmith 方法 ⭐⭐⭐⭐⭐ 困难 核心攻击手段,需要 SageMath
已知明文攻击 ⭐⭐⭐⭐ 中等 利用已知的 yukko 和 flag 前缀
多项式环理论 ⭐⭐⭐⭐ 困难 Z/NZ 上的多项式构造

四、解题思路

4.1 初步分析

步骤 1: 解析 yukko 字符串

从代码中可以看到:

print(yukko + "the length of the flag = {}".format(len(flag)))

对应输出:

Yukko the ESPer: My amazing ESP can help you to get the flag! -----> the length of the flag = 39

因此:

  • yukko = "Yukko the ESPer: My amazing ESP can help you to get the flag! -----> "
  • yukko 长度 = 69 字节
  • flag 长度 = 39 字节
  • 明文总长度 = 69 + 39 = 108 字节
步骤 2: 分析加密方式
c = pow(bytes_to_long((yukko + flag).encode()), e, n)
  • 明文 P = yukko || flag(字符串拼接后转整数)
  • 加密指数 e = 5(很小)
  • 模数 N 为 1536 位(两个 1024 位素数的乘积)
步骤 3: 检查直接开方的可能性

如果 P^e < N,则可以直接对 c 开 5 次方根。

计算验证:

  • 明文长度 ≈ 108 字节 = 864 位
  • P^5 ≈ 4320 位 >> N 的 1536 位

结论: 无法直接开方,需要更高级的攻击方法。

4.2 Coppersmith 攻击可行性分析

核心思想

将明文分为两部分:

  • 已知部分 (m_known): yukko + flag 前缀 “KosenCTF{” + 第一个字符
  • 未知部分 (x): flag 剩余的 29 个字符

构造多项式:

f(x) = (m_known + x)^e - c ≡ 0 (mod N)
Coppersmith 定理

对于上述多项式,如果 |x| < N^(1/e),则可以在多项式时间内求出所有满足条件的小根。

数值验证
  • N^(1/5)2^(1536/5)2^30710^92
  • 未知部分 x < 256^29 = 2^23210^70
  • 232 位 < 307 位

结论: 完全满足 Coppersmith 攻击条件!

4.3 为什么需要枚举第一个字符?

由于 flag 格式为 KosenCTF{...},我们知道:

  • 前缀 yukko + "KosenCTF{" 是已知的
  • 但剩余 29 个字符中,第一个字符不确定
  • 通过枚举第一个可打印字符,可以将问题转化为:已知 30 字节中的高 1 字节,求解低 29 字节

这样构造的多项式为:

P = (known_prefix || c1 || 0x00*29 || '}') + x * 256

其中 x 就是我们要解的 29 字节未知部分。

五、详细解题步骤

5.1 准备工具

需要安装 SageMath(或使用在线环境):

  • 本地安装:https://www.sagemath.org/
  • 在线环境:https://sagecell.sagemath.org/ 或 https://cocalc.com/

5.2 编写攻击脚本

import string

# ---------- 替代 Crypto 的函数 ----------
def bytes_to_long(b: bytes) -> int:
    return int.from_bytes(b, 'big')

def long_to_bytes(n: int, length: int = None) -> bytes:
    if length is None:
        length = (n.bit_length() + 7) // 8
    return n.to_bytes(length, 'big')
# --------------------------------------

# 硬编码参数(从 out.txt 提取)
N = 11854673881335985163635072085250462726008700043680492953159905880499045049107244300920837378010293967634187346804588819510452454716310449345364124188546434429828696164683059829613371961906369413632824692460386596396440796094037982036847106649198539914928384344336740248673132551761630930934635177708846275801812766262866211038764067901005598991645254669383536667044207899696798812651232711727007656913524974796752223388636251060509176811628992340395409667867485276506854748446486284884567941298744325375140225629065871881284670017042580911891049944582878712176067643299536863795670582466013430445062571854275812914317
e = 5
c = 4463634440284027456262787412050107955746015405738173339169842084094411947848024686618605435207920428398544523395749856128886621999609050969517923590260498735658605434612437570340238503179473934990935761387562516430309061482070214173153260521746487974982738771243619694317033056927553253615957773428298050465636465111581387005937843088303377810901324355859871291148445415087062981636966504953157489531400811741347386262410364012023870718810153108997879632008454853198551879739602978644245278315624539189505388294856981934616914835545783613517326663771942178964492093094767168721842335827464550361019195804098479315147
yukko = "Yukko the ESPer: My amazing ESP can help you to get the flag! -----> "
flag_total_len = 39
flag_len = flag_total_len - len("KosenCTF{}") - 1   # 29

# 枚举第一个字符
for c1 in string.printable:
    # 构造已知部分的高位填充
    high_pad = bytes_to_long(
        yukko.encode() + b"KosenCTF{" + c1.encode() + b"\x00" * flag_len + b"}"
    )
    low_pad = 256  # x 的系数,表示未知部分左移 8 位

    # 在 Z/NZ 上构造多项式环
    PR.<x> = PolynomialRing(Zmod(N))
    
    # 构造多项式 f(x) = (high_pad + x * 256)^e - c
    f = (high_pad + x * low_pad)^e - c
    f = f.monic()
    
    # Coppersmith 求小根
    xs = f.small_roots(X=2^(flag_len * 8), beta=1)
    
    for x in xs:
        # 将 Sage 整数转换为 Python int
        x_int = int(x)
        flag = b"KosenCTF{" + c1.encode() + long_to_bytes(x_int) + b"}"
        print(flag)

5.3 执行攻击

运行上述脚本,SageMath 会自动:

  1. 构造格基(Lattice)
  2. 使用 LLL 算法进行规约
  3. 从规约后的基向量中提取小根
  4. 还原未知的明文字节

5.4 验证结果

将得到的 flag 代回原加密公式验证:

full_plain = yukko + full_flag
m = bytes_to_long(full_plain.encode())
assert pow(m, e, N) == c  # 应该通过

六、完整代码

6.1 优化版攻击脚本 (solve.py)

本脚本特点:

  • 无需外部依赖:自实现 bytes_to_longlong_to_bytes
  • 硬编码参数:直接从 out.txt 提取所有参数
  • 在线兼容:可在 SageMath Cell 等在线环境运行
  • 类型转换安全:正确处理 Sage 整数到 Python int 的转换
import string

# ---------- 替代 Crypto 的函数 ----------
def bytes_to_long(b: bytes) -> int:
    return int.from_bytes(b, 'big')

def long_to_bytes(n: int, length: int = None) -> bytes:
    if length is None:
        length = (n.bit_length() + 7) // 8
    return n.to_bytes(length, 'big')
# --------------------------------------

N = 11854673881335985163635072085250462726008700043680492953159905880499045049107244300920837378010293967634187346804588819510452454716310449345364124188546434429828696164683059829613371961906369413632824692460386596396440796094037982036847106649198539914928384344336740248673132551761630930934635177708846275801812766262866211038764067901005598991645254669383536667044207899696798812651232711727007656913524974796752223388636251060509176811628992340395409667867485276506854748446486284884567941298744325375140225629065871881284670017042580911891049944582878712176067643299536863795670582466013430445062571854275812914317
e = 5
c = 4463634440284027456262787412050107955746015405738173339169842084094411947848024686618605435207920428398544523395749856128886621999609050969517923590260498735658605434612437570340238503179473934990935761387562516430309061482070214173153260521746487974982738771243619694317033056927553253615957773428298050465636465111581387005937843088303377810901324355859871291148445415087062981636966504953157489531400811741347386262410364012023870718810153108997879632008454853198551879739602978644245278315624539189505388294856981934616914835545783613517326663771942178964492093094767168721842335827464550361019195804098479315147
yukko = "Yukko the ESPer: My amazing ESP can help you to get the flag! -----> "
flag_total_len = 39
flag_len = flag_total_len - len("KosenCTF{}") - 1   # 29

for c1 in string.printable:
    high_pad = bytes_to_long(
        yukko.encode() + b"KosenCTF{" + c1.encode() + b"\x00" * flag_len + b"}"
    )
    low_pad = pow(256, 1)   # 即 256

    PR.<x> = PolynomialRing(Zmod(N))
    f = (high_pad + x * low_pad)^e - c
    f = f.monic()
    xs = f.small_roots(X=2^(flag_len * 8), beta=1)
    for x in xs:
        # 关键:将 Sage 整数转换为 Python int
        x_int = int(x)
        flag = b"KosenCTF{" + c1.encode() + long_to_bytes(x_int) + b"}"
        print(flag)
Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐