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 action
とhashed_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で暗号化して、サーバーが返してきた暗号文と比較すればわかる。
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)
Flask。Jinja2のテンプレートを指定することができる。ただし、(
と)
は除去され、先頭に{{% set config=None%}}{{% set self=None%}}
が追加される。config['FLAG']
にフラグが書かれている。
そもそも、テンプレートが指定できるというのはとても危険で、クラスの継承関係を辿って任意コード実行ができるらしい。
が、()
が使えないので関数呼び出しができず、これは無理そう。
config
とself
以外にも、request
とsession
、g
がテンプレ途中では参照できるけれど、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風の暗号。
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$の値を変えていけば良い。
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},
他に、Q
とg
も無効化されている。
ソースコードを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
にフラグが置かれていた。
皆こう解いたのかと思いきや、想定回答も他の参加者の解法も違っていて面白い。
vimshellは簡単な問題ですが割と自信作なので楽しんでいただけたなら幸いです。ちなみに想定解はShift-Kでmanを起動して!/bin/shです
— icchy (@icchyr) 2018年9月3日
vimshellはInsert modeで<C-r>=system(‘cat /flag’)をしました
— れっくす (@xrekkusu) 2018年9月3日
なお、vimshellは /flag と書いた上にカーソルを置いて C-w f でファイルを開いた。普通にC-wを押下するとブラウザのタブが閉じてしまうので、別のキーストロークの通信をBurpで横取り、書き換えて入力した。この問題も勉強になった。
— onotch (@onotch512) 2018年9月3日
TWCTF{the_man_with_the_vim}
mixed cipher (crypto)
コンテスト中には解けず、下記のヒントを見て、終了後に解いた。
1024ビットRSAと、128ビットAESについて、次の操作ができるサーバー。AESの暗号モードはCBC。RSAでの暗号化では、パディングなどは無く、単純に文字列を数値に変換したものに対して、$M^e \mod n$を計算する。RSAとAESの鍵は1回のセッション中では同じものが使われる。
- 任意の文字列を入力して、RSAとAESで暗号化した結果がそれぞれ得る
- RSAの暗号文を入力して、復号結果を得る。ただし、最後の1バイト以外はブルトーザーに潰される。256で割った余りが分かるということ
- フラグを暗号化した結果が得られる。ただし、IVは潰される
- AESの鍵をRSAで暗号化した結果が得られる
server.pyのunpad
の実装がマズくて、詰めている値が全部同じ値かどうかをチェックしていない。暗号文の最後のブロックを適当に書き換えてunpad
で切り詰められる長さを変えて解くのかな?と最初は思ったけれど、違った。最後の1バイト以外が潰されるのは、AESではなくRSAだし、そもそもunpad
を呼び出すaes_decrypt
はプログラム中で使用されていなかった。
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は、
import random
:
iv = long_to_bytes(random.getrandbits(BLOCK_SIZE*8), 16)
と計算されている。Pythonのrandomの実装はメルセンヌ・ツイスタ。暗号論的擬似乱数生成器ではないので、暗号に使ってはいけない。32ビット整数624個分のIVから内部状態が完全に復元できて、以降の乱数が予測できるようになる。
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}