好奇心の足跡

飽きっぽくすぐ他のことをしてしまうので、忘れないため・形にして頭に残すための備忘録。

Houseplant CTF 2020 [Crypto] category writeup

Houseplant CTF 2020、Beginnerに引き続き、Cryptoもやってみました。

tech.kusuwada.com

RiceTeaCatPandaCTF同様、古典暗号が多い。いくつか現代暗号に通じる行列演算の問題やRSAっぽい問題も出題されていました。

古典暗号は今回初めて見るのもあって面白かったです。と同時に、古典暗号、思いついたもん勝ち感があるので無限にありそうで、全部網羅するのは無理という気持ちになってきた。メジャーどころだけ抑えて、あとは問題文から推測する能力とgoogle力の争いみたいになってる気がする。

Broken Yolks 50 Points

Fried eggs are the best. Oh no! I broke my yolk... well, I guess I have to scramble it now.

Ciphertext: smdrcboirlreaefd

Dev: Delphine

Hint!

words are separated with underscores

問題文から推測するに、アナグラムかな?
一語ではなさそうなので、Anagram Solverに突っ込んでも出てこない。

見つめていたら問題文に登場する単語が見えてきたので解けた。

fried_or_scrambled かな。archiveなので答え合わせがしづらいという。

flag: rtcp{fried_or_scrambled}

Sizzle 50 Points

Due to the COVID-19 outbreak, we ran all out of bacon, so we had to use up the old stuff instead. Sorry for any inconvenience caused...

Dev: William

Hint!

Wrap your flag with rtcp{}, use all lowercase, and separate words with underscores.

Hint!

Is this really what you think it is?

encoded.txt 66ad1da967199839c7a2c8381054a506

....- ..... ...-. .--.- .--.. ....- -..-- -..-. ..--. -.... .-... .-.-. .-.-. ..-.. ...-- ..... .--.. ...-- .-.-- .--.- -.... -...- .-... ..-.- .-... ..-.. ...--

お、モールス信号だと思ったけど、ヒントの2番目が気になる。あと全部の信号長が同じなのも気になる。とりあえずモールス信号をdecodeしてみる。

$ git clone git@github.com:kusuwada/morse-code.git
$ cd morse-code
$ python morse.py
input your message > ....- ..... ...-. .--.- .--.. ....- -..-- -..-. ..--. -.... .-... .-.-. .-.-. ..-.. ...-- ..... .--.. ...-- .-.-- .--.- -.... -...- .-... ..-.- .-... ..-.. ...--
[ERROR] There's invalid character: .--.-
[ERROR] There's invalid character: .--..
[ERROR] There's invalid character: -..--
[ERROR] There's invalid character: -..-.
[ERROR] There's invalid character: ..--.
[ERROR] There's invalid character: ..-..
[ERROR] There's invalid character: .--..
[ERROR] There's invalid character: .-.--
[ERROR] There's invalid character: .--.-
[ERROR] There's invalid character: ..-.-
[ERROR] There's invalid character: ..-..
45understood*.--.-**.--..*4*-..--**-..-.**..--.*6&endend*..-..*35*.--..*3*.-.--**.--.-*6=&*..-.-*&*..-..*3

ほー、解読できない信号が多いみたいだ。やっぱりmorseではなさそう。

となると、2値で表現されていると捉えて、0,1に変換、binaryとして読んでみるとどうなるかな?

-1に、.0に。

00001 00000 00010 01101 01100 00001 10011 10010 00110 10000 01000 01010 01010 00100 00011 00000 01100 00011 01011 01101 10000 10001 01000 00101 01000 00100 00011

Cyberchefにくわせると、いい感じに出してくれた。

Cyberchef

2進 -> 10進 -> alphabetのindex、でいけたみたい。
hintの通りflagに変換すると、

flag; rtcp{bacon_but_grilled_and_morsified}

CH₃COOH 50 Points

