⚠️ Wrteupではなく"参加記"です。読み物としてご覧ください。 書いてみたらそんなことはないかも、いやそうかも?わかんない

はじめに

一人チーム(UdagawaWhiteBears)で出て、42位2602点でした。 内訳としては、cryptoとrevは全部解き、他ジャンルは簡単な問題をいくつか解きました。

普通にwriteup書いてもな〜(なんで?)となったので、今回はどのような流れでcryptoを解いていったのか流れを書いていこうと思います。 なんでこんなことをやりだしたかというと、ptr-yudaiが書いたTSG LIVE ! 6 CTFのWriteupが面白かったからです。 あとは、数式を書くのが面倒だからです(ぶっちゃけ)。

作問者writeupも出ていますし、SECCON Beginnersなので日本人の参加者は多いと思うので、詳細は他の人が書いてくれるでしょうと信じています。 あと、全然理解せずにこいつ解いてるんだな〜みたいな様子が伝わればいいなと思います😇

開始前

16:00から開始だと思ってたら14:00からで、ちょっと焦った記憶があります(遅刻はしてないけど)。 pwnerと違ってcrytpoでは特に準備することがありません。 わくわくしておけば💯だと思います。

真面目な話をすると、先述のptr-yudaiの記事ではpythonとsageをまず実行しておくと次に起動するときに早くなるらしいですが、全然知らなかったしやってないです。 まあ誤差でしょう()

戦略的にはcrypto全部解きたいな〜ぐらいに考えていて、beginner向けなので、難しい問題から手をつけていこうと思っていました。

はじまりはじまり〜🎉

14:00! はじまり〜〜、わくわくしすぎてワクワクさんになりました。

早速、問題に取り掛かろうと思うのですが、ファイルのダウンロードができません😢 問題サーバーは生きてるので、ncする問題(Imaginary)があったのでこれを見てみることにしました(難しい問題からやる?そんなことはとうの昔に忘れました)。

[crypto] Imaginary

接続した感じと問題文で、わぁ〜なんか虚数じゃん、ツラと思います。

$ nc imaginary.quals.beginners.seccon.jp 1337
Welcome to Secret IMAGINARY NUMBER Store!
1. Save a number
2. Show numbers
3. Import numbers
4. Export numbers
0. Exit
>

べそをかいていると、ファイルのダウンロードができるようになりました。 さすがLMT、30秒もかかってないんじゃないでしょうか(適当)。

ファイルを見ていきます。

うえ〜〜、100行以上あるじゃん、ぴえんです。 CTFというのはフラグと呼ばれる文字列を見つけるゲームなので、とりあえずCtrl + sflagを探してみます(emacs user)。

    def _secret(self):
        if '1337i' in self.numbers:
            self.request.sendall(b'Congratulations!\n')
            self.request.sendall(f'The flag is {flag}\n'.encode())

いますね。 self.numbers1337iが含まれていればOKっぽいです。 あとは_secret(self)を呼んでいる箇所を探します。 Ctrl + sで(略)

                elif num == 5:
                    self._secret()

???、おまっ?どこから湧いてきたんや!?

失礼、近所の関西弁のおじさんが出てきてしまいましたが、気にせずにいきましょう。

接続した際にはいなかった、選択肢がありましたね。 とりあえず、この辺でflagを得る道筋がすこし見えてきました。 今わかっている限りでは、self.numbers1337isaveとかで含めて、5を呼べば良さそうです。

とりあえず、self.numbersに値を入れたいので、_save()を見に行きます。

    def _save(self):
        try:
            self.request.sendall(b'Real part> ')
            re = int(self.request.recv(128).strip())

            self.request.sendall(b'Imaginary part> ')
            im = int(self.request.recv(128).strip())

            name = f'{re} + {im}i'
            self.numbers[name] = [re, im]
        except ValueError:
            pass

