VolgaCTF 2022 Qualifier Writeup

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

Find the flag (misc)

ブラウザのタイトルにURL表示の他に1文字が次々に表示される。文字を追うのはきついので、HTMLソースを見て、リンクされているjsを見ていく。
http://flag-title.q.2022.volgactf.ru:3000/_next/static/chunks/pageindex-f6df80d9480f611f.jsbase64文字列が含まれているので、デコードしていく。

$ echo 4oCcV2lubmluZyBpZ25pdGVzIGEgc2VsZi1jb25zY2lvdXMgYXdhcmVuZXNzIHRoYXQgb3RoZXJzIGFyZSA= | base64 -d
“Winning ignites a self-conscious awareness that others are 

$ echo d2F0Y2hpbmcuIEl04oCZcyBhIGxvdCBlYXNpZXIgdG8gbW92ZSB1bmRlciB0aGUgcmFkYXIgd2hlbiBubyBvbmU= | base64 -d
watching. It’s a lot easier to move under the radar when no one

$ echo d2F0Y2hpbmcuIEl04oCZcyBhIGxvdCBlYXNpZXIgdG8gbW92ZSB1bmRlciB0aGUgcmFkYXIgd2hlbiBubyBvbmU= | base64 -d
watching. It’s a lot easier to move under the radar when no one

$ echo YmUgcm91Z2ggYW5kIGdldCBkaXJ0eSBiZWNhdXNlIG5vIG9uZSBldmVuIGtub3dzIHlvdeKAmXJlIHRoZXJlLiA= | base64 -d
be rough and get dirty because no one even knows you’re there.

$ echo QnV0IGFzIHNvb24gYXMgeW91IHN0YXJ0IHRvIHdpbiwgYW5kIG90aGVycyBzdGFydCB0byBub3RpY2UsIHlvdeKAmXJl | base64 -d
But as soon as you start to win, and others start to notice, you’re

$ echo c3VkZGVubHkgYXdhcmUgdGhhdCB5b3XigJlyZSBiZWluZyBvYnNlcnZlZC4gWW914oCZcmUgYmVpbmcganVkZ2VkLiA= | base64 -d
suddenly aware that you’re being observed. You’re being judged.

$ echo WW91IHdvcnJ5IHRoYXQgb3RoZXJzIHdpbGwgZGlzY292ZXIgeW91ciBmbGF3cyBhbmQgd2Vha25lc3Nlcyw= | base64 -d
You worry that others will discover your flaws and weaknesses,

$ echo YW5kIHlvdSBzdGFydCBoaWRpbmcgeW91ciB0cnVlIHBlcnNvbmFsaXR5LCBzbyB5b3UgY2FuIGJlIGEgZ29vZCA= | base64 -d
and you start hiding your true personality, so you can be a good 

$ echo cm9sZSBtb2RlbCBhbmQgZ29vZCBjaXRpemVuIGFuZCBWb2xnYUNURntEWU40TTFDXzcxN0wz | base64 -d
role model and good citizen and VolgaCTF{DYN4M1C_717L3

$ echo X1IzNEM3XzQ4MzF9IGEgbGVhZGVyIHRoYXQgb3RoZXJzIGNhbiByZXNwZWN0LiBUaGVyZSBpcyBub3RoaW5n | base64 -d
_R34C7_4831} a leader that others can respect. There is nothing

                 :

フラグが現れた。

VolgaCTF{DYN4M1C_717L3_R34C7_4831}

Poem (crypto)

暗号処理の概要は以下の通り。

・generator = LCG(a, b, m, x0)
 LCGパラメータ設定(各パラメータ不明)
・cipertext = b''
・poem.txtの各行について以下を実行
 ・iv: generator.get_byte()の結果を16回結合
  ・x0 = (a * x0 + b) % m
 ・key: generator.get_byte()の結果を16回結合
  ・x0 = (a * x0 + b) % m
 ・ct_block = encrypt(key, iv, line.strip().encode())
  ・plaintext: "\x00"でパディング
  ・ctr: keyでivをAES-ECB暗号化
  ・ctr_int: ctrの数値化
  ・pt_blocks: plaintextの16バイトのブロック配列
  ・pt_blocksの各ブロックについて以下を実行
   ・block_int: ブロックの文字列の数値化
   ・ct_block_int: block_intとctr_intのXOR
   ・ct_block: ct_block_intの文字列化
   ・ctr_int = (ctr_int + 1) % (2 ** 128)
   ・ct_blockを結合した文字列を返却