Owaczoe gl oa yjirmng fmeigghb bd tqrrbq nabr nlw heyvs pfxavatzf raog ktm vlvzhbx tyyocegguf. Tbbretf gwiwpyezl ahbgybbf dbjr rh sveah cckqrlm opcmwp yvwq zr jbjnar. Slinjem gfx opcmwp yvwq gl demwipcw pl ras sckarlmogghb bd xhuygcy mk ghetff zr opcmwp yvwq ztqgckwn. Rasec tfr ktbl rrdrq ht iggstyk, rrnxbqggu bl lchpvs zymsegtzf. Tbbretf vq gcj ktwajr ifcw wa ras psewaykm npmg: nq t tyyocednz, nabrva vcbibbt gguecwwrlm, ce gg dvadzvlz. Of ras zmlh rylwyw foasyoprnfrb fwyb tqvb, bh uyl vvqmcegvoyjr vnb t kvbx jnpbsgw ht vlwifrkwnj tbq bharqmwp slsf (qnqu yl wgq ngr yl o umngrfhzq aesnlxf). Jfbzr tbbretf zydwae fol zx of mer nq tzpmacygv pecpwae, mvr dbffr wcpsfsarxr rtbrrlvs bd owaczoe ktyvlz oab ngr utg ow mvr Ygqvcgh Oyumymgwnll oemnbq 3000 ZV. Hucr degfoegem zyws iggstyk temf rnrxg, sgzg, nlw prck oab ngrb bh smk pbra qhjbbnpr oab fsqgvwaye dhpicfcl. Heyvsf my wg yegb ftjr zxsa dhiab bb Rerdggtb hpgg. Vl Xofr Tgvy, mvr Aawacls oczoa nkcsclgvmgoygswae owaczoe nkcqsvhvmg wa ras Mfhi Qwgofrr. Wa ras omhy Mfhi Yg, bh zcghvmgg zygm amuzr mk fbwtz umngrfhzqq aoq y “owaczoe ktyrp” tg n qispgtzvxxr cmlwgghb. Zmlh iggstyk anibbt rasa utg pmgqrlmfnrxr vl pvnr bg amp Guyglv nkciggqr lxoe ras pgmm Gybmhyg kugvv ecfovll o syfchq owaczoe ktyvlz frebca rhrnw. Foaw Vvvlxgr tbbretff ygr gfxwe slsf dhf psewaykm nlw arbbqvltz cskdbqxg jcks jpbhgcg rbug wa ras nekwpsehhptz zyginj Jwzgg Mnmlvh. pmqc{tbbretf_bl_fm_sglv_nlw_qugig_cjxofc}

Dev: William

Hint!

Short keys are never a good thing in cryptography.

お、これは得意なゲスパー式換字暗号かな?最後にflag formatが見えることから、これを手がかりに対応を考えていけそう。

一旦問題文を全部小文字に直して、下記の変換をかける。

p -> R  +2
m -> T  +7
q -> C  +12
c -> P  +13 

ここまで置換してみて気づいたけど、今回の問題文には規則性がない。例えば a の可能性が高い一文字の単語は、今回 y, n, o, t とたくさんの形で出てきているし、頻出しそうな the の形も上記の変換後に T** となっているはずなのだが、あまり決まった形で出現していない。

となると、ヒントのshort keyが俄然きになる。これは鍵を使うヴィジュネル暗号っぽいな。鍵は短いっぽいので、上記で対応の取れた[2,7,12,13]をまず使ってみる。

import string
alphabet = string.ascii_lowercase

cipher = """Owaczoe gl oa yjirmng fmeigghb bd tqrrbq nabr nlw heyvs pfxavatzf raog ktm vlvzhbx tyyocegguf.
Tbbretf gwiwpyezl ahbgybbf dbjr rh sveah cckqrlm opcmwp yvwq zr jbjnar.
Slinjem gfx opcmwp yvwq gl demwipcw pl ras sckarlmogghb bd xhuygcy mk ghetff zr opcmwp yvwq ztqgckwn.
Rasec tfr ktbl rrdrq ht iggstyk, rrnxbqggu bl lchpvs zymsegtzf.
Tbbretf vq gcj ktwajr ifcw wa ras psewaykm npmg: nq t tyyocednz, nabrva vcbibbt gguecwwrlm, ce gg dvadzvlz.
Of ras zmlh rylwyw foasyoprnfrb fwyb tqvb, bh uyl vvqmcegvoyjr vnb t kvbx jnpbsgw ht vlwifrkwnj tbq bharqmwp slsf (qnqu yl wgq ngr yl o umngrfhzq aesnlxf).
Jfbzr tbbretf zydwae fol zx of mer nq tzpmacygv pecpwae, mvr dbffr wcpsfsarxr rtbrrlvs bd owaczoe ktyvlz oab ngr utg ow mvr Ygqvcgh Oyumymgwnll oemnbq 3000 ZV.
Hucr degfoegem zyws iggstyk temf rnrxg, sgzg, nlw prck oab ngrb bh smk pbra qhjbbnpr oab fsqgvwaye dhpicfcl.
Heyvsf my wg yegb ftjr zxsa dhiab bb Rerdggtb hpgg.
Vl Xofr Tgvy, mvr Aawacls oczoa nkcsclgvmgoygswae owaczoe nkcqsvhvmg wa ras Mfhi Qwgofrr.
Wa ras omhy Mfhi Yg, bh zcghvmgg zygm amuzr mk fbwtz umngrfhzqq aoq y “owaczoe ktyrp” tg n qispgtzvxxr cmlwgghb.
Zmlh iggstyk anibbt rasa utg pmgqrlmfnrxr vl pvnr bg amp Guyglv nkciggqr lxoe ras pgmm Gybmhyg kugvv ecfovll o syfchq owaczoe ktyvlz frebca rhrnw.
Foaw Vvvlxgr tbbretff ygr gfxwe slsf dhf psewaykm nlw arbbqvltz cskdbqxg jcks jpbhgcg rbug wa ras nekwpsehhptz zyginj Jwzgg Mnmlvh.
pmqc{tbbretf_bl_fm_sglv_nlw_qugig_cjxofc}"""

key = [2,7,12,13]
plain = ""
cnt = 1
for c in cipher:
    if c.islower():
        plain += alphabet[(alphabet.index(c) + key[cnt%len(key)])%26]
        cnt += 1
    elif c.isupper():
        plain += alphabet[(alphabet.index(c.lower()) + key[cnt%len(key)])%26].upper()
        cnt += 1
    else:
        plain += c
