您现在的位置是:首页 >技术教程 >[DASCTF Apr.2023 X SU战队2023开局之战] crypto复现网站首页技术教程

[DASCTF Apr.2023 X SU战队2023开局之战] crypto复现

石氏是时试 2023-06-16 09:20:46
简介[DASCTF Apr.2023 X SU战队2023开局之战] crypto复现

感觉突然啥都不会了,后来拿到官方WP,也没整明白,这官方的WP没有代码只讲了些道理,复现一下也不容易。

1,easySign

这是个自制的签名题。

from secret import r, t
from Crypto.Util.number import *

flag = b'xxx'
flag = bytes_to_long(flag)
e = 0x10001

def gen_keys():
    p = getPrime(1024)
    q = getPrime(1024)
    phi = (p-1)*(q-1)
    d = inverse(e,phi)
    n = p*q
    print(f'n = {n}')
    WHATF = (d ** 3 + 3) % phi
    print(f'WHATF= {WHATF}')
    return d, n, WHATF

def easy_sign(n,d):
    m = flag * pow(r,e**2+d**2,n) % n
    s = pow(m,d,n)   #用d加密用e解密
    return s

def gift():
    assert t > 0
    gift = pow(r,t) - 1
    #print(t)
    print(isPrime(gift))

d,n,WHATF = gen_keys()
gift()
sign = easy_sign(n,d)
print(f'sign = {sign}')

gen_keys先生成RSA的d,由于e是已知的,给出d以后基本解密不是问题。并给出了

WHATF = d^3 + 3 (mod phi)

签名是用: m = flag * r^(e^2+d^2) (mod n)

这里r未知,但给出一个:gift = pow(r,t) - 1 (t>0,gift是素数)

由于素数只有2是偶数,所以如果r不是2的话一定是奇数再减1就是偶数(t>1时),当t==1时r可以是任意一个素数+1,所以r的取值只能是2或随便什么数。。。猜测是题出漏了,这里是想让r==2

这里式子里没有d^3需要作个变换: 两边乘d次幂

m^{d} = flag^{d}* r^{e^2*d + d^3} = flag^{d}*r^{e}*r^{d^{3}} = flag^{d}*r^{e}*r^{WHATF-d}

并且sign = m^d

所以这里就能得到flag^d 由于e已知,直接乘e次幂flag^d^e = flag

(这里猜r=2)

n = 17501785470905115084530641937586010443633001681612179692218171935474388105810758340844015368385708349722992595891293984847291588862799310921139505076364559140770828784719022502905431468825797666445114531707625227170492272392144861677408547696040355055483067831733807927267488677560035243230884564063878855983123740667214237638766779250729115967995715398679183680360515620300448887396447013941026492557540060990171678742387611013736894406804530109193638867704765955683067309269778890269186100476308998155078252336943147988308936856121869803970807195714727873626949774272831321358988667427984601788595656519292763705699
WHATF = 7550872408895903340469549867088737779221735042983487867888690747510707575208917229455135563614675077641314504029666714424242441219246566431788414277587183624484845351111624500646035107614221756706581150918776828118482092241867365644233950852801286481603893259029733993572417125002284605243126366683373762688802313288572798197775563793405251353957529601737375987762230223965539018597115373258092875512799931693493522478726661976059512568029782074142871019609980899851702029278565972205831732184397965899892253392769838212803823816067145737697311648549879049613081017925387808738647333178075446683195899683981412014732
# 1
sign = 12029865785359077271888851642408932941748698222400692402967271078485911077035193062225857653592806498565936667868784327397659271889359852555292426797695393591842279629975530499882434299824406229989496470187187565025826834367095435441393901750671657454855301104151016192695436071059013094114929109806658331209302942624722867961155156665675500638029626815869590842939369327466155186891537025880396861428410389552502395963071259114101340089657190695306100646728391832337848064478382298002033457224425654731106858054291015385823564302151351406917158392454536296555530524352049490745470215338669859669599380477470525863815