機能的にLCGパラメータの各値は256未満。mを256に固定し、a, b, x0でブルートフォースし、平文と暗号文の関係を満たすものを探す。パラメータを割り出したら、それを元に暗号文を復号すると、最終行にフラグがあるはず。

#!/usr/bin/env python3
from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes
from Crypto.Util.strxor import strxor

class LCG(object):
    def __init__(self, a, b, m, x0):
        self.a = a
        self.b = b
        self.m = m
        self.x0 = x0

    def get_byte(self):
        self.x0 = (self.a * self.x0 + self.b) % self.m

        return self.x0.to_bytes(1, byteorder='big')

def encrypt(key, iv, plaintext):
    encryptor = Cipher(algorithms.AES(key), modes.ECB()).encryptor()

    ciphertext = b''
    plaintext = plaintext + b'\x00' * (16 - len(plaintext) % 16)
    ctr = encryptor.update(iv) + encryptor.finalize()
    ctr_int = int.from_bytes(ctr, byteorder='big')

    pt_blocks = []
    for i in range(0, len(plaintext), 16):
        pt_blocks.append(plaintext[i:i+16])

    for block in pt_blocks:
        block_int = int.from_bytes(block, byteorder='big')
        ct_block_int = block_int ^ ctr_int
        ct_block = ct_block_int.to_bytes(16, byteorder='big')
        ctr_int = (ctr_int + 1) % (2 ** 128)
        ciphertext += ct_block

    return ciphertext


def decrypt(key, iv, ciphertext):
    encryptor = Cipher(algorithms.AES(key), modes.ECB()).encryptor()

    plaintext = b''
    ctr = encryptor.update(iv) + encryptor.finalize()

    ct_blocks = []
    for i in range(0, len(ciphertext), 16):
        ct_blocks.append(ciphertext[i:i + 16])

    for block in ct_blocks:
        block_int = int.from_bytes(block, byteorder='big')
        ctr_int = int.from_bytes(ctr, byteorder='big')
        pt_block_int = block_int ^ ctr_int
        pt_block = pt_block_int.to_bytes(16, byteorder='big')
        ctr = ((ctr_int + 1) % (2 ** 128)).to_bytes(16, byteorder='big')
        plaintext += pt_block.rstrip(b'\x00')

    return plaintext

with open('poem.txt', 'rb') as f:
    lines = f.read().splitlines()

with open('poem.txt.enc', 'rb') as f:
    ct = f.read()

ct_size = []
for line in lines:
    ct_size.append((len(line) // 16 + 1) * 16)
ct_size[-1] = len(ct) - sum(ct_size[:-1])

pt0 = lines[0]
ct0 = ct[:ct_size[0]]

m = 256
found = False
for a in range(256):
    for b in range(256):
        for x0 in range(256):
            generator = LCG(a, b, m, x0)
            iv, key = b'', b''
            for _ in range(16):
                iv += generator.get_byte()
            for _ in range(16):
                key += generator.get_byte()
            ct_block = encrypt(key, iv, pt0)
            if ct_block == ct0:
                found = True
                print('[+] a =', a)
                print('[+] b =', b)
                print('[+] m =', m)
                print('[+] x0 =', x0)
                break
        if found:
            break
    if found:
        break

generator = LCG(a, b, m, x0)

index = 0
for size in ct_size:
    iv, key = b'', b''
    for _ in range(16):
        iv += generator.get_byte()
    for _ in range(16):
        key += generator.get_byte()
    pt_block = decrypt(key, iv, ct[index:index + size]).decode()
    print(pt_block)
    index += size

実行結果は以下の通り。

[+] a = 113
[+] b = 7
[+] m = 256
[+] x0 = 13
Over hill, over dale,
Thorough bush, thorough brier,
Over park, over pale,
Thorough flood, thorough fire!
I do wander everywhere,
Swifter than the moon's sphere;
And I serve the Fairy Queen,
To dew her orbs upon the green;
The cowslips tall her pensioners be;
In their gold coats spots you see;
Those be rubies, fairy favours;
In those freckles live their savours;
I must go seek some dewdrops here,
And hang a pearl in every cowslip's ear.
tw0_t1m3_p4d_1s_st1ll_tw0_t1m3_p4d
tw0_t1m3_p4d_1s_st1ll_tw0_t1m3_p4d