print(plain)

最初のcntの値を調整することで、鍵をどこから使うかが調整できる。今回はフラグフォーマット部分がrtcp{になるように調整すると、ctn=1であった。

実行結果

$ python CHCOOH.py 
Vinegar is an aqueous solution of acetic acid and trace chemicals that may include flavorings.
Vinegar typically contains five to eight percent acetic acid by volume.
Usually the acetic acid is produced by the fermentation of ethanol or sugars by acetic acid bacteria.
There are many types of vinegar, depending on source materials.
Vinegar is now mainly used in the culinary arts: as a flavorful, acidic cooking ingredient, or in pickling.
As the most easily manufactured mild acid, it has historically had a wide variety of industrial and domestic uses (such as its use as a household cleaner).
While vinegar making may be as old as alcoholic brewing, the first documented evidence of vinegar making and use was by the Ancient Babylonians around 3000 BC.
They primarily made vinegar from dates, figs, and beer and used it for both culinary and medicinal purposes.
Traces of it also have been found in Egyptian urns.
In East Asia, the Chinese began professionalizing vinegar production in the Zhou Dynasty.
In the book Zhou Li, it mentions many noble or royal households had a “vinegar maker” as a specialized position.
Most vinegar making then was concentrated in what is now Shanxi province near the city Taiyuan which remains a famous vinegar making region today.
Many Chinese vinegars and their uses for culinary and medicinal purposes were written down in the agricultural manual Qimin Yaoshu.
rtcp{vinegar_on_my_fish_and_chips_please}

更に意味のある文章になったので、これでflagゲット!

"fences are cool unless they're taller than you" - tida 50 Points

archiveには問題のタイトルがないぞ?と、問題文から検索してタイトルっぽいものを突き止めた。

They say life's a roller coaster, but to me, it's just jumping over fences.

tat_uiwirc{s_iaaotrc_ahn}pkdb_esg

並び替えかな?と、アナグラム解析サイトを使って色々やってみるも、成果なし。ヒントなしに並び替えるにはちょっと長過ぎる。

このCTFはCyberChef推しとのことでCyberChefに突っ込んでみるも、オススメのOutputは特にないみたい。

ヒントがよくわからないので、CyberChefの検索に、タイトルの文字からfenceをいれてみると、Rail Fence Ciphe というのが引っかかった!なんか問題文と関係が深そうだ。試してみる。

初期設定だと ttartc__uaihwni}rpck{dsb__ieasago

Keyを調整してみると、Key=3で tcp{ask_tida_about_rice_washing}r!おや!
Offsetを3にするとflagが出た。

f:id:kusuwada:20201113063116p:plain

解説はここがわかりやすかった。

Rail Fence Cipher - Crypto Corner

暗号探し問題みたいになってあまり解いた気がしなかったので、CyberChefに助けてもらった上で手動でやってみた。keyが3というのがわかっていないと手動は厳しいな。ツールを使うか作るかしたいところ。

tat_uiwirc{s_iaaotrc_ahn}pkdb_esg
t   a   t   _   u   i   w   i   r
 c { s _ i a a o t r c _ a h n }
  p   k   d   b   _   e   s   g

縦にジグザグに読むと、flagになってる👍

Returning Stolen Archives 50 Points

So I was trying to return the stolen archives securely, but it seems that I had to return them one at a time, and now it seems the thieves stole them back! Can you help recover them once and for all? It seems they had to steal them one at a time...

Dev: William

Hint!

Well you sure as hell ain't going to solve this one through factorization.

intercepted.txt, returningstolenarchives.py

n = 54749648884874001108038301329774150258791219273879249601123423751292261798269586163458351220727718910448330440812899799 
e = 65537
ct = [52052531108833646741308670070505961165002560985048445381912028939564989677616205955826911335832917245744890104862186090,24922951057478302364724559167904980705887738247412638765127966502743153757232333552037075100099370197070290632101808468,31333127727137796897042309238173536507191002247724391776525004646835609286736822503824661004274731843794662964916495223,37689731986801363765434552977964842847326744893755747412237221863834417045591676371189948428149435230583786704331100191,10128169466676555996026197991703355150176544836970137898778443834308512822737963589912865084777642915684970180060271437,31333127727137796897042309238173536507191002247724391776525004646835609286736822503824661004274731843794662964916495223,32812400903438770915197382692214538476619741855721568752778494391450400789199013823710431516615200277044713539798778715,48025916179002039543667066543229077043664743885236966440148037177519549014220494347050632249422811334833955153322952673,52052531108833646741308670070505961165002560985048445381912028939564989677616205955826911335832917245744890104862186090,32361547617137901317806379693272240413733790836009458796321421127203474492226452174262060699920809988522470389903614273,4363489969092225528080759459787310678757906094535883427177575648271159671231893743333971538008898236171319923600913595,47547012183185969621160796219188218632479553350320144243910899620916340486530260137942078177950196822162601265598970316,32361547617137901317806379693272240413733790836009458796321421127203474492226452174262060699920809988522470389903614273,33230176060697422282963041481787429356625466151312645509735017885677065049255922834285581184333929676004385794200287512,32315367490632724156951918599011490591675821430702993102310587414983799536144448443422803347161835581835150218650491476,6693321814134847191589970230119476337298868688019145564978701711983917711748098646193404262988591606678067236821423683,32710099976003111674253316918478650203401654878438242131530874012644296546811017566357720665458366371664393857312271236,49634925172985572829440801211650861229901370508351528081966542823154634901317953867012392769315424444802884795745057309,50837960186490992399835102776517955354761635070927126755411572132063618791417763562399134862015458682285563340315570436]
p = [redacted]
q = [redacted]
e = 65537
flag = "[redacted]"

def encrypt(n, e, plaintext):
  print("encrypting with " + str(n) + str(e))
  encrypted = []
  for char in plaintext:
    cipher = (ord(char) ** int(e)) % int(n)
    encrypted.append(cipher)
  return(encrypted)

n = p * q
ct = encrypt(n, e, flag)
print(ct)

pythonコードを見る限り、普通のRSAっぽい。flagを一文字ずつ暗号化してctに配列として保存されているみたい。

nの桁数が少ないので無理やり素因数分解できそうではあるが、ヒントより想定解ではなさそう。flagを1文字ずつ暗号化しているので、flag format部分の平文は既知。
他に既知なのは n,e,c

問題のスクリプトより、

cipher = (ord(char) ** int(e)) % int(n)

なのでord(char)がわかれば嬉しいわけだけど、charはflagから1文字ずつとってきているので、ascii文字列の範囲でマップを作ってあげれば良さそう。

#!/usr/bin/env python3

from Crypto.Util.number import inverse
import string

n = 54749648884874001108038301329774150258791219273879249601123423751292261798269586163458351220727718910448330440812899799 
e = 65537
ct = [52052531108833646741308670070505961165002560985048445381912028939564989677616205955826911335832917245744890104862186090,24922951057478302364724559167904980705887738247412638765127966502743153757232333552037075100099370197070290632101808468,31333127727137796897042309238173536507191002247724391776525004646835609286736822503824661004274731843794662964916495223,37689731986801363765434552977964842847326744893755747412237221863834417045591676371189948428149435230583786704331100191,10128169466676555996026197991703355150176544836970137898778443834308512822737963589912865084777642915684970180060271437,31333127727137796897042309238173536507191002247724391776525004646835609286736822503824661004274731843794662964916495223,32812400903438770915197382692214538476619741855721568752778494391450400789199013823710431516615200277044713539798778715,48025916179002039543667066543229077043664743885236966440148037177519549014220494347050632249422811334833955153322952673,52052531108833646741308670070505961165002560985048445381912028939564989677616205955826911335832917245744890104862186090,32361547617137901317806379693272240413733790836009458796321421127203474492226452174262060699920809988522470389903614273,4363489969092225528080759459787310678757906094535883427177575648271159671231893743333971538008898236171319923600913595,47547012183185969621160796219188218632479553350320144243910899620916340486530260137942078177950196822162601265598970316,32361547617137901317806379693272240413733790836009458796321421127203474492226452174262060699920809988522470389903614273,33230176060697422282963041481787429356625466151312645509735017885677065049255922834285581184333929676004385794200287512,32315367490632724156951918599011490591675821430702993102310587414983799536144448443422803347161835581835150218650491476,6693321814134847191589970230119476337298868688019145564978701711983917711748098646193404262988591606678067236821423683,32710099976003111674253316918478650203401654878438242131530874012644296546811017566357720665458366371664393857312271236,49634925172985572829440801211650861229901370508351528081966542823154634901317953867012392769315424444802884795745057309,50837960186490992399835102776517955354761635070927126755411572132063618791417763562399134862015458682285563340315570436]

candidates = string.printable
cipher_map = []
for a in candidates:
    cipher_map.append((ord(a) ** int(e)) % int(n))

for c in ct:
    print(candidates[cipher_map.index(c)], end='')

実行結果

$ python returning.py 
rtcp{cH4r_bY_Ch@R!}

🙌
候補文字列は、最初string.ascii_lowercase + '{}_'あたりでやってたんだけど、エラーが出るたび追加していったら@がflagにあったので結局string.printableまで広げることになった。それでも計算時間は1~3秒。十分早い。
平文が極端に短かいと、こういう攻撃も成り立つんだな。

Rivest Shamir Adleman 338 Points

A while back I wrote a Python implementation of RSA, but Python's really slow at maths. Especially generating primes.

Dev: Tom

Hint!

There are two possible ways to get the flag ;-) chall.7z