なんのcheckも入っていません。 てか、self.numbersが配列かな〜と勝手に推測していたのですが、dictです。 え?これ入れるだけでは?と思って試してみます。

$ nc imaginary.quals.beginners.seccon.jp 1337
Welcome to Secret IMAGINARY NUMBER Store!
1. Save a number
2. Show numbers
3. Import numbers
4. Export numbers
0. Exit
> 1
Real part> 0
Imaginary part> 1337
1. Save a number
2. Show numbers
3. Import numbers
4. Export numbers
0. Exit
> 2
--------------------------------------------------
0 + 1337i: (0, 1337)
--------------------------------------------------

う、なるほど、0が入るのね〜とわかります。

ここで、これpythonで hoge in dictって、比較されるのkey?value?と思って確認します。

$ python3
Python 3.8.5 (default, Jan 27 2021, 15:41:15)
[GCC 9.3.0] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> a = {"x": 1}
>>> "x" in a
True
>>> 1 in a
False
>>>

御意。 さっきやった実験では、0 + 1337iがkeyに入っているので、これではダメそうです。

まだ、_save()を見ただけなので、もう一つ値を入れれそうな_import()を見ます。

    def _import(self):
        self.request.sendall(b'Exported String> ')
        data = self.request.recv(1024).strip().decode()
        enc = bytes.fromhex(data)
        cipher = AES.new(key, AES.MODE_ECB)
        plaintext = unpad(cipher.decrypt(enc), AES.block_size)

        self.numbers = json.loads(plaintext.decode())
        self.request.sendall(b'Imported.\n')
        self._show()

AESの文字がちらつきます。 cryptoになってきました。 虚数から数学問の可能性がありましたが、そうではなさそうです。 与えた文字列をAESECBモードで復号してから、self.numberjson.dumpしたものを代入しています。 ECBモードなので、まぁ、なんかできるでしょう!という気持ちになります。

残るは_exportを見ていきます(_show()には申し訳ないが、スルーさせてもらう)。

    def _export(self):
        cipher = AES.new(key, AES.MODE_ECB)
        dump = pad(json.dumps(self.numbers).encode(), AES.block_size)
        self.request.sendall(dump + b'\n')
        enc = cipher.encrypt(dump)
        self.request.sendall(b'Exported:\n')
        self.request.sendall(enc.hex().encode() + b'\n')

こちらも、特に変わったことはしてなくて、self.numberを文字列にして暗号化したものがもらえます。

ここで、ECBモードなので、“いい感じ"にブロックを分けて作ってから、がっちゃんこすればOKみたいな方針が立ちます。ECBモードだと、基本的には同じ平文は同じ暗号文になることを使いがちムーブをしていきます。

さて、どうやるといい感じになるかな〜と手元で試してみます。

from json import dumps

def gen_blocks(s):
    return [s[i:i+16] for i in range(0, len(s), 16)]

x = {"-100 + 1000i": [-100, 1000], "0 + 1337i": [0, 1337]}
s = dumps(x)
blocks = gen_blocks(s)
for block in blocks:
    print(block)
$ python3 test.py
{"-100 + 1000i":
 [-100, 1000], "
0 + 1337i": [0,
1337]}

なんで、マイナスとかつけちゃってるの?という気もしますが、つけたい気分だったんでしょう。 わかりません。

...], "1337i":...をがっちゃんこすれば...], "1337i":...となり、良さそうですね。 あとは、1337i":...から始まるブロックを調整して作って、後ろの2ブロックを使えば、念願のフラグゲットです。

saveで、{"-100 + 1000i": [-100, 1000], "0 + 1337i": [0, 1337]}となるように値を入れます。 これを、exportして前の2ブロックを使います。 一度、接続を切ってから、次はx = {"10 + 1000i": [10, 1000], "0 + 1337i": [0, 1337]}となるようにsaveしていきます。 また、exportして次は後ろの2ブロックを使って、前の2ブロックと今回の後ろ2ブロックをつなげたhex値をimportします。 これで隠し選択肢5を選択すれば、フラグゲットです🚩

