本記事について
2021年5月22日と2021年5月23日に開催された「SECCON Beginners CTF 2021」の「crypto」の「Imaginary」問題について、解きましたので、手順を詳細に記載します。
crypto(Imaginary)
以下の問題文に記載がありますが、虚数に関する問題のようです。
提供されたプログラム(aap.py)は以下の通りです。
プログラムを確認すると、「変数self.numbersが1337iにして、5のself._secret()」を実行したらいい。
import json
import os
from socketserver import ThreadingTCPServer, BaseRequestHandler
from Crypto.Cipher import AES
from Crypto.Util.Padding import pad, unpad
from secret import flag, key
class ImaginaryService(BaseRequestHandler):
def handle(self):
try:
self.request.sendall(b'Welcome to Secret IMAGINARY NUMBER Store!\n')
self.numbers = {}
while True:
num = self._menu()
if num == 1:
self._save()
elif num == 2:
self._show()
elif num == 3:
self._import()
elif num == 4:
self._export()
elif num == 5:
self._secret()
else:
break
except Exception as e:
try:
self.request.sendall(f'ERR: {e}\n'.encode())
except Exception:
pass
def _menu(self):
self.request.sendall(b'1. Save a number\n')
self.request.sendall(b'2. Show numbers\n')
self.request.sendall(b'3. Import numbers\n')
self.request.sendall(b'4. Export numbers\n')
self.request.sendall(b'0. Exit\n')
self.request.sendall(b'> ')
try:
return int(self.request.recv(128).strip())
except ValueError:
return 0
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
def _show(self):
self.request.sendall(b'-' * 50 + b'\n')
for name in self.numbers:
re, im = self.numbers[name]
self.request.sendall(f'{name}: ({re}, {im})\n'.encode())
self.request.sendall(b'-' * 50 + b'\n')
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()
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')
def _secret(self):
if '1337i' in self.numbers:
self.request.sendall(b'Congratulations!\n')
self.request.sendall(f'The flag is {flag}\n'.encode())
if __name__ == '__main__':
host = os.getenv('CTF4B_HOST')
port = os.getenv('CTF4B_PORT')
if not host:
host = 'localhost'
if not port:
port = '1337'
ThreadingTCPServer.allow_reuse_address = True
server = ThreadingTCPServer((host, int(port)), ImaginaryService)
print(f'Start server at {host}:{port}')
server.serve_forever()
そのため、問題のサーバに接続して、以下のコマンドを実行しましたが、「1337i」ではなく「0 + 1337i」になってしまいます。
$ 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)
--------------------------------------------------
1. Save a number
2. Show numbers
3. Import numbers
4. Export numbers
0. Exit
> 5 ←フラグが表示されるか確認します。
1. Save a number
2. Show numbers
3. Import numbers
4. Export numbers
0. Exit
> 4 ←どのようにデータが保存されているか表示します。
{"0 + 1337i": [0, 1337]}
Exported:
817351cf3a18d9fd90837146b4f0d3341a4a895666e342950bb77e9683918d4c
1. Save a number
2. Show numbers
3. Import numbers
4. Export numbers
0. Exit
> 1
Real part> 123
Imaginary part> 56789
1. Save a number
2. Show numbers
3. Import numbers
4. Export numbers
0. Exit
> 4
{"0 + 1337i": [0, 1337], "123 + 56789i": [123, 56789]}
Exported:
817351cf3a18d9fd90837146b4f0d3343c6233d81b8c476fa82e2be85f3a5dee92451f4aeda1d03ded8edfd624c9358f852c6bade1c4bf8aba7b7c0c884570e6
「def _import(self)」と「def _import(self)」でAESのECBモードで暗号化と復号化しています。
AESのECBモードとは以下のイメージ図のように平文をブロックに分けて、それぞれ暗号化する方式です。
今回のプログラムは自身で作成したAESのECBモードの暗号化の文字列をインポートできるため、データを作成してインポートする方法を検討します。
以下のようなデータを入力して、出力します。
# 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> 12345678
Imaginary part> 1
1. Save a number
2. Show numbers
3. Import numbers
4. Export numbers
0. Exit
> 1
Real part> 1234
Imaginary part> 1234
1. Save a number
2. Show numbers
3. Import numbers
4. Export numbers
0. Exit
> 1
Real part> 12
Imaginary part> 12
1. Save a number
2. Show numbers
3. Import numbers
4. Export numbers
0. Exit
> 1
Real part> 1234567
Imaginary part> 1337
1. Save a number
2. Show numbers
3. Import numbers
4. Export numbers
0. Exit
> 4
{"12345678 + 1i": [12345678, 1], "1234 + 1234i": [1234, 1234], "12 + 12i": [12, 12], "1234567 + 1337i": [1234567, 1337]}
Exported:
0fbc6fb787d5b0b56f14be9b4775cba59a1344ebac6b5aff7919452c00ac2f5b5117599006744dbe5ddf4d60d51ab2ea94abcd3a925a967168930c9f8d8a3697929c268f98a3ab1310ac85dc483318fc9b8bed0b9438686bf2b1d50e82a20822712cf584affea482cdba77dc6c642e3c1a4a895666e342950bb77e9683918d4c
1. Save a number
2. Show numbers
3. Import numbers
4. Export numbers
0. Exit
上記の出力結果(AESのECBモードの平文)を32文字区切りにして、特定の行を削除すると、「”1337i”」を作成することができます。
【AESのECBモードの平文(1行32文字区切り)】
{"12345678 + 1i": [12345678, 1],
"1234 + 1234i": [1234, 1234], "
12 + 12i": [12, 12], "1234567 + ←この行を削除予定
1337i": [1234567, 1337]}
【AESのECBモードの平文(削除後の値)】
{"12345678 + 1i": [12345678, 1], "1234 + 1234i": [1234, 1234], "1337i": [1234567, 1337]}
暗号文も16文字で区切ると平文と同じブロックに同じデータがあるため、同様の場所を削除した文字列を作成します。
【AESのECBモードの暗号文(1行32文字区切り)】
0fbc6fb787d5b0b56f14be9b4775cba5
9a1344ebac6b5aff7919452c00ac2f5b
5117599006744dbe5ddf4d60d51ab2ea
94abcd3a925a967168930c9f8d8a3697
929c268f98a3ab1310ac85dc483318fc ←この行を削除予定
9b8bed0b9438686bf2b1d50e82a20822 ←この行を削除予定
712cf584affea482cdba77dc6c642e3c
1a4a895666e342950bb77e9683918d4c
【AESのECBモードの暗号文(削除後の値)】
0fbc6fb787d5b0b56f14be9b4775cba59a1344ebac6b5aff7919452c00ac2f5b5117599006744dbe5ddf4d60d51ab2ea94abcd3a925a967168930c9f8d8a3697712cf584affea482cdba77dc6c642e3c1a4a895666e342950bb77e9683918d4c
上記の作成した暗号文を使って、以下のコマンドを実行すると、フラグ(ctf4b{yeah_you_are_a_member_of_imaginary_number_club})が表示されました。
# 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
> 3
Exported String> 0fbc6fb787d5b0b56f14be9b4775cba59a1344ebac6b5aff7919452c00ac2f5b5117599006744dbe5ddf4d60d51ab2ea94abcd3a925a967168930c9f8d8a3697712cf584affea482cdba77dc6c642e3c1a4a895666e342950bb77e9683918d4c
Imported.
--------------------------------------------------
12345678 + 1i: (12345678, 1)
1234 + 1234i: (1234, 1234)
1337i: (1234567, 1337)
--------------------------------------------------
1. Save a number
2. Show numbers
3. Import numbers
4. Export numbers
0. Exit
> 5
Congratulations!
The flag is ctf4b{yeah_you_are_a_member_of_imaginary_number_club}
SECCON Beginners CTF 2021に関する記事
SECCON Beginners CTF 2021についての関連記事は以下の通りです。
必要に応じて、ご確認ください。
No | タイトル | 記事の概要 |
---|---|---|
1 | SECCON Beginners CTF 2021 writeup | 以下のカテゴリの問題のwriteupを記載します。 【カテゴリ:welcome】 ・welcome 【カテゴリ:crypto】 ・imple_RSA 【カテゴリ:reversing】 ・only_read ・children 【カテゴリ:pwnable】 ・rewriter 【カテゴリ:web】 ・osoba 【カテゴリ:misc】 ・git-leak ・Mail_Address_Validator |
2 | SECCON Beginners CTF 2021 writeup (crypto:simple_RSA) | cryptカテゴリのsimple_RSAの問題の writeupを記載します。 |
3 | SECCON Beginners CTF 2021 writeup (crypto:Logical_SEESAW) | cryptカテゴリのLogical_SEESAWの問題の writeupを記載します。 |
4 | SECCON Beginners CTF 2021 writeup (crypto:GFM) | cryptカテゴリのGFMの問題の writeupを記載します。 |
5 | SECCON Beginners CTF 2021 writeup (crypto:Imaginary) | cryptカテゴリのImaginaryの問題の writeupを記載します。 |
6 | SECCON Beginners CTF 2021 writeup (web:Werewolf) | webカテゴリのWerewolfの問題の writeupを記載します。 |
7 | SECCON Beginners CTF 2021 writeup (web:check_url) | webカテゴリのcheck_urlの問題の writeupを記載します。 |
8 | SECCON Beginners CTF 2021 writeup (web:json) | webカテゴリのjsonの問題の writeupを記載します。 |
9 | SECCON Beginners CTF 2021 writeup (web:cant_use_db) | webカテゴリのcant_use_dbの問題の writeupを記載します。 |