添付ファイルのリンクが切れてる!

ってdiscordで作問者に伝えたところ、zipファイルくれました。
が、archiveサイトの更新は時間がなくてできそうにないとのこと…。

配布されたファイルを開いてみます。

$ tree
.
├── component.txt
├── decrypt.py
├── encrypt.py
├── generate_keys.py
├── public-key.json
├── requirements.txt
├── secrets.txt.enc
└── yafu
    ├── session.log
    ├── yafu.exe
    └── yafu.ini

1 directory, 10 files

なんだかファイルがたくさんあります…。
ざっと全体を見たところ、RSA暗号っぽい。
きになったのは public-key.json

{
  "n": 14789124973111021476296848088105637239631744360711283702759029615571993184538343483812762692261679989751571257397390225745462680903607825113594507659826181522873182546993168846548843434080116275590253299232301446533493766517382629712939381570463830464968402090000679601287118087864718742951323976290587726656895452247608683265863531245461656212765924225420198173623126243972001309978154459610017452823541578805220501111759679896845096182233646514951266730763397603397535030246649573902622813813121524598407341360863308895641672743294142829595585828027752131203338628041034978445233902122594930368822359472321137170149,
  "e": 5
 }

eが5!小さすぎるきがする。
これと、encrypt結果っぽい secrets.txt.enc