'''
#1, sign = m^d 
#2, m = flag * r^(e^2 + d^2) 两边取d次幂=> m^d = flag^d * r^(d*e^2 + d^3)
  => sign = flag^d * r^(e + d^3) = flag^d * r^(e + WHATF-3)
#flag = (flag^d)^e  
#3, r=2
'''
from Crypto.Util.number import *
fd = sign * pow(pow(2,e+WHATF-3,n),-1,n)
flag = pow(fd,e,n)
print(long_to_bytes(int(flag)))
#DASCTF{RSA_Bl1nd_Signatur3_With_M4th}

 2,ECC

这是个椭圆曲线和RSA结合的题,这种叫ECRSA

from Crypto.Util.number import *
from secret import flag, p, q, a, b, e1, e2, e3

assert isPrime(p) and isPrime(q)
assert flag.startswith("DASCTF{") and flag.endswith("}")

class ECC():
    def __init__(self, a, b, p, q, e):
        self.p, self.q = p, q
        self.a, self.b = a, b
        self.N         = p * q
        self.e         = e
        self.Kbits     = 8
        self.Ep        = EllipticCurve(IntegerModRing(p), [a, b])
        self.Eq        = EllipticCurve(IntegerModRing(q), [a, b])

        N1 = self.Ep.order()
        N2 = 2 * p + 2 - N1
        N3 = self.Eq.order()
        N4 = 2 * q + 2 - N3

        self.d = {
            ( 1,  1): inverse_mod(e, lcm(N1, N3)),
            ( 1, -1): inverse_mod(e, lcm(N1, N4)),
            (-1,  1): inverse_mod(e, lcm(N2, N3)),
            (-1, -1): inverse_mod(e, lcm(N2, N4))
        }

        self.E = EllipticCurve(IntegerModRing(self.N), [a, b])

    def enc(self, plaintext):
        msg_point = self.msg_to_point(plaintext, True)
        mp = self.Ep(msg_point)
        mq = self.Eq(msg_point)
        cp = (self.e * mp).xy()
        cq = (self.e * mq).xy()
        cp = (int(cp[0]), int(cp[1]))
        cq = (int(cq[0]), int(cq[1]))
        c  = (int(crt([cp[0], cq[0]], [self.p, self.q])), 
              int(crt([cp[1], cq[1]], [self.p, self.q])))
        c = self.E(c)
        return c.xy()

    def dec(self, ciphertext):
        x = ciphertext
        w = x^3 + self.a*x + self.b % self.N

        P.<Yp> = PolynomialRing(Zmod(self.p))
        fp = x^3 + self.a*x + self.b -Yp^2
        yp = fp.roots()[0][0]

        P.<Yq> = PolynomialRing(Zmod(self.q))
        fq = x^3 + self.a*x + self.b -Yq^2
        yq = fq.roots()[0][0]

        y = crt([int(yp), int(yq)], [self.p, self.q])

        cp, cq = self.Ep((x, y)), self.Eq((x, y))
        legendre_symbol_p = legendre_symbol(w, self.p)
        legendre_symbol_q = legendre_symbol(w, self.q)

        mp = (self.d[(legendre_symbol_p, legendre_symbol_q)] * cp).xy()
        mq = (self.d[(legendre_symbol_p, legendre_symbol_q)] * cq).xy()

        return crt([int(mp[0]), int(mq[0])], [self.p, self.q]) >> self.Kbits

    def msg_to_point(self, x, shift=False):
        if shift:
            x <<= self.Kbits
        res_point = None
        for _ in range(2 << self.Kbits):
            P.<Yp> = PolynomialRing(Zmod(self.p))
            fp = x^3 + self.a*x + self.b - Yp^2
            P.<Yq> = PolynomialRing(Zmod(self.q))
            fq = x^3 + self.a*x + self.b - Yq^2
            try:
                yp, yq = int(fp.roots()[0][0]), int(fq.roots()[0][0])
                y = crt([yp, yq], [self.p, self.q])
                E = EllipticCurve(IntegerModRing(self.p*self.q), [self.a, self.b])
                res_point = E.point((x, y))
                return res_point
            except:
                x += 1
        return res_point


