RaRCTF 2021 Writeup

この大会は2021/8/7 02:00(JST)~2021/8/10 02:00(JST)に開催されました。
今回もチームで参戦。結果は1920点で845チーム中104位でした。
自分で解けた問題をWriteupとして書いておきます。

minigen (crypto 100)

難読化されていて、暗号処理が難しい。何回か試すと、最初のnext(g)はランダムな値になるが、次以降のnext(g)は最初の値が決まると同じ値になる。フラグが'r'から始まることを前提にこの結果を使って復号する。

#!/usr/bin/python3
exec('def f(x):'+'yield((x:=-~x)*x+-~-x)%727;'*100)
g=f(id(f))

with open('output.txt', 'r') as f:
    enc = [int(x) for x in f.read().rstrip().split(' ')]

flag = 'r'

while True:
    if next(g) ^ enc[0] == ord(flag):
        break

for i in range(1, len(enc)):
    flag += chr(next(g) ^ enc[i])

print(flag)
rarctf{pyg01f_1s_fun}

sRSA (crypto 100)

一瞬パラメータでRSA暗号に見えるが、暗号はct = (flag * e) % nとなっている。この暗号の式を変形すると、以下のようになることを使って、逆算すればよい。

flag = (ct * inverse(e, n)) % n
from Crypto.Util.number import *

with open('output.txt', 'r') as f:
    n = int(f.readline().split(' ')[-1])
    e = int(f.readline().split(' ')[-1])
    ct = int(f.readline().split(' ')[-1])

flag = (ct * inverse(e, n)) % n
flag = long_to_bytes(flag)
print flag
rarctf{ST3GL0LS_ju5t_k1dd1ng_th1s_w4s_n0t_st3g_L0L!_83b7e829d9}

unrandompad (crypto 150)

$ nc 193.57.159.27 24431
e: 3
n: 14174549034167001838553639244441993402272196402134871564313609220466901319587051081067669507788543070884496093160997332834787301608827637590884971773110573268882540771580979353802022911078124295443887833794738680245053087215981343238615552434398462979746673989866598083559237568217403872467252934361427116295965538256310492458191203857546600858369211580498455946305323127350608956796924277613129045594864138089136018275430444556257142344879835865345756900762093526897962812015488439309424479277819853137208061867061679295566608563560413475720570385105551003138694323463007083111269182659567046343726658081010925567713
[1] enc()
[2] enc(flag)
[3] quit

opt: 1
msg: 1
c: 1
[1] enc()
[2] enc(flag)
[3] quit

opt: 2
c: 11678954079645559221430200140201456531641464717449433679887246431026189279868984447800261651103892829673485177099182262607748212131933353764840966580819005085681490510964499535034546625722168805043933926717257314908005373084367414105661488842269735929147287203590956364122801281250824764268095179434465992258249652548237477174388056131073114119940649603293032397282955571641155257389334075966229094776705208276336880667914838988686660485377276998006781670816122987662299963431610131827953741100982791416220236756166365496716585837413326810417112511159661228440005942263892215683110265809116998738667263437066041948442
[1] enc()
[2] enc(flag)
[3] quit

サーバの処理概要は以下の通り。

・e, n = keygen()
 ・primes = []
 ・e = 3
 ・2回以下繰り返し
  ・p: 1024bit素数
  ・条件:(p - 1) % 3 != 0
  ・primesにpを追加
 ・e, primes[0] * primes[1]を返却
・以下繰り返し
 ・メニュー表示
 ・1選択
  ・数値入力
  ・encrypt(入力数値, e, n)
   ・res = pad(入力数値, e, n) ※使われない。
   ・pow(入力数値, e, n)を表示
 ・2選択
  ・encrypt(フラグ, e, n)
   ・pow(フラグ, e, n)を表示

1回接続し2選択でフラグの暗号化データを入手する。再度接続し、2選択でフラグの暗号化データを入手することを繰り返し、全部で3つのnとフラグの暗号化データを入手する。これでHastad's broadcast Attackで復号できる。

import socket
from sympy.ntheory.modular import crt
from Crypto.Util.number import *

def recvuntil(s, tail):
    data = ''
    while True:
        if tail in data:
            return data
        data += s.recv(1)

def inv_pow(c, e):
    low = -1
    high = c+1
    while low + 1 < high:
        m = (low + high) // 2
        p = pow(m, e)
        if p < c:
            low = m
        else:
            high = m
    m = high
    assert pow(m, e) == c
    return m

ns = []
cs = []
for _ in range(3):
    s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    s.connect(('193.57.159.27', 24431))

    data = recvuntil(s, 'opt: ')
    print data[:-1],
    n = int(data.split('\n')[1].split(' ')[-1])
    ns.append(n)

    print '2'
    s.sendall('2\n')
    data = recvuntil(s, '\n').rstrip()
    print data
    c = int(data.split(' ')[-1])
    cs.append(c)

    s.close()
    print

