Octane的moeCTF_2022 Writeup

Octane的moeCTF_2022 Writeup

OctaneのCTF初体验!

准备在这里补(shui)一篇WP。因为题目数量挺多的,有些题目也是新手引导向的,或者仅仅涉及一些工具的基本使用方法,所以不会把所有的题目全都写一遍题解,还是主要写Crypto板块的一些题解。感觉有些题还是有记录一下的价值的,在复习的同时也可以把那些做题时没太深究的题深入研究一下。

Crypto

一次就好

先看题目:

from Crypto.Util.strxor import strxor
from Crypto.Util.number import *
from gmpy2 import powmod,next_prime
from FLAG import flag
import codecs

c = b'Just once,I will accompany you to see the world'
flag = flag.ljust(len(c),'#')
key = strxor(flag.encode(), c)
m = bytes_to_long(key)

p = getPrime(512)
q = next_prime(p)
N = p*q
e = 0x10001

gift = powmod(m, e, N)

print(gift)
print(N)

# 输出略

大概就是用一次性密码本加密flag,将key再用rsa加密。rsa加密时用的是相邻素数,因此用yafu很快就能分解N得到p和q然后解密出一次性密码本的key。再用该key和c进行异或就能得到flag。当时就是通过这道题了解到了yafu这个工具。

解题脚本:

import gmpy2
from Crypto.Util.number import long_to_bytes
from Crypto.Util.strxor import strxor

#使用yafu.exe分解n得到pq
p = 12821668064849826676074701213910298504451620184307130249376361333490782040849300923713647818247010549622664747770828229853003308659470956068108542842690571
q = 12821668064849826676074701213910298504451620184307130249376361333490782040849300923713647818247010549622664747770828229853003308659470956068108542842690393
gift = 127749242340004016446001520961422059381052911692861305057396462507126566256652316418648339729479729456613704261614569202080544183416817827900318057127539938899577580150210279291202882125162360563285794285643498788533366420857232908632854569967831654923280152015070999912426044356353393293132914925252494215314
e = 65537
c = b'Just once,I will accompany you to see the world'

d = gmpy2.invert(e, (p - 1) * (q - 1))
m = pow(gift, d, p * q)
flag = strxor(long_to_bytes(m), c)
print(flag)

# moectf{W0w_y02_k5ow_w6at_1s_one_t1m3_pa7}######

0rsa0

题目:

from Crypto.Util.number import *
from flag import flag

assert flag[0:7] == b'moectf{'
assert flag[-1:] == b'}'
flag = flag[7:-1]
assert len(flag) == 32

m1 = bytes_to_long(flag[0:16])
m2 = bytes_to_long(flag[16:32])

def enc1(m):
p = getPrime(512)
q = getPrime(512)
n = p * q
e = 3
c = pow(m,e,n)
return n,e,c

def enc2(m):
p = getPrime(512)
q = getPrime(512)
e = 65537
d = inverse(e,(p-1)*(q-1))
n = p * q
dp2 = d % (p-1)
c = pow(m,e,n)
return n,e,c,dp2

n1,e1,c1 = enc1(m1)
n2,e2,c2,dp2 = enc2(m2)

print("n1="+ str(n1))
print("e1="+ str(e1))
print("c1="+ str(c1))
print("n2="+ str(n2))
print("e2="+ str(e2))
print("c2="+ str(c2))
print("dp2="+ str(dp2))

# 输出略

两个常见的rsa攻击套路。

flag被分为两段分别加密。enc1是低指数小明文攻击。由于$e$比较小(只有3),明文的$e$次方仍然小于$n$,加密得到的$c=m^{e}$。因此将c直接开e次方即可得到flag。解题脚本如下:

import gmpy2
from Crypto.Util.number import long_to_bytes

c1 = 1402983421957507617092580232325850324755110618998641078304840725502785669308938910491971922889485661674385555242824
e1 = 3