$ cat secrets.txt.enc 
0x454e051614d8b27c3762b732545c673028ae126857f55eeaa70eac305cd83ab68e1cae1f939997579955c667d40188fa09de133ce742fe70285359a974cc3a043dcb572df47974d0b0d048ca761ecb5a1f6eecb4e37761ac8e9fc03375341e1809f8cfdffdddc260be57afdd5233c1cf290e0e577deddce947522f5f6096e9de651ce6201450be53274c59e130a9dc3aa89936d007f6c417db6d7dd1683c2447031bc351856259503fb4cd520a6a856173fa5d6eb1d6c1f337388ec093a959662c9e5ae7193ed24c7632c49e48f86ff61fcdd0c3bc50c8ec6be584bc8c6ef80ccd263ff45918c83561f836d90f450ac0a137a977b0903f3daec7a1b2400acf16

eが小さすぎるときに使えるlow exponentialを使って平文出るかな?と思ったけど出なかった。

気を取り直してもうちょっとコードを見てみると(殆ど見てなかった)、component.txtという怪しいファイルが。

88761620475672281797897005732643499821690688597370440945258776182910533850401433150065043871978311565287949564292158396906865512113015114468175188982916489347656271125993359554057983487741599275948833820107889167078943493772101668339096372868672343763810610724807588466294391846588859523658456534735572626377

怪しい数列。
どこで参照されているのか調べてみると、generate_keys.py

primes[0] = int(open("component.txt").read())
...
p = primes[0]

ほほう!pじゃん!pが与えられてた!
n,p,q(=n/p), e, cが与えられているので、RSAの基本公式通りに計算すれば平文mが得られる。

#!/usr/bin/env python3
# -*- coding:utf-8 -*-

import gmpy2
from Crypto.Util.number import inverse

n = 14789124973111021476296848088105637239631744360711283702759029615571993184538343483812762692261679989751571257397390225745462680903607825113594507659826181522873182546993168846548843434080116275590253299232301446533493766517382629712939381570463830464968402090000679601287118087864718742951323976290587726656895452247608683265863531245461656212765924225420198173623126243972001309978154459610017452823541578805220501111759679896845096182233646514951266730763397603397535030246649573902622813813121524598407341360863308895641672743294142829595585828027752131203338628041034978445233902122594930368822359472321137170149
e = 5
c = 0x454e051614d8b27c3762b732545c673028ae126857f55eeaa70eac305cd83ab68e1cae1f939997579955c667d40188fa09de133ce742fe70285359a974cc3a043dcb572df47974d0b0d048ca761ecb5a1f6eecb4e37761ac8e9fc03375341e1809f8cfdffdddc260be57afdd5233c1cf290e0e577deddce947522f5f6096e9de651ce6201450be53274c59e130a9dc3aa89936d007f6c417db6d7dd1683c2447031bc351856259503fb4cd520a6a856173fa5d6eb1d6c1f337388ec093a959662c9e5ae7193ed24c7632c49e48f86ff61fcdd0c3bc50c8ec6be584bc8c6ef80ccd263ff45918c83561f836d90f450ac0a137a977b0903f3daec7a1b2400acf16
p = 88761620475672281797897005732643499821690688597370440945258776182910533850401433150065043871978311565287949564292158396906865512113015114468175188982916489347656271125993359554057983487741599275948833820107889167078943493772101668339096372868672343763810610724807588466294391846588859523658456534735572626377

q = n // p
d = inverse(e, (p-1)*(q-1))
m = pow(c, d, n)

flag = bytes.fromhex(hex(m)[2:]).decode('ascii')
print(flag)

実行結果

$ python rivest.py 
VERIFICATION-UpTheCuts-END
 .--.
/.-. '----------.
\'-' .--"--""-"-'
 '--'

rtcp{f1xed_pr*me-0r_low_e?}

いやー、flagのコメントそのままだった。引っ掛け問題だったのかな?

Rainbow Vomit 535 Points

o.O What did YOU eat for lunch?!

The flag is case insensitive.

Dev: Tom

Hint!

Replace spaces in the flag with { or } depending on their respective places within the flag.

Hint!

Hues of hex

Hint!

This type of encoding was invented by Josh Cramer.

output.png

やたらとヒントが多い問題だ。

f:id:kusuwada:20201113062922p:plain

こんなpngが配布される。
ヒントの3つ目が大きな手がかりっぽい。Josh Cramer でググる。

