[DiceCTF 2023] rSabin

news2025/6/8 15:48:10

一点点学习别人的WP,这回看到一个大姥(r3kapig)的帖子,DiceCTF第二名,不过有好多东西一时还理解不了,得慢慢来。

题目

这个题有3个功能:

  1. rsa加密功能,p,q,N未知,e=17低加密指数

  1. 解密,不过解密方法比较特别,分别对p,q求nth_root不过未给出nth_root函数,所以不能直接使用。

  1. 对flag加密,用PKCS1_OAEP填充。多数情况下低加密指数如果明文比较小会导致加密后比N小或者仅比N大一点,可以通过开根号爆破。但填充后长度基本与N长度一致,爆破无效。

import asyncio
import traceback
from Crypto.Util.number import getPrime, bytes_to_long
from Crypto.Cipher import PKCS1_OAEP
from Crypto.PublicKey import RSA

from nth_root import nth_root, chinese_remainder # not provided

class Server:
    def __init__(self):
        e = 17
        nbits = 512

        p = getPrime(nbits)
        q = getPrime(nbits)
        N = p * q

        self.p = p
        self.q = q
        self.N = N
        self.e = e

    def encrypt(self, m):
        assert 0 <= m < self.N
        c = pow(m, self.e, self.N)
        return int(c)

    def decrypt(self, c):
        assert 0 <= c < self.N
        mp = int(nth_root(c, self.p, self.e))
        mq = int(nth_root(c, self.q, self.e))
        m = chinese_remainder([mp, mq], [self.p, self.q])
        return int(m)

    def encrypt_flag(self):
        with open("flag.txt", "rb") as f:
            flag = f.read()

        key = RSA.construct((self.N, self.e))
        cipher = PKCS1_OAEP.new(key)
        c = cipher.encrypt(flag)
        c = bytes_to_long(c)
        return c


async def handle(a):
    S = Server()
    while True:
        cmd = (await a.input("Enter your option (EDF) > ")).strip()
        if cmd == "E":
            m = int(await a.input("Enter your integer to encrypt > "))
            c = S.encrypt(m)
            await a.print(str(c) + '\n')
        elif cmd == "D":
            c = int(await a.input("Enter your integer to decrypt > "))
            m = S.decrypt(c)
            await a.print(str(m) + '\n')
        elif cmd == "F":
            c = S.encrypt_flag()
            await a.print(str(c) + '\n')
            return

class Handler:
    def __init__(self, reader, writer):
        self.reader = reader
        self.writer = writer
    async def print(self, data):
        self.writer.write(str(data).encode())
        await self.writer.drain()
    async def input(self, prompt):
        await self.print(prompt)
        return (await self.reader.readline()).decode()
    async def __aenter__(self):
        return self
    async def __aexit__(self, exc_t, exc_v, exc_tb):
        self.writer.close()
        await self.writer.wait_closed()
        if exc_v is not None and not isinstance(exc_v, asyncio.TimeoutError):
            traceback.print_exception(exc_v)
        return True


async def main():
    async def callback(*args):
        async with Handler(*args) as a:
            await asyncio.wait_for(handle(a), 20)
    server = await asyncio.start_server(callback, '0.0.0.0', 5000)
    print('listening')
    async with server:
        await server.serve_forever()


if __name__ == "__main__":
    asyncio.run(main())

思路:

求N

首先要求N,我本来是想弄几个17次幂后比N略大的值求gcd,看到大姥的解法眼前一亮。

先随机取m,然后求enc(m),enc(m^2),enc(m^4)然后分别用没有模过N的原值求差m^e,(m^2)^e,(m^4)^e减,再求gcd这个更方便。

函数头部

from pwn import *
import random 
from Crypto.Util.number import GCD,long_to_bytes,bytes_to_long 
from gmpy2 import iroot 

context.log_level = 'debug'

def enc(m):
    io.sendlineafter(b"Enter your option (EDF) > ", b'E')
    io.sendlineafter(b"Enter your integer to encrypt > ", str(m).encode())
    return int(io.recvline())

