【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沒有作用了。首先分析一下演算法:
- 隨機生成一個64位0和1組成的key
- 將其所有為0的位置儲存到一個數組中,從中取兩個數p < q
- 要求我們輸入一個mask,然後對key進行處理,對於(p, q)之間的部分異或我們的mask,其餘部分不動,得到新key,讓我們猜
- 回到步驟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 % p
和pow(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))