HeroCTF v3 Writeup

この大会は2021/4/24 5:00(JST)~2021/4/26 9:00(JST)に開催されました。
今回もチームで参戦。結果は5546点で645チーム中3位でした。
自分で解けた問題をWriteupとして書いておきます。

Discord (Misc 1)

Discordに入り、#announcementsチャネルのルールを記載しているところにフラグが書いてあった。

Hero{3njoy_th3_3v3nt}

Record (Misc 15)

ドメイン heroctf.fr について何か見つける問題。DNSのTXTレコードを見てみる。

$ dig heroctf.fr TXT

; <<>> DiG 9.11.3-1ubuntu1.14-Ubuntu <<>> heroctf.fr TXT
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 32787
;; flags: qr rd ra; QUERY: 1, ANSWER: 2, AUTHORITY: 0, ADDITIONAL: 1

;; OPT PSEUDOSECTION:
; EDNS: version: 0, flags:; udp: 65494
;; QUESTION SECTION:
;heroctf.fr.			IN	TXT

;; ANSWER SECTION:
heroctf.fr.		5	IN	TXT	"v=spf1 mx a -all"
heroctf.fr.		5	IN	TXT	"Hero{cl34rtXt_15_b4d}"

;; Query time: 265 msec
;; SERVER: 127.0.0.53#53(127.0.0.53)
;; WHEN: Sat Apr 24 13:42:56 JST 2021
;; MSG SIZE  rcvd: 102

フラグが設定されていた。

Hero{cl34rtXt_15_b4d}

PRo Random Guesser (Prog 50)

次の数値を推測する問題だが、問題文に"Mersenne"というワードが入っている。Mersenne Twisterの問題のようだ。624個のランダム値を入手し、次の値を予測する。

import socket
import random

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

def untemper(rand):
    rand ^= rand >> 18;
    rand ^= (rand << 15) & 0xefc60000;
 
    a = rand ^ ((rand << 7) & 0x9d2c5680);
    b = rand ^ ((a << 7) & 0x9d2c5680);
    c = rand ^ ((b << 7) & 0x9d2c5680);
    d = rand ^ ((c << 7) & 0x9d2c5680);
    rand = rand ^ ((d << 7) & 0x9d2c5680);
 
    rand ^= ((rand ^ (rand >> 11)) >> 11);
    return rand

s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect(('chall0.heroctf.fr', 7003))

N = 624
state = []
for i in range(N):
    data = recvuntil(s, ': ')
    print data + '1'
    s.sendall('1\n')
    data = recvuntil(s, '\n').rstrip()
    print data
    state.append(untemper(int(data.split(' ')[3])))

state.append(N)
random.setstate([3, tuple(state), None])

guess = str(random.getrandbits(32))
data = recvuntil(s, ': ')
print data + guess
s.sendall(guess + '\n')
data = recvuntil(s, '\n').rstrip()
print data

実行結果は以下の通り。

        :
Guess me : 1
Nop, it was 2855423616 :) But you can try again

Guess me : 1
Nop, it was 973691288 :) But you can try again

Guess me : 1
Nop, it was 1603588550 :) But you can try again

Guess me : 1
Nop, it was 1025656036 :) But you can try again

Guess me : 1
Nop, it was 3190350390 :) But you can try again

Guess me : 44888449
how can you be so lucky... here you go... Hero{n0t_s0_r4nd0m_4ft3r_4ll}
Hero{n0t_s0_r4nd0m_4ft3r_4ll}

Puzzle Me (Prog 100)

20000個のpngファイルがあるが、すべて先頭3, 4バイト目が壊れている。修正すると15x15の画像の断片になっているようだった。先頭3バイト目が数値にすると、0~199、先頭4バイト目が数値にすると、0~99なので、先頭3バイト目が横の位置、先頭4バイト目が縦の位置を示していると推測できる。以上をもとに画像を復元する。