www.boxentriq.com

こんなページを発見。これだ!!!更に2つ目のヒントより、Translate hexahue codeの方を使えば良さそう。
しかしこれを解読するのは自動でできるかな?かなり面倒そうだな。でも長いからちゃんとプログラムを組むのが良さそう。

#!/usr/bin/env python3

from PIL import Image

img = Image.open('output.png')
width, height = img.size
print(width, height)

for h in range(2,height-2):
    for w in range(2,width-2):
        print(img.getpixel((w,h)))

ひとまずこんなプログラムを組んで出力を見たところ、RBGの値は(0,128,255)のみ出てくる様子。色の判定は楽ができそう。

調子に乗ってライブラリ作って公開した。encode機能も付いてるよ。

github.com

$ git clone git@github.com:kusuwada/hexahue.git
$ cd hexahue
$ vi settings.yaml

setting.yamlのpaddingを2,2,2,2に調節

$ python hexahue.py 
input mode (d: decode, e: encode) > d
input source filename > ../output.png   
THERE IS SUCH AS THING AS A TOMCAT BUT HAVE YOU EVER HEARD OF A TOMDOG. THIS IS THE MOST IMPORTANT QUESTION OF OUR TIME, AND UNFORTUNATELY ONE THAT MAY NEVER BE ANSWERED BY MODERN SCIENCE. THE DEFINITION OF TOMCAT IS A MALE CAT, YET THE NAME FOR A MALE DOG IS MAX. WAIT NO. THE NAME FOR A MALE DOG IS JUST DOG. REGARDLESS, WHAT WOULD HAPPEN IF WE WERE TO COMBINE A MALE DOG WITH A TOMCAT. PERHAPS WED END UP WITH A DOG THAT VOMITS OUT FLAGS, LIKE THIS ONE RTCP SHOULD,FL5G4,B3,ST1CKY,OR,N0T     

Hintのとおりにflag形式にすると

flag: rtcp{should,fl5g4,b3,st1cky,or,n0t}

Post-Homework Death 570 Points

My math teacher made me do this, so now I'm forcing you to do this too.

Flag is all lowercase; replace spaces with underscores.

Dev: Claire

Hint!

When placing the string in the matrix, go up to down rather than left to right.

Hint!

Google matrix multiplication properties if you're stuck.

posthomeworkdeaths.txt

Decoding matrix:

1.6  -1.4  1.8
2.2  -1.8  1.6
-1     1    -1


String:

37 36 -1 34 27 -7 160 237 56 58 110 44 66 93 22 143 210 49 11 33 22

3×3の行列と、21の要素の配列が与えられる。
どうやらヒントより、行列演算をすれば良いようだが、行列のほうが少数点以下があるのが気になる。演算結果は何になるんだろう?

解いた際の順序としては下記。

与えられた行列・配列をnumpyのarrayに変換。
stringsの方を行列に配置し直すことがHintから読み取れる & 上から下に配置するようなので、stringsの行列の形は下記のいずれかと思われる。

strings.reshape(7,3).transpose()
strings.reshape(3,7).transpose()

matrixの方はそのまま使うと仮定し、色々演算の順序や行列の形(shape)を変えたりして、最もmultiplication結果(numpy.dot)の答えが整数に近いやつを導く。

np.dot(matrix, string.reshape(7,3).transpose())

この計算順序だと計算結果がほぼ整数の行列になった。 計算結果はどうやら0~26に収まっていそうなので、alphabetのindexっぽいと当たりをつける。また、0が含まれていることと、問題文にspaceに関する注釈があることから、0、そしてflagの形式だとさらに_に置き換えられるものと当たりをつける。

この行列も、横に読むのか縦に読むのかわからなかったので両方試してみると、縦に読むようにすると意味の通る言葉になったのでこれがフラグ👍

#!/usr/bin/env python3
# -*- coding:utf-8 -*-

import numpy as np
import string
alphabets = string.ascii_lowercase

matrix = np.array([[1.6, -1.4, 1.8], [2.2, -1.8, 1.6], [-1, 1, -1]])
string = np.array([37, 36, -1, 34, 27, -7, 160, 237, 56, 58, 110, 44, 66, 93, 22, 143, 210, 49, 11, 33, 22])

mul = np.dot(matrix, string.reshape(7,3).transpose())
trans = np.round(mul.transpose().reshape(1,21)).astype(np.int64)
print(trans)

flag = ''
for t in trans[0]:
    if t == 0:
        flag += '_'
    else:
        flag += alphabets[t-1]
print(flag)

実行結果

$ python solve.py 
[[ 7 15  0  4 15  0 25 15 21 18  0  8 15 13  5 23 15 18 11  0  0]]
go_do_your_homework__

Parasite 784 Points

paraSite Killed me A liTtle inSide

Flag: English, case insensitive; turn all spaces into underscores

Dev: Claire

Hint!

Make sure you keep track of the spacing- it's there for a reason

Parasite.txt

.---  -..  ..-    --  .  -.-     -.-  -..  ..-.     .--.  ..-  ..-.     .--.  -  -.-    .---  .  ..-.    .-..  ..-    --.  .  ..-  -.-    -.-.  ....  -.-    -.-  ..-  .--   ..-.  ..-    -...  .

