easyRSA - Writeup by AI

easyRSA - Writeup by AI

题目信息

  • 来源: BugKu
  • 类别: Crypto (RSA)
  • 难度: 简单/中等

考点分析

  1. RSA 共模攻击(Common Modulus Attack)
  2. 扩展欧几里得算法
  3. Python 大整数运算与字节转换
  4. PKCS#1 v1.5 填充格式处理

考点权重表

考点 权重 说明
RSA 共模攻击识别 30% 发现同一明文用相同 N、不同 e 加密两次
扩展欧几里得算法 30% 计算 s1, s2 使得 e1s1 + e2s2 = 1
负指数处理 20% 当 s1 或 s2 为负数时需计算模逆元
数据提取 20% 从填充的随机数中提取原始 flag

解题思路

1. 代码分析

阅读 RSA.py 可知:

python 复制代码
N = 大整数 (4096 位)
e1 = 17
e2 = 65537

# 读取 flag 并添加随机填充
data = flag
while (len(data)<512-11):
    data = chr(random.randint(0,255)) + data

data_num = int(data.encode('hex'), 16)

# 使用相同的 N,不同的 e 加密两次
encrypt1 = pow(data_num, e1, N)  # c1
encrypt2 = pow(data_num, e2, N)  # c2

2. 攻击原理

这是典型的 RSA 共模攻击场景:

  • 同一明文 mmm 使用相同模数 NNN、不同公钥指数 e1,e2e_1, e_2e1,e2 加密
  • 密文:c1=me1(modN)c_1 = m^{e_1} \pmod{N}c1=me1(modN), c2=me2(modN)c_2 = m^{e_2} \pmod{N}c2=me2(modN)
  • 若 gcd⁡(e1,e2)=1\gcd(e_1, e_2) = 1gcd(e1,e2)=1,则存在整数 s1,s2s_1, s_2s1,s2 使得:e1⋅s1+e2⋅s2=1e_1 \cdot s_1 + e_2 \cdot s_2 = 1e1⋅s1+e2⋅s2=1
  • 根据扩展欧几里得算法可求得 s1,s2s_1, s_2s1,s2
  • 则明文可通过下式恢复:
    m=c1s1⋅c2s2(modN)m = c_1^{s_1} \cdot c_2^{s_2} \pmod{N}m=c1s1⋅c2s2(modN)

证明 :
c1s1⋅c2s2≡(me1)s1⋅(me2)s2(modN)≡me1⋅s1+e2⋅s2(modN)≡m1(modN)≡m(modN) \begin{aligned} c_1^{s_1} \cdot c_2^{s_2} &\equiv (m^{e_1})^{s_1} \cdot (m^{e_2})^{s_2} \pmod{N} \\ &\equiv m^{e_1 \cdot s_1 + e_2 \cdot s_2} \pmod{N} \\ &\equiv m^1 \pmod{N} \\ &\equiv m \pmod{N} \end{aligned} c1s1⋅c2s2≡(me1)s1⋅(me2)s2(modN)≡me1⋅s1+e2⋅s2(modN)≡m1(modN)≡m(modN)

3. 实现细节

  • 使用 gmpy2 库进行高精度整数运算
  • 当 s1s_1s1 或 s2s_2s2 为负数时,需要计算对应的模逆元:c−s≡(c−1)∣s∣(modN)c^{-s} \equiv (c^{-1})^{|s|} \pmod{N}c−s≡(c−1)∣s∣(modN)
  • 解密后需去除 PKCS#1 v1.5 格式的随机填充

详细步骤

步骤 1: 读取密文文件

python 复制代码
def read_hex_file(filename):
    with open(filename, 'rb') as f:
        hex_data = f.read().hex()
    return mpz(hex_data, 16)

c1 = read_hex_file('flag.enc1')
c2 = read_hex_file('flag.enc2')

步骤 2: 使用扩展欧几里得算法求 s1, s2

python 复制代码
import gmpy2

e1 = mpz(17)
e2 = mpz(65537)

# gcdext 返回 (g, s1, s2) 使得 e1*s1 + e2*s2 = g
s1, s2 = gmpy2.gcdext(e1, e2)[1:3]
# 结果:s1 = 30841, s2 = -8

步骤 3: 计算模逆元(如果需要)

python 复制代码
if s1 < 0:
    c1_inv = gmpy2.invert(c1, N)
    c1_pow = gmpy2.powmod(c1_inv, -s1, N)
else:
    c1_pow = gmpy2.powmod(c1, s1, N)

if s2 < 0:
    c2_inv = gmpy2.invert(c2, N)
    c2_pow = gmpy2.powmod(c2_inv, -s2, N)
else:
    c2_pow = gmpy2.powmod(c2, s2, N)

步骤 4: 恢复明文

python 复制代码
m = (c1_pow * c2_pow) % N

步骤 5: 转换为字节并提取 flag

python 复制代码
byte_length = (m.bit_length() + 7) // 8
plaintext_bytes = m.to_bytes(byte_length, 'big')

# 查找 flag 标记
for i in range(len(plaintext_bytes)):
    candidate = plaintext_bytes[i:]
    if b'flag{' in candidate:
        flag_index = candidate.find(b'flag{')
        print(candidate[flag_index:].decode('utf-8'))
        break

