defcon-ctf qualifer crypto writeup

ooo-flag-sharing

閱讀題目,我們發現這是一個密鑰分享的密碼系統。題目使用100*5的矩陣A對填充爲5*1明文向量C(向量第一個數是明文)加密得到100*1的向量B。在系統中保存部分向量數據b1,b2……,並將剩下的向量數據b3,b4……分發。當我們和系統由於5個向量數據b1,b2,b3,b4,b5。我們就可以將A中對應向量行組合成一個新矩陣,並求得逆矩陣A^{-1}。我們將A^{-1}的第一行(s1,s2,s3,s4,s5)和密文向量(b1,b2,b3,b4,b5)相乘即爲明文。即m=s1*b1+s2*b2+s3*b3+s4*b4+s5*b5。在redeem_actual_flag中系統中有兩個向量B的數據bn,bm。和用戶發送3個向量B的數據組合解密。我們可以將b5更改爲b5+a*s5^-1。這時我們發現解密結果變爲m+a。已知m的最低爲是OOO{,並且在redeem_actual_flag()中進行檢驗flag的頭部是OOO{。由於整個操作在模p之下,所以我們嘗試改變a,且不改變m的低位“OOO{"。當m+a>=p的時候。我們發現解密的結果結尾不再是OOO{。當m+a<p的時候,我們發現解密的結果結尾是OOO{。這時我們找到臨界值a,計算p-a獲得m。

首先我們首先使用share_actual_flag()獲得一組密文[(numi,bnumi),(numj,bnumj),(numk,bnumk)]

這裏我們發現在上述方案中我們需要知道p。我們首先在相同的flagid下輸入密文[(0,1),(1,0),(2,0),(3,0),(4,0)]。我們可以得到s1。然後我們更改密文爲[(0,2),(1,0),(2,0),(3,0),(4,0)]……[(0,n),(1,0),(2,0),(3,0),(4,0)]。獲得a*s1。當我們發現a*s1>a*s1mopp。我們相減獲得p。

其次我們需要知道s5^-1是多少。我們可以嘗試輸入向量(0,0,0,0,1)從而獲得s5,繼而獲得s5^-1。我們需要知道在redeem_actual_flag()中系統中存儲的bn和bm具體是對應哪一行。所以我們需要進行爆破。不斷輸入密文[(i,0),(j,0),(numi,0),(numj,0),(numk,1)]解出a5,再計算a5^-1。我們將b5改爲b5+(1<<32)*s5^-1。當驗證成功時。說明我們爆破成功,已經獲得a5^-1。我們再按照上面的方案求解處flag

#!/usr/bin/env python3
import ast
import copy
from pwn import *
from Crypto.Util.number import *

r = remote('ooo-flag-sharing.challenges.ooo', 5000)
#r = remote('127.0.0.1', 20000)

name = 'hahaha'
r.sendlineafter('Username: ', name)

def redeem(secret_id, shares):
    r.sendlineafter('Choice: ', '2')
    r.sendlineafter("Enter the secret's ID: ", secret_id)
    r.sendlineafter("Enter your shares of the secret: ", str(shares))
    r.recvuntil("Your secret is: ")
    secret = r.recvline().strip()
    return int.from_bytes(eval(secret), 'little')

def store_flag():
    r.sendlineafter('Choice: ', '3')
    r.recvuntil("Our secret's ID is: ")
    secret_id = r.recvline().strip().decode()
    r.recvuntil("Your shares are: ")
    shares = ast.literal_eval(r.recvline().strip().decode())
    return secret_id, shares

def redeem_flag(secret_id, shares):
    r.sendlineafter('Choice: ', '4')
    r.sendlineafter("Enter the secret's ID: ", secret_id)
    r.sendlineafter("Enter your shares of the secret: ", str(shares))
    return r.recvline().startswith(b'Congrats')

secret_id, shares = store_flag()
a = redeem(secret_id, [(0, 1)] + [(i, 0) for i in range(1, 5)])

now = 1
while True:
    now += 1
    aa = redeem(secret_id, [(0, now)] + [(i, 0) for i in range(1, 5)])
    P = a * now - aa
    if P > 0:
        print(f'P = {P}')
        break

shares = sorted(shares)

for i in range(1, 100):
    for j in range(i + 1, 100):
        if i in [shares[x][0] for x in range(3)] or j in [shares[x][0] for x in range(3)]:
            continue

        print(i, j)

        fake_shares = sorted([(s[0], 1 if s == shares[-1] else 0) for s in shares] + [(i, 0), (j, 0)])
        a = redeem(secret_id, fake_shares)
        ai = inverse(a, P)

        fail = False
        for k in range(1, 3):
            newshares = copy.deepcopy(shares)
            newshares[2] = (newshares[2][0], (newshares[2][1] + ai * (k << 32)) % P)
            valid = redeem_flag(secret_id, newshares)
            if not valid:
                fail = True
                break

        if not fail:
            L = 0
            H = P >> 32
            while L != H:
                print(((P >> 32) - L).to_bytes(32, 'little'))
                print(((P >> 32) - H).to_bytes(32, 'little'))
                M = (L + H) >> 1
                newshares = copy.deepcopy(shares)
                newshares[2] = (newshares[2][0], (newshares[2][1] + ai * (M << 32)) % P)
                valid = redeem_flag(secret_id, newshares)
                if valid:
                    L = M + 1
                else:
                    H = M
            exit()

coooppersmith

對文件逆向,我們發現這是一個RSA加密。首先需要我們輸入一個120位的數字。之後系統在我們的數字後加8位並將其更改爲一個素數。然後藉助該素數生成RSA的私鑰和公鑰。當公鑰中的n小於消息長度的時候將會報錯。所以我們需要輸入一個儘量小的數字。既保證運算的簡單。有保證可以對消息進行加密。這裏我們嘗試輸入數字000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000010000000000000000000。系統將會在[0,0x10000]中隨機生成一個數s和0x1000000000000000000000000000相加爲x。當x是素數的時候,就從[1,x]之中隨機生成a,b。使得p=(2*x*a)+1,q=(2*x*b)+1。我們獲得n=p*q。之後將n和e組合成公鑰輸出。並輸出又該公鑰加密的消息。

我們發現n=p*q=((2*x*a)+1)*((2*x*b)+1)=(4*x*x*a*b)+(2*x*a)+(2*x*b)+1。而(n-1)%x=0。我們可以藉助此首先將x爆破出來。然後繼續推算a,b。由於n-1=(4*x*x*a*b)+(2*x*a)+(2*x*b),(n-1)/(2*x)=(2*x*a*b)+a+b。由於a+b<2*x。所以((n-1)/(2*x))%(2*x)=a+b.然後我們計算((n-1)/(2*x)-(a+b))=2*x*a*b。((n-1)/(2*x)-(a+b))/(2*x)=a*b。我們已知a+b和a*b。那麼a-b=\sqrt{(a+b)^{2}-4*a*b},a=((a+b)+(a-b))/2,b=a+b-a。我們已知a,b。即可輕鬆獲得p,q。我們獲取私鑰後即可對消息解密。

import gmpy2
from Crypto.PublicKey import RSA
def shuchu(mingwenstr):
    if mingwenstr[len(mingwenstr)-1]=='L':
        mingwenstr=mingwenstr[2:len(mingwenstr)-1]
    else:
        mingwenstr=mingwenstr[2:len(mingwenstr)]
    if not len(mingwenstr)%2==0:
            mingwenstr='0'+mingwenstr
    i=len(mingwenstr)
    mingwen=""
    while i>=1:
        str1=mingwenstr[i-2:i]
        if int(str1,16)>33 and int(str1,16)<126:
            mingwen=chr(int(str1,16))+mingwen
        else :
            mingwen=" "+mingwen
        i=i-2
    print mingwen

pubkey="""-----BEGIN RSA PUBLIC KEY-----
MD0CNkrHjOABLwGYp+ILQURK13nBx4JzR7PBTERgWlTfizn2WLs8xvD0S0w8v2BR
3jkoQ3Lk2al72QIDAQAB
-----END RSA PUBLIC KEY-----"""
key=RSA.importKey(pubkey)
n=key.n
e=key.e
prime=0x1000000000000000000000000000
for i in xrange(0x1000000):
    primex=prime+i
    if gmpy2.is_prime(primex):
        if (n-1)%primex==0:
            prime=primex
            print primex
            break

aandb=((n-1)/(2*prime))%(2*prime)
c=0x216f28e567436bb97d6d5bc084be2f816eb7b3d6d2f4d7765de28f9cf5279c63ce7272dd9902fe0d0e03209189f9cf694e5c8325f61f
amulb=(((n-1)/(2*prime))-aandb)/(2*prime)
print gmpy2.iroot(pow(aandb,2)-4*amulb,2)[1]
asubb=gmpy2.iroot(pow(aandb,2)-4*amulb,2)[0]
a=(aandb+asubb)/2
b=aandb-a
print a,b
assert a+b==aandb
assert a*b==amulb
p=2*prime*a+1
q=2*prime*b+1
assert n==p*q
print p,q
phi=(p-1)*(q-1)
d=gmpy2.invert(e,phi)
m=pow(c,d,n)
shuchu(hex(m))

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章