一見モールス信号っぽいので変換してみる。一旦ヒントは無視して、spaceを全部一つにして変換。

$ git clone git@github.com:kusuwada/morse-code.git
$ python morse.py
input your message > .--- -.. ..- -- . -.- -.- -.. ..-. .--. ..- ..-. .--. - -.- .--- . ..-. .-.. ..- --. . ..- -.- -.-. .... -.- -.- ..- .-- ..-. ..- -... .
JDUMEKKDFPUFPTKJEFLUGEUKCHKKUWFUBE

うーん。全部変換はできたみたいだけど意味が通らない。
考えられるのは、スペースの数だけ前後の文字をshiftさせるとか?

#!/usr/bin/env python3
# -*- coding:utf-8 -*-

from morse import Morse

cipher = '.---  -..  ..-    --  .  -.-     -.-  -..  ..-.     .--.  ..-  ..-.     .--.  -  -.-    .---  .  ..-.    .-..  ..-    --.  .  ..-  -.-    -.-.  ....  -.-    -.-  ..-  .--   ..-.  ..-    -...  .'

m = Morse()
arry = []
signal = ''
cnt = 0
for i in range(len(cipher)):
    if cipher[i] != ' ':
        signal += cipher[i]
        cnt = 0
    else:
        cnt += 1
        if cipher[i-1] != ' ':
            arry.append(m.dec(signal))
            signal = ''
        if cipher[i+1] != ' ':
            arry.append(str(cnt))
            cnt = 0
arry.append(m.dec(signal))
print(arry)