import os
from PIL import Image
from Crypto.Util.number import *

def fix_png_save(data):
    save_data = data[:2]
    save_data += 'NG'
    save_data += data[4:]
    with open('tmp.png', 'wb') as f:
        f.write(save_data)

WIDTH_NUM = 200
HEIGHT_NUM = 100
UNIT_SIZE = 15
WIDTH = UNIT_SIZE * WIDTH_NUM
HEIGHT = UNIT_SIZE * HEIGHT_NUM

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

DIR = './pieces/'

files = os.listdir(DIR)
for file in files:
    fname = DIR + file
    with open(fname, 'rb') as f:
        data = f.read()
    x = bytes_to_long(data[2])
    y = bytes_to_long(data[3])
    fix_png_save(data)
    input_img = Image.open('tmp.png').convert('RGB')
    output_img.paste(input_img, ((x * UNIT_SIZE), (y * UNIT_SIZE)))
    os.remove('tmp.png')

output_img.save('flag.png')

f:id:satou-y:20210430074531p:plain

Hero{PuZzzZZzzzZzzzzL3}

We need you 1/5 (Forensic 50)

PCのメモリが添付されているので、PCの名前を答える問題。

$ volatility -f capture.mem imageinfo
Volatility Foundation Volatility Framework 2.6.1
INFO    : volatility.debug    : Determining profile based on KDBG search...
          Suggested Profile(s) : Win7SP1x86_23418, Win7SP0x86, Win7SP1x86_24000, Win7SP1x86
                     AS Layer1 : IA32PagedMemoryPae (Kernel AS)
                     AS Layer2 : FileAddressSpace (/mnt/hgfs/Shared/work/capture.mem)
                      PAE type : PAE
                           DTB : 0x185000L
                          KDBG : 0x82780c28L
          Number of Processors : 1
     Image Type (Service Pack) : 1
                KPCR for CPU 0 : 0x82781c00L
             KUSER_SHARED_DATA : 0xffdf0000L
           Image date and time : 2021-04-19 17:30:00 UTC+0000
     Image local date and time : 2021-04-19 19:30:00 +0200

$ volatility -f capture.mem --profile=Win7SP1x86_23418 hivelist
Volatility Foundation Volatility Framework 2.6.1
Virtual    Physical   Name
---------- ---------- ----
0x8b0a1008 0x6b1ed008 \??\C:\Windows\ServiceProfiles\NetworkService\NTUSER.DAT
0x8b12d008 0x697f4008 \??\C:\Windows\ServiceProfiles\LocalService\NTUSER.DAT
0x9b1d89c8 0x79a459c8 \SystemRoot\System32\Config\SECURITY
0x9fee59c8 0x3c8ab9c8 \??\C:\Users\Razex\ntuser.dat
0x9fefc008 0x6fd89008 \??\C:\Users\Razex\AppData\Local\Microsoft\Windows\UsrClass.dat
0x823859c8 0x2b5d99c8 \SystemRoot\System32\Config\SAM
0x8940c5e8 0x2d5415e8 [no name]
0x8941a2c0 0x2d58d2c0 \REGISTRY\MACHINE\SYSTEM
0x89441008 0x2d436008 \REGISTRY\MACHINE\HARDWARE
0x894ca4c8 0x5fa804c8 \SystemRoot\System32\Config\DEFAULT
0x895975c0 0x2d4165c0 \Device\HarddiskVolume1\Boot\BCD
0x8a5fb008 0x13776008 \SystemRoot\System32\Config\SOFTWARE
0x8b04e9c8 0x3e6ad9c8 \??\C:\System Volume Information\Syscache.hve

SYSTEMのレジストリ情報からコンピュータ名を取得する。

$ volatility -f capture.mem --profile=Win7SP1x86_23418 printkey -o 0x8941a2c0 -K 'ControlSet001\Control\ComputerName\ComputerName'
Volatility Foundation Volatility Framework 2.6.1
Legend: (S) = Stable   (V) = Volatile

