1. 程式人生 > 其它 >【CTF WriteUp】2021 starCTF部分Crypto題解

【CTF WriteUp】2021 starCTF部分Crypto題解

(打比賽感想:大佬真TM多。。。)

Crypto

Crypto-GuessKey

題目

from random import randint
import os
from flag import flag
N=64
key=randint(0,2**N)
print key
key=bin(key)[2:].rjust(N,'0')
count=0
while True:
	p=0
	q=0
	new_key=''
	zeros=[0]
	for j in range(len(key)):
		if key[j]=='0':
			zeros.append(j)
	p=zeros[randint(0,len(
zeros))-1] q=zeros[randint(0,len(zeros))-1] try: mask=int(raw_input("mask:")) except: exit(0) mask=bin(mask)[2:] if p>q: tmp=q q=p p=tmp cnt=0 for j in range(0,N): if j in range(p,q+1): new_key+=str(int(mask[cnt])^int(key[j])) else: new_key+=key[j] cnt+=1 cnt%=
len(mask) key=new_key try: guess=int(raw_input("guess:")) except: exit(0) if guess==int(key,2): count+=1 print 'Nice.' else: count=0 print 'Oops.' if count>2: print flag

解答

題目出錯了,給出key讓猜key,輸入mask=0保持key不變就可以了。

Crypto-GuessKey2

題目

from random import randint
import os
from
flag import flag N=64 key=randint(0,2**N) # print key key=bin(key)[2:].rjust(N,'0') count=0 while True: p=0 q=0 new_key='' zeros=[0] for j in range(len(key)): if key[j]=='0': zeros.append(j) p=zeros[randint(0,len(zeros))-1] q=zeros[randint(0,len(zeros))-1] try: mask=int(raw_input("mask:")) except: exit(0) mask=bin(mask)[2:] if p>q: tmp=q q=p p=tmp cnt=0 for j in range(0,N): if j in range(p,q+1): new_key+=str(int(mask[cnt])^int(key[j])) else: new_key+=key[j] cnt+=1 cnt%=len(mask) key=new_key try: guess=int(raw_input("guess:")) except: exit(0) if guess==int(key,2): count+=1 print 'Nice.' else: count=0 print 'Oops.' if count>2: print flag

解答

修正後的題目,隱藏了key值,因此輸入mask=0沒有作用了。首先分析一下演算法:

  1. 隨機生成一個64位0和1組成的key
  2. 將其所有為0的位置儲存到一個數組中,從中取兩個數p < q
  3. 要求我們輸入一個mask,然後對key進行處理,對於(p, q)之間的部分異或我們的mask,其餘部分不動,得到新key,讓我們猜
  4. 回到步驟2

注意到key在每次輸入後是發生變化的,因此嘗試通過輸入將key修正為我們想要的值。注意到p的選擇來自zeros陣列,因此key[p]一定是0。當我們輸入一個不是0的mask時,mask轉換為二進位制參與運算,其首位為1,則新的key[p]將變成1。如果我們隨機到了最左邊的0的位置參與運算,那麼在此位變成1以後,後續每個選擇的p都會在這一位的右邊(因為p只能選擇為0的位置),p的左邊在更新key時原樣照抄,即從左邊開始數連續為1的位數只增不減。因此我們只需要輸入mask為全1,然後瘋狂猜測key = 2 ** 64 - 1。當某次正確後,再輸入兩次mask = 0即可。完整解題程式碼如下:

#!/usr/bin/env python
# -*- coding: utf-8 -*-
from pwn import *

# context.log_level = 'debug'

p = remote("52.163.228.53", 8082)
count = 0
while True:
    p.recvuntil("mask:")
    p.sendline("18446744073709551615")
    p.recvuntil("guess:")
    p.sendline("18446744073709551615")
    rec = p.recvline()
    print rec.strip() + " " + str(count)
    count += 1
    if(rec.startswith("Oops.")):
        continue
    else:
        for i in range(2):
            p.recvuntil("mask:")
            p.sendline("0")
            p.recvuntil("guess:")
            p.sendline("18446744073709551615")
            print p.recvline()
        print p.recvline()
        break
p.close()

(後話:mask = 2 ** 64 - 1 沒有問題,但按照解題原理輸入任意非0值最後都能得到結果,實際沒跑出來)

Crypto-little case

題目

challenge.py

from Crypto.Util.number import *
from libnum import *
from secret import flag,special,p,q,n