ctf4b{yeah_you_are_a_member_of_imaginary_number_club}

たぶん、30分前後で解けたと思います。 記憶が正しければfirst bloodを頂いていたはずですが、今回のスコアサーバーでは解いた問題の情報が見れないので真相は闇の中…

ここまで書いてきましたが、普通にWriteupを書くより大変ということに気づいて泣いています。 解法の詳細は他の人に譲って、考えてることを軽く書こうとしてたら、試行錯誤付きのwriteupになってしまって大変疲労しています。 ここからは、解法の詳細は他の人、と復唱しながら書いていきます。

[crypto] p-8RSA

気を取り直して、次こそは一番むずかしい問題に取り組みます。

ファイルを落としてきます。 問題名からRSAであることは自明ですが、簡単な問題であることを祈ります🙏

ばっと見た感じ暗号化は普通のRSAで、素数の生成が特殊なようです。 qを生成した後、pの生成はqから8を引いていって素数になるまで回してそうです。

お!これはpqの差がそこまでなさそうという気持ちになるので、fermat法とかで素因数分解できそうです。 fermat法のスクリプトもってねーと思ったので、google先生にsage fermat factorizationと聞いてみると、いい感じのコードが乗っているサイトを見つけました。 コピって、ペってします。

yoshiking@yoshiking-vm:~/ctf/seccon_begginers/p-8RSA$ sage
┌────────────────────────────────────────────────────────────────────┐
│ SageMath version 9.2, Release Date: 2020-10-24                     │
│ Using Python 3.8.5. Type "help()" for help.                        │
└────────────────────────────────────────────────────────────────────┘
sage: n = 1692217701880003415077640053307690427052236117123084244791201925961363188187081357161572555509365632685003108528944898394703205166453173384730181508859979770089258399395605909244353802395195544
....: 75266121835753044660177349444503693993991253475530436734034224314165897550185719665717183285653938232013807360458249
....: e = 17
....: c = 1002331319313602783327343416523045558140944872521511317352860746165554027951907976470018896694722907709258390131313562125744552746904221132780155717506533655129986694531619553020085990299191012
....: 44702933443124944274359143831492874463245444294673660944786888148517110942002726017336219552279179125115273728023902
....:
sage: def fermatfactor(N):
....:        if N <= 0: return [N]
....:        if is_even(N): return [2,N/2]
....:        a = ceil(sqrt(N))
....:        while not is_square(a^2-N):
....:          a = a + 1
....:        b = sqrt(a^2-N)
....:        return [a - b,a + b]
....:
sage: fermatfactor(n)
[13008526826201356667891590694678121516071641430494347349438757349219893000439927852950504383765791466428599814640460028507882213264934492728368742844727189,
 13008526826201356667891590694678121516071641430494347349438757349219893000439927852950504383765791466428599814640460028507882213264934492728368742844741541]

わいわい

できてそうですね。 あとは、復号するだけじゃん〜〜と思っていたんですが、素数生成時にpが素数になるまでと他にも条件がありました…

        if isPrime(p) and GCD(phi, e) != 1:
            break

GCD(phi, e) != 1はツラ

といっても、よくあることなのでがんばります。

片方の素数は $(p-1)\nmid e$だったので、pでニセd作って復号できんじゃね?と思ったら、しっかりとパディングされていました。ぴえん。

flag = flag.encode("utf-8") + urandom(64)

のこる手札は、GCD(phi, e)で割ってやる方法ですが、これもGCD(phi, e) = 17なので、ぴえんこえてぱおん。

もう手札0だよ〜〜ぴえんぴえんとしていると、ある記事を思い出します(bookmarkにあった)。

中国語の記事だけど、よくまとまってると思います。 https://0xdktb.top/2020/02/28/Summary-of-Crypto-in-CTF-RSA/

ここに乗ってなかったかな〜と思うと、ありました!! ばんざい🙌

