LoginSignup
3
4

More than 5 years have passed since last update.

TokyoWesterns CTF 4th 2018 write-up

Posted at

superflipは760点で71位。

Welcome!! (warmup)

TWCTF{Welcome_TokyoWesterns_CTF_2018!!}

SimpleAuth (warmup, web)

<?php

require_once 'flag.php';

if (!empty($_SERVER['QUERY_STRING'])) {
    $query = $_SERVER['QUERY_STRING'];
    $res = parse_str($query);
    if (!empty($res['action'])){
        $action = $res['action'];
    }
}

if ($action === 'auth') {
    if (!empty($res['user'])) {
        $user = $res['user'];
    }
    if (!empty($res['pass'])) {
        $pass = $res['pass'];
    }

    if (!empty($user) && !empty($pass)) {
        $hashed_password = hash('md5', $user.$pass);
    }
    if (!empty($hashed_password) && $hashed_password === 'c019f6e5cd8aa0bbbcc6e994a54c757e') {
        echo $flag;
    }
    else {
        echo 'fail :(';
    }
}
else {
    highlight_file(__FILE__);
}

なぜ$_POSTなどではなく、parse_strを使っているのだろう? parse_strは第2引数を指定しないとグロバール変数に値を設定するらしい。値を返さないからifの中には入らないが、正常に動いてしまうと。これはひどいw actionhashed_passwordだけを設定すれば良い。

TWCTF{d0_n0t_use_parse_str_without_result_param}

scs7 (warmup, crypto)

encrypted flag: zFc7idGakmUELBPPqweT5PdLR2ZCj0qGGyvB7YLAD6S1SsR0K9FT9cp1DQAqS9NP
You can encrypt up to 100 messages.
message: 11111111111111111111111111111111111111111111111111111111111111111111111111111
ciphertext: ciE8NB0pLKjgK9cjnzHq8CCVGVXj4T5po5MGNPRUSFNy0RDPm2miph7Q7DPoFSy6MZra7n5keN1WLQHiNfWigZYB4kmunp76L9UYfS92q
message: 22222222222222222222222222222222222222222222222222222222222222222222222222222
ciphertext: cZAKCRm636G6rZC5VMB1rVnNY3L0mFTvgteUoyRcSnMsP3iP81vGF7RkF03q3khNdJ4B78HiELip4CEsXRC2SW5EXkZeVzdu51DPM22e2
message: 33333333333333333333333333333333333333333333333333333333333333333333333333333
ciphertext: cYb4vQcoKXE6kgTQbGYa5x2ijDoSoB6YpYk3svjhVg9q195PxSM6RrGKSJQ7fbWGUAGe7MnctQV2fPwNqJ0xL8enBkbMZrQbDTVneXqh9
message: 44444444444444444444444444444444444444444444444444444444444444444444444444444
ciphertext: cQRjoXGpp1wxAKPYR0DYNH50B1jrT7sboEEGS0j5A9rnHw4PC0JVNu4XeMrApDaJ5QP3Kq5WV0sqd8Cr54PMKH4aWkYpFE2tvWGSunxPi

こんな感じでメッセージを暗号化して返してくる。Base64やBase58のように見えるけれど、59種類の文字が出てくる。Base59らしい。0から58がどの文字に対応しているかは、手元でBase59で暗号化して、サーバーが返してきた暗号文と比較すればわかる。

solve.py
A = "0123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz"

def base59enc(t):
  n = int(t.encode("hex"), 16)
  b = ""
  while n>0:
    b += A[n%59]
    n /= 59
  return b[::-1]

def base59dec(t):
  n = 0
  for i in range(len(t)):
    n = 59*n + A.index(t[i])
  return ("%x"%n).decode("hex")