def dec(c):
    io.sendlineafter(b"Enter your option (EDF) > ", b'D')
    io.sendlineafter(b"Enter your integer to encrypt > ", str(c).encode())
    return int(io.recvline())

def get_flag():
    io.sendlineafter(b"Enter your option (EDF) > ", b'F')
    return int(io.recvline())

def decrypt(c, N, p, q):
    assert 0 <= c < N
    mp = int(c.nth_root(e))
    mq = int(c.nth_root(e))
    m = chinese_remainder([mp, mq], [p, q])
    return int(m)

求N

m = random.randrange(0,2**155)
m2 = m**2
m4 = m**4
c1 = enc(m)
c2 = enc(m2)
c4 = enc(m4)
N = GCD(GCD(c1**2 - c2, c2**2 - c4), c1**4 - c4)

分解N

这个方法头一回见。

先取一个略小于的值,使p<m<q(大概率),求c = m^e %N

由于e=17所以gcd(e,(p-1)*(q-1))有1/17的概率不为1,p,q两个出现1个的概率略大于1/9,对于爆破来说这个概率并不小。

当不互素时 decrypt(c)-m = kp 与N求gcd就能得到p

这时候获取enc(flag),(远端会在获取后结束,对flag无法交互)

    tmpn = iroot(N,2)[0] - 1000
    c = enc(tmpn)
    ret = dec(c)
    
    if ret == tmpn:
        io.close()
        continue 
    else:
        iflag = get_flag()
        print('N = ',N)
        print('tmpn = ', tmpn)
        print('c = ',c)
        print('ret = ', ret)
        print('iflag = ',iflag)

        #e=17 gcd(e,p-1) != 1 的概率是1/17 
        io.interactive()

经过x 次交互得到如下数据

e = 17
N = 145929886027830605678430202427323053628064442310464018856395565973995064472578943595719088909803787366850912624656960966772751178490892976055180188367608145038609558294202567019869852120311834412433602187079592510589435977725095316257649141862850904221294264419961365596274045500230679371213475300930406042261
tmpn = 12080144288369680134663865822252253203358727058793479854567933546272937742973360100460050936204099841676294371963062308235668122560773478644865802421986920
ret = 38626509565846846198929657581252980560445889902524802003755764516997686363556486348466834915881637092111849253058180514729213545303952798006800009337375370781676698957130798722071959097949405886433880476180556708960839753606831748033044468904639617141322699562124769255797179947555095545345666523628726328021
iflag = 94785540286244324280900673502395494485593520218609389745579915172323211491609524359277466592150462516952301308455222973538441633205212054875400879171885042191555256518152907528122607881031719899722188867464126986611318409258138548887258038636675128271840693263847776054879375943419688129202875859618405032469

这时候就能得到p,q

p = GCD(ret-tmpn, N)
q = N//p 
'''
p = 12489852031586615822311701100326231241806260275896449364532516898411555577529972957144893166576911381503372838312839610202975336506868820457393001178785531
q = 11683876290830066998757443847623160481197019426815171259465107520260429703525441378146027740470276103931704547758704704292062887913193269825188377531686831
'''

修改PKCS1_OAEP.py增加unpad函数

pycryptodome库在PKCS1_OAEP.py提供了OAEP的解密功能,在RSA解密后进行了unpad但是没有独立的unpad函数。而由于gcd(e,phi)!=1所以也就不能直接用decrypt函数。

修改的方法是将decrypt函数复制一下,改为unpad然后将第2a,2b步的解密删掉改为从参数直接获取明文