----------------------------
Registry: \REGISTRY\MACHINE\SYSTEM
Key name: ComputerName (S)
Last updated: 2021-04-19 17:00:09 UTC+0000

Subkeys:

Values:
REG_SZ                        : (S) mnmsrvc
REG_SZ        ComputerName    : (S) KANNIBAL
Hero{KANNIBAL}

We need you 2/5 (Forensic 75)

1/5 の続き。ユーザ名とパスワードを答える問題。

$ volatility -f capture.mem --profile=Win7SP1x86_23418 hivelist
Volatility Foundation Volatility Framework 2.6.1
Virtual    Physical   Name
---------- ---------- ----
0x8b0a1008 0x6b1ed008 \??\C:\Windows\ServiceProfiles\NetworkService\NTUSER.DAT
0x8b12d008 0x697f4008 \??\C:\Windows\ServiceProfiles\LocalService\NTUSER.DAT
0x9b1d89c8 0x79a459c8 \SystemRoot\System32\Config\SECURITY
0x9fee59c8 0x3c8ab9c8 \??\C:\Users\Razex\ntuser.dat
0x9fefc008 0x6fd89008 \??\C:\Users\Razex\AppData\Local\Microsoft\Windows\UsrClass.dat
0x823859c8 0x2b5d99c8 \SystemRoot\System32\Config\SAM
0x8940c5e8 0x2d5415e8 [no name]
0x8941a2c0 0x2d58d2c0 \REGISTRY\MACHINE\SYSTEM
0x89441008 0x2d436008 \REGISTRY\MACHINE\HARDWARE
0x894ca4c8 0x5fa804c8 \SystemRoot\System32\Config\DEFAULT
0x895975c0 0x2d4165c0 \Device\HarddiskVolume1\Boot\BCD
0x8a5fb008 0x13776008 \SystemRoot\System32\Config\SOFTWARE
0x8b04e9c8 0x3e6ad9c8 \??\C:\System Volume Information\Syscache.hve

SAMとSYSTEMからパスワードのハッシュ情報を取得する。

$ volatility -f capture.mem --profile=Win7SP1x86_23418 hashdump -y 0x8941a2c0 -s 0x823859c8 > hash.txt
Volatility Foundation Volatility Framework 2.6.1
$ cat hash.txt
Administrateur:500:aad3b435b51404eeaad3b435b51404ee:31d6cfe0d16ae931b73c59d7e0c089c0:::
Invit:501:aad3b435b51404eeaad3b435b51404ee:31d6cfe0d16ae931b73c59d7e0c089c0:::
Razex:1000:aad3b435b51404eeaad3b435b51404ee:78d9c7e905c695087ee3baa755ce43e4:::

ユーザ名はRazex。CrackStationでパスワードをクラックする。

78d9c7e905c695087ee3baa755ce43e4 → liverpoolfc123
Hero{Razex:liverpoolfc123}

We need you 3/5 (Forensics 80)

2/5 の続き。感染マシンへの接続に使うIPアドレスとポート番号を答える問題。