P = [
("11111111111111111111111111111111111111111111111111111111111111111111111111111", "ciE8NB0pLKjgK9cjnzHq8CCVGVXj4T5po5MGNPRUSFNy0RDPm2miph7Q7DPoFSy6MZra7n5keN1WLQHiNfWigZYB4kmunp76L9UYfS92q"),
("22222222222222222222222222222222222222222222222222222222222222222222222222222", "cZAKCRm636G6rZC5VMB1rVnNY3L0mFTvgteUoyRcSnMsP3iP81vGF7RkF03q3khNdJ4B78HiELip4CEsXRC2SW5EXkZeVzdu51DPM22e2"),
("33333333333333333333333333333333333333333333333333333333333333333333333333333", "cYb4vQcoKXE6kgTQbGYa5x2ijDoSoB6YpYk3svjhVg9q195PxSM6RrGKSJQ7fbWGUAGe7MnctQV2fPwNqJ0xL8enBkbMZrQbDTVneXqh9"),
("44444444444444444444444444444444444444444444444444444444444444444444444444444", "cQRjoXGpp1wxAKPYR0DYNH50B1jrT7sboEEGS0j5A9rnHw4PC0JVNu4XeMrApDaJ5QP3Kq5WV0sqd8Cr54PMKH4aWkYpFE2tvWGSunxPi"),
]

M = {}
for p in P:
  b = base59enc(p[0])
  for i in range(len(b)):
    M[p[1][i]] = b[i]

flag = "zFc7idGakmUELBPPqweT5PdLR2ZCj0qGGyvB7YLAD6S1SsR0K9FT9cp1DQAqS9NP"
flag = "".join(M[c] for c in flag)
print base59dec(flag)

TWCTF{67ced5346146c105075443add26fd7efd72763dd}

mondai.zip (warmup, misc)

暗号化されたZIPが入れ子になっている。元ネタはネットエージェントの入社試験でしょう。

最初はy0k0s0.zipというファイル名で、ファイル名のy0k0s0がパスワード。

2番目はパケットダンプが問題。PINGを打っている。データ部分はabcdefghijklmnopqrstuvwabcdefghijklmnopqrstuvwabcdefghijklmnopqrstuvwabcdefghijklmnopqrのような文字列でPINGごとに長さが異なる。長さのアスキーコードの文字がパスワード。

x = [
"abcdefghijklmnopqrstuvwabcdefghijklmnopqrstuvwabcdefghijklmnopqrstuvwabcdefghijklmnopqr",
"abcdefghijklmnopqrstuvwabcdefghijklmnopqrstuvwabcdefghijklmnopqrstuvwabcdefghijklmnopqrstuvwabcdefghi",
"abcdefghijklmnopqrstuvwabcdefghijklmnopqrstuvwabc",
"abcdefghijklmnopqrstuvwabcdefghijklmnopqrstuvwabcdefghijklmnopqrstuvwabcdefghijklmnopqrstuvwabcdefg",
"abcdefghijklmnopqrstuvwabcdefghijklmnopqrstuvwabcdefghijklmnopqrstuvwabcdefghijklmnopqrstuvwabcdefghijklmnopqrs",
"abcdefghijklmnopqrstuvwabcdefghijklmnopqrstuvwabcdefghijklmnopqrstuvwabcdefghijklmnopqrstuvwabcdefghijklmnopq",
"abcdefghijklmnopqrstuvwabcdefghijklmnopqrstuvwabcdefghijklmnopqrstuvwabcdefghijklmnopqrstuvwabcdefghi",
]

>>> "".join(chr(len(t)) for t in x)
'We1come'

We1come

3番目はパスワードのリストが入っている。懐かしのPika Zipに投げたら、パスワードに記号が含まれているからか落ちたので、Pythonのスクリプトを書いた。

import zipfile

z = zipfile.ZipFile("mondai.zip")
for p in open("list.txt"):
  try:
    z.open("1c9ed78bab3f2d33140cbce7ea223894", pwd=p[:-1]).read()
    print p[:-1]
  except:
    pass

openでは例外を出さなくても、readで例外を出すパスワードがある。ZIPのパスワードが正しいかどうかのチェックは8ビットしかなくて、これが通った場合は、実際にファイルを復号して、解答できるかやCRCが一致するかを調べるしかない。openでは最初のチェックしかしていないのだろう。パスワードはeVjbtTpvkU

4番目のファイル名は1c9ed78bab3f2d33140cbce7ea223894。ファイル形式はZIPで他に問題となるようなファイルは無し。このファイル名をMD5をクラックしてくれるサイトに投げると、元の文字列はhappyhappyhappyであることがわかる。これがパスワード。

