Attack All Around

今やCTFと競プロばかりだが、トビタテ生のアメリカ留学やTOEFL奮闘記

DawgCTF 2021 writeup

解けそうで解けなくて、解法見て解けるか!!!ってなった。

DawgCTF 2021

Results

f:id:partender810:20210509134630p:plain
久しぶりの2桁順位!

f:id:partender810:20210509134723p:plain
あと数問解きたかった


Writeup

Misc

DawgCTF Discord 5pt

Please join the DawgCTF Discord using the link below:

https://discord.gg/TbJQXJb6

You can use it for discussing challenges, getting assistance from challenge authors, and networking with our sponsors! The flag for this challenge is located in the #flags channel.

開催前からflagが見えてた…。

DawgCTF{3nj0y_th3_c0mp3t1t10n!}


Two Truths and a Fib 100pt

Can you catch the fibber?
nc umbccd.io 6000

3つの数字が渡されて、その内一つだけあるフィボナッチ数を答えればOKです。

時間制限があるので、毎回フィボナッチ数列を計算してる暇はありません。サーバに繋ぐ前に計算しておきましょう。

def fibo(n):
    fib = [0,1]
    for i in range(2,n):
        x = fib[i-1] + fib[i-2]
        fib.append(x)

    return fib

fib = fibo(10000)
#print(fib)
#HOSTはIPアドレスでも可
HOST, PORT = "umbccd.io", 6000
s, f = sock(HOST, PORT)
for _ in range(10): print(read_until(f))
cnt = 0
for i in range(100):
    recv_m = read_until(f).split()
    #print(recv_m)
    a = int(recv_m[0][1:-1])
    b = int(recv_m[1][:-1])
    c = int(recv_m[2][:-1])
    #print(a,b,c)
    #print(read_until(f,">> "))
    if a in fib: s.send(str(a).encode()+b"\n")
    elif b in fib: s.send(str(b).encode()+b"\n")
    else: s.send(str(c).encode()+b"\n")
    for _ in range(2): read_until(f)
    cnt += 1
    print(cnt)

while True: print(read_until(f))

DawgCTF{jU$T_l1k3_w3lc0me_w33k}


Crypto

Really Secure Altgorithm 150pt

I like my e's like I like my trucks: big and obnoxious
ファイル:reallysecure.txt

n: 1063494238636905330671898279123020701722241177838742822812173978727720269828464796177466331816675300997219760473399150899338190503499441304612339501295713174906319744094945565844664372365921409430229356934682156557249826723147031652843433859344718768493183522524995480377138743798310313783408725321419870843554822150601536373735923419276343616677440442774544203945706641152517137477442684440329779076981535293867470891276594740058202983415251883426242386508849130959905432961654910957147313116759921173654729071152981682554792584462863534617943384988632032130835087976957452863581161399454295389753849954195624356779281196493728732643445649356033158461867533398892265000228558146288424480232820613034689816560319929705959290376265550914058448343308161173100473161643834475548888676356572581129193395124610558172636505697071928778350452726229098387020587814634712035171712313035012109421792643188405752849278190287414108308734638519593282032082768153331276317440224645157072560878195004847185217741752846484430459047014205368551175641186962966731731946128786111994668528579102737764964521437485037695161775036622411218739549286577109028626220150452705854596994751235894610227300222070678106023292138580496517177268042770934391185798181598618563332872419401223903806812404310665174941843727792999745655534108889130325189241267039092501129173520194489329592776789648244263220437261594447066833175026748830694496235756029688061559449109400248449366143822446893851310444152168531390880512280359096438303124398155397910138799660941243464476642041104225318910175143988510614445494598098558426300612294667831401095538851181871031466580808942102239297182977785401087460226345045290147371931284725756179151791539310603340196586480494033673522637677423221202352493653286430691931273676649062037570851083535722738207802574643773975006788646467981693396925922930573766914743566111012462215653872417726475122775377641591778444141816733462035690735543990556767891443301312941168828619850007793197693295002346977318117653857994731382292035666024397790972920502626243999541832942059274728220802530163223188484361653845185336386588669397688474323385816925410493569923865462650449548121898936835205060632513390578074550881170405889665319159308800795056447244869407145217360018494614236328487464266591617854909647808315406639117270321158016494893469025866752746911948790708005075752364953010067274475470453957941422189404716860354111166203043679764568407375052809648827400302926099178569
e: 322080206518256091443899533297838582806903462189212623492459529527398362853578807723331748892091281476489691674322396825893568981731186597175657851460964692083587224231830304595753200276915353388440323973696723177120007866661510911934423352216586106031397002127519163858107192766128665700540985814443511274004469695128927172454976219787146706562954392698315026949257322529441349029783228167181158744356828575460114272675952388130344874175195393881248661753342888300368969470477541152888408256683251028110005741172636776279619483668723660512026112365800539035538500635904281702733475127339140385714006560153071610279780303018848372325359598739283968138816333125764253403325773002607652913882484078902775827169048401031393263955166695217841400017855979724317225872294531492451624247032809524082714281043873127461832051383511298796820369453358960824162684362741938604084210435623099328622028419710290325683380378726085007158903982932912214314158223921219724759717266136246703830446993309980595073110001804483058339461412460693911416430728558495048873597685942089531373734578638349738930086910038003088294940942692030998047041393152747526278088574238755027474019265539054527491401757165011505470582647900401492273402847703170162847259159161319094910753659832147964969052296859561769298825881593753592121708897035728873795159475926749806998737812501868665513946666352941497086651818553871606417281352599234688183547212675353626023151426982640664474136377374110023532481101565870359846621748326349516467938614155834462639061592390266451169971250010491497379073868786106821570448253182042906240682833067783409574735400739329311810053094530811477002973464432651755811246151509011287858077298295987954915889199100328695730233096226912526329144478198121096489396083876129542516602969866961376423685647767885680559757094208574124411496017291060228388949556065235333802142865557844913535276572535282671404020237763405558477020152910105019008364237315330047605257380696367871417207254833979064342650664181309067142909106945469319731754805506564282047041605728503555870882010025649797753726253285119740979484849951129514070748168270413416940958393138417596025358589062839735425553556206423183484639265605269615685651949641759227283257819425264608389110223455267792764547470141745830149226062457331548317230637497633273069300415564503833751637575125936072041989787691982221885384446295804003751739608564016981200019839941768866474797817202494560129096305497153712068566001154013937
c: 329889278578044016824313741527705229624826354380113199851837764563746872233807021113693371778072747023303193661391256917654673579748983619101229337776995574989101525295578632981918777232038222679949264372167418981038519164359046193397794833575692294838270919137212503594644756884879905102382013616716795766055806380675079122193261937202152727372307035197702671407008933906723580158843896939160889881874945976423829414877735269690727711347872615864084627631956403177338185780100778564548976884299086453421725163428017908949325966904530291069025584097022695816511626589485257615664532774194555809017763622197728156453680059300808277471558450818004384751746190317910501772671219117514746584045928056487904112720801176609889740173288130073788687010544220250814378467249611243953690831406523455960639957029937819775398561228599467536715020954136970283137688613486109370883547218314163119613810764259334933209435078926856747403933578685724271075988136268967520808025339001863614193092075106995811355116213778057037256625729238040020810096266917394213617319914026291093309897483557317625696133298013326746629673265558468135602690674704939910172338556035967840157228859997765219241095551758253889312610691956445984657535082546460420349808372702307807697037778668585720318640246334216650054353036505301550387620089144331383076791604944171531121861009872807022569971425034887955393207445086587528972631782104261610625226982484798915695532492666822649105680868782554501246818156815043534857204078057748607289822387462529373683511672270708474273078574153649263666927268413520984191265086647728912692418609093325194826161869428270138209430215739290181617579745939639392608498596400274014103435747462262045586624613109970954762445247628187031774393639286689201449970646288560996969456145518290732375783779950601901268751888374247634804346090070762202809312421725537938059723148831745384765961875359917754708570262909323774973728101735046489385116839098154905761289565030660932858839402457684704605894701939226586411257561719440368089980555960049063794123068432799043630558103308335378100690170353973384441557259766075780510887009923794374174414344793891145106172614982174022423725641446878993111773629101974963001417653742183922637679467704643683488299451383820099923197374567580088833681469257525555566554059017269673597621231456370183587051700138951722854738823417346171701112221512801669470086625272428387110466009926633732340715338158014022960380535876415340423270463298180055

eが大きいのでWiener's Attackですね。

あれ?出ない…。

他のWiener用のプログラムをいくつか試してみましたがダメ…。ダメ元でnをfactorDBに投げると平方数だったことが判明。あとはφ(n)の計算に気を付ければOKです。

φ(n) = φ(p2) = p×(p-1) (=n-p)

φ(n)とはnと互いに素になる自然数n以下の個数になります。今回はn=p×qではなく、n=p×pです。p2に互いに素にならないのはpの倍数だけでその個数はp個です。故にφ(n)=p×(p-1)です。

p = (省略)

assert p*p == n

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

DawgCTF{sm@ll_d_b1g_dr3am5}


The Obligatory RSA Challenge 200pt

Would you believe last year someone complained because we didn't have any RSA challenges?
ファイル:rsa.txt

n = 475949103910858550021125990924158849158697270648919661828320221786290971910801162715741857913263841305791340620183586047714776121441772996725204443295179887266030140253810088374694440549840736495636788558700921470022460434066253254392608133925706614740652788148941399543678467908310542011120056872547434605870421155328267921959528599997665673446885264987610889953501339256839810594999040236799426397622242067047880689646122710665080146992099282095339487080392261213074797358333223941498774483959648045020851532992076627047052728717413962993083433168342883663806239435330220440022810109411458433074000776611396383445744445358833608257489996609945267087162284574007467260111258273237340835062232433554776646683627730708184859379487925275044556485814813002091723278950093183542623267574653922976836227138288597533966685659873510636714530467992896001651744874195741686965980241950250826962186888426335553052644834563667046655173614036106867858602780687612991191030530253828632354662026863532605714273940100720042141793891322151633985026545935269398026536029250450509019273191619994794225225837195941413997081931530563686314944827757612844439598729054246326818359094052377829969668199706378215473562124250809041972492524806233512261976041
e = 65537
c = 402152770613351738677048755708324474554170176764376236321890073753918413309501149040535095814748232081435325267703210634002909644227960630174709988528642707754801508241021668904011536073077213912653767687757898322382171898337974911700337832550299932085103965369544431307577718773533194882182023481111058393084914882624811257799702110086578537559675833661097129217671283819819802719020785020449340858391080587707215652771744641811550418602816414116540750903339669304799230376985830812200326676840611164703480548721567059811144937314764079780635943387160912954258110357655610465371113884532394048454506662310124118115282815379922723111955622863507979527460353779351769204461491799016534724821436662464400182076767643570270346372132221638470790194373337215168535861219992353368908816850146790012604023887493693793270280077392301335013736929937492555191042177475011094313978657365706039774511145223613781837484571546154539993982179172011867034689022507760853121804219571982660393205589671062476958539437099789304135763092469236641459611160765143625998223459045923936551054351546033776966693997323972592968414107451804594097481574453747907874383069514662912314790514989026350766602740419907710031860078783498791071782013064557781230616536

情報がこれしかないのでとりあえずnを素因数分解してみます。これも平方数でした。先の問題と同じですね。

先の問題、他にも方法があったのかな?

DawgCTF{wh0_n33ds_Q_@nyw@y}


TrashChain 250pt

It seems that my problems with hashing just keep multiplying...
nc umbccd.io 3100
ファイル:trashchain.py

chains[0]に値1つ、chains[1]に値4つ入れるとします。それぞれ、v0, v11, v12, v13, v14とすると以下の式が成り立つ時、Hash値が同じになりFLAGがもらえます。

(v0+1)B mod A = (v11+1)B × (v12+2)B × (v13+3)B × (v14+4)B mod A

ここで左辺を24 = 16になるようv0を調整します。右辺を2×2×2×2になるようv1*を調整します。まず左辺について考えていきましょう。

16φ(A)+1 = 16 mod A がオイラーの定理より成り立ちます。k×B = 1 mod φ(A)とすると、
16kB = 16 mod A となります。故に、(v0+1) = 16k mod Aなのでv0を求められました。

v1*に関しても、2φ(A)+1 = 2 mod A を利用して求めます。

ここでtrashchain.pyの48行目のif文より、v0がどのv1*よりも大きい場合弾かれてしまいます。なので、弾かれたら24 → 34と変えそのif文が通るまで試して終了です。