e = 3
a = int(crt(ns, cs)[0])
m = inv_pow(a, e)
flag = long_to_bytes(m)
print flag

実行結果は以下の通り。

e: 3
n: 17690792369833830581294030354064657819793033968742394381473441164328607116131439026304840955330998880053979467333674015235589754341440242587814306326385104447366581680310847600129338913960925244623466709233532608926523235504676377618228886488098959846849915819008565944371571550688717265029683796262582881992297586604715545050868640954351780154297216748106472052700649720708421381702242770580655551440468310131342770011681270718670545048268454137838169479380907893650680060192536623694302990498705431456503814819985515285382847745832225946897720536680231715838942847631952446071639956611959586020477911971358674565571
[1] enc()
[2] enc(flag)
[3] quit

opt: 2
c: 11769278930632630366140360322679012958370363575442991116507369728984151920063051851302181273136390146283578788628854341021639800553395193523183213473758318236336182729054070419856221520327203295349788850220093600086041324579095613920021287328622880133961875851647373219880653609507245480330434948395226557930210217540944142881926700647209334218374305656486022825008665733283323596731278495902165372991134499092754542038533983834453290312925323626001066939669396120844681231659245562908250799873118803731265597872482120386304395484265304742894650829513983198318130745103274644940266933448658420844411247529828604649057

e: 3
n: 23500984636524522936718025289856380998692826835898022519809647099465606207386965691273581993390918013901366826129904165148125758265958770473983823370033526758482791984662888429536738350929856875953805177930658728432666716653535059851145598651486826418044249410507585899901226329190550109551146548779013404511947737875069785458715832745459561544464654400197466100278935918661194385220098242969506273386964441138148103748654048913034837496692201524022344040746060648101593154958679057100595680875021675815306278589982428022683055052275085580832758939985355378884575783440768412997365301864205099546724517177109017041161
[1] enc()
[2] enc(flag)
[3] quit

opt: 2
c: 20419257417138538464692239861056265365836312205455134797989203607404459091608158715141630119119425576418773507532790126178991838963630757197366268128082010814735020246051515419362172720930888759750937546251927816542659554543417853022632506782261549093856224840663355104980154464875029738993771591825248221050092525011794473884469196792713051285316231633744180309553150639562641355803656423870967143311815699361265953149134482860435465038047691202173749134653890511534164830354953815387431927041969703572217722328489594701409180234550085284156037470632093230489739239831762678574126792907891628129296174874420264327723

e: 3
n: 16970885632571038154066623391234201125155702089121576538840593499372396369100528376694497439151429542465496206899547547510916886479286308090719287906691384016524372039756391840094100378457830378154903624219290887431142516955715725281031263533987296823713371920826667814676653028388815205120333567245303691587198998830095030660902687598314704565233639236204907708496394598018342673600918918591978338996826637260857713474539043892369700376389443069501528290734637781677139994867353472179740834168878126167060959417457770115225740945780246668270525995678016544634364994306165749003403906921828918355938461674014970626351
[1] enc()
[2] enc(flag)
[3] quit

opt: 2
c: 74121005500491571244189962266093170881441737665147084155231422315541816151721278091812443368963839819993484754732138289666495365042507468631832982412631929285676912010986845939691080486797942798540801591515103728970588639712370622459194392496292882258873188659641335626101822369446189717939256310505569467310117093501195633994277790308707685647171633782958484406685290585057799594898831711830854687538081316239819139776957770837020002846650373628376042013860001257937897293253626480609042675643410450066894476315826529816780533710629437175089148546770607288042121926748526764741883964395415441691431615911843505457