5番目は、README.txtが入っていてpassword is too shortと書かれている。理由はわからないが、Pika ZIPは解いてくれなかったので、John the Ripperに掛けた。ZIPのパスワードの解析には、まずzip2johnでハッシュ値を抽出する必要がある。aptではJohn the Ripper本体しか入らなかったので、ソースからビルドした。

kusano@chino:/mnt/d/documents/ctf/tokyowesterns2018/mondai.zip$ JohnTheRipper/run/zip2john mondai.zip > mondai.hash
ver 2.0 mondai.zip->secret.txt PKZIP Encr: cmplen=144, decmplen=167, crc=4FD5A446
kusano@chino:/mnt/d/documents/ctf/tokyowesterns2018/mondai.zip$ JohnTheRipper/run/john mondai.hash
Using default input encoding: UTF-8
Loaded 1 password hash (PKZIP [32/64])
Will run 8 OpenMP threads
Press 'q' or Ctrl-C to abort, almost any other key for status
to               (mondai.zip)
1g 0:00:00:02 DONE 3/3 (2018-09-04 03:01) 0.4784g/s 206170p/s 206170c/s 206170C/s miamms..camous
Use the "--show" option to display all of the cracked passwords reliably
Session completed

パスワードはto

最後のファイルにフラグの構成方法が書かれている。

TWCTF{We1come_to_y0k0s0_happyhappyhappy_eVjbtTpvkU}

Shrine (web)

FlaskJinja2のテンプレートを指定することができる。ただし、()は除去され、先頭に{{% set config=None%}}{{% set self=None%}}が追加される。config['FLAG']にフラグが書かれている。

そもそも、テンプレートが指定できるというのはとても危険で、クラスの継承関係を辿って任意コード実行ができるらしい。

が、()が使えないので関数呼び出しができず、これは無理そう。

configself以外にも、requestsessiongがテンプレ途中では参照できるけれど、request.__dict__などで見てみてもめぼしいものは無い。

{%block hoge%}{%endblock%}の中ではselfが復活することに気が付いた。ここから色々と辿ったら、フラグが得られた。

{%block a%}{{self._TemplateReference__context.parent.config.FLAG}}{%endblock%}

TWCTF{pray_f0r_sacred_jinja2}

Revolutional Secure Angou (crypto)

問題は簡潔なRSA風の暗号。

generator.rb
require 'openssl'

e = 65537
while true
  p = OpenSSL::BN.generate_prime(1024, false)
  q = OpenSSL::BN.new(e).mod_inverse(p)
  next unless q.prime?
  key = OpenSSL::PKey::RSA.new
  key.set_key(p.to_i * q.to_i, e, nil)
  File.write('publickey.pem', key.to_pem)
  File.binwrite('flag.encrypted', key.public_encrypt(File.binread('flag')))
  break
end

qの作り方がおかしい。本来はpとは独立した素数にするべきなのに、$q = e^{-1} \mod p$となっている。

$n = pq$から$en = p \mod n$となるかというと、法が違うので当然そんなことはない。しかし、この方向性で考えると解ける。$eq = 1 \mod p$なので、$eq = kp+1$となる。ここで、$k$は$e=65537$より小さい。$en = pq = p(kp+1) = kp^2+p$。$k$の値を固定すれば、二分探索で$p$が求められる。等式が成り立つ$p$が出てくるまで、$k$の値を変えていけば良い。

solve.py
from Crypto.PublicKey import RSA
from Crypto.Util.number import inverse
from Crypto.Cipher import PKCS1_v1_5

pubkey = RSA.importKey(open("publickey.pem").read()).publickey()
e = pubkey.e
n = pubkey.n

for k in range(54000, e):
#for k in range(e):
  print k
  p = 0
  b = 1<<1023
  while b > 0:
    m = p+b
    if k*m*m+m <= e*n:
      p += b
    b >>= 1
  if k*p*p+p == e*n:
    break

assert n%p == 0
q = n/p
d = inverse(e, (p-1)*(q-1))

print "p: %x"%p
print "q: %x"%q
print "d: %x"%d

privkey = RSA.construct((n, e, d))
print PKCS1_v1_5.new(privkey).decrypt(open("flag.encrypted", "rb").read(), "!")
 :