$ volatility -f capture.mem --profile=Win7SP1x86_23418 cmdscan
Volatility Foundation Volatility Framework 2.6.1
**************************************************
CommandProcess: conhost.exe Pid: 1672
CommandHistory: 0x3d14a8 Application: cmd.exe Flags: Allocated, Reset
CommandCount: 20 LastAdded: 19 LastDisplayed: 19
FirstCommand: 0 CommandCountMax: 50
ProcessHandle: 0x58
Cmd #0 @ 0x3c8040: cd Documents
Cmd #1 @ 0x3ce390: dir
Cmd #2 @ 0x3c8068: cd "Ma musique"
Cmd #3 @ 0x3ce3c0: dir
Cmd #4 @ 0x3c7428: clear
Cmd #5 @ 0x3ce3e0: dir
Cmd #6 @ 0x3c7440: cd ..
Cmd #7 @ 0x3ce3f0: dir
Cmd #8 @ 0x3c7458: clear
Cmd #9 @ 0x3cd560: cd Malw4r3
Cmd #10 @ 0x3ce400: dir
Cmd #11 @ 0x3b1de8: ./nc.exe-lvp 4444
Cmd #12 @ 0x3d69c0: ./nc.exe -lvp 4444
Cmd #13 @ 0x3d6620: nc.exe -lvp 4444
Cmd #14 @ 0x3cd5a0: ipconfig
Cmd #15 @ 0x3d6f00: nc.exe -lvp 4444
Cmd #16 @ 0x3d6f98: nc.exe 146.59.156.82 4444
Cmd #17 @ 0x3d6650: nc.exe -lvp 4444
Cmd #18 @ 0x3cd580: ipconfig
Cmd #19 @ 0x3d6fd8: nc.exe 146.59.156.82 4444
Cmd #36 @ 0x3a00c4: =?=?:???:
Cmd #37 @ 0x3cd038: =?;?????:?:

**************************************************
CommandProcess: conhost.exe Pid: 1672
CommandHistory: 0x3d6b00 Application: nc.exe Flags: Allocated
CommandCount: 0 LastAdded: -1 LastDisplayed: -1
FirstCommand: 0 CommandCountMax: 50
ProcessHandle: 0x84
Hero{146.59.156.82:4444}

HolyAbbot (Steganography 15)

添付されているテキストは以下のようになっている。

Dans son règne, Dans la béatitude
À tout jamais Dans son règne
Dans son royaume Irrévocablement
Dans son royaume Dans la béatitude
Dans son royaume Irrévocablement
Toujours En paradis

http://www.jellevy.com/Classes/terminale/Arithmetique/Exercices/exo7_codage_term.htmlにあるアルファベット対応表を参考に置き換える。

su
bs
ti
tu
ti
on
Hero{substitution}

Nice PDF (Steganography 20)

PDFファイルが添付されている。本文をコピペすると、以下のようになる。

DansHseseHistoires,rl'historienogrec{HérodoteErapporte4ainsiSuneYanecdote_quiPeutDlieuFau}mo-ment de la seconde guerre médique. En 484 av. J.-C., Xerxès Ier, roi des Perses, décide de préparer une armée gigantesque pour envahir la Grèce (Livre VII, 5-19). Quatre ans plus tard, lorsqu'il lance l'offensive, les Grecs sont depuis longtemps au courant de ses intentions. C'est que Démarate, ancien roi de Sparte réfugié auprès de Xerxès, a appris l'existence de ce projet et décide de transmettre l'information à Sparte (Livre VII, 239) :
« il prit une tablette double, en gratta la cire, puis écrivit sur le bois même les projets de Xerxès ; ensuite il recouvrit de cire son message : ainsi le porteur d'une tablette vierge ne risquait pas d'ennuis. »
Un autre passage de la même oeuvre fait également référence à la stéganographie : au paragraphe 35 du livre V, Histiée incite son gendre Aristagoras, gouverneur de Milet, à se révolter contre son roi, Darius, et pour ce faire,
« il fit raser la tête de son esclave le plus fidèle, lui tatoua son message sur le crâne et attendit que les cheveux eussent repoussé ; quand la chevelure fut redevenue normale, il fit partir l'esclave pour Milet. »
En Chine, on écrivait le message sur de la soie, qui ensuite était placée dans une petite boule recouverte de cire. Le messager avalait ensuite cette boule.
Dès le ier siècle av. J.-C., Pline l'Ancien décrit comment réaliser de l'encre invisible (ou « encre sympathique »). Les enfants de tous les pays s'amusent à le faire en écrivant avec du lait ou du jus de citron : le passage de la feuille écrite sous une source chaude (fer à repasser chaud, flamme de bougie...) révèle le message.
Durant la Seconde Guerre mondiale, les agents allemands utilisaient la technique du micropoint de Zapp, qui consiste à réduire la photo d'une page en un point d'un millimètre ou même moins. Ce point est ensuite placé dans un texte normal. Le procédé est évoqué dans une aventure de Blake et Mortimer, S.O.S. Météores. Il reçoit aussi une belle illustration dans le film de Claude Pinoteau, Le Silencieux.
Un couple célèbre d'artistes de music-hall des années 1960, Myr et Myroska, communiquait les yeux bandés, en apparence par « transmission de pensée » et, en réalité, par un astucieux procédé stéganographique à base de phrases codées (dont, en particulier, des variantes de la phrase : « Myroska, êtes-vous avec moi ? »).