A = 340282366920938460843936948965011886881
B = 127605873542257115442148455720344860097
a1 = 18446744073709551533
a2 = A//a1
assert a1*a2 == A
phia = (a1-1)*(a2-1)
k = inverse(B,phia)
#HOSTはIPアドレスでも可
cnt = 2
while True:
    HOST, PORT = "umbccd.io", 3100
    s, f = sock(HOST, PORT)
    for _ in range(8): print(read_until(f))

    #phase1
    print(read_until(f))
    print(read_until(f,"> "))
    x = cnt**4
    t = pow(x,k,A)
    t -= 1
    s.send(str(t).encode()+b"\n")
    t += 1
    assert pow(t,B,A) == x

    print(read_until(f,"> "))
    s.send(b"done\n")

    #phase2
    print(read_until(f))
    temp = pow(cnt,k,A)
    for i in range(4):
        temp -= 1
        print(read_until(f,"> "))
        s.send(str(temp).encode()+b"\n")

    print(read_until(f,"> "))
    s.send(b"done\n")
    recv_m = read_until(f).split()
    print(recv_m)
    if "smallest" in recv_m:
        s.close()
        cnt += 1
    else:
        break 

while True: print(read_until(f))

DawgCTF{We1rd_RSA_2nd_Pre1m4g3_th1ng}


Unsolved

チャレンジしたけど解けなかった問題をご紹介します。

Crypto

cookin the ramen 50pt

Apparently we made cookin the books too hard, here's some ramen to boil as a warmup: .--- ...- ...- . ....- ...- ... ..--- .. .-. .-- --. -.-. .-- -.- -.-- -. -... ..--- ..-. -.-. ...- ...-- ..- --. .--- ... ..- .. --.. -.-. .... -- ...- -.- . ..- -- - . -. ...- -. ..-. --- -.-- --.. - .-.. .--- --.. --. --. ...-- ... -.-. -.- ..... .--- ..- --- -. -.- -..- -.- --.. -.- ...- ..- .-- - -.. .--- -... .... ..-. --. --.. -.- -..- .. --.. .-- ...- ... -- ...-- --.- --. ..-. ... .-- --- .--. .--- .....

恐らくモールス信号なのでしょう。

JVVE4VS2IRWGCWKYNB2FCV3UGJSUIZCHMVKEUMTENVNFOYZTLJZGG3SCK5JUONKXKZKVUWTDJBHFGZKXIZWVSM3QGFSWOPJ5

ここから分からん…。

結果、モールス信号 → Base32 → Base64 → Base58でした。こんな複雑なのに200以上のsolves…。


What the Flip?! 300pt

Hackers have locked you out of your account! Fortunately their netcat server has a vulnerability.
nc umbccd.io 3000
This netcat server is username and password protected. The admin login is known but forbidden. Any other login entered gives a cipher.

AESのCBCモードでの暗号化/復号です。

まず任意の文字列を暗号化してくれますが、"admin&password=goBigDawgs123"が含まれる文字列だけは暗号化してくれません。

次に、任意の文字列を復号してくれます。unhexlifyされるよう16進数で送る必要があります。復号後、平文に"admin&password=goBigDawgs123"が含まれていたらflagを表示してくれます。

CBCモードなのでPadding Oracle Attackなのかなといつものサイトで勉強。CTF4年目にしてようやく理解できた気がする。けどまあ、今回は復号後の平文を教えてくれないから無理かな?

p1 = "admin&password=g", p2 = "oBigDawgs123\x04\x04\x04\x04"としてenc(p2)を求めれば行けそうでした。p1に対するc1が分かれば、enc(p2) = c1p2で求まります。しかし、サーバに文字列を送る際に上手くいっていないようで(str->bytesで送っているので、もともとbytesは上手く送れない?)、FLAGは手に入りませんでした。考え方は合っていそうなのに。

keyとiv, randomと言っているのに毎回同じ値になっているのも気になる。

この問題のwriteupを探しています!みつけたら教えてください。

What the Flip?! - CTFs

byte flipping attackを利用して暗号文を作るようです。先頭16文字を犠牲にして後ろ28文字を"admin&password=goBigDawgs123"にします。

"admin&parsword=goBigDawgs123" で暗号化してもらい、得られた暗号文の"r"の16bytes前の部分をord("r")ord("s")でxorします。そうすると、rだった部分がsになり、flagがもらえます。

Problems&solver

GitHub - ksbowler/DawgCTF_2021

WaniCTF'21-spring Crypto Writeup


大阪大学のCTF サークル Wani Hackaseさんが開催してくださりましたWaniCTFのWriteupです!初心者向けのCTFということもあり、かなり面白かったです。

WaniCTF'21-spring - WaniCTF'21-spring

今回はCrypto問について書いていきます。他の分野も少しだけ解けたのですが、もう少し出来るようになりたいですね。

Crypto問のOUCSは解き方に加え証明も書いているので、是非とも読んでいただければと思います。分かりやすく書いたつもりですが、不明瞭な点がありましたら、コメントしていただけますと嬉しいです。

f:id:partender810:20210501004041p:plain
全完達成!


Simple conversion [Begineer]

戻し方を忘れました…
ファイル:cry-simple-conversion.zip

convert.pyを見るとバイト列を整数にしているようですね。とりあえずlong_to_bytesしてみたらflagが出てきました。little endianならto_bytesを使えば良さそうですね。

from Crypto.Util.number import *
x = 709088550902439876921359662969011490817828244100611994507393920171782905026859712405088781429996152122943882490614543229
print(long_to_bytes(x))

FLAG{7h1s_i5_h0w_we_c0nvert_m3ss@ges_1nt0_num63rs}


Easy [Easy]

手始めに
ファイル:cry-easy.zip

encrypt.pyを見ると、[A-Z]を[0-25]に対応させてそれをxに入れ、a*x+b mod 26の式で暗号化しています。それの結果がoutput.txtに入っています。

outputの先頭4文字は"FLAG"が暗号化された文字列なので、そこからa, bを求めていきます。

  • "H" = a×"F" + b
  • "L" = a×"L" + b
  • "I" = a×"A" + b
  • "M" = a×"G" + b

となるa, bを求めていきますが、複雑なことはしません。mod 26の世界なのでa, b共に0~25でbrute forceすればOKです。

enc = "HLIM{OCLSAQCZASPYFZASRILLCVMC}"
test = "FLAG"
ans = []
for i in range(len(test)):
    temp = []
    for a in range(26):
        for b in range(26):
            t = encrypt(test[i],a,b)
            if t == enc[i]:
                temp.append([a,b])

    ans.append(temp)


for i in range(len(ans[0])):
    #ans[0][i]が他にもあるか探索
    check = True
    for j in range(1,len(ans)):
        ch = False
        for k in range(len(ans[j])):
            if ans[0][i] == ans[j][k]:
                ch = True
                break
        if not ch:
            check = False
            break
    if check:
        print(ans[0][i])

上記の4つの式の内、一番上の式に当てはまるa, bはans[0]に記憶し、その下の式はans[1], ans[2], ans[3]に入っています。ans[0][i]がans[1~3]にも含まれているかをcheckします。もっといいコードありそう。

コードを走らせると、a=5, b=8と一意に決まりました。あとはencrypt関数の逆を作ればOKです。

def decrypt(ct,a,b):
    pt = ""
    for x in ct:
        if ord("A") <= ord(x) <= ord("Z"):
            x = ord(x) - ord("A")
            for i in range(26):
                if x == (a*i+b)%26:
                    x = chr(i + ord("A"))
                    pt += x
                    break
        else: pt += x
    return pt

FLAG{WELCOMETOCRYPTOCHALLENGE}


Extra [Normal]

いつものRSA
ファイル:cry-extra.zip

RSAの公開鍵(e,N)に加えて、M = 2×p+qが公開されています。これは解と係数の関係を使って解けそうです。Mがp+qならx2-N×x+Mの解がp, qになりますが、M = 2×p+qなので2×p, qが解になる二次方程式を立てます。二つの解の積2×p×qはNを2倍すれば手に入るので、解と係数の関係を利用してp, qを求めます。

二次方程式における解と係数の関係 | 高校数学の美しい物語

from Crypto.Util.number import *
import gmpy2
import math

N = (省略)
M = (省略)
e = 65537
c = (省略)
n = 2*N
ro, check = gmpy2.iroot(M*M-4*n,2)
assert check
q = (M-ro)//2
assert math.gcd(N,q) != 1
p = N//q
phi = (p-1)*(q-1)
d = inverse(e,phi)
m = pow(c,d,N)
print(long_to_bytes(m))

FLAG{@n_ex7ra_param3ter_ru1n5_3very7h1n9}


Can't restore the flag? [Hard]

ちりつもですよ
nc crt.cry.wanictf.org 50000
ファイル:cry-cant-restore-the-flag.zip

server.pyを見るとbytes_to_longしたflagに対して、300以下の数値を送るとその数で割った余りを教えてくれます。これはもう中国剰余定理ですね。2~300を全て送ってその余りを記憶してライブラリで解きます。300! > 10103 なので確実にflagを復元できます。

from sympy.ntheory.modular import crt
mod = []
val = []
for i in range(2,301):
    print(i)
    read_until(f,"Mod > ")
    s.send(str(i).encode()+b"\n")
    recv = int(read_until(f).strip())
    mod.append(i)
    val.append(recv)
m,_ = crt(mod,val)
print(long_to_bytes(m))

FLAG{Ch1n3s3_r3m@1nd3r_7h30r3m__so_u5eful}


OUCS [Very hard]

OUによるHomomorphicなCryptoSystemです
nc oucs.cry.wanictf.org 50010
ファイル:cry-oucs.zip

この問題解けたの嬉しかったですね。

ではまず暗号化について。n = p×p×qとしgp-1 mod p2 != 1であるgを選び、h=gn mod nとした(n,g,h)が公開鍵となります。plaintextをmとすると暗号文cは、c=(gm mod n × hr mod n) mod nとなります(r : 乱数)。hの条件より上記の式は

c = (gm mod n × (gn)r mod n) = gm+n×r mod n ・・・①

となります。

次に復号について。なぜserver.pyのコードで復号できるのでしょうか。

a = ( (cp-1 mod p2) - 1) // p ・・・②
b = ( (gp-1 mod p2) - 1) // p ・・・③
m = a × b-1 mod p ・・・④

まず、フェルマーの小定理 ( xp-1 mod p = 1) を使います。cp-1 = k'×p+1 (k' : 整数)となるので、k' != pである限り、②より(cp-1 mod p2) = k'×p+1となり、a=k'となります。同様に、gp-1 = k×p+1 (k : 整数)となるので、b=kとなります。ここで④より、k'/k = mとなります。故に、k'=k×mと置くことが出来ます。

k×m = ( (cp-1 mod p2) - 1) // p
k×m×p+1 = cp-1 mod p2 ・・・⑤

⑤を証明していきます。式①と式⑤に代入しています。n = p×p×qであるので、mod nの式をmod p2 の式に落とし込むことが出来ます。

k×m×p+1 = (gm+n×r mod n)p-1 mod p2 = (gm+n×r )p-1 mod p2
= g (m+n×r)×(p-1) mod p2

となります。さらに展開していきます。

k×m×p+1 = g (m+n×r)×(p-1) mod p2 = gm×(p-1) × gn×r×(p-1) mod p2

ここで、オイラーの公式( x φ(n) = 1 mod n )を使います。φ(p2) = p×(p-1)となります(p2と互いに素ではないp2以下の自然数は、p, 2p, 3p, ... , (p-1)p, pp のp個)。

n×r×(p-1) = p×p×q×r×(p-1) = A×p×(p-1)

であるので、gn×r×(p-1) mod p2 = 1です。故に、k×m×p+1 = gm×(p-1) となります、これをgp-1 = k×p+1 (k : 整数) が出てくるようにします。

k×m×p+1 = gm×(p-1) mod p2 = gp-1m mod p2 = (k×p+1)m mod p2

となります。ここで二項係数とパスカルの三角形を考えると、

パスカルの三角形 - Wikipedia

(k×p+1)m = (k×p)m + m×(k×p)m-1 + mC2 × (k×p)m-2 + ... + mC(m-2) × (k×p)2 + m×(k×p) + 1

となります。これをmod p2すると、後ろ2項以外はp2が含まれるので0となるのでm×(k×p) + 1となり、式⑤が証明されました。

では、これを使ってどのようにmを求めればいいのでしょうか。ここまで長い証明を読んで下さった方には申し訳ないですが、答えはあっさりです。
a = ( (cp-1 mod p2) - 1) // p = k×mとなるのに対しa' = 2×k×mとなるa'があれば、
a'×b-1 mod p2= 2×m mod p2となり、復号結果がflagと異なるので復号後その値をサーバが教えてくれるはずです。2×m < p となるのは希望的観測です。このくらいは耐えるだろう。