54077
54078
54079
54080
p: cb403330e7eb50ca824b3ae4545024a7b879c7d08a789820646493b9790454374509d95b8ecb35d8d7db5eaf882c7d1cbca3f5c789f48f8a6a200a8f4cb1b86483b11b9886e11897a80af67260f2081443e4c25d54306c5a35728ddb7db47b511672c02779818b8b46d32124376ec33279f4aed40e56b6a66e678fdbc800d6b7
q: a7b8128608dae59036745de48cae9590d169acb9298ad9fde0da1e0caf10236e7849e852e5424aece532e0af786f3dceb8db93b554e1ba913d81b3350cd49a9457178eee0e5f312bf43f179ecae0ee49cabcbaa6c9122c574443834bdc2f52935732d9ddb9266d4040f17827aa967bfceb263e1e3eb6cf820718ee5d2e6d82c1
d: 647d8b3ae4231762618ed40831e0e4dc8c91d28ab53c500001c4dff3c5db940577fbe6c3d915619cfdde8c354ca891dc916c57c2e4077b35246a19fab1d8a96e744534a34330d1e79ffb1a61a595ded829c7af9f66bf452d0480d0b50268e80a0cfa83a361b2b284aaec81019e58ebace1bb6ff53851fd063dc3f2f7bad1213aea0aab1a878e7d8f79d7c1ddbeedaf612b1fa2c71a4e0a294ad7790208e80f1dbfc930b4eab07685b42acd8399a9afa58be1a7848efcb323fa067c4ef2fd4547dacb88c9294da6cbcab7924ab81533c87d42e20b25c0a5528816baedfe83beed8620e86b0b57d116de8e0985c74fcaacbd2b434cdc77f39b87177ba60f884101
TWCTF{9c10a83c122a9adfe6586f498655016d3267f195}

TWCTF{9c10a83c122a9adfe6586f498655016d3267f195}

vimshell (misc)

指示されたURLを開くと、vimの画面が表示される。「あ、これ知ってる。:!shだ」と思ったが、:が入力できず、表示されているものは:を無効にするパッチだった。

diff --git a/src/normal.c b/src/normal.c
index 41c762332..0011afb77 100644
--- a/src/normal.c
+++ b/src/normal.c
@@ -274,7 +274,7 @@ static const struct nv_cmd
     {'7',      nv_ignore,      0,                      0},
     {'8',      nv_ignore,      0,                      0},
     {'9',      nv_ignore,      0,                      0},
-    {':',      nv_colon,       0,                      0},
+    // {':',   nv_colon,       0,                      0},
     {';',      nv_csearch,     0,                      FALSE},
     {'<',      nv_operator,    NV_RL,                  0},

他に、Qgも無効化されている。

ソースコードをnv_colonで検索すると、Ctrl+wの中でも呼び出されていた。

/*
 * CTRL-W: Window commands
 */
    static void
nv_window(cmdarg_T *cap)
{
    if (cap->nchar == ':')
    {
        /* "CTRL-W :" is the same as typing ":"; useful in a terminal window */
        cap->cmdchar = ':';
        cap->nchar = NUL;
        nv_colon(cap);
    }
    else if (!checkclearop(cap->oap))
        do_window(cap->nchar, cap->count0, NUL); /* everything is in window.c */
}

Ctrl+wの後に:!shでシェルが実行できる。ChromeだとCtrl+wでブラウザのタブが閉じてしまうので、Edgeを使った。/flagにフラグが置かれていた。

皆こう解いたのかと思いきや、想定回答も他の参加者の解法も違っていて面白い。

TWCTF{the_man_with_the_vim}

mixed cipher (crypto)

コンテスト中には解けず、下記のヒントを見て、終了後に解いた。

1024ビットRSAと、128ビットAESについて、次の操作ができるサーバー。AESの暗号モードはCBC。RSAでの暗号化では、パディングなどは無く、単純に文字列を数値に変換したものに対して、$M^e \mod n$を計算する。RSAとAESの鍵は1回のセッション中では同じものが使われる。

  1. 任意の文字列を入力して、RSAとAESで暗号化した結果がそれぞれ得る
  2. RSAの暗号文を入力して、復号結果を得る。ただし、最後の1バイト以外はブルトーザーに潰される。256で割った余りが分かるということ
  3. フラグを暗号化した結果が得られる。ただし、IVは潰される
  4. AESの鍵をRSAで暗号化した結果が得られる