最初の部分はPDFの見た目は "Dans"のあとスペースがあり、"ses"となっていて、"H"が隠れていたことがわかる。この隠れていた文字を取り出していく。

Hero{E4SY_PDF}

ShakePng (Steganography 30)

PNGファイルが添付されている。IHDRチャンクと、IENDチャンクが真ん中あたりにあり、順序がおかしいので、TweakPNGで直す。
f:id:satou-y:20210430080406p:plain

HERO{SH4K3_UR_PNG}

Subliminal (Steganography 95)

aviのフレームは720あり、画像の高さも720。各フレームで行単位で上から順にサブリミナルで別画像になっている。AVI2JPGで各フレームの画像をPNGファイルとして切り出し、1行ずれていることに注意し別画像を結合する。

from PIL import Image

WIDTH = 1280
HEIGHT = 720
IFILE_FORMAT = './div/subliminal_challenge %07d.png'

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

for h in range(1, HEIGHT):
    filename = IFILE_FORMAT % (h + 1)
    input_img = Image.open(filename).convert('RGB')
    for w in range(WIDTH):
        rgb = input_img.getpixel((w, h - 1))
        output_img.putpixel((w, h - 1), rgb)

output_img.save('flag.png')

f:id:satou-y:20210430080822p:plain

Hero{Fr4gM3nt3D_1m4Ge}

h4XOR (Crypto 75)

XORで暗号化している。鍵の長さは9バイト。PNGのヘッダはわかっているので、それをもとに鍵を割り出し、復号する。

with open('flag.png.enc', 'rb') as f:
    enc = f.read()

PNG_HEAD = '\x89PNG\x0d\x0a\x1a\x0a\x00'

key = []
for i in range(9):
    key.append(ord(PNG_HEAD[i]) ^ ord(enc[i]))

flag = ''
for i in range(len(enc)):
    flag += chr(ord(enc[i]) ^ key[i%len(key)])

with open('flag.png', 'wb') as f:
    f.write(flag)

復号したPNG画像にフラグが書いてあった。
f:id:satou-y:20210430081002p:plain

Hero{123_xor_321}

ExtractMyBlocks (Crypto 125)

入力した文字列を含め、フラグを含む文字列をAES-ECBで暗号化している。フラグを1文字ずつはみ出させ、比較する方式でフラグを割り出すことができる。

$ nc chall0.heroctf.fr 10000
[SUPPORT] Password Reset
Please enter your account ID : a
faa78fc293fd67a74dcebc00776135ba6b56599461bccda5c67456a60fac0b7833442e3bca75e7a54eccc4158ff9a3f48eb3dfb48b35155a8f5f19d5737da232

$ nc chall0.heroctf.fr 10000
[SUPPORT] Password Reset
Please enter your account ID : aa
d833bbdd43688c0a61e40eb25576e8fa6c73dd874dac7b6f803e159954a72c3da2c2dea5af93a42e4a5a740e4373091a1ebd13214f780906876e00a7f40e0916