ここでもコピってペっとすればOKと言いたいところですが、記事に乗っている方法は$(p-1) | e$ かつ $(q-1) | e$なので少々オーバーキルです。 そのあたりのスクリプトをいい感じに修正してやると、フラグゲットです。

from binascii import unhexlify
from Crypto.Util.number import *

def rthroot(c, r, q):
    c %= q
    assert(isPrime(r) and (q - 1) % r == 0 and (q - 1) % (r**2) != 0)
    l = ((q - 1) % (r**2)) // r
    alpha = (-inverse(l, r)) % r
    root = pow(c, ((1 + alpha * (q - 1) // r) // r), q)
    return root


def allroot(r, q, root):
    all_root = set()
    all_root.add(root)
    while len(all_root) < r:
        new_root = root
        unity = pow(getRandomRange(2, q), (q - 1) // r, q)
        for i in range(r - 1):
            new_root = (new_root * unity) % q
            all_root.add(new_root)
    return all_root


def crt(ai, mi):
    a1, m1 = ai[0], mi[0]
    a2, m2 = ai[1], mi[1]
    return (a1 * inverse(m2, m1) * m2 + a2 * inverse(m1, m2) * m1) % (m1 * m2)


def decrypt(proot, qroot, p, q):
    count = 0
    total = len(proot) * len(qroot)
    t1 = inverse(q, p)
    t2 = inverse(p, q)
    for i in proot:
        for j in qroot:
            count += 1
            root = (i, j)
            m = crt(root, (p, q))
            m = (i * t1 * q + j * t2 * p) % (p * q)
            flag = long_to_bytes(m)
            if flag.startswith(b"ctf"):
                print('\n', flag)


def main():
    p = 13008526826201356667891590694678121516071641430494347349438757349219893000439927852950504383765791466428599814640460028507882213264934492728368742844727189
    q = 13008526826201356667891590694678121516071641430494347349438757349219893000439927852950504383765791466428599814640460028507882213264934492728368742844741541
    n = 169221770188000341507764005330769042705223611712308424479120192596136318818708135716157255550936563268500310852894489839470320516645317338473018150885997977008925839939560590924435380239519554475266121835753044660177349444503693993991253475530436734034224314165897550185719665717183285653938232013807360458249
    e = 17
    c = 100233131931360278332734341652304555814094487252151131735286074616555402795190797647001889669472290770925839013131356212574455274690422113278015571750653365512998669453161955302008599029919101244702933443124944274359143831492874463245444294673660944786888148517110942002726017336219552279179125115273728023902
    
    print('[+] Calculating e-th root...')
    proot = rthroot(c, e, p)
    #qroot = rthroot(c, e, q)
    print('[+] Calculating all e-th roots...')
    all_proot = allroot(e, p, proot)
    #all_qroot = allroot(e, q, qroot)
    d_ = inverse(e, q-1)
    all_qroot = {pow(c, d_, q)}
    
    print('[+] CRT cracking...')
    decrypt(all_proot, all_qroot, p, q)



if __name__ == '__main__':
    main()

ctf4b{4r3_y0u_up5id3_d0wn?_Fr0m_6310w?_0r_60th?}

何もわからずに解いてるが、解ければOK

想定解のカーマイケルの定理、全然頭になかったので、へ〜ってなりました。 手札が増えたので、とりあえずおけまろ水産🐟

これも30分前後で解いた気がする。 わかりません。 (書くのは詳細をだいぶ省いたのですぐ書けた、このくらいの気持ちで書いていかないと疲れちゃうよ〜、writeup書く人類全員えらすぎる)

[crypto] Field_trip

次はこれを取り組んでいきました。

配布ファイルを見た感じ、ソースコードcipherの生成はここでやっていることがわかります。

cipher = sum([int(flag[i]) * pub_key[i] for i in range(length)])

output.txtにはpub_keyが入っていたので、LLLでよく見るやつやんけ!!と思ってsolverを書きます(ただ、このときはまだflag[i]が1byteだと思っている)。

flag[i]を1bytesだと勘違いしているので、雑に書いたら全然違う値が出てきて???となりました。 てか、なんか行列のサイズでかくね?? みたいな違和感から、もうちょっと問題ファイルを見ると、

flag = bin(flag)[2:]

がありました。ぴえん。早とちり早男と申します。

完全にknapsack暗号ですね。 昔に同じような問題を解いていたので、前ブログの記事からsolverを引っ張り出してきます。 厳密には、密度によっては失敗するかもしれないですが、それでもまだ手札はあったのでとりあえずこのsolverで試してみます。

from sage.all import *
from binascii import unhexlify

exec(open("output.txt").read())
pk = pub_key
c = cipher

def create_matrix(c, pk):
    n = len(pk)
    i = matrix.identity(n) * 2
    last_col = [-1]*n
    first_row = []
    for p in pk:
        first_row.append(p)
    first_row.append(-c)
    
    m = matrix(ZZ, 1, n+1, first_row)
    m = 1000 * m
    bottom = i.augment(matrix(ZZ, n, 1, last_col))
    m = m.stack(bottom)
    return m

def find_short_vector(matrix):
    for col in matrix.columns():
        if col[0] != 0:
            continue
        if is_short_vector(col):
            return col

def is_short_vector(vector):
    for v in vector:
        if v != 1 and v != -1 and v != 0:
            return False
    return True

m = create_matrix(c, pk)
lllm = m.transpose().LLL().transpose()
short_vector = find_short_vector(lllm)
solution_vector = []
for v in short_vector:
    if v == 1:
        solution_vector.append(1)
    elif v == -1:
        solution_vector.append(0)
m = "".join(map(str, solution_vector))

print(unhexlify(hex(int(m, 2))[2:]))

.py“でsageを書くのにハマってる時代&sageに外部パッケージを入れる方法を知らない時代のスクリプトで懐かしくなりました。 今はもう外部パッケージも使えますし、sageは.sageで書きますが、気にせずに実行します。

$ sage knapsack.sage
b'ctf4b{Y35!_I_ju5t_n33d3d_th353_num63r5!}'

💪 🚩 💪

20分くらい?で解いた?わかんない

ここまで、解ければ後は消化試合という感じですね。 ラストスパートやっていきましょう。

[crypto] GFM

なんか行列がちゃがちゃしてるな〜という所感

keyencはもらえてるっぽいので、これ普通に逆行列計算してやればMでるな〜とわかります。 あとは、Mをいろいろやっているところを見ていきます。

M = copy(MS.zero())
for i in range(SIZE):
    for j in range(SIZE):
        n = i * SIZE + j
        if n < len(FLAG):
            M[i, j] = FLAG[n]
        else:
            M[i, j] = GF(p).random_element()

M行列の要素の初めの方はflagが1文字づつはいってることがわかります。 keyも逆行列を持つことが保証されているので、解けそう。 sageにいれたら、簡単に解けるでしょ〜〜と思ったら、output.txtの出力が空白くぎりで必殺技"コピってペ"ができないです。 これは試合終了かと思われましたが、yoshiking選手、なんとか持ちこたえて修正していきました。

p = 331941721759386740446055265418196301559
SIZE = 8

MS = MatrixSpace(GF(p), SIZE)
key =  [[116401981595413622233973439379928029316,198484395131713718904460590157431383741,210254590341158275155666088591861364763,63363928577909853981431532626692827712,85569529885869484584091358025414174710,149985744539791485007500878301645174953,257210132141810272397357205004383952828,184416684170101286497942970370929735721], [42252147300048722312776731465252376713,199389697784043521236349156255232274966,310381139154247583447362894923363190365,275829263070032604189578502497555966953,292320824376999192958281274988868304895,324921185626193898653263976562484937554,22686717162639254526255826052697393472,214359781769812072321753087702746129144], [211396100900282889480535670184972456058,210886344415694355400093466459574370742,186128182857385981551625460291114850318,13624871690241067814493032554025486106,255739890982289281987567847525614569368,134368979399364142708704178059411420318,277933069920652939075272826105665044075,61427573037868265485473537350981407215], [282725280056297471271813862105110111601, 183133899330619127259299349651040866360, 275965964963191627114681536924910494932,290264213613308908413657414549659883232,140491946080825343356483570739103790896,115945320124815235263392576250349309769,240154953119196334314982419578825033800, 33183533431462037262108359622963646719], [53797381941014407784987148858765520206, 136359308345749561387923094784792612816, 26225195574024986849888325702082920826,262047729451988373970843409716956598743,170482654414447157611638420335396499834,270894666257247100850080625998081047879, 91361079178051929124422796293638533509, 34320536938591553179352522156012709152], [266361407811039627958670918210300057324, 40603082064365173791090924799619398850, 253357188908081828561984991424432114534, 322939245175391203579369607678957356656, 63315415224740483660852444003806482951, 224451355249970249493628425010262408466, 80574507596932581147177946123110074284, 135660472191299636620089835364724566497],[147031054061160640084051220440591645233, 286143152686211719101923153591621514114, 330366815640573974797084150543488528130, 144943808947651161283902116225593922999, 205798118501774672701619077143286382731, 317326656225121941341827388220018201533,  14319175936916841467976601008623679266, 112709661623759566156255015500851204670],[306746575224464214911885995766809188593,  35156534122767743923667417474200538878,  35608800809152761271316580867239668942, 259728427797578488375863755690441758142,  29823482469997458858051644485250558639, 137507773879704381525141121774823729991,  29893063272339035080311541822496817623, 292327683738678589950939775184752636265]]
enc = [[133156758362160693874249080602263044484, 293052519705504374237314478781574255411,  72149359944851514746901936133544542235,  56884023532130350649269153560305458687,  67693140194970657150958369664873936730, 227562364727203645742246559359263307899,  98490363636066788474326997841084979092, 323336812987530088571937131837711189774],[244725074927901230757605861090949184139,  63515536426726760809658259528128105864, 297175420762447340692787685976316634653, 279269959863745528135624660183844601533, 203893759503830977666718848163034645395, 163047775389856094351865609811169485260, 103694284536703795013187648629904551283, 322381436721457334707426033205713602738],[ 17450567396702585206498315474651164931, 105594468721844292976534833206893170749,  10757192948155933023940228740097574294, 132150825033376621961227714966632294973, 329990437240515073537637876706291805678,  57236499879418458740541896196911064438, 265417446675313880790999752931267955356,  73326674854571685938542290353559382428],[270340230065315856318168332917483593198, 217815152309418487303753027816544751231,  55738850736330060752843300854983855505, 236064119692146789532532278818003671413, 104963107909414684818161043267471013832, 234439803801976616706759524848279829319, 173296466130000392237506831379251781235,  34841816336429947760241770816424911200],[140341979141710030301381984850572416509, 248997512418753861458272855046627447638,  58382380514192982462591686716543036965, 188097853050327328682574670122723990784, 125356457137904871005571726686232857387,  55692122688357412528950240580072267902,  21322427002782861702906398261504812439,  97855599554699774346719832323235463339],[298368319184145017709393597751160602769, 311011298046021018241748692366798498529, 165888963658945943429480232453040964455, 240099237723525827201004876223575456211, 306939673050020405511805882694537774846,   7035607106089764511604627683661079229, 198278981512146990284619915272219052007, 255750707476361671578970680702422436637],[ 45315424384273600868106606292238082349,  22526147579041711876519945055798051695,  15778025992115319312591851693766890019, 318446611756066795522259881812628512448, 269954638404267367913546070681612869355, 205423708248276366495211174184786418791,  92563824983279921050396256326760929563, 209843107530597179583072730783030298674],[   662653811932836620608984350667151180, 304181885849319274230319044357612000272, 280045476178732891877948766225904840517, 216340293591880460916317821948025035163,  79726526647684009633247003110463447210,  36010610538790393011235704307570914178, 284067290617158853279270464803256026349,  45816877317461535723616457939953776625]]

C = matrix(enc)
K = matrix(key)

inv_K = K.inverse()

M = inv_K * C * inv_K

print([bytes([i%p]) for i in list(M)[0]])

flag = b""
for row in list(M):
    for x in list(row):
        x = x % p
        if 0 <= x <= 255:
            flag += bytes([x])
        else:
            flag += b"?"
print(flag)

ctf4b{d1d_y0u_pl4y_w1th_m4tr1x_4nd_g4l0is_f1eld?}

出力形式がぴえんこえてぱおん🐘

[crypto] Logical_SEESAW

複数回、同じ鍵でflagとandを取っていることがわかります。 andなので、flagがもともの0の箇所は鍵が何であれ0になります。 また、flag1の場合は、鍵が0の場合は出力が2通りになり、鍵が1の場合は確率によらす1となります。

これを使えば、解けそうです (なんか普通のwriteupになってきましたね、非常に疲れてきました、あと簡単な問題は試行錯誤もないので…許してください…)

import collections

exec(open("output.txt").read())

length = len(cipher[0])
flag = ""
for i in range(length):
    bits = [c[i] for c in cipher]
    cnt = collections.Counter(bits)
    if len(cnt) == 2:
        flag += "1"
    else:
        flag += cnt.most_common()[0][0]

flag = int(flag, 2)
print(bytes.fromhex(hex(flag)[2:]))

ctf4b{Sh3_54w_4_SEESAW,_5h3_54id_50}

[crypto] simple_RSA

のこりcrypto一問です、最後の有終の美を飾るのはsimpleなRSA問題でした。

ビット長からわかるように、flage乗してもnより大きくなっていなさそうです。 square rootを取れば終わりです!

OK!sagemath!

$ sage
┌────────────────────────────────────────────────────────────────────┐
│ SageMath version 9.2, Release Date: 2020-10-24                     │
│ Using Python 3.8.5. Type "help()" for help.                        │
└────────────────────────────────────────────────────────────────────┘
sage: n = 1768667184240039357473051203420012852133691956973597279167660505628677847323071842695850887894263158470481734230495929306050761407480055367057903339967904133486315690203093489519767754314220211
....: 07816294944514533513969621373774114778994925558309827014496925615941751626235809874531513284088501164540581623702737363560683196485671055124528937368669392002970716029942882582952317511179914081605
....: 69998347640357251625243671483903597718500241970108698224998200840245865354411520826506950733058870602392209113565367230443261205476636664049066621093558272244061778795051583920491406620090704660526
....: 753969180791952189324046618283
....: e = 3
....: c = 2137917515300171115086910841683630246868780573379713198802569241853937371507043427250428414885473159259719603892304533323193718760929680325131490239762871586989902516402983608765893308108131992
....: 60879441426084508864252450551111064068694725939412142626401778628362399359107132506177231354040057205570428678822068599327926328920350319336256613
....:

output.txtからパラメータペっとして、nth_root()を使えば…

sage: m = c.nth_root(e)
sage: bytes.fromhex(hex(m)[2:])
b"ctf4b{0,1,10,11...It's_so_annoying.___I'm_done}"

うお〜〜〜〜終わった!!!!!

RTA: 約2h00m00s

正確なタイムはわかりませんが、16:00にはツイートしてたので、2時間かからないぐらいでしょうか。

たぶんこの記事を書くほうが時間かかっています。 完全にImaginaryで燃え尽きていますね。 なんでこんな記事書いてるんだろって何度も思いました。

おわりに

やっべ〜〜、魔女のお茶会に遅刻する〜〜〜