完整代码

python 复制代码
#!/usr/bin/env python
# -*- coding: utf-8 -*-
import gmpy2
from gmpy2 import mpz

# 模数 N
N = mpz(0x00b0bee5e3e9e5a7e8d00b493355c618fc8c7d7d03b82e409951c182f398dee3104580e7ba70d383ae5311475656e8a964d380cb157f48c951adfa65db0b122ca40e42fa709189b719a4f0d746e2f6069baf11cebd650f14b93c977352fd13b1eea6d6e1da775502abff89d3a8b3615fd0db49b88a976bc20568489284e181f6f11e270891c8ef80017bad238e363039a458470f1749101bc29949d3a4f4038d463938851579c7525a69984f15b5667f34209b70eb261136947fa123e549dfff00601883afd936fe411e006e4e93d1a00b0fea541bbfc8c5186cb6220503a94b2413110d640c77ea54ba3220fc8f4cc6ce77151e29b3e06578c478bd1bebe04589ef9a197f6f806db8b3ecd826cad24f5324ccdec6e8fead2c2150068602c8dcdc59402ccac9424b790048ccdd9327068095efa010b7f196c74ba8c37b128f9e1411751633f78b7b9e56f71f77a1b4daad3fc54b5e7ef935d9a72fb176759765522b4bbc02e314d5c06b64d5054b7b096c601236e6ccf45b5e611c805d335dbab0c35d226cc208d8ce4736ba39a0354426fae006c7fe52d5267dcfb9c3884f51fddfdf4a9794bcfe0e1557113749e6c8ef421dba263aff68739ce00ed80fd0022ef92d3488f76deb62bdef7bea6026f22a1d25aa2a92d124414a8021fe0c174b9803e6bb5fad75e186a946a17280770f1243f4387446ccceb2222a965cc30b3929)

# 公钥指数
e1 = mpz(17)
e2 = mpz(65537)

# 读取密文
def read_hex_file(filename):
    with open(filename, 'rb') as f:
        hex_data = f.read().hex()
    return mpz(hex_data, 16)

c1 = read_hex_file('flag.enc1')
c2 = read_hex_file('flag.enc2')

# 扩展欧几里得算法
s1, s2 = gmpy2.gcdext(e1, e2)[1:3]

# 处理负指数
if s1 < 0:
    c1_inv = gmpy2.invert(c1, N)
    c1_pow = gmpy2.powmod(c1_inv, -s1, N)
else:
    c1_pow = gmpy2.powmod(c1, s1, N)

if s2 < 0:
    c2_inv = gmpy2.invert(c2, N)
    c2_pow = gmpy2.powmod(c2_inv, -s2, N)
else:
    c2_pow = gmpy2.powmod(c2, s2, N)

# 恢复明文
m = (c1_pow * c2_pow) % N

# 转换为字节
byte_length = (m.bit_length() + 7) // 8
plaintext_bytes = m.to_bytes(byte_length, 'big')

# 提取 flag
for i in range(len(plaintext_bytes)):
    candidate = plaintext_bytes[i:]
    if b'flag{' in candidate:
        flag_index = candidate.find(b'flag{')
        print(f"[+] Flag: {candidate[flag_index:].decode('utf-8')}")
        break

运行结果

bash 复制代码
[*] N 的位数:4096
[*] c1 的位数:4095
[*] c2 的位数:4096
[*] e1: 17, e2: 65537
[*] gcd(e1, e2) = 1
[*] s1 = 30841, s2 = -8
[*] 验证:e1*s1 + e2*s2 = 1
[+] Flag: flag{cry_is_so_hard_but_this_is_so_easy}
相关推荐
Ruiery13 小时前
Linux 6.6内核内存管理深度解析(十八):KASLR 内核地址空间随机化
linux·运维·服务器·安全
mennekes15 小时前
临时用电线路标识:插头颜色、钟面标识配套规范
运维·网络·科技·安全·制造
是真的小外套19 小时前
网络安全实战第7期:Hydra爆破SSH实战
安全·web安全·ssh
jimmyleeee20 小时前
Agent安全之七:当智能体之间开始“互相信任”------深入理解 OWASP ASI07 不安全的智能体间通信
人工智能·安全
卓豪终端管理20 小时前
终端零信任落地实践:应用控制与权限管理的五层管控体系
安全·web安全
深蓝AI21 小时前
一个版本号失守:Claude Code 2.1.313 扩展后门事件,开发者机器如何变成别人的 SSH 入口
安全·程序员
金海星科技21 小时前
从“装摄像头“到“用摄像头管安全“:船舶智能监控的范式变化
安全
jimmyleeee21 小时前
Agent安全之六:(ASI06)记忆与上下文投毒——当 AI 把“昨天的错误”当成“今天的真理”
网络·人工智能·安全
QYR-分析21 小时前
7.0% 年增!新能源汽车 EPB 电机赛道升温,安全执行件的博弈正在底盘上演
安全·汽车
福建佰胜张工1 天前
深度解析 Allen-Bradley 150-B135NBDA 智能电机控制器:选型、参数与工业应用
运维·数据库·单片机·嵌入式硬件·安全·自动化