def little_trick(msg):
    p1 = getPrime(1024)
    q1 = getPrime(1024)
    n1 = p1 * q1
    d1=random.randint(1,2**256)
    e1=inverse(d1,(p-1)*(q-1))
    print(n1)
    print(e1)
    print(pow(msg,e1,n1))


def real_trick():
    assert (special > (ord("*")*100) and gcd(special,(p-1)*(q-1))!=1 )
    print(n)
    print(pow(libnum.s2n(flag),special,n))


if __name__ == '__main__':
    little_trick(p-1)
    real_trick()

out.sage

21669699875387343975765484834175962461348837371447024695458479154615348697330944566714587217852888702291368306637977095490953192701450127798670425959768118384915082017373951315699899009631834471691811815393784748930880954114446745814058132752897827717077886547911476575751254872623927783670252969995075629255541621917767501261249192653546875104532649043219697616464205772025267019328364349763854659490144531087349974469079255236823096415094552037488277752927579909539401311624671444833332618177513356173537573280352724384376372955100031534236816681805396608147647003653628203258681097552049114308367967967184116839561

20717541468269984768938524534679430706714860712589983300712432366828367981392533792814384884126053081363266457682162675931547901815985830455612301105504518353600255693451085179954519939635263372257973143178677586338992274607959326361412487748088349413448526455377296931144384663805056580662706419414607407821761761574754611275621927387380065975844282519447660467416826579669726178901884060454994606177784839804528666823956703141147239309978420776148158425922031573513062568162012505209805669623841355103885621402814626329355281853436655713194649170570579414480803671531927080535374958180810697826214794117466378050607

17653913822265292046140436077352027388518012934178497059850703004839268622175666123728756590505344279395546682262531546841391088108347695091027910544112830270722179480786859703225421972669021406495452107007154426730798752912163553332446929049057464612267870012438268458914652129391150217932076946886301294155031704279222594842585123671871118879574946424138391703308869753154497665630799300138651304835205755177940116680821142858923842124294529640719629497853598914963074656319325664210104788201957945801990296604585721820046391439235286951088086966253038989586737352467905401107613763487302070546247282406664431777475

2346087036331379968192118389403047568445805414881948978518580277027027486284293415097623011228506968071753709256352246733181304513713003096615266613365080909760605498017330085960699607777361429562376124376340215426398797920168016137830563564636922257215066266075494625782943973857490781916694118187094786034792437781964601089843549995939887939410763350338658901108020658475956489391300528691289604149598720803012371765770928211044755626045817053870803040863722458554924076011151695567147976903053993914859714631837755435592006986598006207692599019026644753575853382810261910332197447386727419606073948645238377595719

12732299056226934743176360461051108799706450051853623472248552066649321279227693844417404789169416642586313895494292082308084823101092675162498154181999270703392144766031531668783213589136974486867571090321426005719333327425286160436925591205840653712046866950957876967715226097699016798471712274797888761218915345301238306497841970203137048433491914195023230951832644259526895087301990301002618450573323078919808182376666320244077837033894089805640452791930176084416087344594957596135877833163152566525019063919662459299054294655118065279192807949989681674190983739625056255497842063989284921411358232926435537518406L

解答

這題損得一批,但也不是不能做,畢竟過程中留下了提示。

首先看little_trick,e1的那個求法明顯和周圍格格不入,正常應當是e1 = inverse(d1, (p1-1) * (q1-1))。且按照這個演算法,可以使用wiener攻擊求出d1,併成功分解n1。這裡隱藏著題目最核心的一個提示:程式碼不一定是對的