unpad后

    def unpad(self, ct_int):
        """Decrypt a message with PKCS#1 OAEP.

        :param ciphertext: The encrypted message.
        :type ciphertext: bytes/bytearray/memoryview

        :returns: The original message (plaintext).
        :rtype: bytes

        :raises ValueError:
            if the ciphertext has the wrong length, or if decryption
            fails the integrity check (in which case, the decryption
            key is probably wrong).
        :raises TypeError:
            if the RSA key has no private half (i.e. you are trying
            to decrypt using a public key).
        """

        # See 7.1.2 in RFC3447
        modBits = Crypto.Util.number.size(self._key.n)
        k = ceil_div(modBits,8) # Convert from bits to bytes
        hLen = self._hashObj.digest_size

        #patch--------------------------------------------
        # Step 1b and 1c
        #if len(ciphertext) != k or k<hLen+2:
        #    raise ValueError("Ciphertext with incorrect length.")
        # Step 2a (O2SIP)
        #ct_int = bytes_to_long(ciphertext)
        # Step 2b (RSADP)
        #m_int = self._key._decrypt(ct_int)
        
        m_int = ct_int    #与decrypt基本相同,只是用ct_int跳过解密
        #------------------------------------------------------

        # Complete step 2c (I2OSP)
        em = long_to_bytes(m_int, k)
        # Step 3a
        lHash = self._hashObj.new(self._label).digest()
        # Step 3b
        y = em[0]
        # y must be 0, but we MUST NOT check it here in order not to
        # allow attacks like Manger's (http://dl.acm.org/citation.cfm?id=704143)
        maskedSeed = em[1:hLen+1]
        maskedDB = em[hLen+1:]
        # Step 3c
        seedMask = self._mgf(maskedDB, hLen)
        # Step 3d
        seed = strxor(maskedSeed, seedMask)
        # Step 3e
        dbMask = self._mgf(seed, k-hLen-1)
        # Step 3f
        db = strxor(maskedDB, dbMask)
        # Step 3g
        one_pos = hLen + db[hLen:].find(b'\x01')
        lHash1 = db[:hLen]
        invalid = bord(y) | int(one_pos < hLen)
        hash_compare = strxor(lHash1, lHash)
        for x in hash_compare:
            invalid |= bord(x)
        for x in db[hLen:one_pos]:
            invalid |= bord(x)
        if invalid != 0:
            raise ValueError("Incorrect decryption.")
        # Step 4
        return db[one_pos + 1:]

文件位置一般在这

"C:\Users\AAAA\AppData\Local\Programs\Python\Python310\Lib\site-packages\Crypto\Cipher\PKCS1_OAEP.py"

求明文

由于e与phi不互素,所以这里要对p,q分别求根

from Crypto.Util.number import isPrime,long_to_bytes,bytes_to_long 
from Crypto.Cipher import PKCS1_OAEP
from Crypto.PublicKey import RSA
import time 