server.pyのunpadの実装がマズくて、詰めている値が全部同じ値かどうかをチェックしていない。暗号文の最後のブロックを適当に書き換えてunpadで切り詰められる長さを変えて解くのかな?と最初は思ったけれど、違った。最後の1バイト以外が潰されるのは、AESではなくRSAだし、そもそもunpadを呼び出すaes_decryptはプログラム中で使用されていなかった。

server.py
def pad(s):
    n = 16 - len(s)%16
    return s + chr(n)*n

def unpad(s):
    n = ord(s[-1])
    return s[:-n]

まずは、AESの暗号鍵の取得を目指す。AESの鍵は128ビットしかなく、RSAのブロック長1024ビットに比べて小さい。そこで、RSAの$n$と、$aeskey \times x$が$n$を超えないなるべく大きな整数$x$を求めるという方針にした。そうすれば、$n/x$がAESの暗号鍵となる。

復号結果は$n$未満になるので、ある整数を暗号化して復号したときに変化するかどうか(変化したら、その整数は$n$以上だったということ)で二分探索をした。手元ではAESの暗号鍵を取得するところまで動いていたけれど、サーバーで試したら、エラーになってしまった。ネイティブコードのOpenSSLが使われているかどうかで挙動が異なるらしい。

2を暗号化すると、暗号文は$C = 2^e \mod n$となる。$2^e - C = k_2n$だから、$2^e - C$を素因数すれば良いかと思ったが、$e=65537$なので、$k_2$もそのくらいのビット数の整数。このくらい大きいと、たとえ合成数でも高い確率で大きな素数が含まれていて、素因数分解はできなかった。ここで諦めた。

3も暗号化して、$3^e - C = k_3n$を求めれば良かった。$\gcd(2^e-C, 3^e-C) = kn$。この$k$は$k_2$と$k_3$の最大公約数なので、そんなに大きくならない。

AESの暗号鍵と$x$をRSA暗号化した結果をそれぞれ、$C_k = aeskey^e \mod n$、$C_x = x^e \mod n$とすると、$C_kC_x$を復号した結果は$aeskey \times x \mod n$となる。$x$を256の倍数にすれば、$aeskey \times x$が$n$未満ならば最後の1バイトは00に、$n$以上ならばそれ以外の値になる。これで二分探索ができる。たまたま00になることも考慮して、$x$と$x+1$で試すようにした。

フラグの暗号文のIVについて。IVは、

server.py
import random
 :
    iv = long_to_bytes(random.getrandbits(BLOCK_SIZE*8), 16)

と計算されている。Pythonのrandomの実装はメルセンヌ・ツイスタ。暗号論的擬似乱数生成器ではないので、暗号に使ってはいけない。32ビット整数624個分のIVから内部状態が完全に復元できて、以降の乱数が予測できるようになる。

attack.py
from socket import *
from Crypto.Cipher import AES
from Crypto.Util.number import long_to_bytes, bytes_to_long, GCD

l2b = long_to_bytes
b2l = bytes_to_long

s = socket(AF_INET, SOCK_STREAM)
s.connect(("crypto.chal.ctf.westerns.tokyo", 5643))

def readline():
    t = s.recv(1)
    while t[-1] != "\n":
        t += s.recv(1)
    return t[:-1]

# Get encrypted aeskey
for _ in range(6):
    readline()
s.send("4\n")
readline()              # here is encrypted key :)
key = readline().decode("hex")

print "key:", key.encode("hex")

IV = []

def encrypt(d):
    for _ in range(6):
        readline()
    s.send("1\n")
    s.recv(256)         # input plain text:
    s.send(d+"\n")
    rsa = readline()    # RSA:
    aes = readline()    # AES:
    global IV
    IV += [aes.split()[-1].decode("hex")[:16]]
    return rsa.split()[-1].decode("hex")

def decrypt(d):
    for _ in range(6):
        readline()
    s.send("2\n")
    s.recv(256)         # input hexencoded cipher text:
    s.send(d.encode("hex")+"\n")
    readline()          # Bulldozer is coming!
    return readline().split()[-1].decode("hex")[-1]