m = gmpy2.iroot(c1, 3)[0]
cleartxt = long_to_bytes(m)
print(cleartxt)

# T8uus_23jkjw_asr

enc2是dp泄露攻击。

import gmpy2
from Crypto.Util.number import long_to_bytes

n2 = 159054389158529397912052248500898471690131016887756654738868415880711791524038820158051782236121110394481656324333254185994103242391825337525378467922406901521793714621471618374673206963439266173586955520902823718942484039624752828390110673871132116507696336326760564857012559508160068814801483975094383392729
e2 = 65537
c2 = 37819867277367678387219893740454448327093874982803387661058084123080177731002392119369718466140559855145584144511271801362374042596420131167791821955469392938900319510220897100118141494412797730438963434604351102878410868789119825127662728307578251855605147607595591813395984880381435422467527232180612935306
dp2 = 947639117873589776036311153850942192190143164329999603361788468962756751774397111913170053010412835033030478855001898886178148944512883446156861610917865

for i in range(1, e2):
p = (dp2 * e2 - 1) // i + 1
if n2 % p == 0:
q = n2 // p
print(p)
print(q)
break

d = gmpy2.invert(e2, (p - 1) * (q - 1))
m = pow(c2, d, p * q)
print(long_to_bytes(m))

# _3d32awd!5f&#@sd

因此得到flag:

moectf{T8uus_23jkjw_asr_3d32awd!5f&#@sd}

Weird_E_Revenge

先看题目:

from Crypto.Util.number import *
import random
from secret import flag
table='abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'
pad=100-len(flag)
for i in range(pad):
flag+=random.choice(table).encode()
e=343284449
m=bytes_to_long(flag)
assert m>(1<<512)
assert m<(1<<1024)
p=getPrime(512)
q=getPrime(512)
r=getPrime(512)
print('p=',p)
print('q=',q)
print('r=',r)
n1=p*q
n2=q*r
c1=pow(m,e,n1)
c2=pow(m,e,n2)
print('c1=',c1)
print('c2=',c2)

# 输出略

flag经过填充后分别用n1、n2加密,而n1、n2的因数p、q、r都是已知的,似乎解密需要的参数都是已知的。先尝试用RSA基本解密方法解。

from gmpy2 import invert
from Crypto.Util.number import long_to_bytes
from libnum import solve_crt

e = 343284449
p = 11820891196647569262137841192985418014377132106496147254821784946481523526822939129065042819464351666077658751406165276121125571355594004514547517855730743
q = 10450390015864176713581330969519712299844487112687677452105216477861582967322473997670559995588440097951786576039009337782247912476227937589298529580432797
r = 9484954066160968219229920429258150817546418633451929876581842443665029377287119340232501682142185708534413073877473741393278935479791561681402673403009771
c1 = 69574855207460025252857869494766338442370688922127811393280455950372371842144946699073877876005649281006116543528211809466226185922844601714337317797534664683681334132261584497953105754257846471069875622054326463757746293958069752489458646460121725019594141157667480846709081917530190233900184428943585065316
c2 = 66183492015178047844987766781469734325646160179923242098082430373061510938987908656007752256556018402101435698352339429316390909525615464024332856855411414576031970267795270882896721069952171988506477519737923165566896609181813523905810373359029413963666924039857159685161563252396502381297700252749204993228

d = invert(e, (p-1)*(q-1))
m1 = pow(c1, d, p*q)
print(long_to_bytes(m1))

# Traceback (most recent call last):
# File "solve_weirdrevenge.py", line 12, in <module>
# d = invert(e, (p-1)*(q-1))
# ZeroDivisionError: invert() no inverse exists

在解密过程中无法找到$e$关于$(p-1)(q-1)$或者关于$(q-1)(r-1)$的逆元。不能用基本方式解密。这是因为$e$与$(q-1)$不互素,从而与$(p-1)(q-1)$和$(q-1)(r-1)$都不互素,因此解不出逆元。但观察题目可以发现相同的明文由不同的n1、n2分别加密了两次。用同余式可以表示成这样:$$m^{e}\ \equiv\ c_{1}\ mod\ n_{1}$$$$m^{e}\ \equiv\ c_{2}\ mod\ n_{2}$$

又$n_{1}=p\cdot q$,$n_{2}=q\cdot r$。由同余式的性质可以得到如下的同余方程式组:$$m^{e}\ \equiv\ c_{1}\ mod\ p$$$$m^{e}\ \equiv\ c_{2}\ mod\ r$$

对于这样的同余方程式组,可以用中国剩余定理(CRT)求出一个与$m^{e}$等价的特解$x$。然后可以用$e$关于$(p-1)(r-1)$的逆元解密$x$得到明文,从而避开与$e$不互素的$(q-1)$。

python的第三方库libnum有函数solve_crt可以直接调用。解题脚本如下:

from gmpy2 import invert
from Crypto.Util.number import long_to_bytes
from libnum import solve_crt

e = 343284449
p = 11820891196647569262137841192985418014377132106496147254821784946481523526822939129065042819464351666077658751406165276121125571355594004514547517855730743
q = 10450390015864176713581330969519712299844487112687677452105216477861582967322473997670559995588440097951786576039009337782247912476227937589298529580432797
r = 9484954066160968219229920429258150817546418633451929876581842443665029377287119340232501682142185708534413073877473741393278935479791561681402673403009771
c1 = 69574855207460025252857869494766338442370688922127811393280455950372371842144946699073877876005649281006116543528211809466226185922844601714337317797534664683681334132261584497953105754257846471069875622054326463757746293958069752489458646460121725019594141157667480846709081917530190233900184428943585065316
c2 = 66183492015178047844987766781469734325646160179923242098082430373061510938987908656007752256556018402101435698352339429316390909525615464024332856855411414576031970267795270882896721069952171988506477519737923165566896609181813523905810373359029413963666924039857159685161563252396502381297700252749204993228

list1 = [c1, c2]
list2 = [p, r]
x = solve_crt(list1, list2)
d = invert(e, (p - 1) * (r - 1))
m = pow(x, d, p * r)
print(long_to_bytes(m))

# moectf{Th1s_iS_Chinese_rEm41nDeR_The0rEm_CRT!}YWMZSTyfRvhjTCJuQCwALQBcWHFCgTDIZWJaxRUzBPCmFOnbDTRBau

Signin

题目:

from Crypto.Util.number import *
from secret import flag
m=bytes_to_long(flag)
p=getPrime(512)
q=getPrime(512)
print('p=',p)
print('q=',q)
n=p*q
e=65537
c=pow(m,e,n)
print('c=',c)

# 输出略

虽然是签到题,但却在很长一段时间内一直都没做出来。后来才从在HNCTF上遇到的cot1007师傅学到了此类情况的做法。

题目看起来很友好,加密用到的所有的数都直接给出来了。但$e$与$q-1$不互素,是无法用常规做法解出flag的。也不能像Weird_E_Revenge一样用中国剩余定理求解。

实际上解法很简单。虽然$e$与$q-1$不互素,但其实$m<p$。因此可以直接将$c\ mod\ p$,将问题变为$m^{e}\ \equiv\ c’\ mod\ p$,绕过有问题的$q$。解题脚本:

from Crypto.Util.number import long_to_bytes
from gmpy2 import invert

e = 65537
p= 12408795636519868275579286477747181009018504169827579387457997229774738126230652970860811085539129972962189443268046963335610845404214331426857155412988073
q= 12190036856294802286447270376342375357864587534233715766210874702670724440751066267168907565322961270655972226761426182258587581206888580394726683112820379
c= 68960610962019321576894097705679955071402844421318149418040507036722717269530195000135979777852568744281930839319120003106023209276898286482202725287026853925179071583797231099755287410760748104635674307266042492611618076506037004587354018148812584502385622631122387857218023049204722123597067641896169655595

c1 = c%p
phi = p-1
d = invert(e, phi)
m = pow(c1, d, p)

print(long_to_bytes(m))

# moectf{Oh~Now_Y0u_Kn0W_HoW_RsA_W0rkS!}


下面几道题先把自己的解题脚本贴在这,解题思路以后一定补完(

Smooth

解题脚本:

import gmpy2
from Crypto.Util.number import long_to_bytes

c = int(
'0x3cc51d09c48948e2485820f6758fb10c7693c236acc527ad563ba8369c50a0bc3f650f39a871ee7ef127950ed916c5f4dc69894e11caf9d178cd7e8f9bf9af77e1c69384cc5444da64022b45636eeb5b7a221792880dd242be2bb99be3ed02c430c2b77d4912bec1619d664e066680910317c2bb0c87fafdf25f0a2400103278f557b8eca51d3b67d61098f1ab68da072bb2810596180afbc81a840cd24efef4d4113235160e725a5af4824dc716d758b3bc792f2458e979398e001b27e44d21682e2ef80ae94e21cd09a12e522ca2e569df72f012fa40341645445c6e68c6233a8a39e5b91eb14b1ccfa61c9bad25e8e3285a22da27cd506ddd63f207517a4e8ede00b104d8806ff4c0e3162c3de69169d7e584952655272b96d39d242bb83019c7eab1ceb0b4b287591e1e0a5b6378e70340a82d3430c5925d215f31fda6d9d0bccea240591b22a3d0f6b5bf4ddf1243d71aca0fd53045c352c8c5497ebcdbd7ac11083d63aba7c053604fda2430c317a4e04702b5ad539e110f101165b21dcd9fdb5ba7324acdba6a506244ce7c911197dfe067441fe7488d164c050f45ef6476aaf399cedde1793cceb8c21d88ec8ecf5e17df27586713d7dd9566ec5023cfef75422b73e2d5a932c661b3cfdf9c4bda12b64380d2be1aa957c3e1416e068937bafe79b8cf303296792388e9c197702e11e7ded6088ae992d352b23a4a27',
16)
N = int(
'0xdc77f076092cbe81c44789ccfc1b2ca55eabae65f44cf34382799e8bbb42d4d6c032bd897c21df1da401929d82deb56264823a757f6cacf63e0037146026cbab32ab9e4abc783dcabaac2b7ccc439937be3ab0fbf149524ff29ef0fe6f27e45215d74b40597c70e8207159dc7f542c2a6828500016480053dfc2d8dbf8fcdf6700640184c8f3318f7aab2e17e116edf680592f5eae951159bb8c20cfbd0cbab8b4b95925b5068038d0377a55a4d346ebbf53a1c2943b7c17e1b9d4a1b77916da2e15140b05b96655906942a07d04b7e25fa7521b3b7ae26eda68375a8b8ef2d5b4704a28168b236de97f24a663f0d0a3aeab47767dfe75a21662f5f25ef7f7d4b25c90fd7bcdd7137c23f03b6ea4209f8fb9b4628355e6ad62e6467d26666d3d1b0e6f078c5f3866413a6fcd3c1dc2ff3a5ab286e339d5c72f4d2f0473a4faddcba6b031bb6ec226fd4b319834b5029f09ea0ffeb5b6ed182d5a13675571b6708c38299118043390343e2f79edebd2ae0e0a765a3aebf776f54ca983cdae8547547cfc8430f7222aefa77301d7cc7c03b1451b6603028b21fea869d35138a9c83919985a91b3fdfa934f25a442cc10349b0ed6f2ee3955d40249e8b3fb9f1955534ee06cee41a3ad2d6ff7dbdb0f01e47b9e4d04f65232f5579135ae035e8ba2d1fe6465a730dcc8b9ba3a558ab38f040ea510757d25e92f886c50c24ad967f1',
16)

def Pollards_p_1(N): # Pollards 光滑数算法
a = 2
n = 1
while True:
a = pow(a, n, N)
res = gmpy2.gcd(a - 1, N)
if res != 1 and res != N:
return res
n += 1

e = 65537
p = Pollards_p_1(N)
q = N // p
d = gmpy2.invert(e, (p - 1) * (q - 1))
m = pow(c, d, N)

for i in range(2, 1730): # 根据同余式的性质和Wilson定理推导
m *= (p - i)
cleartxt = m % p

print(long_to_bytes(cleartxt))

# moectf{Charming_primes!_But_Sm0oth_p-1_1s_vu1nerab1e!}

ezcbc

解题脚本:

from Crypto.Util.number import *

c0 = 748044282
c = [748044282, 2053864743, 734492413, 675117672, 1691099828, 1729574447, 1691102180, 657669994, 1741780405, 842228028, 1909206003, 1797919307]

IV = bytes_to_long(b'cbc!')
cleartxt0 = b'moec'
cleartxt = []

cleartxt.append(cleartxt0.decode('utf8', 'ignore'))
k = IV ^ bytes_to_long(cleartxt0) ^ c0

for i in range(len(c) - 1):
txt = long_to_bytes((k ^ c[i + 1] ^ c[i]))
cleartxt.append(txt.decode('utf8', 'ignore'))
print(cleartxt)

# moectf{es72b!a5-njad!@-#!@$sad-6bysgwy-1adsw8}

ezhash

babyNET

part1

解题脚本:

from Crypto.Util.number import long_to_bytes
from Crypto.Util.number import bytes_to_long
from Crypto.Cipher import AES

def encrypt(plaintext, key):
assert len(plaintext) == 32
assert len(key) == 16

left = plaintext[:16]
right = plaintext[16:]

for i in range(3):
aes = AES.new(key, AES.MODE_ECB)
new_right = long_to_bytes(
bytes_to_long(aes.encrypt(right)) ^ bytes_to_long(left))
new_left = right
left = new_left
right = new_right
return left + right


def decrypt(ciphertext, key):
assert len(ciphertext) == 32
assert len(key) == 16

left = ciphertext[:16]
right = ciphertext[16:]

for i in range(3):
aes = AES.new(key, AES.MODE_ECB)
last_right = left
last_left = long_to_bytes(
bytes_to_long(right) ^ bytes_to_long(aes.encrypt(left)))
left = last_left
right = last_right
return left + right


key1 = b'this_is_the_key~'
key2 = b'to_enlength_key!'
cipher = long_to_bytes(
int(0x8b6d863f3e89fd2698ff90e8409502ebf485e17449cdaceb6f5cb2e781524ce4))

decipher = decrypt(cipher, key1)
decipher = encrypt(decipher, key2)
decipher = decrypt(decipher, key1)
print(decipher)

# b'it_is_just_the_fy0u_can_d0_what?'

part2

解题脚本:

from Crypto.Util.number import long_to_bytes
from Crypto.Cipher import AES

class AES_CBC(object):

def __init__(self, key, iv):
self.key = key
self.mode = AES.MODE_CBC
self.iv = iv

def pad_byte(self, b):
bytes_num_to_pad = AES.block_size - (len(b) % AES.block_size)
return b + bytes([bytes_num_to_pad]) * bytes_num_to_pad

def encrypt(self, text):
cryptor = AES.new(self.key, self.mode, self.iv)
text = self.pad_byte(text)
self.ciphertext = cryptor.encrypt(text)
return self.ciphertext

def decrypt(self, text):
unpad = lambda s: s[:-ord(s[len(s) - 1:])]
cryptor = AES.new(self.key, self.mode, self.iv)
aesStr = cryptor.decrypt(text)
aesStr = str(unpad(aesStr), encoding='utf8')
return aesStr

encdata = long_to_bytes(int(0x1fe9c9b13b8af4f59857d9bc5df1bfbc9df34b8a4a33455b244fb0d857f98c3ea8a62b0190e7336dde76365e65e955c4))
K = b'it_is_just_the_f'
IV = b'y0u_can_d0_what?'
pc = AES_CBC(K, IV)
data = pc.decrypt(encdata)
print(data)

# Congratulations!_You_have_get_the_flag!

MiniMiniBackpack

解题脚本:

from Crypto.Util.number import long_to_bytes

key = [......] # 输入略
c = ......

L = len(key)
cleartxt = []
v1 = c

for i in range(L):
if v1 - key[-1 - i] < 0:
v1 -= 1
cleartxt.append(0)
if v1 - key[-1 - i] >= 0:
v1 -= key[-1 - i]
cleartxt.append(1)

print(long_to_bytes(int(''.join(map(str, cleartxt)),2)))

# moectf{Co#gRa7u1at1o^s_yOu_c6n_d3c0de_1t}

不止一次

Misc

Hamming

解题脚本:

noisemsg = [......] # 输入略
flag = '0'

for i in range(len(noisemsg)):
tmp = noisemsg[i]

check1mem = 0
check1 = [
tmp[1], tmp[3], tmp[5], tmp[7], tmp[9], tmp[11], tmp[13],
tmp[15]
]
if check1.count(1) % 2 == 0:
pass
else:
check1mem = 1

check2mem = 0
check2 = [
tmp[2], tmp[3], tmp[6], tmp[7], tmp[10], tmp[11], tmp[14],
tmp[15]
]
if check2.count(1) % 2 == 0:
pass
else:
check2mem = 1

check3mem = 0
check3 = [
tmp[4], tmp[5], tmp[6], tmp[7], tmp[12], tmp[13], tmp[14],
tmp[15]
]
if check3.count(1) % 2 == 0:
pass
else:
check3mem = 1

check4mem = 0
check4 = [
tmp[8], tmp[9], tmp[10], tmp[11], tmp[12], tmp[13], tmp[14],
tmp[15]
]
if check4.count(1) % 2 == 0:
pass
else:
check4mem = 1

check0mem = [check1mem, check2mem, check3mem, check4mem]

if check4mem == 1:
if check3mem == 1:
if check2mem == 1:
if check1mem == 1:
errorplace = 15
else:
errorplace = 14
else: #c2==0
if check1mem == 1:
errorplace = 13
else:
errorplace = 12
else: #c3==0
if check2mem == 1:
if check1mem == 1:
errorplace = 11
else:
errorplace = 10
else: #c3==0
if check1mem == 1:
errorplace = 9
else:
errorplace = 8
else: #c4==0
if check3mem == 1:
if check2mem == 1:
if check1mem == 1:
errorplace = 7
else:
errorplace = 6
else: #c2==0
if check1mem == 1:
errorplace = 5
else:
errorplace = 4
else: #c3==0
if check2mem == 1:
if check1mem == 1:
errorplace = 3
else:
errorplace = 2
else: #c2=0
if check1mem == 1:
errorplace = 1
else:
errorplace = 0

if tmp[errorplace] == 1:
tmp[errorplace] = 0
elif tmp[errorplace] == 0:
tmp[errorplace] = 1

cleartxt = [
tmp[3], tmp[5], tmp[6], tmp[7], tmp[9], tmp[10], tmp[11],
tmp[12], tmp[13], tmp[14], tmp[15]
]

for k in range(len(cleartxt)):
cleartxt[k] = str(cleartxt[k])

flag+=''.join(cleartxt)

cleartxt = ''
for i in range(len(flag)//8):
cleartxt += chr(int(flag[8*i:8*i+8], 2))
print(cleartxt)

# Once upon a time, there were 1023 identical bottles, 1022 of which were plain water and one of which was poison. Any creature that drinks the poison will die within a week. Now, with 10 mice and a week, how do you tell which bottle has poison in it?
# moectf{Oh_Bin4ry_Mag1c_1s_s0o_c0O1!} Great!

评论