n1 = 21669699875387343975765484834175962461348837371447024695458479154615348697330944566714587217852888702291368306637977095490953192701450127798670425959768118384915082017373951315699899009631834471691811815393784748930880954114446745814058132752897827717077886547911476575751254872623927783670252969995075629255541621917767501261249192653546875104532649043219697616464205772025267019328364349763854659490144531087349974469079255236823096415094552037488277752927579909539401311624671444833332618177513356173537573280352724384376372955100031534236816681805396608147647003653628203258681097552049114308367967967184116839561
e1 = 20717541468269984768938524534679430706714860712589983300712432366828367981392533792814384884126053081363266457682162675931547901815985830455612301105504518353600255693451085179954519939635263372257973143178677586338992274607959326361412487748088349413448526455377296931144384663805056580662706419414607407821761761574754611275621927387380065975844282519447660467416826579669726178901884060454994606177784839804528666823956703141147239309978420776148158425922031573513062568162012505209805669623841355103885621402814626329355281853436655713194649170570579414480803671531927080535374958180810697826214794117466378050607
c1 = 17653913822265292046140436077352027388518012934178497059850703004839268622175666123728756590505344279395546682262531546841391088108347695091027910544112830270722179480786859703225421972669021406495452107007154426730798752912163553332446929049057464612267870012438268458914652129391150217932076946886301294155031704279222594842585123671871118879574946424138391703308869753154497665630799300138651304835205755177940116680821142858923842124294529640719629497853598914963074656319325664210104788201957945801990296604585721820046391439235286951088086966253038989586737352467905401107613763487302070546247282406664431777475
d1 = 36167461773898995192586226632578677184913220227461899855497899052924496298787
p1 = 172725732665160218766764654273481422951178250693790825716359642314019315013321867434849375780994146440118500992456863730311444752367847389052666095324149584230474450693683416636565463345342017830651196454735159385924736453857045827989347928771104006978412412405607299616293396656259291378061505961429915687703
q1 = 125457275769068125757485908164006976153846494199951055956773829512867658202991509882220598391130551501414264867302691498062034690987771420463818096949614464177297177742292221196272475473441901790650971196178740160270878352307469807794533558079725928555246759355468676092854618354193968504322694550607414849887
msg = pow(c1, d1, n1)

以這個觀點繼續看real_trick,可以發現n比餘數c少一位數字。加上之前我們通過little_trick求出的msg與p的關係,可以猜測出題人故意刪掉了一位,經實驗發現在n前補一個2,正好可以被msg+1整除,所以這才是真正的n

n = 22346087036331379968192118389403047568445805414881948978518580277027027486284293415097623011228506968071753709256352246733181304513713003096615266613365080909760605498017330085960699607777361429562376124376340215426398797920168016137830563564636922257215066266075494625782943973857490781916694118187094786034792437781964601089843549995939887939410763350338658901108020658475956489391300528691289604149598720803012371765770928211044755626045817053870803040863722458554924076011151695567147976903053993914859714631837755435592006986598006207692599019026644753575853382810261910332197447386727419606073948645238377595719
p = 199138677823743837339927520157607820029746574557746549094921488292877226509198315016018919385259781238148402833316033634968163276198999279327827901879426429664674358844084491830543271625147280950273934405879341438429171453002453838897458102128836690385604150324972907981960626767679153125735677417397078196059
q = 112213695905472142415221444515326532320352429478341683352811183503269676555434601229013679319423878238944956830244386653674413411658696751173844443394608246716053086226910581400528167848306119179879115809778793093611381764939789057524575349501163689452810148280625226541609383166347879832134495444706697124741

然後我們只剩最後一個條件可以用了,即gcd(special, (p-1)(q-1)) != 1。用factordb嘗試分解一下p-1和q-1,發現有公共因數2和4919。以出題人這道題的尿性,估計special就是4919沒跑了。後邊很像NCTF的easyrsa,即求出滿足pow(x, 4919, p) = c % ppow(x, 4919, q) = c % q的各4919個根,然後用中國剩餘定理求出4919 * 4919種情況下的m並分別進行驗證,找到真正的flag。解題程式碼如下:(需要執行約15分鐘)

import random
import time

def AMM(o, r, 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))
    t = 0
    s = q - 1
    while s % r == 0:
        t += 1
        s = s // r
    k = 1
    while (k * s + 1) % r != 0:
        k += 1
    alp = (k * s + 1) // r
    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:
            j = - dicreat_log(a, d)
        b = b * (c^r)^j
        h = h * c^j
        c = c ^ r
    result = o^alp * h
    return result