# Guess n
c2 = 2**65537 - b2l(encrypt(l2b(2L)))
c3 = 3**65537 - b2l(encrypt(l2b(3L)))
n = GCD(c2, c3)

d = 2
while n>=2**1024:
    while n%d==0:
        n /= d
    d += 1

print "n:", "%x"%n

# Guess aeskey
x = 256
b = 2**(1024-120)

while b>2**(1024-260):
    print "x:", "%x"%x
    if (decrypt(l2b(pow(x+b,     65537, n)*b2l(key)%n))[-1] == "\0" and
        decrypt(l2b(pow(x+b+256, 65537, n)*b2l(key)%n))[-1] == "\0"):
            x += b
    b /= 2

aeskey = l2b(n/x)
print "aeskey:", aeskey.encode("hex")

# Guess IV
while len(IV)<624/4:
    print "%d/%d" % (len(IV)+1, 624/4)
    encrypt("hoge")

def tempering(y):
    y ^= y>>11
    y ^= y<< 7 & 0x9d2c5680
    y ^= y<<15 & 0xefc60000
    y ^= y>>18
    return y

# http://plusletool.hatenablog.jp/entry/2014/10/24/213816
def untempering(x):
    x ^= x>>18
    x ^= x<<15 & 0xefc60000
    x ^= x<< 7 & 0x9d2c5680
    x ^= x<<14 & 0x94284000
    x ^= x<<28 & 0x10000000
    x ^= x>>11
    x ^= x>>22
    return x

mt = []
for iv in IV:
    iv = b2l(iv)
    mt += [
        iv     & 0xffffffff,
        iv>>32 & 0xffffffff,
        iv>>64 & 0xffffffff,
        iv>>96 & 0xffffffff,
    ]

for i in range(624):
    mt[i] = untempering(mt[i])

for i in range(624):
    y = mt[i]&0x80000000 | mt[(i+1)%624]&0x7fffffff
    mt[i] = mt[(i+397)%624] ^ y>>1 ^ [0, 0x9908b0df][y&1]

iv = l2b(
    tempering(mt[0]) |
    tempering(mt[1])<<32 |
    tempering(mt[2])<<64 |
    tempering(mt[3])<<96,
    16)
print "iv:", iv.encode("hex")

# Decrypt flag
for _ in range(6):
    readline()
s.send("3\n")
readline()              # here is encrypted flag :)
readline()              # another bulldozer is coming!
flag = readline().decode("hex")

aes = AES.new(aeskey, AES.MODE_CBC, iv)
print "flag:", repr(aes.decrypt(flag[16:]))
kusano@chino:/mnt/d/documents/ctf/tokyowesterns2018/mixed cipher$ python attack.py
key: 35e464bbb63f484df03f554568366dd5592b684c0e99fd227e89df65f78e18dcc44a9247c7c6553cf47f1ecb5d8172f4f5f9dbb353d4453a82fba8f0fd4d3af1632f892374fbf33c1edd72b2700d6fb7e4a9dd74d44cd5a7343d79c5fb0d97d762ebac0e852df948e8875aa0835cd2852d7f6c9cfa6fc0eb44603e7502622789
n: a8b93c44d17096a03705a866e60ae42be2ebf34ae8ed6b59830e878ea334799900f231fab46a0746d04fade14e8f25cee1b9655b9aa1a2be7aaa9062e0216a12293687767d1c4eae87468bbac309df2b30ffacafcc55f5784979510ed66094b87750f10fc40f6058a3fe657769aa67755c6354b3ebe9838aa3f8e22e432c9869
x: 100
x: 100
x: 100
x: 100
x: 100
x: 100
x: 100
x: 100
x: 100
x: 100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000100
x: 100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000100
 :
x: 136fd508e568782bf898b3ee3971c4bee000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000100
x: 136fd508e568782bf898b3ee3971c4bee000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000100
aeskey: 8ae3c274a39bb54de4202d873affac19
3/156
4/156
 :
155/156
156/156
iv: b6a3b5eb91042e96b06752293d4f8a7e
flag: 'TWCTF{L#B_de#r#pti#n_ora#le_c9630b129769330c9498858830f306d9}\x03\x03\x03'

TWCTF{L#B_de#r#pti#n_ora#le_c9630b129769330c9498858830f306d9}

3
4
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
4