$ nc chall0.heroctf.fr 10000
[SUPPORT] Password Reset
Please enter your account ID : aaa
d833bbdd43688c0a61e40eb25576e8fa399accd507443f6aea1f607b9e75e1c03ae73ae8b401b91bbfd93dc12852cb86f7e83d7dd84753e2b6a8abacffc120b4

$ nc chall0.heroctf.fr 10000
[SUPPORT] Password Reset
Please enter your account ID : aaaa
d833bbdd43688c0a61e40eb25576e8fa7becc66c928d89c4fb252164a7cbbaa349092c169c14eae50b89e3da7288850f6e0c02b34bc172c299cbf19bb905cf24

$ nc chall0.heroctf.fr 10000
[SUPPORT] Password Reset
Please enter your account ID : aaaaa
d833bbdd43688c0a61e40eb25576e8fa32ee7f792df9ecfcaca00e1b6b8fa11db79f1fc3a01b076ebfa9a2edb96fa5f2ad6bed10ff950694f77d3fc3813304d8

$ nc chall0.heroctf.fr 10000
[SUPPORT] Password Reset
Please enter your account ID : aaaaaa
d833bbdd43688c0a61e40eb25576e8faf01612ce888c4a12606e83e7445aff2da9aaa40033e7eb3876bd3ada845e75110c1707afb9a404ca4a392e7c82ee445279035c8bfd4a239bfc4a1d207ae1f423

改行を"_"と表すと以下のようなイメージになる。

0123456789abcdef
_Welcome back aa
aaaa !__Your pas
sword : FFFFFFFF
FFFFFFF_Regards_
PPPPPPPPPPPPPPPP

0123456789abcdef
_Welcome back aa
our password : ?
aaaaaaaaaaaaaaaa
aaaaaaaaaaa !__Y
our password : F
FFFFFFFFFFFFFF_R
egards_PPPPPPPPP
import socket

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

TEXT = '_Your password : '

flag = ''
for i in range(15):
    for code in range(32, 127):
        id = TEXT[-17+i:] + flag + chr(code) + 'a' * (27 - i)

        s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        s.connect(('chall0.heroctf.fr', 10000))
        data = recvuntil(s, ': ')
        print data + id
        s.sendall(id + '\n')
        data = recvuntil(s, '\n').rstrip()
        print data
        if data[32*1:32*2] == data[32*4:32*5]:
            flag += chr(code)
            break

print flag

実行結果は以下の通り。

        :
Please enter your account ID :  : Hero{_BL0CK5_}|aaaaaaaaaaaaa
f64b5fb29c37fbca0ae8191ad3707c966ddc9ec234135addadb83734dd0af5f3fb73817c1c39849485468b27faf75fa9d67983eb94ddb73378047d0efb7dc886d7b1dcb3342a83fddd6547cef6120eae8c9f8056c05b7a9e83990a53d8420a13
[SUPPORT] Password Reset
Please enter your account ID :  : Hero{_BL0CK5_}}aaaaaaaaaaaaa
f64b5fb29c37fbca0ae8191ad3707c96e67883660618a917ebb0123194a54516fb73817c1c39849485468b27faf75fa9d67983eb94ddb73378047d0efb7dc886d7b1dcb3342a83fddd6547cef6120eae8c9f8056c05b7a9e83990a53d8420a13
[SUPPORT] Password Reset
Please enter your account ID :  : Hero{_BL0CK5_}~aaaaaaaaaaaaa
f64b5fb29c37fbca0ae8191ad3707c966dfb07b93ef34200197273193a9f605efb73817c1c39849485468b27faf75fa9d67983eb94ddb73378047d0efb7dc886d7b1dcb3342a83fddd6547cef6120eae8c9f8056c05b7a9e83990a53d8420a13
Hero{_BL0CK5_}
Hero{_BL0CK5_}

Yours_truely (Crypto 150)

