vsCTF crypto 部分wp

目录

Recovery

Baby RSA

Strongest RSA


最近比赛太多了,要不是有人提醒我我已经忘记这个比赛了,但我知道的时候已经是晚上了QAQ,匆匆忙忙就做了两道题

Recovery

# I coded this so that I wouldn't have to use a database!
from random import randint
from base64 import b64encode


def validate(password: str) -> bool:
    if len(password) != 49:
        return False

    key = ['vs'.join(str(randint(7, 9)) for _ in range(ord(i))) + 'vs' for i in password[::-2]]
    gate = [118, 140, 231, 176, 205, 480, 308, 872, 702, 820, 1034, 1176, 1339, 1232, 1605, 1792, 782, 810, 1197, 880,
            924, 1694, 2185, 2208, 2775]
    if [randint(a, b[0]) for a, b in enumerate(zip(gate, key), 1) if len(b[1]) != 3 * (b[0] + 7 * a) // a]:
        return False

    hammer = {str(a): password[a] + password[a + len(password) // 2] for a in range(1, len(password) // 2, 2)}
    block = b'c3MxLnRkMy57XzUuaE83LjVfOS5faDExLkxfMTMuR0gxNS5fTDE3LjNfMTkuMzEyMS5pMzIz'
    if b64encode(b'.'.join([((b + a).encode()) for a, b in hammer.items()])) != block:
        return False

    return True


if __name__ == "__main__":
    passwd = input('Please validate your ID using your password\n> ')
    if validate(passwd):
        print('Access Granted: You now have gained access to the View Source Flag Vault!')
    else:
        print('Access Denied :(')

加密代码主要又两段:

第一段

key = ['vs'.join(str(randint(7, 9)) for _ in range(ord(i))) + 'vs' for i in password[::-2]]
    gate = [118, 140, 231, 176, 205, 480, 308, 872, 702, 820, 1034, 1176, 1339, 1232, 1605, 1792, 782, 810, 1197, 880,
            924, 1694, 2185, 2208, 2775]
    if [randint(a, b[0]) for a, b in enumerate(zip(gate, key), 1) if len(b[1]) != 3 * (b[0] + 7 * a) // a]:
        return False

在这一段中看到

len(b[1]) != 3 * (b[0] + 7 * a) // a

时返回false

b[1]就是key,key的长度=3*(b[0]+7*a)//a

而key的长度又和password的特定位置字符的ascii值,那么找到长度就能恢复一部分的password

根据此段可以恢复password的奇数位

第二段

hammer = {str(a): password[a] + password[a + len(password) // 2] for a in range(1, len(password) // 2, 2)}
    block = b'c3MxLnRkMy57XzUuaE83LjVfOS5faDExLkxfMTMuR0gxNS5fTDE3LjNfMTkuMzEyMS5pMzIz'
    if b64encode(b'.'.join([((b + a).encode()) for a, b in hammer.items()])) != block:
        return False

在这一段中发现:

b64encode(b'.'.join([((b + a).encode()) for a, b in hammer.items()])) != block

时会返回false,那么就让它相等好了,把hammer打印出来再把block base64解码一下就能看懂了

根据此段恢复password的偶数位

exp:

from random import *
from string import *
from base64 import *
from tqdm import tqdm

block = b'c3MxLnRkMy57XzUuaE83LjVfOS5faDExLkxfMTMuR0gxNS5fTDE3LjNfMTkuMzEyMS5pMzIz'
#b'sstd{_hO5__hL_GH_L3_31i3'
b64=b64decode(block).split(b".")
base=b''
for i in b64:
    base+=i[:2]
#print(base)
flag=[0]*49
for a in range(1,24,2):
    flag[a]=base[a-1]
    flag[a+24]=base[a]
print(flag)

gate = [118, 140, 231, 176, 205, 480, 308, 872, 702, 820, 1034, 1176, 1339, 1232, 1605, 1792, 782, 810, 1197, 880,
            924, 1694, 2185, 2208, 2775]

length=[]
a=1
for i in gate:
    length.append(3 * (i + 7 * a) // a)
    a+=1

m=''
for i in length:
    m+=chr(i//3)
m=m[::-1]
index=0
for i in range(0,len(flag),2):
    flag[i]=ord(m[index])
    index+=1
print(flag)
for i in flag:
    print(chr(i),end='')

Baby RSA

from Crypto.PublicKey import RSA
from Crypto.Util.number import *
from secret import e

with open("flag.txt",'r') as f:
    flag = f.read().strip()

p = getPrime(128)
q = getPrime(128)

while p % e != 1:
    p = getPrime(128)
while q % e != 1:
    q = getPrime(128)

n = p * q
m = bytes_to_long(flag.encode())
c = pow(m, e, n)
print(f"Ciphertext: {hex(c)}")

with open("pubkey.pem",'w') as f:
    pk = RSA.construct([n, e])
    f.write(pk.exportKey('PEM').decode('utf-8'))

# Ciphertext: 0x459cc234f24a2fb115ff10e272130048d996f5b562964ee6138442a4429af847

通过pubkey导出e和n,n 很小可以直接分解

from Crypto.PublicKey import RSA
f=open("pubkey.pem").read()
n,e=RSA.import_key(f).n,RSA.import_key(f).e


c=0x459cc234f24a2fb115ff10e272130048d996f5b562964ee6138442a4429af847
p=184980129074643957218827272858529362113
q=283378097758180413812138939650885549231
#e=101

尝试求d的时候发现e没有phi的逆元,检查发现e是phi的因子

参考NCTF2019的easyRSA[909pt 2solvers]

第一次出这个题只有两个解,不得不承认论文确实很难看懂

需要涉及到如何在有限域内开r次方根,而AMM算法是一个十分有效的方法。

此外,我们还要找到其它所有的根

再用CRTGF(p)GF(q)上的两组e个解组合成mod n下的解,最后check检验

给出此类题通用的exp:

import random
import time
# About 3 seconds to run
def AMM(o, r, q):
    start = time.time()
    print('\n----------------------------------------------------------------------------------')
    print('Start to run Adleman-Manders-Miller Root Extraction Method')
    print('Try to find one {:#x}th root of {} modulo {}'.format(r, o, q))
    g = GF(q)
    o = g(o)
    p = g(random.randint(1, q))
    while p ^ ((q-1) // r) == 1:
        p = g(random.randint(1, q))
    print('[+] Find p:{}'.format(p))
    t = 0
    s = q - 1
    while s % r == 0:
        t += 1
        s = s // r
    print('[+] Find s:{}, t:{}'.format(s, t))
    k = 1
    while (k * s + 1) % r != 0:
        k += 1
    alp = (k * s + 1) // r
    print('[+] Find alp:{}'.format(alp))
    a = p ^ (r**(t-1) * s)
    b = o ^ (r*alp - 1)
    c = p ^ s
    h = 1
    for i in range(1, t):
        d = b ^ (r^(t-1-i))
        if d == 1:
            j = 0
        else:
            print('[+] Calculating DLP...')
            j = - dicreat_log(a, d)
            print('[+] Finish DLP...')
        b = b * (c^r)^j
        h = h * c^j
        c = c ^ r
    result = o^alp * h
    end = time.time()
    print("Finished in {} seconds.".format(end - start))
    print('Find one solution: {}'.format(result))
    return result
def findAllPRoot(p, e):
    print("Start to find all the Primitive {:#x}th root of 1 modulo {}.".format(e, p))
    start = time.time()
    proot = set()
    while len(proot) < e:
        proot.add(pow(random.randint(2, p-1), (p-1)//e, p))
    end = time.time()
    print("Finished in {} seconds.".format(end - start))
    return proot
def findAllSolutions(mp, proot, cp, p):
    print("Start to find all the {:#x}th root of {} modulo {}.".format(e, cp, p))
    start = time.time()
    all_mp = set()
    for root in proot:
        mp2 = mp * root % p
        assert(pow(mp2, e, p) == cp)
        all_mp.add(mp2)
    end = time.time()
    print("Finished in {} seconds.".format(end - start))
    return all_mp
c = 10562302690541901187975815594605242014385201583329309191736952454310803387032252007244962585846519762051885640856082157060593829013572592812958261432327975138581784360302599265408134332094134880789013207382277849503344042487389850373487656200657856862096900860792273206447552132458430989534820256156021128891296387414689693952047302604774923411425863612316726417214819110981605912408620996068520823370069362751149060142640529571400977787330956486849449005402750224992048562898004309319577192693315658275912449198365737965570035264841782399978307388920681068646219895287752359564029778568376881425070363592696751183359
p = 199138677823743837339927520157607820029746574557746549094921488292877226509198315016018919385259781238148402833316033634968163276198999279327827901879426429664674358844084491830543271625147280950273934405879341438429171453002453838897458102128836690385604150324972907981960626767679153125735677417397078196059
q = 112213695905472142415221444515326532320352429478341683352811183503269676555434601229013679319423878238944956830244386653674413411658696751173844443394608246716053086226910581400528167848306119179879115809778793093611381764939789057524575349501163689452810148280625226541609383166347879832134495444706697124741
e = 0x1337
cp = c % p
cq = c % q
mp = AMM(cp, e, p)
mq = AMM(cq, e, q)
p_proot = findAllPRoot(p, e)
q_proot = findAllPRoot(q, e)
mps = findAllSolutions(mp, p_proot, cp, p)
mqs = findAllSolutions(mq, q_proot, cq, q)
print mps, mqs
def check(m):
    h = m.hex()
    if len(h) & 1:
        return False
    if h.decode('hex').startswith('NCTF'):
        print(h.decode('hex'))
        return True
    else:
        return False
# About 16 mins to run 0x1337^2 == 24196561 times CRT
start = time.time()
print('Start CRT...')
for mpp in mps:
    for mqq in mqs:
        solution = CRT_list([int(mpp), int(mqq)], [p, q])
        if check(solution):
            print(solution)
    print(time.time() - start)
end = time.time()
print("Finished in {} seconds.".format(end - start))

修改一下参数即可

paper详情参考上文链接

Strongest RSA

2022年7月12日,身体不是很舒服,睡醒午觉起来看了看这个题,发现并不难。

from Crypto.Util.number import getStrongPrime, bytes_to_long
from sympy import prevprime, factorial
from math import gcd
import random

flag="123"

e = 0x10001

def getStrongestPrime(nbits):
    while True:
        p = getStrongPrime(nbits)
        delta = random.randint(0x1337, 0x1337 + 0x1337)
        pp = p - delta
        ppp = prevprime(factorial(pp) % p)
        if gcd(ppp-1, e) == 1:
            return p, ppp
    
NBITS = 1024
p0, p = getStrongestPrime(NBITS)
q0, q = getStrongestPrime(NBITS)
N = p * q
m = bytes_to_long(FLAG.encode())
c = pow(m, e, N)

print(f"p0 = {p0}\nq0 = {q0}")
print(f"N = {N}\ne = {e}\nc = {c}")

好久没用到威尔逊定理了,看到这道题,第一印象是有一个定理可以用

威尔逊定理:

若p是素数,则   (p-1)! = -1 mod p

                           (p-2)! = 1 mod p

这道题发现

delta = random.randint(0x1337, 0x1337 + 0x1337)     范围并不是很大,所以遍历这些数得到所有符合条件的p和q,最后相乘为n的就是我们要找的

这里关键是

ppp = prevprime(factorial(pp) % p)

这一步factorial是求阶乘,但pp太大了求不出来,需要用到威尔逊定理

我们设    x=(p-2)!  ,    y=pp!

因为     (p-2)! = 1 mod p ,所以x = 1 mod p,

所以 \frac{x}{y}*y\equiv 1(mod (p))

我们求出x/y再求它的模拟就得到y%p

exp:

import sys
from Crypto.Util.number import getStrongPrime, bytes_to_long
from sympy import prevprime, factorial
from math import gcd
import random
from tqdm import tqdm
import gmpy2
from Crypto.Util.number import long_to_bytes

p0 = 163753477176210014003355280732229891908166074468271556144642666169325605017666799921295576722168608401188682320182653287668989748162506955989407213845500704903463544753049275828138559289189335596749709834289278256382427251831790026921563375111737350084174473833546767952081017613072491759534988253353621530923
q0 = 157598184809589313845990455272198459548591786211953253450211152128535343234857067521711590445365424087430728267491317690639227988484930088637483194045435135802590588269993794073236513557034321374876808546159597997280236993358749182432517011554239468502233558179815446959403076134284375214662245037202945590183
N = 11884142558095727641000594156833818117849240126500615037738361957005811068956622520280143210434649198031005585252791693777710458190732464123269660559382653636999601459113099276826723072914352276709761755328542359490331355061792823458149611674845846523699218971126655186522340818792078719216860046464292413878045842425132308544311887062610272360069819975798905665533964761527225558339025724872067751916657135473510775709503714808686565298632040214249698116863336246844759838665285888816202570667521796553678688293761589082062045634768520102235077364345013564344229095323239077977717497503322831684471959195555281580807
e = 65537
c = 11776079752956619284016871274992903352398310565005810097721997339193718454945819135683541554652454321040530044545154341786048659896370226535387839157317585368391189570502841702311449000698372030666509296004039398083488490698999338894328619127149024309470011330855840757405205104944658961386764569043610715311746676861275270073394069269043429092551681704290340091149637137627751767730812255069347108706434972786681985484368054390699974613090342753508097177008167140924577095976699437810398922852319420301082587264411993737330188227703869101718515748828944300463051133118636928879090217708121368293440440444106196607645

def wilson(p,pp):
    tmp=1
    for i in (range(pp+1,p-1)):
        tmp=tmp*i%p
    return gmpy2.invert(tmp,p)


ps=[]

for delta in tqdm(range(0x1337, 0x1337 + 0x1337)):
    p=p0
    pp = p - delta
    tmp=wilson(p,pp)
    ppp = prevprime(tmp % p)
    if gcd(ppp-1, e) == 1:
        ps.append(ppp)
print("ppp is over")
print(ps)


qs=[]
for delta in tqdm(range(0x1337, 0x1337 + 0x1337)):
    p=q0
    pp = p - delta
    tmp=wilson(p,pp)
    ppp = prevprime(tmp % p)
    if gcd(ppp-1, e) == 1:
        qs.append(ppp)
print("qqq is over")
print(qs)

for p in tqdm(ps):
    for q in qs:
        if N==p*q:
            phi=(p-1)*(q-1)
            d=gmpy2.invert(e,phi)
            m=pow(c,d,N)
            print(long_to_bytes(m))
            print("over")
            sys.exit(0)

大概三四十分钟就出来了

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Paintrain

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值