rarctf{https://cdn.discordapp.com/attachments/751845431063085160/866641917714235392/unknown.png_8538853c64}
rarctf{https://cdn.discordapp.com/attachments/751845431063085160/866641917714235392/unknown.png_8538853c64}

Shamir's Stingy Sharing (crypto 200)

サーバの処理概要は以下の通り。

・poly: 30個の128bitランダム整数の配列
・random.seed(poly[0])
・フラグと同じサイズのランダム文字列とフラグをXORしてhex表示
・x: 数値入力(1以上の整数)
・poly[i] * pow(x, i)の和を表示

x = 2**128の場合、最後の数値の計算は以下となる。

poly[0] + poly[1] * pow(2**128, 1) + ... + poly[29] * poly(2**128, 29)

この値を2**128で割り算した余りはpoly[0]になり、seedの値がわかるため、XORの鍵が算出できる。あとはXORの鍵からフラグを復号すればよい。

#!/usr/bin/env python3
import socket
import random
from Crypto.Util.number import long_to_bytes

def recvuntil(s, tail):
    data = b''
    while True:
        if tail in data:
            return data.decode()
        data += s.recv(1)

def bxor(ba1,ba2):
    return bytes([_a ^ _b for _a, _b in zip(ba1, ba2)])

s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect(('193.57.159.27', 25057))

data = recvuntil(s, b'\n').rstrip()
print(data)
enc = bytes.fromhex(data)

x = pow(2, 128)
data = recvuntil(s, b'ONE. ')
print(data + str(x))
s.sendall(str(x).encode() + b'\n')
data = recvuntil(s, b'\n').rstrip()
print(data)

seed = int(data) % x
random.seed(seed)
flag = bxor(enc, long_to_bytes(random.getrandbits(len(enc) * 8))).decode()
print(flag)

実行結果は以下の通り。

30a645c4c7a44173e087790c0944347a67b11d10e75a48a3290ab713bc96f1
Take a share... BUT ONLY ONE. 340282366920938463463374607431768211456
6414580899551693438248684242372185498107107182106144301437456546799924446810982621807170867756256652194746915125320288178446194341578487618024960192051335454378164052454172118419463623356731365636120460634725878489134250685033185311256545259292551152417753514416618171975621385697093813971520824983588418424369413191194262050675638861335694588465216634161197192444463225749959596061726758050935583707933125972516661570510340611943687206573617829354703532445598591616769114660637513015282158462850352084819510946777746810495277554159895881015105403389983548870421938893008400760405821377802028931175989526826387718981752644155915927243207183308141126843759618422408006852748727880506644930948563928567858558252586375136756230371581872796551831073348144395541704661419341283086596288429830168061087842364970359816414423141796685842404957744194764115892083461898396714592727375016761178456329672076984886951963647862666988674054234676132581616130956088336332509110248126691901763111175319927843365573735227683856804530707164179926011979123973350100440750126949603928776323787244750895056566725607046689802182179277041035156874963619282605770161897746196204237
rarctf{n3v3r_trust_4n_1nt3g3r}
rarctf{n3v3r_trust_4n_1nt3g3r}

babycrypt (crypto 200)

$ nc 193.57.159.27 60123
pubkey: (65537, 7879675942254289575557643609782735539999957680119218608027040720916713649630670542847097861824296657151309634069573019997895948784642051781229759637504441)
hint: 18576186563444887452357796752365552662327376465939712421422409143952993108001
c: 3465129272161361820331681229952786391735790238384244602960070431070200038820353354470914015958364514702761776469451406145316631076456507540636249910142012

通常のRSA暗号。ただhint情報として以下の値が表示される。

hint = n % (q - 1)

コードからp > qであるため、以下のようになる。

n = p * q = p * (q - 1) + p
→ n % (q - 1) = p % (q - 1) = hint

またpとqは両方とも256bit素数のため、以下のようになる。このとき、p = (q - 1) * 2 + hintなどの可能性もあるが、まずは1倍で考える。

p - (q - 1) = hint 
→ q = p - hint + 1
→ n = p * (p - hint + 1)

この2次方程式を解けば、pがわかる。あとはそのまま復号していけばよい。

import socket
from Crypto.Util.number import *
from sympy import *

def recvuntil(s, tail):
    data = ''
    while True:
        if tail in data:
            return data
        data += s.recv(1)

s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect(('193.57.159.27', 60123))

data = recvuntil(s, '\n').rstrip()
print data
pubkey = eval(data.split(': ')[-1])
data = recvuntil(s, '\n').rstrip()
print data
hint = int(data.split(' ')[-1])
data = recvuntil(s, '\n').rstrip()
print data
c = int(data.split(' ')[-1])

e = pubkey[0]
n = pubkey[1]

p = Symbol('p')
eq = Eq(p * (p - hint + 1) - n, 0)
ans = solve(eq)

for p in ans:
    if p > 0:
        p = int(p)
        break

q = n // p
assert p * q == n

phi = (p - 1) * (q - 1)
d = inverse(e, phi)
m = pow(c, d, n)
flag = long_to_bytes(m)
print flag

実行結果は以下の通り。

pubkey: (65537, 6294679377574700932382797677533955609651946475262561570821499591102008747232818517556238944691117818534674958303452909284931763790703561864535554250327871)
hint: 14484698452125331244247054195851692050069145251823171700683678383137933623955
c: 5014157281661206563883389112112731916660463668620202955642182754509896102739028135465351249728470762294134667794755921334712623783524156621593062942085021
rarctf{g3n3r1c_m4th5_equ4t10n_th1ng_ch4ll3ng3_5a174f54e6}
rarctf{g3n3r1c_m4th5_equ4t10n_th1ng_ch4ll3ng3_5a174f54e6}