c = gm+n×r mod n より、c2 mod n = (gm+n×r mod n) × (gm+n×r mod n) = g2×(m+n×r) mod n

c' = c2 mod nとして復号してもらうと、

a = ( (c'^(p-1) mod p2) - 1) // p = ( (g2×(m+n×r) )p-1 mod p2 - 1) // p = (g2×m×(p-1) mod p2 - 1) // p

b = ( (gp-1 mod p2) - 1) // p

gp-1 = k×p+1 (k : 整数) より、

b = ( ( k×p+1 mod p2) - 1) // p = (k×p) // p = k

a = (g2×m×(p-1) mod p2 - 1) // p = ( (k×p+1)2×m mod p2 - 1) // p = (先程のパスカルの三角形より) ( ( 2×m×k×p+1 ) - 1) // p = 2×m×k

よって、a×b-1 mod p = 2×m×k×k-1 = 2×m となり、a'×b-1 mod p2= 2×m mod p2が成り立つa'をサーバに計算させることが出来ました。

長ったらしい証明をしましたが、公開鍵とcを受け取ってc2 mod nを計算して復号させ、得られた平文の値を2で割ってlong_to_bytes()するだけです。solverは結構短めです。

HOST, PORT = "oucs.cry.wanictf.org", 50010
s, f = sock(HOST, PORT)
for _ in range(10): read_until(f)
for _ in range(7): read_until(f)
read_until(f,"> ")
s.send(b"1\n")
recv_m = read_until(f).split()
c = int(recv_m[-1][2:],16)
for _ in range(8): read_until(f)
read_until(f,"> ")
s.send(b"4\n")
recv_m = read_until(f).split()
n = int(recv_m[-1][2:],16)
recv_m = read_until(f).split()
g = int(recv_m[-1][2:],16)
for _ in range(9): read_until(f)
c2 = pow(c,2,n)
read_until(f,"> ")
s.send(b"3\n")
read_until(f)
read_until(f,"> ")
s.send(str(c2).encode()+b"\n")
recv_m = read_until(f).split()
m2 = int(recv_m[-1][2:],16)
print(long_to_bytes(m2//2))

FLAG{OU_d0es_n0t_repre53nt_Osaka_University_but_Okamoto-Uchiyama}


おわりに

今回はcrypto問について書いていきました。

OUCSにおいて、証明までしっかりして解いたのは久しぶりでしたしflagが出たときの爽快感が半端なかったです!解いた人の中で証明までした人と、たまたま見つけた人との割合が気になります。この問題ならたまたまの人はそれなりにいるんじゃないかな。

他の分野はまだまだ分からないことだらけで、今年から少しずつ勉強していきたいと思います。もう5月だけど…
m1z0r3として出場するコンテストならともかく、個人戦の初心者向けのCTFならある程度解けるようになりたいですね。

CpawCTF Level1 writeup

初心者向けであるCpawCTF Level 1のwriteupです。今回はかなり噛み砕いて説明していきます。

常設CTFは勉強する上で有難いですね。

ctf.cpaw.site


[Misc] Test Problem

この問題の答え(FLAG)は、cpaw{this_is_Cpaw_CTF} です。
下の入力欄にFLAGを入力してSubmitボタンを押して、答えを送信しましょう!
Enjoy CpawCTF!!!!

そのままです。

cpaw{this_is_Cpaw_CTF}


[Crypto] Classical Cipher

暗号には大きく分けて、古典暗号と現代暗号の2種類があります。特に古典暗号では、古代ローマの軍事的指導者ガイウス・ユリウス・カエサル(英語読みでシーザー)が初めて使ったことから、名称がついたシーザー暗号が有名です。これは3文字分アルファベットをずらすという単一換字式暗号の一つです。次の暗号文は、このシーザー暗号を用いて暗号化しました。暗号文を解読してフラグを手にいれましょう。
暗号文: fsdz{Fdhvdu_flskhu_lv_fodvvlfdo_flskhu}

文字をずらすだけの暗号です。クイズ番組でも日本語をずらした問題とかありますよね。その英語版です。

いつもはrot13と呼ばれる13文字ずらす暗号方式がとても多いのですが、問題文をよく読まず古典暗号といえばrot13だろうとやってしまう早とちり。rot13はアルファベットは26文字あるのでちょうど反対側となる文字にシフトされます。 こちらのサイトではROT0からROT25までやってくれます。ROT23を選び出てきた文字列を提出します。

rot13.com

cpaw{Caesar_cipher_is_classical_cipher}


[Reversing] Can you execute?

拡張子がないファイルを貰ってこのファイルを実行しろと言われたが、どうしたら実行出来るのだろうか。
この場合、UnixLinuxのとあるコマンドを使ってファイルの種類を調べて、適切なOSで実行するのが一般的らしいが…
問題ファイル: exec_me

linuxではfileコマンドでファイルがどういうファイルか教えてくれます。

$ file exec_me
exec_me: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, for GNU/Linux 2.6.24, BuildID[sha1]=663a3e0e5a079fddd0de92474688cd6812d3b550, not stripped

どうやらELFファイルのようです。少し難しいですが、ELFファイルについてのサイトです。

[Linux] バイナリファイル(ELFファイル)の調査に使えるコマンドまとめ - Qiita

ELFファイルは"./(ファイル名)"とやると実行できます。C言語の時よく使ったなぁ。

$ ./exec_me
cpaw{Do_you_know_ELF_file?}

ただこのflagの文字列を出力するだけのプログラムですね。

cpaw{Do_you_know_ELF_file?}


[Misc] Can you open this file?

このファイルを開きたいが拡張子がないので、どのような種類のファイルで、どのアプリケーションで開けば良いかわからない。
どうにかして、この拡張子がないこのファイルの種類を特定し、どのアプリケーションで開くか調べてくれ。
問題ファイル: open_me

先程と同様、このファイルの種類が分からないのでfileコマンドを利用します。

$ file open_me
open_me: Composite Document File V2 Document, Little Endian, Os: Windows, Version 10.0, Code page: 932, Author: v, Template: Normal.dotm, Last Saved By: v, Revision Number: 1, Name of Creating Application: Microsoft Office Word, Total Editing Time: 28:00, Create Time/Date: Mon Oct 12 04:27:00 2015, Last Saved Time/Date: Mon Oct 12 04:55:00 2015, Number of Pages: 1, Number of Words: 3, Number of Characters: 23, Security: 0

先頭の文字列をググってみます。CTFでは知らない単語出てきたらすぐググるのが攻略への近道です。

linux - Composite Document File V2ドキュメントとは何ですか? [閉まっている] - ITツールウェブ

.docか.xlsファイルのようです。
他にも、バイナリデータの可読部分を表示してくれるstringsコマンドをよく使います。

$ strings open_me
bjbj
IHDR
sRGB
        pHYs
IDATx^
 @<8
i^v6{
xX*4
wB/B
}{q?
*+KZ
j4?%
=/v>h^r7z]
o+?`
?)fe
<@6Z
:_TOS
okm_(
v.jH
:^678
Y'4z
VdDd
VJC;
|da`D
ovWTV[
D_Su
_yn@
wr6N
h6[.
H^D[
RX00
&S?q
d~~}
>umG
+1bW
.'!#
'}_Ox
4Nb1
_C47{
IEND
nn0h
0j0W0
[Content_Types].xml
_rels/.rels
theme/theme/themeManager.xml
sQ}#
theme/theme/theme1.xml
?99~vr
{a9A
dFTl
KN1a
"AbG,fx
1$y8|
rr||
0j[u
Kh.Pds9D
oyTL
&3IT-$
P+{#,
Vn&
A8>r
pL8<
sDUC
u_m$#
WP;M
u&Ie
"SoV
theme/theme/_rels/themeManager.xml.rels
6?$Q
K(M&$R(.1
[Content_Types].xmlPK
_rels/.relsPK
theme/theme/themeManager.xmlPK
theme/theme/theme1.xmlPK
theme/theme/_rels/themeManager.xml.relsPK
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<a:clrMap xmlns:a="http://schemas.openxmlformats.org/drawingml/2006/main" bg1="lt1" tx1="dk1" bg2="lt2" tx2="dk2" accent1="accent1" accent2="accent2" accent3="accent3" accent4="accent4" accent5="accent5" accent6="accent6" hlink="hlink" folHlink="folHlink"/>
[c:'wc:'
 0 2 3
Normal.dotm
Microsoft Office Word
Microsoft Word 97-2003
MSWordDoc
Word.Document.8

Wordファイルのようなので、cpやmvコマンドでこのファイルに拡張子.docを付けてあげます。

$ cp open_me open_me.doc

開いてみるとワードにflagの書かれた画像を貼り付けています。今後PNG画像ファイルを扱う時によく出てくるIHDRチャンクやIDATチャンクがstringsコマンドを実行した時に出てきていますね。

cpaw{Th1s_f1le_c0uld_be_0p3n3d}


[Web] HTML page

HTML(Hyper Text Markup Language)は、Webサイトを記述するための言語です。
ページに表示されている部分以外にも、ページをより良くみせるためのデータが含まれています。
次のWebサイトからフラグを探して下さい。
http://q9.ctf.cpaw.site

このサイトは一見するとflagがどこにも無いように見えます。しかし、このサイトのコードはどうでしょうか。F12キーを押すとこのページのソースコードが見れます。そこを色々探しているとhead部分にありました。

f:id:partender810:20210429143411p:plain

cpaw{9216ddf84851f15a46662eb04759d2bebacac666}


[Forensics] River

JPEGという画像ファイルのフォーマットでは、撮影時の日時、使われたカメラ、位置情報など様々な情報(Exif情報)が付加されることがあるらしい。
この情報から、写真に写っている川の名前を特定して欲しい。
問題ファイル: river.jpg
FLAGの形式は、"cpaw{river_name}"
例:隅田川 → cpaw{sumidagawa}

この画像が撮られた川の名前を当てる問題です。問題文にある通りJPEG画像ファイルは様々な情報が付与されているのでそれを見ていきましょう。これらはexiftoolというツールで確認できます。こちらからインストールできます。

Installing ExifTool

$ exiftool river.jpg
ExifTool Version Number         : 10.80
File Name                       : river.jpg
(省略)
GPS Latitude                    : 31 deg 35' 2.76" N
GPS Longitude                   : 130 deg 32' 51.73" E
GPS Position                    : 31 deg 35' 2.76" N, 130 deg 32' 51.73" E
(省略)

このようにすることで、この写真が撮られた場所の座標が手に入りました。この座標をgoogle mapを使って検索してみましょう。

場所の座標を確認して使用する - Google Earth ヘルプ

「31°35'2.76"N, 130°32'51.73"E」と検索すればOKです。

f:id:partender810:20210429144237p:plain
甲突川の橋の上で撮ったよう

cpaw{koutsukigawa}


[Network] pcap

ネットワークを流れているデータはパケットというデータの塊です。 それを保存したのがpcapファイルです。
pcapファイルを開いて、ネットワークにふれてみましょう!

pcapファイルはWireSharkというソフトを使って色々調べます。CTFではよく出てくるので是非インストールしてください!

Wireshark · Download

WireSharkでpcapファイルを開くと通信パケットを見ることが出来ます。そのパケットをダブルクリックするとflagがありました。

f:id:partender810:20210429144831p:plain
ごちうさ好きなのかな

他にもTCPストリームなどを使って通信した文字列を見ることなどができます。今回の問題に限って言えばstringsコマンドでもflagを入手できます。

cpaw{gochi_usa_kami}


[Crypto] HashHashHash!

ハッシュ関数とは、値を入れたら絶対にもとに戻せないハッシュ値と呼ばれる値が返ってくる関数です。
ですが、レインボーテーブルなどでいくつかのハッシュ関数は元に戻せてしまう時代になってしまいました。
以下のSHA1というハッシュ関数で作られたハッシュ値を元に戻してみてください!(ヒント:googleで検索)
e4c6bced9edff99746401bd077afa92860f83de3
フラグは
cpaw{ハッシュを戻した値}
です。

ハッシュ関数とは、あるデータをハッシュ値と呼ばれる数値に変換する関数です。どのように変換するかアルゴリズムは公表されていないので、ハッシュ値から元のデータから予測することは出来ません。しかし、誰でも(ツールなどを使うことによって)ハッシュ値を計算することはできるので、よくある文字列のハッシュ値は知られてます。それをまとめたのがレインボーテーブルです。

このサイトでは、よくある文字列や短い文字列のハッシュ値は記憶しているため、ハッシュ値で検索するとその入力データを求めてくれます。

https://hashtoolkit.com/decrypt-hash/?hash=e4c6bced9edff99746401bd077afa92860f83de3

ハッシュ値から元のデータを求めることをハッシュを破るというのですが、破られないためには元のデータに乱数を加えてハッシュ値を取るなどする必要があります。

cpaw{Shal}


[PPC] 並び替えろ!

下にある配列の中身を大きい順に並べ替えて、くっつけてcpaw{並べ替えた後の値}をフラグとして提出してください。
例:もし配列{1,5,3,2}っていう配列があったら、大きい順に並べ替えると{5,3,2,1}となります。
そして、フラグはcpaw{5321}となります。
同じようにやってみましょう(ただし量が多いので、ソートするプログラムを書いたほうがいいですよ!)
[15,1,93,52,66,31,87,0,42,77,46,24,99,10,19,36,27,4,58,76,2,81,50,102,33,94,20,14,80,82,49,41,12,143,121,7,111,100,60,55,108,34,150,103,109,130,25,54,57,159,136,110,3,167,119,72,18,151,105,171,160,144,85,201,193,188,190,146,210,211,63,207]

プログラムを書かない人には厳しい問題かもしれません。ですが、今後pythonというプログラミング言語をよく使うので学んでおきましょう。

本来、ソートにはいくつものアルゴリズムがありその速さなどがよく比較されます。ただ、今回はそんなに難しい話はしません。pythonには勝手にソートしてくれる関数があります。

a = [15,1,93,52,66,31,87,0,42,77,46,24,99,10,19,36,27,4,58,76,2,81,50,102,33,94,20,14,80,82,49,41,12,143,121,7,111,100,60,55,108,34,150,103,109,130,25,54,57,159,136,110,3,167,119,72,18,151,105,171,160,144,85,201,193,188,190,146,210,211,63,207]
b = sorted(a,reverse=True)
flag = "cpaw{"
for i in b:
    flag += str(i)
print(flag+"}")

1行目にはaという変数に問題文にある数列を入れます。[]はpythonのリストを表します。他の言語だと配列などと呼ばれます。

2行目でいきなりソートしています。ソートした後のリストをbという変数に入れています。sortedについて、まずリストaを渡します。もう一つのreverse=Trueというのはリストaを降順に並べるよう指定しています。これが無いとbにはリストaを昇順に並べたものが入ります。

3行目以降は、flagを提出しやすいよう出力しています。

cpaw{2112102072011931901881711671601591511501461441431361301211191111101091081051031021009994938785828180777672666360585755545250494642413634333127252420191815141210743210}


これら全部できたらCTF初心者は卒業と言っても過言ではありません!

HeroCTF v3 writeup

m1z0r3はWPICTFにも参加していたのですが難しすぎて私はすぐ撤退…。
HeroCTFはそこそこの難易度でcryptoは全完できたのでよかったです!

https://www.heroctf.fr

Result

f:id:partender810:20210426143616p:plain
result

もう少しで1000ptと2桁順位が見えたので悔しいですね…


Writeup

Blockchain

Transfer 25pt

We've been tracking drug dealers for a few years. The only thing we got from their network is a hash : bfa8b1c8b7f6acc867d6984968756cafd0da99060aa6d90dfb0db1a9ef0c96a2
Can you tell us where the money is gone?

Blockchainというジャンルは初めてです。いつもはよくEtherscanを使っているのですがそちらでは見つからず、こちらでhash検索で見つかりました。

Transaction: bfa8b1c8b7f6acc867d6984968756cafd0da99060aa6d90dfb0db1a9ef0c96a2 | Blockchain Explorer

Hero{1PQBh6tddet14bYDsSbUiox1YJb5EL185A}


Crypto

h4XOR 75 pt

Can you recover the flag.png image ?
Hint : The xor function is from the pwntools module.
ファイル:flag.png.enc, xor.py

まずxor.pyについて読んでいきます。flag.pngのバイナリデータと、ランダムな8bytesと[0-9]の内どれかをバイト列として繋げた9bytesをxorしてflag.png.encに書き込んでいます。ここでpwntoolsでのxorは特殊で、xorする際に両方の長さを揃えるようにします。長い方に合わせる形で短い方を伸ばします。短い方の長さが長い方の倍数で無い場合、先頭bytesを付け加えます。例えばb"ABCDE"を12bytesにしたい場合、b"ABCDEABCDEAB"という感じです。

ランダムな9bytesの方ですが、後ろ1bytesは10通りしかないのでbrute force可能です。ですが前8bytesをbrute forceするのは2568 = O(1020)とかなりかかるので不可能です。なので別の方法を考えましょう。

PNG画像ファイルのフォーマットはこちらで勉強しました。

PNG ファイルフォーマット

PNG画像ファイルの先頭8bytesは16進数で "89504E470D0A1A0A"と決まっています。なのでこの値とencの先頭8bytesをxorするとランダムな8bytesの値が手に入ります。あとは0~9の10通りを試せばOKです。

from os import urandom
from random import randint
from pwn import xor
from Crypto.Util.number import *


outpout_img = open("flag.png.enc", "rb").read()
head = 0x89504E470D0A1A0A
k = long_to_bytes(bytes_to_long(outpout_img[:8])^head)
img0 =open("flag0.png", "wb")
img1 =open("flag1.png", "wb")
img2 =open("flag2.png", "wb")
img3 =open("flag3.png", "wb")
img4 =open("flag4.png", "wb")
img5 =open("flag5.png", "wb")
img6 =open("flag6.png", "wb")
img7 =open("flag7.png", "wb")
img8 =open("flag8.png", "wb")
img9 =open("flag9.png", "wb")
key0 = k + bytes([0])
key1 = k + bytes([1])
key2 = k + bytes([2])
key3 = k + bytes([3])
key4 = k + bytes([4])
key5 = k + bytes([5])
key6 = k + bytes([6])
key7 = k + bytes([7])
key8 = k + bytes([8])
key9 = k + bytes([9])
img0.write(xor(outpout_img, key0))
img1.write(xor(outpout_img, key1))
img2.write(xor(outpout_img, key2))
img3.write(xor(outpout_img, key3))
img4.write(xor(outpout_img, key4))
img5.write(xor(outpout_img, key5))
img6.write(xor(outpout_img, key6))
img7.write(xor(outpout_img, key7))
img8.write(xor(outpout_img, key8))
img9.write(xor(outpout_img, key9))

f:id:partender810:20210425222308p:plain
9でした

もっと綺麗なsolverはあるはず

Hero{123_xor_321}


ExtractMyBlocks 125 pt

The company PasswordS3cure created a new password reset functionality. Could you crack it and find the flag ?
nc chall0.heroctf.fr 10000
ファイル:challenge.py

challenge.pyを読んでいきます。

文字列を投げるとaccount_idとして処理され、それやflagを含むmsg変数にある文字列がAESのECBモードで暗号化され、それを16進数にした値が渡されます。KEYの値は不明です。

AESのECBモードはもっとも単純な暗号化方式になります。

f:id:partender810:20210425224952p:plain
AES ECB mode wikipediaから引用

KEYが同じ場合、同じ平文なら同じ暗号文が返されます。CBCモードと異なり、同じ16bytesの平文を二つ繋げた文字列を暗号化すると、16bytesの同じ暗号文が二つ繋げた状態で返ってきます。何ブロック目かなんて関係ないです。flagの文字列も一緒に暗号化されるので、同じ文字列を送れば同じ暗号文になり情報が得られそうです。ただし16bytes毎に区切っているので開始位置に注意する必要があります。これらを考慮しながら入力を調整していく方向で考えていきます。

flagをいきなりbrute forceするのは無理があります。長さも分からないので無謀です。しかし、一文字ごと特定するのであれば64くらい×(flag長)なのでそんなに時間がかからないです。では一文字ずつ特定していきましょう。

flagが含まれるmsg変数のflagが始まる10文字を注目していきます。(その10文字)+"Hero{"+(flagの1文字目) (=Fとします )が一つのblockとして扱われるよう(blockを跨ぐことのないよう)入力を調整していきます。(その10文字)の先頭が16*k+1文字目になればOKです。※(その10文字) = "assword : "

ではこちらが送る文字列も(その10文字)+"Hero{"+(flagの1文字目をbrute forceで試している1文字) (=Tとします )が一つのblockとして扱われるようにこちらも入力を調整していきます。

下の図について、\は改行を、*はT,Fが1blockに収まるように調整を、?はbrute forceで試す1文字を表しています。これはflagの{以降の最初の文字を調べる時の図です。

f:id:partender810:20210426155738p:plain
flagの1文字目を求めたい

上から2,4番目の文字列が等しくなる場合、つまり?にflagの1文字目が入った場合、暗号文の2,4 block目(16~32bytesと48~64bytes目)が同じ値になります。これで1文字目が"_"ということが分かりました(1文字目でこの文字は不安になる)。

次に2文字目についてです。

f:id:partender810:20210426155838p:plain
2文字目を求めたい

2block目を1文字シフトさせ3block目のpaddingを一つ減らすことで、上手く2,4block目を対応させます。
このようにして?に"}"が当てはまるまで繰り返していきます。3block目のpaddingが0になったら次は16個増やして2,5 block目が同じならOKという感じにシフトしていきます。flag長が16を超えたら3,6block目にしないと…と考えてたらかなり短かったです。

candi = "ABCDEFGHJIKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz01234567890{}_"
FLAG = "Hero{test_flag}" #簡易的なflag。途中msgを出力している際に入力として送る文字列が正しい長さ確認する
flag = "Hero{"
isbreak = False #終了条件
#tempをシフトさせていって1文字ずつbrute forceしていく
temp = "assword : Hero{"
k = len(" !\n\nYour p") #paddingする際の長さ
print(k)
L = 16 #paddingで送る長さ調整。k=16になった時は32にする。
M = 3 #暗号化されたflagが何blockにあたるか
while True:
    printmsg = True
    print("k :",k)
    for i in candi:
        print(i)
        HOST, PORT = "chall0.heroctf.fr", 10000
        s, f = sock(HOST, PORT)
        _ = read_until(f)
        read_until(f,"ID : ")
        account_id = "12" + temp + i + "0"*(L-k)
        _msg = f"""
Welcome back {account_id} !

Your password : {FLAG}

Regards
"""
        #送る入力が正しいものになっているか。特に長さの確認
        if printmsg:
            for j in range(0,len(_msg),16):
                print(j//16,_msg[j:j+16])
        msg = "12" + temp + i + "0"*(L-k)
        s.send(msg.encode()+b"\n")
        ct = read_until(f).strip()
        assert len(ct)%32 == 0
        cip = []
        for j in range(0,len(ct),32):
            cip.append(ct[j:j+32])
        if cip[1] == cip[M]:
            print(i)
            s.close()
            flag += i
            if i == "}":
                print("This is the flag!")
                print(flag)
                isbreak = True
                break
            print(flag)
            temp = temp[1:] + i
            k += 1
            if k == 16:
                L += 16
                M += 1
            break
        s.close()
        printmsg = False
    if isbreak: break

Hero{_BL0CK5_}


Forensics

We need you 1/5 50pt

Interpol and the FBI have been investigating for over a year now. They are trying to get their hands on two hackers very well known for their ransomware and their ultra efficient botnet.
After long months of investigation, they managed to get their hands on one of their servers. But, when they got it back the PC caught fire because of a defense mechanism set up by the two hackers.
The hard drive could not be saved, but they had time to put the RAM in liquid nitrogen and analyze it later.
You know what you have to do!
For this first step, find the name of the PC!
Download, here.
Format: Hero{Name}
ファイル:Challenge.zip

zipファイルを解凍するとcapture.memが得られます。memファイルの解析ってなんだ、とググるとvolatilityが使えることが判明!

ずばり欲しいデータを得られるコマンドを教えてくれるサイトがありました。

Volatility/Retrieve-hostname - aldeid

$ volatility -f capture.mem imageinfo
Volatility Foundation Volatility Framework 2.6
INFO    : volatility.debug    : Determining profile based on KDBG search...
          Suggested Profile(s) : Win7SP1x86_23418, Win7SP0x86, Win7SP1x86
          (省略)

$ volatility -f capture.mem --profile=Win7SP1x86 hivelist
Volatility Foundation Volatility Framework 2.6
Virtual    Physical   Name
---------- ---------- ----
(省略)
0x823859c8 0x2b5d99c8 \SystemRoot\System32\Config\SAM
0x8940c5e8 0x2d5415e8 [no name]
0x8941a2c0 0x2d58d2c0 \REGISTRY\MACHINE\SYSTEM
(省略)


$ volatility -f capture.mem --profile=Win7SP1x86 printkey -o 0x8941a2c0 -K 'ControlSet001\Control\ComputerName\ComputerName'
Volatility Foundation Volatility Framework 2.6
Legend: (S) = Stable   (V) = Volatile

----------------------------
Registry: \REGISTRY\MACHINE\SYSTEM
Key name: ComputerName (S)
Last updated: 2021-04-19 17:00:09 UTC+0000

Subkeys:

Values:
REG_SZ                        : (S) mnmsrvc
REG_SZ        ComputerName    : (S) KANNIBAL

Hero{KANNIBAL}


We need you 2/5 75pt

It must be their team name.
For this second step, find the user's name and password in clear text.
Format: Hero{Username:Password}

先程使ったサイト、他にも欲しい情報くれるので最高過ぎる

Volatility/Retrieve-password - aldeid

1/5で使ったimageinfoとhivelistを使います

$ volatility -f capture.mem --profile=Win7SP1x86 hashdump -y 0x8941a2c0 -s 0x823859c8
Volatility Foundation Volatility Framework 2.6
Administrateur:500:aad3b435b51404eeaad3b435b51404ee:31d6cfe0d16ae931b73c59d7e0c089c0:::
Invit:501:aad3b435b51404eeaad3b435b51404ee:31d6cfe0d16ae931b73c59d7e0c089c0:::
Razex:1000:aad3b435b51404eeaad3b435b51404ee:78d9c7e905c695087ee3baa755ce43e4:::

f:id:partender810:20210426001458p:plain
crack成功!

UsernameがRazexであることに気付かず、crackしてから時間かかりました…。

Hero{Razex:liverpoolfc123}


We need you 3/5 80pt

We know for sure that this server allowed to connect to infected machines. Can you check if a connection was instantiated?
Format: Hero{IP:Port}

接続を確認するのはconnectionsコマンドだろうと実行するもサポートしてないと指摘される。うーん、他にないかな。

Volatility, my own cheatsheet (Part 5): Networking | Andrea Fortuna

netscanでもいいのかありがたい。

$ volatility -f capture.mem --profile=Win7SP1x86 netscan
Volatility Foundation Volatility Framework 2.6
Offset(P)          Proto    Local Address                  Foreign Address      State            Pid      Owner          Created
(省略)
0x7ee41538         TCPv4    10.0.2.15:49159                146.59.156.82:4444   ESTABLISHED      3296     nc.exe
(省略)

他を省略していますがStateが唯一ESTABLISHEDとなっているものがあったのでForeign Addressを投げたら通りました。

Hero{146.59.156.82:4444}


Misc

Discord 1pt

Join our Discord server and read the rules !
URL : https://discord.gg/PhNkPrfeJG

discord内に分かりやすく書いてほしい…。

Hero{3njoy_th3_3v3nt}


Cubes 40pt

#257 ; #260 ; #282 ; #12 ; #349:2 ; #344 ; #383:120 ; #368
The flag is in in lowercase, without spaces.

これ解けたの嬉しい!

暗号ぽいので「cube cipher」でググるとrubik's cube cipherがヒットしました。ルービックキューブは9個*6面の54種類のスペース(?)があるのに対し、暗号文の数が大きすぎます。そもそも「383:120」などのコロンが付いているやつが何も分かりません。

この特徴的な値に注目し「383:120 cube」でググると…。

Minecraft ID List - Minecraft Info

めちゃくちゃぽい!!

頭文字を並べてみると"iamscese"となる。"iam"はそれっぽいのに最後がなんか変。提出してもincorrectでした。

方向性は間違っていないだろうとminecraftのIDリストを調べてみるとどうやら見ていたのが古いバージョンだった模様。こちらで調べて頭文字を調べるとcorrectとなりました!

Minecraft ID List (1.16) | Minecraft Item IDs

Hero{iamsteve}


Russian Doll 50pt

Go deeper !
ファイル:archive.zip

問題文と問題名よりzip解凍するとめちゃくちゃディレクトリ階層の深いのだろうと…ビンゴ。ただ予想をはるかに超える深さでした。最下層にflag.txtがあるのが見えたので、なんとか検索できる術はないかと模索したところ、見つかりました!

[Linux]grepでディレクトリ以下の全てのファイルを検索する │ TEAM T3A

$ find ./ -type f | xargs grep "Hero{"
Binary file ./archive.zip matches
./you/are/about/to/get/rick/rolled/rickRollIncoming/no/iMean/really/lastWarning/toldYou/you/should/have/listened/now/stop/looking/in/someoneElse/stuff/cuteCatPictures/goAway/stop/clicking/manually/through/folders/like/that/write/a/script/to/do/it/for/you/come/on/waste/of/time/what/about/a/recipe/now/200/g/de/pois/chiches/500/g/de/feve/seches/1/oignon/moyen/2/gousses/dail/1/bouquet/de/persil/3/cuilleres/a/soupe/de/farine/1/cuillere/a/cafe/de/cumin/en/poudre/1/cuillere/a/cafe/de/coriandre/en/poudre/1/cuillere/à/cafe/de/paprika/3/cuilleres/a/soupe/de/basilic/frais/hache/sel/huile/de/friture/faites/tremper/les/pois/chiches/et/les/feves/dans/leau/12h/les/egoutter/et/les/cuire/45/mn/a/l/auto/cuiseur/peler/oignon/et/ail/les/hacher/ainsi/que/le/persil/passer/les/feves/et/les/pois/chiches/au/mixer/ourobot/melanger/avec/le/persil/loignon/lail/la/farine/les/epices/le/sel/petrissez/le/tout/avec/vos/mains/en/ajoutant/un/peu/deau/si/necessaire/rassemblez/la/pate/et/laisser/reposer/au/refrigerateur/pendant/minimum/30mn/faconner/une/trentaine/de/boulettes/de/la/grosseur/dune/piece/de/2/euros/les/faire/frire/2-3/mn/puis/les/egoutter/sur/du/papier/absorbant/servir/chaud/ou/froid/avec/des/petites/sauces/tomates/aux/herbes/ou/sauces/yaourts/et/voila/cest/pret/bon/appetit/notPorn/stillNotPorn/reallyNot/seriousStuff/work/realWork/porn/did/you/really/think/there/would/be/porn/inA/work/directory/now/stop/looking/in/someoneElseS/stuff/cuteCatPictures/goAway/stop/clicking/manually/through/folders/like/that/write/a/script/to/do/it/for/you/come/on/waste/of/time/what/about/a/recipe/now/200/g/de/pois/chiches/500/g/de/feve/seches/1/oignon/moyen/2/gousses/dail/1/bouquet/de/persil/3/cuilleres/a/soupe/de/farine/1/cuillere/a/cafe/de/cumin/en/poudre/1/cuillere/a/cafe/de/coriandre/en/poudre/1/cuillere/à/cafe/de/paprika/3/cuilleres/a/soupe/de/basilic/frais/hache/sel/huile/de/friture/faites/tremper/les/pois/chiches/et/les/feves/dans/leau/12h/les/egoutter/et/les/cuire/45/mn/a/l/auto/cuiseur/peler/oignon/et/ail/les/hacher/ainsi/que/le/persil/passer/les/feves/et/les/pois/chiches/au/mixer/ourobot/melanger/avec/le/persil/loignon/lail/la/farine/les/epices/le/sel/petrissez/le/tout/avec/vos/mains/en/ajoutant/un/peu/deau/si/necessaire/rassemblez/la/pate/et/laisser/reposer/au/refrigerateur/pendant/minimum/30mn/faconner/une/trentaine/de/boulettes/de/la/grosseur/dune/piece/de/2/euros/les/faire/frire/2-3/mn/puis/les/egoutter/sur/du/papier/absorbant/servir/chaud/ou/froid/avec/des/petites/sauces/tomates/aux/herbes/ou/sauces/yaourts/et/voila/cest/pret/bon/appetit/ok/you/win/hereIsTheFlag/haha/what/about/a/recipe/now/200/g/de/pois/chiches/500/g/de/feve/seches/1/oignon/moyen/2/gousses/dail/1/bouquet/de/persil/3/cuilleres/a/soupe/de/farine/1/cuillere/a/cafe/de/cumin/en/poudre/1/cuillere/a/cafe/de/coriandre/en/poudre/1/cuillere/à/cafe/de/paprika/3/cuilleres/a/soupe/de/basilic/frais/hache/sel/huile/de/friture/faites/tremper/les/pois/chiches/et/les/feves/dans/leau/12h/les/egoutter/et/les/cuire/45/mn/a/l/auto/cuiseur/peler/oignon/et/ail/les/hacher/ainsi/que/le/persil/passer/les/feves/et/les/pois/chiches/au/mixer/ourobot/melanger/avec/le/persil/loignon/lail/la/farine/les/epices/le/sel/petrissez/le/tout/avec/vos/mains/en/ajoutant/un/peu/deau/si/necessaire/rassemblez/la/pate/et/laisser/reposer/au/refrigerateur/pendant/minimum/30mn/faconner/une/trentaine/de/boulettes/de/la/grosseur/dune/piece/de/2/euros/les/faire/frire/2-3/mn/puis/les/egoutter/sur/du/papier/absorbant/servir/chaud/ou/froid/avec/des/petites/sauces/tomates/aux/herbes/ou/sauces/yaourts/et/voila/cest/pret/bon/appetit/bonOk/flag.txt:Hero{if_yOu_gOt_HEre_By_clIcKInG_mANnUaLly_YoU_sHOuLd_REalLy_SeE_SoMeOne}

Hero{if_yOu_gOt_HEre_By_clIcKInG_mANnUaLly_YoU_sHOuLd_REalLy_SeE_SoMeOne}


OSINT

Find me 10pt

Could you retrieve where this photo was taken ?
Format : Hero{place}
ファイル:find_me.jpg

画像検索で一発です。

f:id:partender810:20210426141422p:plain
便利だなぁ

Hero{portes mordelaises}


Social ID #1 15pt

Can you find the twitter ID of @HeroCTF.
Format : Hero{twitter_id}

twitter ID って@HeroCTFじゃないのか?と調べたところある整数値がIDだそうです。スクリーンネーム(@以降)を投げると教えてくれるサイトがありました。

idtwi | Twitter IDチェッカー(変更履歴の追跡)

Hero{815907006708060160}


Pushhhh 35pt

The flag is in one of the two Github repositories of the last HeroCTF edition. Could you find it ?

今回がv3なのでv2のgithubを調べれば良さそうです。色々見たところbranchが複数…。

f:id:partender810:20210426142049p:plain
怪しすぎる

ここを覗いてみるとflagがありました。

HeroCTF_v2/flag.txt at flag · HeroCTF/HeroCTF_v2 · GitHub

Hero{y0u_re_g00d_4t_g1t_250820}


Social ID #2 50pt

Can you find the @username of this Twitter ID 44196397.
Format : Hero{username}

先程のidtwiでOKです。

Hero{@elonmusk}


Prog

Ping Pong 45pt

Could you get the flag ?
ファイル:output.txt

ProgってProgrammingかな?

output.txtを見ると"PING"か"PONG"のどちらかが書いてあります。長さが176と8の倍数であることと、文字列として違うのがIとOなので、"PING" = 1, "PONG" = 0とした2進数を8桁ごとでchrしたらうまく復号されました。

from Crypto.Util.number import *
f = open("output.txt")
a = f.readlines()
ans = ""
for i in a:
    if i.strip() == "PING": ans += "1"
    else: ans += "0"
for i in range(0,len(ans),8):
    print(chr(int(ans[i:i+8],2)),end="")
print()

Hero{p1n6_p0n6_15_fun}


PRo Random Guesser 50pt

Everybody loves a little guessing challenge right ? Guess the number \o/
- Love, Mersenne
/!\ If you are using a script, you have to append '\n' to any data you wish to send back.
Host : chall0.heroctf.fr Port : 7003

数字を予測しろとのことなので、何かの乱数を生成しているのだろう。問題文から恐らくメルセンヌ・ツイスターかな。

メルセンヌ・ツイスタ - Wikipedia

細かい仕組みは全く分からないですが、こちらをコードをパクって解けました。ももテクさんには頭が上がらないです。

Mersenne Twisterの出力を推測してみる - ももいろテクノロジー

def untemper(x):
    x = unBitshiftRightXor(x, 18)
    x = unBitshiftLeftXor(x, 15, 0xefc60000)
    x = unBitshiftLeftXor(x, 7, 0x9d2c5680)
    x = unBitshiftRightXor(x, 11)
    return x

def unBitshiftRightXor(x, shift):
    i = 1
    y = x
    while i * shift < 32:
        z = y >> shift
        y = x ^ z
        i += 1
    return y

def unBitshiftLeftXor(x, shift, mask):
    i = 1
    y = x
    while i * shift < 32:
        z = y << shift
        y = x ^ (z & mask)
        i += 1
    return y

HOST, PORT = "chall0.heroctf.fr", 7003
s, f = sock(HOST, PORT)
print(read_until(f))
print(read_until(f))
print(read_until(f))
x = []
for i in range(624):
    print(i)
    read_until(f,"Guess me : ")
    s.send(b"1337\n")
    recv_m = read_until(f).split()
    x.append(int(recv_m[3]))
    read_until(f)
mt_state = tuple([untemper(z) for z in x] + [624])
random.setstate((3, mt_state, None))
ans = int(random.getrandbits(32))
s.send(str(ans).encode()+b"\n")
while True: print(read_until(f))

Hero{n0t_s0_r4nd0m_4ft3r_4ll}


Reverse

EasyAssembly 40pt

Don't worry, this one is quite easy :) Could be a good introduction to assembly !
Format : Hero{input:modified}
ファイル:EasyAssembly.asm

IDAなど色々ツールを使ったのですが、結局catすれば分かりました。

$ cat EasyAssembly.asm
(省略)
# EasyAssembly.c:19:    modified = input >> 2;
        movl    -8(%rbp), %eax  # input, tmp89
        sarl    $2, %eax        #, tmp88
        movl    %eax, -4(%rbp)  # tmp88, modified
# EasyAssembly.c:21:    if(modified == 1337404)
        cmpl    $1337404, -4(%rbp)      #, modified
        jne     .L5     #,
# EasyAssembly.c:22:            isGood = 0;
        movl    $0, isGood(%rip)        #, isGood
.L5:
# EasyAssembly.c:24:    if(!isGood)
        movl    isGood(%rip), %eax      # isGood, isGood.1_1
# EasyAssembly.c:24:    if(!isGood)
        testl   %eax, %eax      # isGood.1_1
        jne     .L6     #,
# EasyAssembly.c:25:            printf("Well done ! You can validate with the flag Hero{%d:%d}\n", input, modified);
(省略)

"if(modified == 1337404)" とあるのでmodifiedは1337404でしょう。"modified = input >> 2" からinputはmodifiedを2ビット左シフトさせたものでしょう。エントロピーが2bitsなのでbrute forceすればいいかなと思い提出したら一発で通りました。

Hero{5349616:1337404}


Steganography

HolyAbbot 15pt

A certain abbot tried to give us a message...
(the message is in lower case) (No need to speak French)
Format : Hero{messageinlowercase}
ファイル:HolyAbbot.txt

steganoって全部画像ファイルの解析と思ってたらテキストでびっくり。
しかもテキストの先頭行をググったら一発。RITSEC CTFでもやった、Ave Maria cipherのフランス語バージョンでした。Steganoというよりcryptoじゃん。

partender810.hatenablog.com

Ave Maria de Trithème - Déchiffrer, Décoder, Encoder en Ligne

decodeしたものを小文字に変更して終了です。

Hero{substitution}


Nice PDF 20pt

Don't think to much ;)
ファイル:NicePDF.pdf

pdfの解析か…。問題文から察するにそんな難しいことはせずに解けそうです。exiftoolやbinwalkなどのツールやPDFを実際に開いてプロパティを見るとかしましたが分からず…。

藁にも縋る思いでgoogleに頼るとこんなオンラインツールが。

PDF Extract Tool

textで解析すると以下の結果が得られました。

 93,02  760,68 H
  99,86  760,68 ses
 113,90  760,68 e
 119,42  760,68 Histoires,
 161,54  760,68 r
 165,38  760,68 l'historien
 209,93  760,68 o
 215,81  760,68 grec
 234,89  760,68 {
 238,37  760,68 Hérodote
 281,09  760,68 E
 286,49  760,68 rapporte
 325,99  760,68 4
 331,63  760,68 ainsi
 352,03  760,68 S
 357,07  760,68 une
 373,99  760,68 Y
 379,39  760,68 anecdote
 421,27  760,68 _
 426,79  760,68 qui
 440,86  760,68 P
 446,62  760,68 eut
 461,50  760,68 D
 468,34  760,68 lieu
 484,66  760,68 F
 489,70  760,68 au
 500,74  760,68 }

一文字おきに読むとflagになりました。

Hero{E4SY_PDF}


Problems&solver

GitHub - ksbowler/HeroCTF_v3

今回、ファイルサイズが大きくあげられないものがありました。ご了承ください。

  • forensics We need you Challenge.zip
  • OSINT Russian doll archive.zip

Cyber Apocalypse CTF 2021 writeup

4/19 ~ 24にて行われましたCyber Apocalypse CTF 2021のCryptoのwriteupです。
問題数が多くてよかったのですが、解けそうで解けない問題多くてもやもやしました。

問題名の横にある★の数はDifficultyです。

Result

f:id:partender810:20210424110538p:plain
それなりに頑張ったけど順位が伴わない…


Writeup

Warmup & Misc

Welcome 25pt ★

Join our Discord Server and the CA-2021 channels…

Discordに入っても全然flagが見つからない…。始まって1日かかっても分からなかったのですが、HackTheBoxのアカウントを作りdiscordのbotに認証させると色んなチャンネルに入れるようになりflagも見つかりました!
でもこのflagでいいの?とはなるflagでした。

CHTB{CA_CTF_i$_F*ing_EPIC}


Alien Camp 300pt ★

The Ministry of Galactic Defense now accepts human applicants for their specialised warrior unit, in exchange for their debt to be erased. We do not want to subject our people to this training and to be used as pawns in their little games. We need you to answer 500 of their questions to pass their test and take them down from the inside.
This challenge will raise 33 euros for a good cause.

dockerを起動させてncするとこんな感じです。

f:id:partender810:20210422221942p:plain
文字化け~~~

読めないです。うーーん。計算させる問題なんだろうけど、文字化けが分からない…。
slackに貼ってみるとなんと絵文字が!!

f:id:partender810:20210422222253p:plain
読めた!

文字化けしてたのが絵文字と分かればchrすれば文字コードを入手できます。それと矢印先の数字と対応させて四則演算をすればOKです。ただここで足し引き算とかけ算の順序に気を付けて実装する必要があります。

  • リストを作りまず最初の数字をappendします。
  • 演算子が+なら数字をそのままappend
  • 演算子が-なら数字に-1を掛けてappend
  • 演算子が×ならリストの最後尾の数字をその数字と対応する数字の積に置き換える
  • 最後にリストの総和を求める

先にかけ算を行って足し引き算のみの式にしようという感じです。

i=1
while(i<=500):
    print("Now :",i)
    r.recvuntil("Question " + str(i) + ":\n\n")
    question = r.recvline().decode()
    que = question.split()
    for j in range(0,len(que)):
        if que[j] == "?": print(que[j],end="")
        elif j%2 == 0: print(num[hel.index(ord(que[j]))]," ",end="")
        else: print(que[j]+" ",end="")
    print()
    ans = [0 for j in range(len(que)//2)]
    bef = "+"
    way = []
    cnt = 1
    #ansに対応する数字を格納していく。a - bの場合、[a,-b]と格納していき、最後にsum(ans)とすれば和が出る
    ans[0] = num[hel.index(ord(que[0]))]
    for ind in range(1,len(que),2):
        if que[ind] == "=":
            break
        if que[ind] == "*":
            #かけ算の場合、足し引き算より先に行わないといけない
            #先に積を導出し、掛けられる数が入ってたansのところを0に、掛ける数のとこを今求めた積を格納する
            # a + b * c * d の場合、[a,0,0,0] -> [a,b,0,0] -> [a,0,b*c,0] -> [a,0,0,b*c*d] -> sum(ans) となる
            temp = ans[cnt-1]*num[hel.index(ord(que[ind+1]))]
            ans[cnt-1] = 0
            ans[cnt] = temp
            cnt += 1
        else:
            #+ or -
            #足し引き算ならそのまま格納するだけでよい
            if que[ind] == "+":
                ans[cnt] = num[hel.index(ord(que[ind+1]))]
                cnt += 1
            elif que[ind] == "-":
                ans[cnt] = -num[hel.index(ord(que[ind+1]))]
                cnt += 1
            else:
                print("division")
    print(ans)
    ans = sum(ans)

500回は長かったなぁ

CHTB{3v3n_4l13n5_u53_3m0j15_t0_c0mmun1c4t3}


Crypto

Nintendo Base64 300pt ★

Aliens are trying to cause great misery for the human race by using our own cryptographic technology to encrypt all our games. Fortunately, the aliens haven't played CryptoHack so they're making several noob mistakes. Therefore they've given us a chance to recover our games and find their flags.
They've tried to scramble data on an N64 but don't seem to understand that encoding and ASCII art are not valid types of encryption!
This challenge will raise 33 euros for a good cause.
ファイル:output.txt

開いてみると綺麗ですね。

f:id:partender810:20210422232114p:plain
これ作るの大変だろうな

スペースを無くしてbase64 decodeしてみると後ろが==となりました。無限にdecodeしてb"CHTB{"が出るまで無限にdecodeして終わりです。

CHTB{3nc0d1ng_n0t_3qu4l_t0_3ncrypt10n}


PhaseStream1 300pt ★

The aliens are trying to build a secure cipher to encrypt all our games called "PhaseStream". They've heard that stream ciphers are pretty good. The aliens have learned of the XOR operation which is used to encrypt a plaintext with a key. They believe that XOR using a repeated 5-byte key is enough to build a strong stream cipher. Such silly aliens! Here's a flag they encrypted this way earlier. Can you decrypt it (hint: what's the flag format?)
2e313f2702184c5a0b1e321205550e03261b094d5c171f56011904
This challenge will raise 33 euros for a good cause.

これと同じです。

ångstromCTF 2021 writeup - Attack All Around

しかもこの問題と違ってmessageのどこかにflagがあるというわけではなく、flag以外余計な文字列は含まれていないので最初の5bytesは"CHTB{"となることが予想されます。それのxorをとると5bytesのkeyが求まるので後はそれを使って復号します。

CHTB{u51ng_kn0wn_pl41nt3xt}


PhaseStream2 300pt ★

The aliens have learned of a new concept called "security by obscurity". Fortunately for us they think it is a great idea and not a description of a common mistake. We've intercepted some alien comms and think they are XORing flags with a single-byte key and hiding the result inside 9999 lines of random data, Can you find the flag?
This challenge will raise 33 euros for a good cause.
ファイル:output.txt

1byteのkeyで、1つのflagと9999個のランダムデータをencryptした結果がoutput.txtです。
(keyの候補 = 256) × (10000行のデータ) = O(106) なのでまあbrute force可能です。各行をdecryptして"CHTB{"が含まれていたら終了です。

CHTB{n33dl3_1n_4_h4yst4ck}


PhaseStream3 300pt ★

The aliens have learned the stupidity of their misunderstanding of Kerckhoffs's principle. Now they're going to use a well-known stream cipher (AES in CTR mode) with a strong key. And they'll happily give us poor humans the source because they're so confident it's secure!
This challenge will raise 33 euros for a good cause.
ファイル:phasestream3.py, output.txt

AESのCTRモードで暗号化しているみたいです。CTRモードは初耳なのでこちらから勉強しました。

共通鍵暗号の暗号モード CTRについて - Qiita

簡単に言えば、ナンスとカウンターをkeyによって暗号化した値と平文をxorしています。

平文と暗号文のペアがあれば「ナンスとカウンターをkeyによって暗号化した値」が分かります。今回はそれが分かります。

同じkeyでインスタンスを作ると同じナンスとカウンターが使われるので、「」内を求めてflagの暗号文をxorしたら終了です。

from Crypto.Util.number import *

test_enc = "464851522838603926f4422a4ca6d81b02f351b454e6f968a324fcc77da30cf979eec57c8675de3bb92f6c21730607066226780a8d4539fcf67f9f5589d150a6c7867140b5a63de2971dc209f480c270882194f288167ed910b64cf627ea6392456fa1b648afd0b239b59652baedc595d4f87634cf7ec4262f8c9581d7f56dc6f836cfe696518ce434ef4616431d4d1b361c"
flag_enc = "4b6f25623a2d3b3833a8405557e7e83257d360a054c2ea"

test = b"No right of private conversation was enumerated in the Constitution. I don't suppose it occurred to anyone at the time that it could be prevented."
#test=平文と、test_enc=暗号文の組み合わせが分かるのでenc(key) xor test = test_encからenc(key)を求める
enc_key = long_to_bytes(bytes_to_long(test)^int(test_enc,16))
#flag長が46//2 = 23ということが分かる。enc(key)の上位23bytesとxorさせればOK
print(long_to_bytes(bytes_to_long(enc_key[:23])^int(flag_enc,16)))

CHTB{r3u53d_k3Y_4TT4cK}


PhaseStream4 300pt ★★

The aliens saw us break PhaseStream 3 and have proposed a quick fix to protect their new cipher.
This challenge will raise 43 euros for a good cause.
ファイル:phasestream4.py, output.txt

前回と違って、平文と暗号文のペアが分かりません。

testquoteとflagの暗号文が一つずつあり、それぞれ96,85bytesです。「平文と暗号文のペア」が分からんくても解けるのか?とノートに数式いっぱい書いたけど分からず。

先頭5bytesがb"CHTB{"とすると、そこからenc_keyを求めてtestquoteを出すと「I alo」となります。「I alone ... 」なのかなとか思ってたけどそこから有力な情報を得ず…。英文になるようにtestquoteを決めてflagが意味ある文字列になればいいな、とか思ったけど人力じゃ無理でした。

次に前回の問題の平文について考えます。

No right of private conversation was enumerated in the Constitution. I don't suppose it occurred to anyone at the time that it could be prevented.

これはWhitfield Diffieの言葉(?)らしいです。

www.brainyquote.com

Diffieと言えばDiffie-Hellman鍵共有ですよね。3でDiffieなら4はHellmanの言葉を使っているのでは?とHellman quotesで検索するも何も出てこず…。この筋は違うと判断。

次にtestquoteが96bytes, 96文字ということに注目して身の回りに96文字の文章が無いのかと探してみると……

The aliens saw us break PhaseStream 3 and have proposed a quick fix to protect their new cipher.

あった。

早速それがtestquoteとしてenc_keyを求めてflagを出すと、意味ある文字列になりません。
え、これ違うのか。96文字でドンピシャだと思ったのに。問題文をrot13してみたり文字数を変えず色々試してみたのですが、そんな複雑なエスパー問題ではないだろうと考えなおします。
solves数が少ないわけではないので簡単に解けるのかなと思ってたのですが、問題文と暗号文の文字数が一緒だから関係あるのでは?から離れられませんでした。

数日悩み続け、一番最初のflagの先頭5bytesはb"CHTB{"であることからtestquoteの先頭5文字は"I alo"となることから調べていくと上手くいきました。
「quote I alo」と打つと予測で文章らしきものが出てきます。マザーテレサのお言葉でした。

I alone cannot change the world, but I can cast a stone across the water to create many ripples.

最初ヒットしたものは途中の"water"にsが付いており97文字でした。なのでflagが見えているんですが最後ぐちゃぐちゃした感じになりここでも悩みました。残り13文字が上手く復号されなかったので、その13文字を"Mother Teresa"と置けばいいのか?となりましたが違った…。その時に出ていたflagはこちらです。

CHTB{stream_ciphers_with_reused_keystreams_are_vulnerable_to_known_plain'1c;pcptr.>q

上手く復号されなかった部分は"plaintext"と続くのかなと予想します。flagがそうなら追加した"text"の部分からtestquoteはどうなるか確認したところ「to」(※アンダースコアは半角スペース)となり、s以降の文字列と一緒ではと考えsを消せば上手く復号されました。sが無いバージョンのquoteもあったのですが、他に解けた人はこのように解けたのか、はたまた97文字だからおかしいとして別のを探したのか気になります。

CHTB{stream_ciphers_with_reused_keystreams_are_vulnerable_to_known_plaintext_attacks}


SoulCrabber 300pt ★

Aliens heard of this cool newer language called Rust, and hoped the safety it offers could be used to improve their stream cipher.
This challenge will raise 33 euros for a good cause.
ファイル:main.rs, out.txt

Rustは全く触ったことがなく環境整備が難しそうだったので、Rust Playgroundを使いました。

Rust Playground

main.rs を見ると、flagを読み込んで何かとxorして出力しているようです。その何かを求める必要がありそうですね。
get_rng関数では13371337をseedとして何かしらの整数を生成しているようです。結局この仕組みは分からないのですが、このくらいの解釈でも解けました。
ではどんな整数を生成しているのか確かめるために、flagと同じ長さになるように"A"を繋げそれをflagの代わりにして実行してみました。flagの長さはout.txtの長さから分かります。

fn main() -> std::io::Result<()> {
    let flag = "AAAAAAAAAAAAAAAAAAAAAAAAAAAAA";
    let xored = rand_xor(flag.to_string());
    println!("{}", xored);
    Ok(())
}

main.jsのmain関数だけ上記のように変更しました。すると、"19500187e1ba0b5bf3e27914c83f1ffd67123543ffc3ae4a4d22a9d54f" が得られました。
"A"29 xor (13371337をseedとして生成した整数) = 195...54f なので
(13371337をseedとして生成した整数) = "A"
29 xor 195...54f となります。あとはこれと貰ったout.txtとxorして終了です。

CHTB{mem0ry_s4f3_crypt0_f41l}


SoulCrabber2 300pt ★★

Aliens realised that hard-coded values are bad, so added a little bit of entropy.
This challenge will raise 43 euros for a good cause.
ファイル:main.js, out.txt

先程は13371337をseedとしていたのに対し、今回はどうやらUNIX TIMEでseedを取っているようです。ここ2週間で作成したものだろうと高を括って解きました。
問題はRustで関数に引数を渡すやり方がいまいち分からなかったので、main関数で完結させました。

use rand::{Rng,SeedableRng};
use rand::rngs::StdRng;
use std::fs;
use std::io::Write;
use std::time::SystemTime;

fn main() -> std::io::Result<()> {
    let mut counter =0;
    while counter != 86400*14{
        let mut rng = StdRng::seed_from_u64(1617537600+counter);
        let flag = "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA";
        let enc = flag.to_string();
        let xored = enc
            .chars()
            .into_iter()
            .map(|c| format!("{:02x}", (c as u8 ^ rng.gen::<u8>())))
            .collect::<Vec<String>>()
            .join("");
        println!("{}", xored);
        counter += 1;
    }
    Ok(())
}

86400*14のbrute forceなので多少時間はかかりますが、回し切れますね。

CHTB{cl4551c_ch4ll3ng3_r3wr1tt3n_1n_ru5t}


RSA jam 325pt ★★

Even aliens have TLA agencies trying to apply rubber hose cryptanalysis.
This challenge will raise 43 euros for a good cause.
ファイル:rsajam.py

与えられたプログラムを読むと、RSAの公開鍵(e,n)に加え秘密鍵dが与えられます。φ(n)未満の与えられたd以外でme×d2 = m mod n となるd2を送るとflagがもらえるようです。

そもそも0以上φ(n)未満で秘密鍵となり得る自然数が複数存在するのか。フェルマーの小定理から、ap-1 = 1 mod p, aq-1 = 1 mod q (a< min(p-1,q-1) ) です。xがp-1とq-1の倍数とするとオイラーの定理より、ax = 1 mod nとなります。つまり、e×d2 = x + 1 (x = k×(p-1) = l×(q-1), k,lは自然数) となればよいです。

では、上記のの式に当てはまるd2を考えていきます。 e × d = 1 mod φ(n) であるので、e×d-1 = k×φ(n) (k : 自然数)と表せます。e, dは与えられるので、e×d-1を素因数分解して素数を上手く分けるとk × φ(n) という形に分けることが出来ます。
ではその上手く分けるためにはどうするかというと、素因数分解後の素数たちをbrute forceして試すんです。素数の個数の合計が20個以下ならまあ間に合います( O(220) 程度)。ビット全探査です。かなり大きい素数はほとんどの確率でφ(n)の方に含まれるので先に決め打ちしても問題ありません。
e×d-1 = a_1 × a_2 × ... × a_n とした時に、phi = a_x1 × a_x2 × ... × a_xm とおきます。phi = φ(n) であれば n - phi = n - φ(n) = p+q-1 となります。m = n - phi + 1 (= p + q) とおくと、n=p × q という情報もあるので解と係数の関係よりp,qを求めることが出来ます。
二次方程式 x2 - mx + n = 0 の解がp, qとなります。解の公式より m±√(m2 - 4 × 1 × n) / 2×1 が素数となればいいので、ルートの中身が平方数なのか判断すればOKです。求めた解p, qの積がnならnの素因数分解が出来ました。

次に、e×d2 = x + 1 (x = k×(p-1) = l×(q-1), k,lは自然数) となるd2を求めていきます。e×d = 1 + k×φ(n) と表せます。ここで、l = lcm(p-1,q-1)とすると、me×(d+l) = me×d + e×l = m×me×l mod n となります。lはp-1とq-1の倍数であるので、me×l = 1 mod nとなります。故に、d2 = d+l とすれば欲しいd以外の秘密鍵を手に入れられました(d+lがφ(n)を超える場合はd-lでもOKです)。

import gmpy2
import random
import math
e = 65537
pl =[2,2,3,3,5,5,7,11,11,13,191,199,1489]
phi = 2954001204170914691805741860654963857940004074429377692284091070811401369951320958401069220899257193610366982095760499087321176610501426227559528834963391573119792751626794365037601107813476160364027477073371928090485550540475603624680000569693639581200408072914193394481017798545653296930496371873
d = 25279851828946689969935508637891642190361805428614897956944707979466882691402752252273969173866247553416847240691284721506378725185547104028957495800217551084649108905497165090047528206605038035320469604378521860671903204212263641356781239235920533291802966072966655090757438950541684952893977777198807710573
N = 123630001441211791699101815506417771377489862127836323215005247880779127747665261872791516882894729192469212567210262427681780651629370984012072785781572863755222453735206569087396934214192134882937330171191632058382828512367532999211413054935111652810223730835723518262117916443844727685713391668125094593189


for i in range(1<<len(pl)):
    temp = phi
    s = str(bin(i))[2:]
    s = "0"*(len(pl)-len(s)) + s
    #print("s",s)
    for j in range(len(pl)):
        if s[j] == "1": temp *= pl[j]
    M = N-temp+1
    #print(M,N)
    rot, ch = gmpy2.iroot(M*M-4*N,2)
    if ch:
        print("find!")
        p = (M-int(rot))//2
        q = (M+int(rot))//2
        phi = (p-1)*(q-1)
        lc = (p-1) * (q-1) // math.gcd((p-1), (q-1))
        print(d+lc)
        print(d+lc < phi)
        m = random.randrange(N)
        c = pow(m,e,N)
        m1 = pow(c,d+lc,N)
        print(m == m1)

e×d-1を出力した後はfactorDBで素因数分解したのでsolverらしきものは作ってはないです。素因数分解後、上のプログラムでd2を求めて手打ちで送って終了です。

CHTB{lambda_but_n0t_lam3_bda}


Problems

問題ファイルやsolverはこちらからどうぞ!

github.com

Plaid CTF 2021 XORSA writeup


Plaid CTF お疲れ様でした!

 

問題数は多いわけではないのですが、難易度が高い…。XORSAをなんとか解けたのですが他の問題には全く歯が立たず、結局この一問のみのsolveです。

 

plaidctf.com

Result

f:id:partender810:20210419111339p:plain
145位でした

 

XORSA Writeup

XOR + RSA = XORSA
ファイル:xorsa.29fb8e0ef0173eef5953d792373b7db98169018db6747f5e27d26c1c7cb98873.tgz 

linux tgz 解凍」でググります。どれ試しても上手くいきません。出てきたエラーをググるとこんな親切なサイトが!

 

tar.gzファイルの解凍 - ZDNet Japan

 

fileコマンドを叩いてみると

$ file test.tar
test.tar: POSIX tar archive (GNU)

と出るので、このサイトの通りmvコマンドでtest.tarなどにしておきます。そしてtar xvfで解凍できました! いやもう最初からtarファイルで配ってくれ。  

$ tar xvf test.tar
dist/
dist/xorsa.sage
dist/public.pem
dist/flag.enc

 

xorsa.sageが問題プログラムということで覗いてみます。

xはpとqのxorした値です。 keyをRSA.constructに(n,e,d,p,q)を投げて生成し、それを用いてPKCS1_OAEPのインスタンスを作ります。 flagを暗号化したものや公開鍵などをファイルに出力します。

まずは公開鍵を知りたいのですが、public.pemを見るとn = 整数という形ではなくopenSSLを用いて解読する必要がありそうです。 openSSLは良くは知らないのですが、このサイトやpicoGymで出た問題から解読できました。

How to extract public key using OpenSSL? - Stack Overflow

picoCTF

openssl rsa -noout -text -inform PEM -in public.pem -pubin

これで以下の結果を得られました。

$ openssl rsa -noout -text -inform PEM -in public.pem -pubin
RSA Public-Key: (4096 bit)
Modulus:
    00:8c:6d:db:f9:a2:dd:53:a2:46:6f:f6:1b:b6:2c:
    f5:be:3a:f8:9d:e8:21:bb:75:a5:81:ab:c8:51:4a:
    84:39:2a:66:13:ea:a3:26:9c:15:98:43:ee:f7:81:
    df:55:22:a9:2d:2d:5a:dc:3c:95:a5:4c:5f:eb:42:
    7d:08:2c:15:7d:4f:2d:b6:90:14:11:c0:ce:29:f1:
    57:15:73:fb:48:88:85:ee:c8:d3:6f:8c:79:48:2b:
    51:23:a8:20:47:d7:95:88:a9:5f:b2:c1:a1:67:10:
    42:78:5f:5d:0c:14:69:eb:a2:18:58:d6:43:f6:64:
    65:9f:9e:63:03:39:5a:05:da:bb:05:03:f2:e8:80:
    a8:99:a6:7e:78:40:bd:20:fa:c2:83:16:93:62:09:
    28:d0:c9:0c:47:ab:d6:95:d0:0d:27:e4:8e:78:62:
    a3:15:01:59:ac:58:e5:0b:64:d1:c9:d4:6c:c7:c6:
    c1:95:df:61:ec:0f:ef:79:93:7f:55:45:b0:8a:53:
    51:73:82:c0:73:62:fc:8a:24:75:a9:9e:9c:b4:1e:
    4f:c5:44:a7:00:e1:1c:a2:3c:65:8a:df:ce:d9:ee:
    bb:79:5e:3a:a5:a3:32:d1:e3:9b:c7:28:d8:e0:39:
    f0:7d:2b:2b:f9:3c:4e:11:20:a3:d4:cb:1b:7a:e1:
    e0:d7:8c:ea:c3:75:95:f0:bd:99:4e:c7:8e:bf:8d:
    39:c5:fb:12:d8:03:cd:f9:bb:01:9a:6f:da:4d:20:
    bf:fd:e8:fe:f0:52:a6:18:94:33:ed:cf:94:f6:d7:
    bb:6a:8d:9c:c1:91:96:50:e0:13:9b:6f:28:4d:44:
    bf:1d:67:2e:5c:e8:a5:6f:2e:7b:d3:e2:eb:ee:6f:
    b7:f6:31:e0:03:9c:f5:07:9e:cf:74:b1:c7:56:5e:
    60:58:b8:1f:46:b5:b6:6e:af:7f:83:4e:94:2c:ce:
    5a:6c:c3:bd:47:ef:4b:d4:18:b1:79:c1:11:4a:a0:
    fb:68:ab:3f:aa:91:ed:bc:51:04:07:e1:05:24:b3:
    79:0f:04:e3:b2:7e:46:eb:c2:5e:5b:b2:b9:34:d4:
    e4:6b:f0:94:05:a5:67:f3:c7:e7:7c:e3:f0:ae:e3:
    83:bd:0c:8d:72:ec:b2:a3:99:73:7c:3f:d2:79:26:
    1b:f2:7b:7c:cf:ee:8b:b2:14:1e:20:2e:fb:78:68:
    7d:15:85:60:9a:af:7c:2c:6f:35:97:f8:4b:00:3b:
    55:a9:f2:ea:d1:ae:df:e0:00:56:cb:b5:50:99:42:
    c2:88:dd:d2:08:fc:05:da:80:33:35:b5:e0:62:b2:
    70:9e:f5:9f:d0:e3:d4:ea:21:7b:8f:29:5d:4a:c0:
    21:ff:37
Exponent: 65537 (0x10001)

 

この16進数をsplit(":")などで強引に整数型として入れます。

次に、暗号化されたflagについてみていきます。
とりあえずbytes_to_longしてみると、なんとnより大きい値に…。これじゃ復号できないじゃないか!
わざわざPKCS1_OAEPを使っているということはここに復号の方法があるのではないかと手元で動かしてみたら、key = RSA.construct( (n,e,d,p,q) )で暗号化時と同じパラメータを投げて生成したkeyによるインスタンスでdecryptすればOKです。

>>> key = RSA.construct((n,e,d,p,q))
>>> flag = b"m1z0r3{test_flag_yeah}"
>>> cipher = PKCS1_OAEP.new(key)
>>> enc = cipher.encrypt(flag)
>>> cipher.decrypt(enc)
b'm1z0r3{test_flag_yeah}'

 

まあ、普段のRSAと同様(n,e,d,p,q)の全てを求めたらOKですね。頑張って求めていきましょう。

nは当然素因数分解出来ませんし、情報としてもp xor qのみ公開されています。これを使ってp,qを求めるのだろうとなりますね。
とりあえずいつもの「RSAでやってはいけないnのこと」を見て、pの上位ビット(1/2程度)知られるとダメとなるcoppersmithかなぁと目星を付けます(違った)。「xor multiple」でググっても複数入力のxorしか出てきません。
数学的な問題なのでm1z0r3の黄coderに聞いてみました。「bit毎に見ていけばいけるのでは?」なるほど。

p,qを2進数でのかけ算の筆算を頭に浮かべると、例えばp,qの下位3bitsが決まればnの下位3bitsが決まります(p,qの下位3bits以外はnの下位3bitsに影響しません)。 pを01どちらかを最上位に追加するbrute forceを行ってどんどんbits数増やしていき、増やしたpの01とxor結果よりqの最上位に追加する01が決まります。増やす途中のp*qの下位bitsとnの下位bitsが一致しなかったら、そのp,qは違うとなり一致していたbitまで戻り01の違う方を最上位に追加する再帰で考えます。

最初これを考えた時は直感的にO(22048)では?となり無理だなと思っていたのですが、最悪計算量を考えたらO(20482)程度かなと思っています(紙に書いたらそうなりました、頭の良い人証明して)。すぐさま実行に移りました。
pの候補が2048bitsの時にnで割り切れるかどうかでチェックして余りが0なら終わりです。

p,q,xorの値からtp="11", tq="01"(temp_p(q)の略)であることが分かるので、後は再帰で解きます。python再帰回数上限を設定しないと怒られるのでsysライブラリから設定します。 とりあえず3000に設定しておきました

ciphertextについては、最後の改行があるとincorrect lengthと怒られるのでそれを抜いてdecryptすると上手く復号されました。

pを仮に置くとqが一意的に決まってしまうことが素因数分解(?)の計算量削減となる脆弱性出したという感じですね。

今回からmarkdown記法で書いているので、solverを張り付けますね(最初からやれ)。githubには完全版と問題ファイルも添付してます。

GitHub - ksbowler/PlaidCTF_2021

import sys
from Crypto.Util.number import *
from Crypto.PublicKey import RSA
from Crypto.Cipher import PKCS1_OAEP

n = (省略)
x = (省略)

M = 2048

binn = str(bin(n))[2:]
binx = str(bin(x))[2:]
binx = "0" + binx
sys.setrecursionlimit(3000)
def recur(tp,tq):
    #再帰でpを求める。tp,tqはp,qの候補の値
    #求めたらpを返し、現在のtp,tqでは求められないのであればFalseを返す
    bits = len(tp)
    if bits == M:
        #tpが2048bitsになった場合
        p = int(tp,2)
        if n%p == 0:
            print("Find!")
            print(p)
            return p
        #nで割った余りが0でないから、tpはpではない
        return False
    tn = int(tp,2)*int(tq,2)
    if str(bin(tn))[-bits:] != binn[-bits:]:
        #tp,tqの積の下位x bits (xはtpの長さ。変数bitsと同義)がnの下位x bitsと異なるのであれば、tp,tqはp,qの候補値から外れる。
        return False

    #xの値より、tp,tqに追加すべき候補は2通りに絞られる
    if binx[-(bits+1)] == "1":
        p = recur("1"+tp,"0"+tq)
        if p: return p

        p = recur("0"+tp,"1"+tq)
        if p: return p
    else:
        p = recur("1"+tp,"1"+tq)
        if p: return p

        p = recur("0"+tp,"0"+tq)
        if p: return p

    return False

p = recur("11","01")
q = n//p
e = 65537
phi = (p-1)*(q-1)
d = inverse(e,phi)
f = open("./dist/flag.enc","rb")
a = f.readlines()
key = RSA.construct((n,e,d,p,q))
cipher = PKCS1_OAEP.new(key)
ciphertext = a[0] + a[1][:-1] #最後の改行が入ると悪さをするのでそれを抜く
flag = cipher.decrypt(ciphertext)
print(flag)

PCTF{who_needs_xor_when_you_can_add_and_subtract}


おわりに

leaky block cipherの途中経過だけでも供養します。

hashcashの突破について、以下の文字列を投げる必要があります。

  • ":" でsplitされる
  • split後、0番目には"1" とする
  • 1番目は"21"より大きい値にすべきだが、大きくし過ぎると後のPoWがきついので"22"とする。
  • 3番目は最初に貰った16進数
  • 2番目は"100", 4,5番目は"0"とテキトーに決める。
  • 全体のSHA-1ハッシュ値を16進数にした時上位5桁が"0"になるようPoWする。

これで突破できます。

しかし、それから自作のクラスのインスタンスのencryptが予測できず終了です。

関係ない話ですが、Markdownで目次が上手くいかないのなんでだろう。

RITSEC CTF 2021 writeup

picoCTF, angstromCTFと2戦連続調子は良かったのですが、このRITSEC CTFはてんでダメでした…。crypto問題は歯が立たず、OSINT問題もチームメイトに先を越され…。cryptoを2問だけ紹介します。自力で解けてませんが…。

 

調子が良かった頃はこちら!

partender810.hatenablog.com

 

partender810.hatenablog.com

 

 

 

Result

f:id:partender810:20210413111611p:plain

1ptも貢献してません…

 

Writeup

Crypto

What an Enigma? 100pt

Turing with 3 allies was the bomb...

FCIMWWCNRAQQGAEKMKNPJIP

 

Enigmaなんていう暗号あったなぁ。調べてみるか。

 

ja.wikipedia.org

 

ざっくり言うと換字式暗号のようです。詳しいアルゴリズムは分からないので、decoderが無いかググってみたらお世話になっているdcodeを発見!

 

Enigma Machine Cipher - Decoder, Encoder, Solver, Translator

 

Resultsの欄に "RSWTCHIMTTIONGAMEISWRTH" とあり、今回のCTFのflag formatはRS{}とのことなのでビンゴ!と RS{WTCHIMTTIONGAMEISWRTH} で提出したらincorrect。うーーん、こんな簡単じゃない?確かに0 solvesだったからもう一つひねるのかな?もっとアルゴリズムを勉強しないと、と時間をかけてました。

 

そしたら、チームメイトが同じ文字列で通ったとのこと。キレそう。

出来た時にちゃんとcorrectだったら1 solves目だったのに…。

 

RS{WTCHIMTTIONGAMEISWRTH}

 

 

lorem ipsum 150pt

Flag is case sensitive.

f:id:partender810:20210411150524j:plain

hint.jpeg

ファイル:cipher.txt

問題に女性の写真が添付されてます。保存しようとするとデフォルトでhint.jpegになることから、この画像が問題を解くのにかなりのヒントになることが予想されます。

 

cipher.txtは以下の通りです。

Incompraehensibilis Conseruator.
Redemptor optimus
Iudex omnipotens
Sapientissimus omnipotens
Redemptor fabricator
Iudex redemptor
Optimus magnus
Aeternus iudex
Auctor omnipotens.

 

RSが見えるからこの単語の頭文字を入れ替えるとか…?と思いましたが、とりあえず貰った画像を検索してみます。どうやら聖母マリア様の絵のようです。

 

"maria cipher" でググるTrithemius ave maria cipherというのがあるよう。dcodeも対応しているようなのでcipher.txtを投げてみると、Resultsに"RSTHISISTRITHEMIUS"と出てきたので、これはもう出来たと{}を付けて提出。incorrect。

 

https://www.dcode.fr/trithemius-ave-maria

 

大文字小文字は区別すると問題文にあるので単語の頭文字のみ大文字にするもダメ。What an Enigma?からこのスコアサーバに不信感があり、「俺嫌われてるから他の人やってみてw」と今までの経緯をslackに残しチームメイトに託します。

 

そしたらすぐスコアサーバ内でこの問題が解けたと色が変わりました。また俺嫌われてるか…と思っていたらflagの文字列が違う。

 

ここからは、解けたチームメイトによるお話なのですが、Trithemius ave maria cipherとは平文一文字に対して英単語一つ生成するようです。cipher.txtの先頭行だけdcodeに投げるとRSだけ返されます。そこから、case sensitiveから対応する単語の頭文字が大文字なら平文を大文字に、そうでないなら小文字にと変えたら通ったようです。

 

スコアサーバに嫌われてるなど変なこと言ってないでちゃんと調べて考えろ、って話です。悔しい…。

 

RS{ThIsIsTrItHeMiUs}

 

 

今回は自力で解けた問題は一つも無く、何なら自分が通した問題すら無いのですが、戒めを込めてブログに残しておきます。解けたチームメイトに許可を頂けたのですが、おそらくニヤニヤしながら通したことを考えると歯がゆく悔しい…。次回はこうならないようがんばりたいですね!

 

それでは!

 

See you next time!