def findAllPRoot(p, e):
    proot = set()
    while len(proot) < e:
        proot.add(pow(random.randint(2, p-1), (p-1)//e, p))
    return proot

def findAllSolutions(mp, proot, cp, p):
    all_mp = set()
    for root in proot:
        mp2 = mp * root % p
        assert(pow(mp2, e, p) == cp)
        all_mp.add(mp2)
    return all_mp

c = 12732299056226934743176360461051108799706450051853623472248552066649321279227693844417404789169416642586313895494292082308084823101092675162498154181999270703392144766031531668783213589136974486867571090321426005719333327425286160436925591205840653712046866950957876967715226097699016798471712274797888761218915345301238306497841970203137048433491914195023230951832644259526895087301990301002618450573323078919808182376666320244077837033894089805640452791930176084416087344594957596135877833163152566525019063919662459299054294655118065279192807949989681674190983739625056255497842063989284921411358232926435537518406
p = 199138677823743837339927520157607820029746574557746549094921488292877226509198315016018919385259781238148402833316033634968163276198999279327827901879426429664674358844084491830543271625147280950273934405879341438429171453002453838897458102128836690385604150324972907981960626767679153125735677417397078196059
q = 112213695905472142415221444515326532320352429478341683352811183503269676555434601229013679319423878238944956830244386653674413411658696751173844443394608246716053086226910581400528167848306119179879115809778793093611381764939789057524575349501163689452810148280625226541609383166347879832134495444706697124741
e = 4919

start_time = time.time()
print "Start time: 0.0"
# find all roots for pow(x, e, p)=1 and pow(x, e, q)=1 
cp = c % p
cq = c % q
p_proot = findAllPRoot(p, e)
print "P roots found: %s" % str(time.time()-start_time)
q_proot = findAllPRoot(q, e)
print "Q roots found: %s" % str(time.time()-start_time)

# find all roots for pow(x, e, p)=cp and pow(x, e, q)=cq
mp = AMM(cp, e, p)
print "mp found: %s" % str(time.time()-start_time)
mq = AMM(cq, e, q)
print "mq found: %s" % str(time.time()-start_time)

mps = findAllSolutions(mp, p_proot, cp, p)
print "mps found: %s" % str(time.time()-start_time)
mqs = findAllSolutions(mq, q_proot, cq, q)
print "mqs found: %s" % str(time.time()-start_time)

def check(m):
    h = m.hex()
    if len(h) & 1:
        return False
    if h.decode('hex').startswith('*CTF'):
        print(h.decode('hex'))
        return True
    else:
        return False

# check 4919*4919 possibles for answer
for mpp in mps:
    for mqq in mqs:
        solution = CRT_list([int(mpp), int(mqq)], [p, q])
        if check(solution):
            print solution
            print "solution found: %s" % str(time.time()-start_time)

在這裡插入圖片描述

Crypto-MyEnc

題目

from Crypto.Util.number import getPrime,bytes_to_long
import time,urandom
from flag import flag
iv=bytes_to_long(urandom(256))
assert len(flag)==15
keystream=bin(int(flag.encode('hex'),16))[2:].rjust(8*len(flag),'0')
p=getPrime(1024)
q=getPrime(1024)
n=p*q
print "n:",n
cnt=0
while True:
	try:
		print 'give me a number:'
		m=int(raw_input())
	except:
		break
	ct=iv
	for i in range(1,8):
		if keystream[cnt]=='1':
			ct+=pow(m^q,i**i**i,n)
			ct%=n
		cnt=(cnt+1)%len(keystream)
	print "done:",ct

解答

根據題目程式碼,flag長度為15,並且針對構成這15個字元的120位,每次要求我們輸入一個m,然後取出其中7位進行一些計算。120位迴圈出現

ct=iv
for i in range(1,8):
	if keystream[cnt]=='1':
		ct+=pow(m^q,i**i**i,n)
		ct%=n
	cnt=(cnt+1)%len(keystream)

由於m^q的異或比較難處理,我們先令m=0,得到2個結果。注意到這些結果在模q的情況下是同餘的,因此任選其中兩個做差,與n求最大公約數,即可求出q

求出q以後就非常好辦了,令m = q xor 2,這樣我們得到的就是ct加上一些2 ** x的結果,而這些x我們又是知道的,只有固定的七種(太大這裡寫不下)。我們可以通過爆破iv的所有可能取值並逐個驗證找到真實iv,再還原flag。解題程式碼分為兩部分,一部分為取數,另一部分為解題。

取數程式碼:

#!/usr/bin/env python
# -*- coding: utf-8 -*-
from pwn import *
import hashlib
import string
import gmpy2

def mysha(text):
    myhash = hashlib.sha256()
    myhash.update(text)
    return myhash.hexdigest()

def passpow(suffix, target):
    dic = string.letters + string.digits
    for a0 in dic:
        for a1 in dic:
            for a2 in dic:
                for a3 in dic:
                    tmp = a0 + a1 + a2 + a3 + suffix
                    if(mysha(tmp)==target):
                        return a0 + a1 + a2 + a3

p = remote("52.163.228.53", 8081)
line = p.readline()
suffix = line[12:28]
target = line[33:97]
passwd = passpow(suffix, target)
p.recvuntil("Give me xxxx:")
p.sendline(passwd)

line = p.readline()
n = int(p.readline().strip()[3:])

# get q
arr = []
for i in range(2):
    p.recvuntil("give me a number:")
    p.sendline("0")
    line = p.readline()
    arr.append(int(p.readline().strip()[6:]))

q = gmpy2.gcd(arr[1]-arr[0], n)
assert n > q
assert q > 1

arr = []
newm = q ^ 2
for i in range(18):
    p.recvuntil("give me a number:")
    p.sendline(str(newm))
    line = p.readline()
    arr.append(int(p.readline().strip()[6:]))
print n
print q
print arr

p.interactive()

解題程式碼:

n = ...
q = ...
arr = [...]
mark = [pow(2, i**i**i, n) for i in range(1, 8)] # 提前算好,這裡數太大

tmpiv1 = []
tmpiv2 = []
for i in range(128):
    tmp = bin(i)[2:].rjust(7, '0')
    s0 = arr[0]
    s1 = arr[1]
    for j in range(7):
        if(tmp[j]=='1'):
            s0 = (s0 - mark[j]) % n
            s1 = (s1 - mark[j]) % n
    tmpiv1.append(s0)
    tmpiv2.append(s1)

ss = list(set(tmpiv1).intersection(set(tmpiv2)))
# 得到的公有iv不止一個,需要逐個驗證

for m in range(len(ss)):
    try:
        iv = int(ss[m])
        res = []
        for i in range(128):
            tmp = bin(i)[2:].rjust(7, '0')
            s = iv
            for j in range(7):
                if(tmp[j]=='1'):
                    s = (s + mark[j]) % n
            res.append(s)

        c = ""
        for i in range(len(arr)):
            tmp = res.index(arr[i])
            tmp = bin(tmp)[2:].rjust(7, '0')
            c += tmp
        c = c[:120]
        # 算q用了14位,調整回來
        c = c[106:] + c[:106]
        print ''.join(chr(int(c[i:i+8], 2)) for i in range(0, 120, 8))
    except:
        continue

Crypto-MyCurve

(感謝大佬提供解答)

題目

from Crypto.Util.number import bytes_to_long
from flag import flag
assert flag[:5]=='*CTF{' and flag[-1]=='}'
flag=flag[5:-1]
def add(P,Q):
	if Q==0:
		return P
	x1,y1=P
	x2,y2=Q
	return (d1*(x1+x2)+d2*(x1+y1)*(x2+y2)+(x1+x1^2)*(x2*(y1+y2+1)+y1*y2))/(d1+(x1+x1^2)*(x2+y2)),(d1*(y1+y2)+d2*(x1+y1)*(x2+y2)+(y1+y1^2)*(y2*(x1+x2+1)+x1*x2))/(d1+(y1+y1^2)*(x2+y2))

def mul(k,P):
	Q=(0,0)
	while k>0:
		if is_even(k):
			k/=2
			P=add(P,P)
		else:
			k-=1
			Q=add(P,Q)
	return Q

F=GF(2**100)
R.<x,y>=F[]
d1=F.fetch_int(1)
d2=F.fetch_int(1)
x,y=(698546134536218110797266045394L, 1234575357354908313123830206394L)
G=(F.fetch_int(x),F.fetch_int(y))
P=mul(bytes_to_long(flag),G)
print (G[0].integer_representation(),G[1].integer_representation())
print (P[0].integer_representation(),P[1].integer_representation())
#(698546134536218110797266045394L, 1234575357354908313123830206394L)
#(403494114976379491717836688842L, 915160228101530700618267188624L)

解答

本題考查二元域上的愛德華茲曲線。該曲線方程通用形式為
在這裡插入圖片描述
曲線的細節見這裡
https://www.hyperelliptic.org/EFD/g12o/auto-edwards.html
https://www.hyperelliptic.org/EFD/g12o/data/edwards/coordinates

使用sagemath中的橢圓曲線設定與離散對數函式可以直接求解

from Crypto.Util.number import long_to_bytes

F = GF(2**100)
R.<x,y> = F[]

def _map(p):
    x,y = F.fetch_int(p[0]), F.fetch_int(p[1])
    u = 3*(x+y)/(x*y+x+y)
    v = 3*(x/(x*y+x+y)+2)
    return (u,v)

G = (698546134536218110797266045394, 1234575357354908313123830206394)
P = (403494114976379491717836688842, 915160228101530700618267188624)
# d1 = 1
# d2 = 1
# a1 = 1
# a2 = d1 ** 2 + d2 = 2
# a3 = 0
# a4 = 0
# a6 = d1**4 * (d1**4 + d1**2 + d2**2) = 3
E = EllipticCurve(GF(2**100), [1, 2, 0, 0, 3])
base = E(_map(G))
res = E(_map(P))
flag = discrete_log(res, base, base.order(), operation="+")
print(long_to_bytes(flag))