ecc1 = ECC(a, b, p, q, e1)
ecc2 = ECC(a, b, p, q, e2)
ecc3 = ECC(a, b ,p, q, e3)
gift = p * q * getPrime(1000)

secret = bytes_to_long(flag[7:-1].encode())
ciphertext1 = ecc1.enc(secret)
ciphertext2 = ecc2.enc(secret)
ciphertext3 = ecc3.enc(secret)

with open("output.txt", "w") as f:
    
    f.write(f"e1 = {e1}
")
    f.write(f"e2 = {e2}
")
    f.write(f"e3 = {e3}
")
    f.write(f"gift = {gift}
")
    f.write(f"C1 = {ciphertext1}
")
    f.write(f"C2 = {ciphertext2}
")
    f.write(f"C3 = {ciphertext3}
")

给了3个e和3个椭圆曲线上的点,我一个gift(则p,q,和一个大因子组成,本来n就分解不了,再加一个因子完全没有意义)

1,椭圆曲线的参数:n,a,b都没有给出。

2,加密是用明文作为点G的x值,C[i]=e[i]*G

所以第一步是获取参数,原来有个模板:

e1 = 516257683822598401
e2 = 391427904712695553
e3 = 431785901506020973
gift = 10954621221812651197619957228527372749810730943802288293715079353550311138677754821746522832935330138708418986232770630995550582619687239759917418738050269898943719822278514605075330569827210725314869039623167495140328454254640051293396463956732280673238182897228775094614386379902845973838934549168736103799539422716766688822243954145073458283746306858717624769112552867126607212724068484647333634548047278790589999183913
C1 = (1206929895217993244310816423179846824808172528120308055773133254871707902120929022352908110998765937447485028662679732041, 652060368795242052052268674691241294013033011634464089331399905627588366001436638328894634036437584845563026979258880828)
C2 = (1819289899794579183151870678118089723240127083264590266958711858768481876209114055565064148870164568925012329554392844153, 1110245535005295568283994217305072930348872582935452177061131445872842458573911993488746144360725164302010081437373324551)
C3 = (1112175463080774353628562547288706975571507012326470665917118873336738873653792420189391867408691423887642725415133046354, 1820636035485820691083758790204536675748006232767111209985774382700260408550258280489088658228739971137550264759084468620)

from gmpy2 import *
#3个c都是曲线上的点
x1,y1 = C1
x2,y2 = C2 
x3,y3 = C3

#求n
n = (y1^2 - y2^2 - x1^3 + x2^3) * (x2 - x3) - (y2^2 - y3^2 - x2^3 + x3^3 ) * (x1 - x2)
n = gcd(n, gift)
#1858284081017011776897142483530706248351014197676168270132930318711536519639284920939350511528325590655669305434894548271

#求a,b
def ecc(a, b, x, y):
    return y ^ 2 - (x ^ 3 + a * x + b)

def solve_lin(f):
    return ZZ(-f[0] / f[1])

P.<a, b> = Zmod(n)[]
f = ecc(a, b, x1,y1)
g = ecc(a, b, x2,y2)
h1 = f.sylvester_matrix(g, b).det().univariate_polynomial()
h2 = f.sylvester_matrix(g, a).det().univariate_polynomial()
a = solve_lin(h1)
b = solve_lin(h2)
print(a,b)

里边好几个字我都不认识,不过模板存下了就能用:三点求模。后边a,b不懂不过不用模板也能用3点求a,b。

官方WP用的另一种方法,感觉更方便也好理解。(我收藏了)

##############################################
#三点求参数a,b,p 
# 根据椭圆曲线表达式构造 groebner_basis() 
P.<a,b>=PolynomialRing(Zmod(gift))
f1 = C1[0]^3 + a*C1[0] + b - C1[1]^2 
f2 = C2[0]^3 + a*C2[0] + b - C2[1]^2
f3 = C3[0]^3 + a*C3[0] + b - C3[1]^2
F = [f1,f2,f3]
Ideal = Ideal(F)
I = Ideal.groebner_basis()
print(I)
# 求解参数a b n
res=[x.constant_coefficient() for x in I]
n = res[2]
a = -res[0]%n
b = -res[1]%n

后边就是根据3组e,C求G,这个就是ECRSA,比照RSA的共模攻击,求CRT,CRT也支持这样的。

E=EllipticCurve(Zmod(n),[a,b])
P1=E(C1)
P2=E(C2)
P3=E(C3)
# 三个e的ECRSA共模攻击
g1,s1,t1=xgcd(e1,e2)
g2,s2,t2=xgcd(g1,e3)
assert g2 == 1
M=s2*s1*P1 + s2*t1*P2 + t2*P3
from Crypto.Util.number import *
print(long_to_bytes(int(M[0])))
#b'RSA_0n_ECC_1s_mor3_Ineres7ingx00'

3,easy_hash

这个题比较难了,没有环境,而且WP上全是字,不大懂,在本地试了一下,大概还有些出入。

import socketserver
import signal
from Crypto.Util.number import *
from random import randint
from gmpy2 import next_prime
from SECRET import flag 

banner = br'''
 _____     ______     ______     ______     ______   ______  
/  __-.  /  __    /  ___   /  ___   /\__  _ /  ___ 
  /     __    \___      \____  /_/ /    __ 
  \____-   \_ \_  /\_____   \_____     \_   \_   
  /____/   /_//_/   /_____/   /_____/     /_/   /_/   
'''

n0 = 30798082519452208630254982405300548841337042015746308462162479889627080155514391987610153873334549377764946092629701
g = 91289086035117540959154051117940771730039965839291520308840839624996039016929

class LCG():
    def __init__(self) -> None:
        self.n = next_prime(2**240)
        self.a = getRandomNBitInteger(85)
        self.seed = randint(1, self.n-1)

    def next(self):
        self.seed = (self.seed ** 3 * self.a + randint(-2**230, 2**230) + randint(1,100)) % self.n
        return self.seed

def gift():
    lcg = LCG()
    outputs = []
    for i in range(30):
        outputs.append(int(lcg.next()))
    return outputs, lcg.a

gift, a = gift()

def hash(msg):
    key = bin(a)[2:]
    n = n0
    msg = list(map(ord,msg))
    for i in range(len(msg)):
        if key[i] == '1':
            n = g * (2 * n + msg[i])
        else:
            continue
        n = n & ((1 << 383) - 1)
    return (n - 0xdeedbeef114514) % (1 << 100)

MENU = br'''
<OPTION>
'''

class Task(socketserver.BaseRequestHandler):
    def _recvall(self):
        BUFF_SIZE = 2048
        data = b''
        while True:
            part = self.request.recv(BUFF_SIZE)
            data += part
            if len(part) < BUFF_SIZE:
                break
        return data.strip()

    def send(self, msg, newline=True):
        try:
            if newline:
                msg += b'
'
            self.request.sendall(msg)
        except:
            pass

    def recv(self, prompt=b'SERVER <INPUT>: '):
        self.send(prompt, newline=False)
        return self._recvall()

    def proof_of_work(self):
        rounds = 1000
        pseudo_prime = int(self.recv(prompt=b'[+] Plz Tell Me your number: '))
        if isPrime(pseudo_prime):
            self.send(b"
No! it's a prime, go away!")
            self.request.close()
        for i in range(rounds):
            if pow(randint(2, pseudo_prime), pseudo_prime - 1, pseudo_prime) != 1:
                self.send(b"
You failed in round " + str(i + 1).encode() + b', bye~~')
                self.request.close()
        self.send(b"
Congratulations, you have passed the proof_of_work!
")
        return True

    def handle(self):
        signal.alarm(300)
        step1 = self.proof_of_work()
        if not step1:
            self.request.close()
        self.send(banner)
        self.send(b"
New challenge now! Please give me 2 diff strings whose hashes are the same~~")
        self.send(b'Here is my gift for you: 
' + str(gift).encode())
        string1 = self.recv().decode()
        string2 = self.recv().decode()
        if string1 == string2:
            self.send(b"
No! They are the same one!")
            self.request.close()
        if hash(string1) == hash(string2):
            self.send(b'
Good job~ Here you are!')
            self.send(flag)
        self.send(b"
Connection has been closed.")
        self.request.close()

class ThreadedServer(socketserver.ThreadingMixIn, socketserver.TCPServer):
    pass

class ForkedServer(socketserver.ForkingMixIn, socketserver.TCPServer):
    pass

if __name__ == "__main__":
    HOST, PORT = '0.0.0.0', 9999
    print("HOST:POST " + HOST+":" + str(PORT))
    server = ForkedServer((HOST, PORT), Task)
    server.allow_reuse_address = True
    server.serve_forever()

题目很长,大概分3部分:

1,proof_of_work

一般情况下proof就是个工作量,比如爆破个4位hash啥的,但这个不是,它是要求一个1000内无法验伪的伪素数:卡迈尔数。WP里给了伪素数生成方法的说明。这类题原来作过,用这个公式生成伪素数是极其困难的。不过这题并不需要生成,只有你知道一个就行了,这个数是一个300以下base的卡迈尔数,就是不存在300以下的因子。对于1000来说已经足够大了,1000以下全是小因子。所以输入这个数就能过。

p = 29674495668685510550154174642905332730771991799853043350995075531276838753171770199594238596428121188033664754218345562493168782883
n = p *(313*(p - 1) + 1)*(353 * (p - 1) + 1)
proof_of_work(n)

2,LCG

这个LCG生成30个随机数,生成公式是: f(n) = f(n-1)^3*a + e (mod p)

这是个标准的hnp问题,有个lbc_toolkit有这个函数,官方WP也给出了这个函数构造矩阵的方法,有函数了还整矩阵干啥。

不过这有个问题,不知道官方是不是作了这题,这题给出的

self.n = next_prime(2**240)

非常特殊,会造成LLL算法不出结果。经过测试,这里改成随机一个getPrime(240)数就没问题。

class LCG():
    def __init__(self) -> None:
        #self.n = next_prime(2**240)
        self.n = getPrime(240)       #不用原来的p
        self.a = getRandomNBitInteger(85)
        self.seed = randint(1, self.n-1)
        print(self.n,self.a)

    def next(self):
        self.seed = (self.seed ** 3 * self.a + randint(-2**60, 2**60) + randint(1,100)) % self.n #
        return self.seed

def gift():
    lcg = LCG()
    outputs = []
    for i in range(30):
        outputs.append(int(lcg.next()))
    return outputs, lcg.a

outputs,a = gift()

#----------------
p = 1113328282865438363384064798855439755994226962679762330059112876627863681
B = 2^230
T = [(i^3)%p for i in outputs[:-1]]
A = outputs[1:]
sol = hnp(p, T, A, B, verbose=True)
print(sol)

说明确实那个p有问题

可以根据这一步求出a或者绕过这一步(非官方预期),所以下边也就非预期了

3,hash

def hash(msg):
    key = bin(a)[2:]
    n = n0
    msg = list(map(ord,msg))
    for i in range(len(msg)):
        if key[i] == '1':
            n = g * (2 * n + msg[i])
        else:
            continue
        n = n & ((1 << 383) - 1)
    return (n - 0xdeedbeef114514) % (1 << 100)

这个hash代码比较特殊,他没有对所有位处理,只对key为1的位处理,那么当输入两位的值时,如果key='10.....'也就跟后边无关,两个值只有第1位相同,大概率hash值也相同。并且key的前两位是10的概率也非常大,大概都是50%,所以就输入AA和AB,由于环境及时关闭了,所以只能本地测

v = 0
for i in range(10000):
    a = getRandomNBitInteger(85)
    if hash('AA') == hash('AB'):
        v+=1

print(v/10000)
#4961/10000

确实对于随机的a可以预期得到大概率的正确值。

这个题由于proof可以绕过,hnp可以不作,50%*50%的概率应该很容易爆破成功。

风语者!平时喜欢研究各种技术,目前在从事后端开发工作,热爱生活、热爱工作。