def rthroot(c, r, q):
    c %= q
    assert(isPrime(r) and (q - 1) % r == 0 and (q - 1) % (r**2) != 0)
    l = ((q - 1) % (r**2)) // r
    alpha = (-inverse(l, r)) % r
    root = pow(c, ((1 + alpha * (q - 1) // r) // r), q)
    return root

def allroot(r, q, root):
    all_root = set()
    all_root.add(root)
    while len(all_root) < r:
        new_root = root
        unity = pow(getRandomRange(2, q), (q - 1) // r, q)
        for i in range(r - 1):
            new_root = (new_root * unity) % q
            all_root.add(new_root)
    return all_root

def decrypt(proot, qroot, p, q):
    count = 0
    total = len(proot) * len(qroot)
    t1 = inverse(q, p)
    t2 = inverse(p, q)
    for i in proot:
        for j in qroot:
            count += 1
            m = (i * t1 * q + j * t2 * p) % (p * q)
            
            assert (pow(m,e,N) == c)
            try:
                print( cipher.unpad((m)))
                print(m)
            except:
                continue

key = RSA.construct((N, e))
cipher = PKCS1_OAEP.new(key)  

#rthroot要求 (q-1)%e == 0 所以必要时是p,q交换,使(q-1)%e == 0
p,q = q,p 

proot = rthroot(c, e, p)
qroot = pow(c,inverse(e,q-1),q)
print('[+] Calculating all e-th roots...')

all_proot = allroot(e, p, proot)
all_qroot = [qroot]# 3 allroot(e, q, qroot)
print('[+] CRT cracking...')

decrypt(all_proot, all_qroot, p, q)

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/334462.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

如何通过极狐GitLab 平滑落地 Java 增量代码规范?

本文来自&#xff1a; 杨周 极狐GitLab 高级解决方案架构师 代码越写越规范是优秀开发者的成长之路&#xff0c;但很多人对老项目感到有心无力&#xff0c;因为太不规范了&#xff0c;所有人停下来一起修复也要花费很长时间&#xff0c;而且一次改动太多难以确保可靠性&#xf…

达梦8的dblink

简介&#xff1a;外部链接对象&#xff08;LINK&#xff09;是 DM 中的一种特殊的数据库实体对象&#xff0c;它记录了远程数据库的连接和路径信息&#xff0c;用于建立与远程数据的联系。通过多台数据库主库间的相互通讯&#xff0c;用户可以透明地操作远程数据库的数据&#…

我的网站上线了!

最近有段时间没有写原创文章了&#xff0c;恰好这两天正在翻阅历史文章的时候&#xff0c;发现文章中的图片竟然裂了&#xff1f;顿时冒了一身冷汗&#xff0c;因为每逢遇到这种情况&#xff0c;动辄需要花费一周的时间迁移图片。。。。。。 当我直接访问图片 url 的时候&#…

直播预告 | 数据库自治平台 KAP 监控告警架构及实例演示

线上沙龙-技术流第 25 期营业啦02月15日&#xff08;周三&#xff09;19:30KaiwuDB - B站直播间企业级数据集群往往有成百上千的各类型运算或应用同时运行&#xff0c;为保障系统的稳定可靠性&#xff0c;势必需要克服庞大数据量、复杂运算逻辑、相互关联大数据组件等重难点&am…

4年测试经验去面试10分钟就被pass,测试现在要求这么高了?

年过完了&#xff0c;大家都开始上班了&#xff0c;各位小伙伴多多注意身体&#xff0c;但是学习也别落下等 做为一名优秀的程序员&#xff0c;技术面试都是不可避免的一个环节&#xff0c;通常技术面试官都会经过本身的方式去考察程序员的技术功底与基础理论知识。 若是你参…

python基础之PyCharm介绍

课程&#xff1a;PyCharm 课程目标 PyCharm的作用下载安装PyCharmPyCharm的基本使用PyCharm的基本设置 一. PyCharm的作用 PyCharm是一种Python IDE&#xff08;集成开发环境&#xff09;&#xff0c;带有一整套可以帮助用户在使用Python语言开发时提高其效率的工具&#x…

《Spring揭秘》记录

IOC部分 IOC不等于IOC容器&#xff0c;即使不使用spring&#xff0c;我们也可以使用IOC&#xff0c;只不过spring提供了IOC容器实现。Spring的IoC容器的功能就包含一个提供依赖注入服务的IoC Service Provider。它提供两方面的支持&#xff0c;业务对象的构建管理和业务对象间的…

GAMES202 PCSS软阴影算法细节解析

在LearnOpenGL框架的基础上实现了一遍GAMES202的PCFPCSS软阴影&#xff0c;之前学习GAMES202时一些没弄清楚的问题顺便搞清楚了。 注&#xff1a;本文中代码和shader均在笔者自学LearnOpenGL的框架中实现&#xff0c;因此有一些细节可能和GAMES202作业框架不一致&#xff0c;且…

【前端CSS面试题】2023前端最新版css模块,高频15问

&#x1f973;博 主&#xff1a;初映CY的前说(前端领域) &#x1f31e;个人信条&#xff1a;想要变成得到&#xff0c;中间还有做到&#xff01; &#x1f918;本文核心&#xff1a;博主收集的CSS面试题 目录 一、CSS必备面试题 1.CSS3新特性 2.CSS实现元素两个盒子垂…

开发技术-Java switch case 的简单用法

文章目录1. integral-selector2. case3. break4. default5. 总结最近开发写 switch 发现有的技术点还没有掌握&#xff0c;在此做个记录。ON JAVA 中文版中&#xff0c;关于 switch 的描述为&#xff1a; switch 有时也被划归为一种选择语句。根据整数表达式的值&#xff0c;s…

Vue路由 —— vue-router

在上一篇内容讲到关于单页面组件的内容&#xff0c;同时也附上补充讲了关于单页面&#xff08;SPA&#xff09;和多页面&#xff08;MPA&#xff09;之间的优缺点&#xff0c;在本篇目当中就要来讲这个路由&#xff08;vue-router&#xff09;&#xff0c;通过路由来实现页面的…

LCR测试仪测量电子元件的4种方法

当今电子元件的设计追求高性能&#xff0c; 而同时又致力于减少尺寸、 功耗和成本。 有效而准确的元件性能描述、设计、评估和制造过程中的测试&#xff0c;对于元件用户和生产厂家是至关重要的。电感、电容、电阻是电子线路中使用广泛的电子器件&#xff0c;在进行电子设计的基…

【图像处理OpenCV(C++版)】——4.5 全局直方图均衡化

前言&#xff1a; &#x1f60a;&#x1f60a;&#x1f60a;欢迎来到本博客&#x1f60a;&#x1f60a;&#x1f60a; &#x1f31f;&#x1f31f;&#x1f31f; 本专栏主要结合OpenCV和C来实现一些基本的图像处理算法并详细解释各参数含义&#xff0c;适用于平时学习、工作快…

Vue中路由缓存及activated与deactivated的详解

目录前言一&#xff0c;路由缓存1.1 引子1.2 路由缓存的方法1.2.1 keep-alive1.2.2 keep-alive标签中的include属性1.2.3 include中多组件的配置二&#xff0c;activated与deactivated2.1 引子2.2 介绍activated与deactivated2.3 解决需求三&#xff0c;整体代码总结前言 在Vu…

【C++】C++11语法 ~ lambda 表达式

&#x1f308;欢迎来到C专栏~~ lambda 表达式 (꒪ꇴ꒪(꒪ꇴ꒪ )&#x1f423;,我是Scort目前状态&#xff1a;大三非科班啃C中&#x1f30d;博客主页&#xff1a;张小姐的猫~江湖背景快上车&#x1f698;&#xff0c;握好方向盘跟我有一起打天下嘞&#xff01;送给自己的一句鸡…

WPF常用UI库和图标库(MahApps、HandyControl、LiveCharts)

WPF常用UI库和图表库&#xff08;MahApps、HandyControl、LiveCharts&#xff09; WPF有很多开源免费的UI库&#xff0c;本文主要介绍常见的MahApps、HandyControl两个UI库&#xff1b;在开发过程中经常会涉及到图表的开发&#xff0c;本文主要介绍LiveCharts开源图表库。 UI…

Dell Precision T7910 工作站做RAID

1&#xff1a;开机根据提示按Ctrl-C 2&#xff1a;进入下面界面直接按回车。Adapter是LSISAS3008IR的卡。 3&#xff1a;回车来到下面的界面&#xff0c;我们选择RAID Propertie回车。 4&#xff1a;回车来到选择RAID级别的界面。根据自己的硬盘数量和需求进行选择。 5&#xf…

云原生丨Prometheus+Grafana监控 OpenGauss 数据库

文章目录前言一、Prometheus的介绍及安装1、Prometheus 介绍2、Prometheus 安装二、Grafana的介绍及安装1.Grafana 介绍2、Grafana 安装三、安装探针1、安装Node Exporter探针2.安装opengauss_exporter探针四、 访问Prometheus与Grafana1、 访问Prometheus2、 访问 Grafana五、…

React 组件性能优化

React 组件性能优化1. 组件卸载前进行清理操作2. PureComponent3. shouldComponentUpdate4. React.memo5. 使用组件懒加载6. 使用 Fragment 避免额外标记7. 不要使用内联函数定义8. 在构造函数中进行函数this绑定9. 类组件中的箭头函数10. 避免使用内联样式属性11. 优化条件渲染…

记录复现一下第一次awd

前言 之前没打过awd&#xff0c;这次学长组织了一场awd娱乐赛&#xff0c;两个web一个pwn&#xff0c;还有一个黑盒&#xff0c;只会web&#xff0c;第一次啥也不会瞎打&#xff0c;被打烂了&#xff0c;不会写脚本&#xff0c;手交flag的感觉真“不错”&#xff0c;感觉awd还…