RSA暗号を復号する必要があるが、pとqは比較的近いので、Fermat法で素因数分解し、復号する。復号できたら、フラグのAES暗号結果が表示される。keyの生成方法に脆弱性があり、生成元のkeyが何であっても、AESの鍵は決まったものになる。ivはUNIXTIMEにより生成されるものなので、その時間の付近でブルートフォースすれば、どれかで復号できる。このことを元に復号する。

#!/usr/bin/python3
import socket
from Crypto.Util.number import *
from Crypto.Util.Padding import pad, unpad
from Crypto.Cipher import AES
import hashlib
import base64
import time

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

def isqrt(n):
    x = n
    y = (x + n // x) // 2
    while y < x:
        x = y
        y = (x + n // x) // 2
    return x

def fermat(n):
    x = isqrt(n) + 1
    y = isqrt(x * x - n)
    while True:
        w = x * x - n - y * y
        if w == 0:
            break
        elif w > 0:
            y += 1
        else:
            x += 1
    return x - y, x + y

s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect(('chall3.heroctf.fr', 9000))

data = recvuntil(s, b'>> ')
print(data, end=''),

n = int(data.split('\n')[6].split(': ')[1])
e = int(data.split('\n')[7].split(': ')[1])

m = b'the_proof_of_your_love'
h = bytes_to_long(hashlib.sha512(m).digest())

p, q = fermat(n)

phi = (p - 1) * (q - 1)
d = inverse(e, phi)
inp = pow(h, d, n)
print(str(inp))
s.sendall(str(inp).encode() + b'\n')

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

now = int(time.time())

ct = eval(data.split('\n')[-1].split(' ')[-2][:-1])
ct = base64.b64decode(ct)

key = hashlib.sha256().update(b'abcd')
key = pad(str(key).encode(), AES.block_size)

init = now - 15
for t in range(init, init + 30):
    iv  = pad(long_to_bytes(t), AES.block_size)
    cipher = AES.new(key, AES.MODE_CBC, iv)
    flag = cipher.decrypt(ct)
    if flag.startswith(b'Hero'):
        print(unpad(flag, AES.block_size))
        break

実行結果は以下の通り。

        Bob my darling is that you ? It's me, Alice !
        To prove you're the one I'm talking to, send me "the_proof_of_your_love".
        Beware, I'll only accept it if it's signed with the (private) key to my heart.
        I know that your love for me is strong enough to factor a 4096bit modulus <3

        public modulus  : 19145973648550935377079611697722671322274118139557042301776642572115274475226281351296738182875290635134813224293883389973049443515269908518704219503952148890097645403491837207177190945431275778576662907734022538613194063606420988350896073217836210777938665578959922821856160472790904337031563650016236178845172255740853588997129614508991748558846565741707595652297076869963768864488229430906362050625238195547618355568022771784515556690384017231801498369585758994892245595251043842090400753728275751497303055355451492048644058953018203147447698953709612746227358321608078111637339941239914029593642702957555080184951
        public exponent : 65537


input >> 16423975165539199710954883838287766637740299595805685657329381711287013713584703485103471670065680045100349517078754281102287050596416275890816715821631750657816282417946253962726927228771205924986723313891882999998030755005321072718264067166511599175471175120380275267114127482251454766907610039672654009713311033905576115240200422450040571457188945059423174693677307893719678698870580379961046268598323124759562856222591353826891403040275219870408720856318265840717591291941558341019396883074756973407710737499131274393030162919093284878894704087214589617733983666288005006041167204167328989997550257721388176549667

        La courbe eliptique de tes yeux fait le tour de mon kernel,
        Un math.rand de danse et de douceur,
        Auréole du temps, anneau nocturne et sûr,
        Et si je ne sais plus tout ce que j’ai vécu
        C’est que tes yeux ne m’ont pas toujours vu.

        - b'Vp13cMqrOGsMXQCycegGfHynrP38+L3ZO/V2kuK4GVo=', 1926
b'Hero{P4ul_Eluh4ck}'
Hero{P4ul_Eluh4ck}