for i in range(len(arry)//2):
    print(chr(ord(arry[i*2])+int(arry[i*2+1])%26),end='')

こんなスクリプトを書いて試してみたが、

$ python solve.py 
['J', '2', 'D', '2', 'U', '4', 'M', '2', 'E', '2', 'K', '5', 'K', '2', 'D', '2', 'F', '5', 'P', '2', 'U', '2', 'F', '5', 'P', '2', 'T', '2', 'K', '4', 'J', '2', 'E', '2', 'F', '4', 'L', '2', 'U', '4', 'G', '2', 'E', '2', 'U', '2', 'K', '4', 'C', '2', 'H', '2', 'K', '4', 'K', '2', 'U', '2', 'W', '3', 'F', '2', 'U', '4', 'B', '2', 'E']
LFYOGPMFKRWKRVOLGJNYIGWOEJOMWZHYD

こんな感じ。引いたり足したり前の文字を処理したり後ろを処理したりしてみたけど、意味のある英語にならない。

わからないときはもう一度タイトルとヒントを読む。をやってみると、そういえば問題分が変な感じ。RiceTeaCatPandaのときもあった、単語の途中で大文字が出てきているやつ。

SKATSだ。とりあえずSKATS morseとかでググってみるとこんなページが。

SKATS - Wikipedia

SKATS stands for Standard Korean Alphabet Transliteration System. It is also known as Korean Morse equivalents.

韓国語?そういえば日本語用のモールス信号あるもんな。再度ググってみると

「パラサイト」を楽しむ豆知識2 カブスカウトとモールス符号 | 一松書院のブログ

ほー!ちょっと前に流行った韓国の映画、「パラサイト」にこのモールス信号出てきたらしい!タイトルともつながった。このページで紹介されていた、変換してくれるサイトに突っ込んで見る。

M³ Translator

f:id:kusuwada:20201113063029p:plain

f:id:kusuwada:20201113063039p:plain

ふむふむ、hope_is_a_true_parasiteがflagなのかな?
他のwriteupを見たところ rtcp{hope_is_the_oarasite}が正解らしい。aとかtheは翻訳する時困るよね。flag通すときに試すとは思うけど翻訳してflagになるのはうーん…。

11 1,527 Points

I wrote a quick script, would you like to read it? - Delphine

(doorbell rings) delphine: Jess, I heard you've been stressed, you should know I'm always ready to help! Jess: Did you make something? I'm hungry... Delphine: Of course! Fresh from the bakery, I wanted to give you something, after all, you do so much to help me all the time! Jess: Aww, thank you, Delphine! Wow, this bread smells good. How is the bakery? Delphine: Lots of customers and positive reviews, all thanks to the mention in rtcp! Jess: I am really glad it's going well! During the weekend, I will go see you guys. You know how much I really love your amazing black forest cakes. Delphine: Well, you know that you can get a free slice anytime you want. (doorbell rings again) Jess: Oh, that must be Vihan, we're discussing some important details for rtcp. Delphine: sounds good, I need to get back to the bakery! Jess: Thank you for the bread! <3

Dev: Delphine

edit: This has been a source of confusion, so: the code in the first hint isn't exactly the method to solve, but is meant to give you a starting point to try and decode the script. Sorry for any confusion.

Hint!

I was eleven when I finished A Series of Unfortunate Events.

Hint!

Flag is in format: rtcp{.*} add _ (underscores) in place of spaces.

Hint!

Character names count too

ヒントの雰囲気と、問題文が長いことから、book暗号的なやつと想像してみる。 11というのがヒントになってるかな?

とりあえず11文字飛ばしで読んでみたりしたけど、意味のある英文にはならない。
ヒントの A Series of Unfortunate Events でググってみると、「世にも不幸なできごと」という邦名がつけられた本が出てきた。

世にも不幸なできごと - Wikipedia

全13巻もあるらしい。タイトルを見るだけで不幸というか気分が暗くなりそうだ。ちなみに11巻の日本語タイトルは「ぶきみな岩屋」(THE GRIM GROTTO)。
本当のbook cipherだったらこの本の全文から文字を抽出するんだろうけど、この本が手元にない上にindexも用意されていないので違うっぽい。

"A Series of Unfortunate Events" cryptoとかでググってみると、変なサイトが出てきた。

Sebald Code at The Lemony Snicket Wiki

Sebald Codeという暗号に関する説明だ。

このサイトを検索も、A Series of Unfortunate Eventsが出てこないので何故引っかかったのかわからないが、紹介部分を読んでみると

The beginning of a coded passage is signaled by the ringing, or mention of the ringing, of a bell.

とある。おや、今回の問題文もベルから始まってるぞ。これっぽいな!?

ベルからベルまでの間を11単語おきに読むらしい。どこから読み始めるかわからなかったので、全部出してみた。

import re

cipher = """delphine: Jess, I heard you've been stressed, you should know I'm always ready to help!
Jess: Did you make something? I'm hungry...
Delphine: Of course! Fresh from the bakery, I wanted to give you something, after all, you do so much to help me all the time!
Jess: Aww, thank you, Delphine! Wow, this bread smells good. How is the bakery?
Delphine: Lots of customers and positive reviews, all thanks to the mention in rtcp!
Jess: I am really glad it's going well! During the weekend, I will go see you guys. You know how much I really love your amazing black forest cakes.
Delphine: Well, you know that you can get a free slice anytime you want."""

words = re.split('[ \n]',cipher.replace(',','').replace('.',''))
texts = [''] * 11
flag = ''
for i in range(len(words)):
    texts[i%11] += words[i] + '_'
for t in texts:
    print('rtcp{' + t[:-1] + '}')

実行結果

$ python 11.py 
rtcp{delphine:_always_Delphine:_you_all_smells_positive_am_go_your_can}
rtcp{Jess_ready_Of_something_the_good_reviews_really_see_amazing_get}
rtcp{I_to_course!_after_time!_How_all_glad_you_black_a}
rtcp{heard_help!_Fresh_all_Jess:_is_thanks_it's_guys_forest_free}
rtcp{you've_Jess:_from_you_Aww_the_to_going_You_cakes_slice}
rtcp{been_Did_the_do_thank_bakery?_the_well!_know_Delphine:_anytime}
rtcp{stressed_you_bakery_so_you_Delphine:_mention_During_how_Well_you}
rtcp{you_make_I_much_Delphine!_Lots_in_the_much_you_want}
rtcp{should_something?_wanted_to_Wow_of_rtcp!_weekend_I_know}
rtcp{know_I'm_to_help_this_customers_Jess:_I_really_that}
rtcp{I'm_hungry_give_me_bread_and_I_will_love_you}

おー!最後のがキレイに文章になったな!これがflagだった👍

.... .- .-.. ..-. 1,696 Points

Ciphertext: DXKGMXEWNWGPJTCNVSHOBGASBTCBHPQFAOESCNODGWTNTCKY

Dev: Sri

Hint!

All letters must be capitalized

Hint!

The flag must be in the format rtcp{.*}

タイトルがモールス信号っぽい。モールス変換すると HALF 。半分。情報量が少ないな。
半分とのことなので、ROT13してみたり、HALFをキーにvigener暗号で復号してみたりしたけど成果なし。
半分で折り返してみたりしても意味のある単語にならなさそう。一文字飛ばしも微妙。

これは全然わからなかったので、他の方のwriteupをカンニング。Fractionated morse cipherというものらしい。説明はこのページが一番わかりやすかった。下の方にある。

https://asecuritysite.com/encryption/frac

plain textをモールス信号にした時、空白をxとして表示する。で、3文字ごとに区切り、下記のマッピングに合わせてアルファベットに変換するとcipherが得られる。

A B C D E F G H I J K L M N O P Q R S T U V W X Y Z
. . . . . . . . . - - - - - - - - - x x x x x x x x
. . . - - - x x x . . . - - - x x x . . . - - - x x
. - x . - x . - x . - x . - x . - x . - x . - x . -

decodeはこの逆。ちょっと頭の方をやってみると、上のマップ上でDXKGMX.-.x-x-.-.x.--.x-x
モールス信号に直すと.-. - -.-. .--. -で、普通のモールス信号変換するとRTCPT。お、flagの形になってそうである。

変換自体は下記のサイトでできる。

Fractionated Morse Cipher - Decoder, Encoder, Solver, Translator

flag: RTCP{TW0GALLONSOFH4LFMAK3WH0LEM1LK}

タイトルはモールス信号との関係を示していただけで「半分」というメッセージは関係なかったのかな?ちなみに HALF は Fractionated morse code変換しても意味のある言葉にならなかった。