bi0sCTF 2022 Writeup

この大会は2023/1/22 0:00(JST)~2023/1/23 0:00(JST)に開催されました。
今回もチームで参戦。結果は10点で294チーム中229位でした。
参加表明の問題しか解けませんでしたが、
自分で解けた問題をWriteupとして書いておきます。

Welcome (Misc)

Discordに入り、#generalチャネルのトピックを見ると、フラグが書いてあった。

bi0sctf{w3lc0m3_t0_bi0sCTF_2022_h4pp3n1ng_1n_2023}

KnightCTF 2023 Writeup

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

KnightCTF Discord Server (Basics 25)

Discordに入り、#announcementsチャネルのメッセージを見ると、以下のように書いてある。

01001011 01000011 01010100 01000110 01111011 01110111 00110011 01001100 01100011 00110000 01001101 00110011 01011111 01010100 00110000 01011111 01001011 01101110 01101001 01100111 01101000 01110100 01000011 01010100 01000110 01111101

ASCIIコードの2進数表記と推測できるので、デコードする。

#!/usr/bin/env python3
codes= '01001011 01000011 01010100 01000110 01111011 01110111 00110011 01001100 01100011 00110000 01001101 00110011 01011111 01010100 00110000 01011111 01001011 01101110 01101001 01100111 01101000 01110100 01000011 01010100 01000110 01111101'
codes = codes.split(' ')

flag = ''
for code in codes:
    flag += chr(int(code, 2))
print(flag)
KCTF{w3Lc0M3_T0_KnightCTF}

Dirt (Misc 50)

解凍すると、ディレクトリパスが以下のようになっている。

challenge\}\s\r\3\d\l\0\f\_\3\d\1\5\n\1\_\s\r\3\d\l\0\f\{\F\T\C\K

階層の深いディレクトリ名からつなげると、フラグになる。

KCTF{f0ld3rs_1n51d3_f0ld3rs}

Logger (Misc 100)

アクセスログを日時でソートする。

                :
2023-01-03 15:46:20.382679 - Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/108.0.0.0 Safari/537.36 - GET /K - 39950
2023-01-03 15:46:20.385679 - Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/108.0.0.0 Safari/537.36 - GET /C - 6142
2023-01-03 15:46:20.388679 - Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/108.0.0.0 Safari/537.36 - GET /T - 48333
2023-01-03 15:46:20.391679 - Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/108.0.0.0 Safari/537.36 - GET /F - 16796
2023-01-03 15:46:20.394679 - Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/108.0.0.0 Safari/537.36 - GET /{ - 45424
2023-01-03 15:46:20.397679 - Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/108.0.0.0 Safari/537.36 - GET /w - 57044
2023-01-03 15:46:20.400679 - Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/108.0.0.0 Safari/537.36 - GET /r - 56376
2023-01-03 15:46:20.403679 - Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/108.0.0.0 Safari/537.36 - GET /1 - 34061
2023-01-03 15:46:20.406679 - Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/108.0.0.0 Safari/537.36 - GET /n - 18217
2023-01-03 15:46:20.409679 - Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/108.0.0.0 Safari/537.36 - GET /k - 13820
2023-01-03 15:46:20.412679 - Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/108.0.0.0 Safari/537.36 - GET /l - 3610
2023-01-03 15:46:20.415679 - Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/108.0.0.0 Safari/537.36 - GET /3 - 43593
2023-01-03 15:46:20.418679 - Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/108.0.0.0 Safari/537.36 - GET /_ - 46224
2023-01-03 15:46:20.421679 - Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/108.0.0.0 Safari/537.36 - GET /1 - 49001
2023-01-03 15:46:20.424679 - Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/108.0.0.0 Safari/537.36 - GET /n - 5135
2023-01-03 15:46:20.427679 - Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/108.0.0.0 Safari/537.36 - GET /_ - 42868
2023-01-03 15:46:20.430679 - Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/108.0.0.0 Safari/537.36 - GET /7 - 51928
2023-01-03 15:46:20.433679 - Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/108.0.0.0 Safari/537.36 - GET /1 - 42167
2023-01-03 15:46:20.436679 - Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/108.0.0.0 Safari/537.36 - GET /m - 9672
2023-01-03 15:46:20.439679 - Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/108.0.0.0 Safari/537.36 - GET /3 - 13974
2023-01-03 15:46:20.442679 - Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/108.0.0.0 Safari/537.36 - GET /} - 5898

ソート後のアクセスログの最後の方のアクセス先の"/"の後の文字を並べると、フラグになった。

KCTF{wr1nkl3_1n_71m3}

GET Me (Web/API 25)

$ curl http://167.99.8.90:9009/
{"success":false,"message":"Sorry ! You can't GET it :P"}

POSTメソッドでアクセスしてみる。

$ curl http://167.99.8.90:9009/ -X POST
{"success":false,"message":"You should send me a url !"}

urlパラメータが必要のようだ。

$ curl http://167.99.8.90:9009/ -d 'url=a'
{"success":false,"message":"Looking for flag ? Visit https:\/\/hackenproof.com\/user\/security"}

https://hackenproof.com/でアカウントを作成して、https://hackenproof.com/user/securityにアクセスすると、フラグが書かれていた。

KCTF{H4ck3nPr00f3d_bY_Kn16h75qu4d}

Hello (Networking 300)

dnsでフィルタリングし、問合せしているドメインの*.knoghtctf.comの*部分を連結するとbase64文字列になりそう。

#!/usr/bin/env python3
from scapy.all import *
from base64 import *

packets = rdpcap('find-me.pcapng')

b64 = ''
for p in packets:
    if not p.haslayer(ICMP) and p.haslayer(DNS) and p[IP].dst == '8.8.8.8':
         b64 += p[DNSQR].qname.decode()[0]

flag = b64decode(b64).decode()
print(flag)

base64デコードした結果、以下になった。

UPBL{o1_mr3a_en0_hk3_i0h}

Vigenere暗号と推測して、https://www.dcode.fr/vigenere-cipherで復号する。KCTFで始まることから、鍵はKNIGHTと推測できる。

KCTF{h1_th3n_wh0_ar3_y0u}

Go Deep! (Forensics 400)

$ binwalk sea.jpg 

DECIMAL       HEXADECIMAL     DESCRIPTION
--------------------------------------------------------------------------------
0             0x0             JPEG image data, JFIF standard 1.01
9247167       0x8D19BF        Zip archive data, at least v2.0 to extract, compressed size: 61520, uncompressed size: 61777, name: deep
9308807       0x8E0A87        End of Zip archive, footer length: 22

jpgの後ろにzipがくっついているので、切り出す。

$ foremost sea.jpg 
Processing: sea.jpg
|foundat=deep4}8���Umj�.���Z�����E[����6Q�i��ڻjT"bo!�V��ޱc���{��x�މ�}�=���9���L�,ܡy��Ջ;T�^���s�.��2R�;w�D�?���x��|~9�p��枽���;e0
<�����㥉ם;t������sg핺����vY�C�a�nӴ��%o܅�M̯���O���GB^��^Z��q྆��eIy�0J���$����e!|���&���/�Cz���i��RhvO�{+�;�
                 [���}@3��~Տ��J

                               ;��S�
                                    ���G����������SI��w�x��{�KG���Zandd��H4��ɵ�o�F��������!������̭���r����?'�����!A�1�;Y�Z������K^�\����<��97װS��4�r�-�����ڞ�������A�x/��m�rkR�[� ��&�X���J��8��
                 &��q�������`3�riA���{/P��&�k|x���s|X_��|��i�����Y�9�̥m<�h ��r�*�E��ha�H
*|

切り出したzipを解凍すると、deepファイルが展開される。deepファイルの先頭10バイトはjpgのヘッダ部になっているが、他はpng形式になっている。バイナリエディタで先頭10バイトを以下のように修正する。

修正前:ff d8 ff e0 00 10 4a 46 49 46
修正後:89 50 4e 47 0d 0a 1a 0a 00 00

pngの高さが異なるようなので、CRCが合うように高さを変える。

#!/usr/bin/env python3
import struct
import binascii

with open('deep.png', 'rb') as f:
    data = f.read()

head = data[:12]
tail = data[29:]

for h in range(265, 1024):
    height = struct.pack('>I', h)
    ihdr = data[12:20] + height + data[24:29]
    crc = struct.pack('!I', binascii.crc32(ihdr))
    if crc == data[29:33]:
        out = head + ihdr + tail
        with open('deep_fix.png', 'wb') as f:
            f.write(out)
        break

最終的に修正した画像の左下にフラグが書いてあった。

KCTF{g0_d33p_d0wn}

Factorie (Cryptography 50)

nを素因数分解するだけの問題。factordbで素因数分解する。

n = 39434538531451803895327 * 55131777675015246472249
KCTF{39434538531451803895327_55131777675015246472249}

Vaulter (Cryptography 100)

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

・byte_data = []
・file_name: 第1引数
・num: 1以上1024以下のランダム整数
・byte_data: file_nameのファイルの内容のバイナリデータ
・bdh: byte_dataの16進数表記
・out = []
・bdhの各文字eachについて以下を実行
 ・outにeachのASCIIコードとnumのXORを追加
 ・concat: outの各数値を文字にし、結合
 ・concatをoutput.encに書き込み

ブルートフォースでXORして16進数になるようにし、復号する。

#!/usr/bin/env python3
import string

def is_hexdigits(s):
    for c in s:
        if c not in string.hexdigits[:16]:
            return False
    return True

with open('vaulter-output.enc.txt', 'r') as f:
    enc = f.read()

for num in range(1, 1024):
    try:
        dec = b''
        for i in range(len(enc)):
            dec += bytes([ord(enc[i]) ^ num])
        bdh = dec.decode()
    except:
        continue

    if is_hexdigits(bdh):
        break

with open('flag.docx', 'wb') as f:
    f.write(bytes.fromhex(bdh))

復号したら、docxファイルになるので、開いてみると、一番下にフラグが書いてあった。

KCTF{w3lc0m3_70_3ncryp710n_w0rLD}

I Love Pi (Cryptography 100)

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

・lengths: 10個の未知の要素を持つ配列
・flag: 長さ39のフラグ
・s = 0
・encoded_flag = ""
・lengthsの各要素lについて以下を実行
 ・seg = flag[s:s+l]
 ・segの長さだけ以下を実行
  ・seg: segをbase64エンコード
 ・s += l
 ・encoded_flag += seg
・encoded_flagを出力

base64の切れ目を確認しながら、復号する。

#!/usr/bin/env python3
import base64

dic = {}
for i in range(1, 17):
    s = 'a' * i
    for _ in range(i):
        s = base64.b64encode(s.encode()).decode()
    dic[len(s)] = i

with open('encoded_flag.txt', 'r') as f:
    enc = f.read()

#### guessing ####
base64_lengths = [12, 4, 24, 4, 32, 144, 8, 44, 32, 12]

#### output ####
flag = b''

index = 0
for i in range(len(base64_lengths)):
    l = base64_lengths[i]
    seg = enc[index:index + l]
    for _ in range(dic[l]):
        seg = base64.b64decode(seg)
    flag += seg
    index += l

flag = flag.decode()
print(flag)
KCTF{4_P1_4_D4Y_K33P5_7H3_H4CK3r5_4W4Y}

Jamal (Cryptography 125)

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

・seed(0)
・flag: フラグ
・x: 2以上9999999以下のランダム整数
・p = get_prime(43)
 ・p = 2**42以上2**43以下のランダム整数
 ・pが素数の場合にpを返却
・g = get_prime(43)
・gとpが同じ間以下を実行
 ・g = get_prime(43)
・gがpより大きい場合
 ・g, p = p, g
・y = pow(g, x, p)
・flagの各文字chについて以下を実行
 ・k = 2以上9999以下のランダム整数
 ・c1, c2 = encrypt(ch, g, p, y, k)
 ・c1, c2をflag.encに書き込み
・pをkey.pubに書き込み

seedでランダム値はわかっているので、逆算してフラグを求める。

#!/usr/bin/env python3
from random import randint, seed

def is_prime(n):
    if n == 2 or n == 3:
        return True
    if n % 2 == 0 or n < 2:
        return False
    for i in range(3, int(n**0.5)+1, 2):
        if n % i == 0:
            return False
    return True

def get_prime(n):
    while True:
        p = randint(2**(n-1), 2**n)
        if is_prime(p):
            return p

seed(0)
x = randint(2, 9999999)
p = get_prime(43)
g = get_prime(43)

while g == p:
    g = get_prime(43)

if g > p:
    g, p = p, g

y = pow(g, x, p)

with open('jamal-key.pub', 'r') as f:
    p_file = int(f.read().split(' ')[-1])

assert p_file == p

with open('jamal-flag.enc.txt', 'r') as f:
    lines = f.read().splitlines()

flag = ''
for line in lines:
    c1 = int(line.split(',')[0])
    c2 = int(line.split(',')[1])
    k = randint(2, 9999)
    assert pow(g, k, p) == c1
    ch = (c2 * pow(pow(y, k, p), -1, p)) % p
    flag += chr(ch)
print(flag)
KCTF{h4v3_y0u_h34rd_0f_3l64m4l?}

Encode Mania (Cryptography 150)

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

・flag: フラグ(kctf{************})
・encrypted_flag = flag.encode('utf-8')
・12回以下を繰り返し実行
 ・option: 0以上3以下のランダム整数
 ・encrypted_flag = encrypt(encrypted_flag, option)
  ・optionが0の場合、base64エンコード
  ・optionが1の場合、base32エンコード
  ・optionが2の場合、base16エンコード
  ・optionが3の場合、base85エンコード
・encode_mania.txtにencrypted_flagを書き込み

1回ずつbase64、base32、base16、base85デコードを試して確認しながらデコードする。

#!/usr/bin/env python3
import base64

with open('encode_mania.txt', 'r') as f:
    flag = f.read()

print('[+]', flag)

#### 1st decryption ####
flag = base64.b32decode(flag)
print('[+]', flag.decode())

#### 2nd decryption ####
flag = base64.b16decode(flag)
print('[+]', flag.decode())

#### 3rd decryption ####
flag = base64.b64decode(flag)
print('[+]', flag.decode())

#### 4th decryption ####
flag = base64.b85decode(flag)
print('[+]', flag.decode())

#### 5th decryption ####
flag = base64.b16decode(flag)
print('[+]', flag.decode())

#### 6th decryption ####
flag = base64.b16decode(flag)
print('[+]', flag.decode())

#### 7th decryption ####
flag = base64.b32decode(flag)
print('[+]', flag.decode())

#### 8th decryption ####
flag = base64.b64decode(flag)
print('[+]', flag.decode())

#### 9th decryption ####
flag = base64.b85decode(flag)
print('[+]', flag.decode())

#### 10th decryption ####
flag = base64.b64decode(flag)
print('[+]', flag.decode())

#### 11th decryption ####
flag = base64.b16decode(flag)
print('[+]', flag.decode())

#### 12th decryption ####
flag = base64.b85decode(flag)
print('[+]', flag.decode())

実行結果は以下の通り。

[+] GUZDGMRUIQ3TENJYGMYTOMBUHA2TSMZRGM4TGOBVIE2DKNRUGZATIQRXIEZTSNRRGUZDGMRUIU3DMNRWGQ3TKMRUHA2TSNZZG4ZTERRVHE3EENRUGZATKOBTGM3TQNRZGUZDGMRUIUZTMNCCGMYTMOBUHA2TSMZRGM4TGOBVIE2DKNRUGZATIQRTGEZTSNRRGUZDGMRUIU3DONCEGQ3DOMBUHA2TSNZZG42DMNRVG42TKNRUGZATIQRXIEZTSNSBGUZDGMRUIQ3TENJQGMYTOMBUHA2TSMZRGM4TGOBVHEZTANRUGZATMNJWHE3TINJZGUZDGMRUIU3DMNRWGQ3TKNRUHA2TSNZZG4ZTERRVIE2DKNRUGZATKOBTGM3TQNSCGUZDGMRUIUZTMNCCGMYTOMBUHA2TSMZRGM4TGOBVIE2DKNRUGZATIQRXIEZTSNJZGUZDGMRUIU3DMNRWGQ3TKMRUHA2TSMZRGM4TGOBVHE2TKNRUGZATIQRXIEZTSNRRGUZDGMRUIQ3TENJYGMYTMQZUHA2TSNZZG42DMNRVG42TKNRUGZATIQRTGEZTSNRZGUZDGMRUIQ3TENJQGMZDKNRUHA2TSMZRGM4TGOBVHEZTANRUGZATKOBTGM3TQNSBGUZDGMRUIU3DMNRWGQ3DOMBUHA2TSNZZG42DMNRVHE2TKNRUGZATIQRTGEZTSNRRGUZDGMRUIU3DMNRWGQ3TKMRUHA2TSNZZG4ZTERRVG43EENRUGZATKOBTGM3TQNSDGUZDGMRUIU3DMNRWGQ3DMQZUHA2TSMZRGM4TGOBVHE2TKNRUGZATKOBTGM3TQNRYGUZDGMRUIU3DMNRWGQ3TMNBUHA2TSNZZG42DMNRVHEZTANRUGZATIQRXIEZTSNSBGUZDGMRUIQ3TENJYGMZDIRJUHA2TSNZZG4ZTERRVHEZTANRUGZATIQRXIEZTSNRRGUZDGMRUIU3DMNRWGQ3TIRJUHA2TSMZTGZDDOMRVG42DKNRUGZATKOBTGM3TQNSDGUZDGMRUIQ3TENJQGMZDKMRUHA2TSMZRGM4TGOBVG43EENRUGZATKOBTGM3TQNSDGUZDGMRUIQ3TENJQGMZDIRJUHA2TSNZZG4ZTERRVG42DKNRUGZATMNJWHE3TINKBGUZDGMRUIQ3TENJQGMYTOMBUHA2TSNZZG4ZTERRVIE2DKNRUGZATIQRTGEZTSNSBGUZDGMRUIU3DMNRWGQ3TKMRUHA2TSNZZG42DMNRVHEZTANRUGZATIQRXIEZTSNSCGUZDGMRUIU3DMNRWGQ3TINRUHA2TSMZTGZDDOMRVG42DKNRUGZATIQRXIEZTSNRYGUZDGMRUIU3DMNRWGQ3DMOBUHA2TSNZZG42DMNRVG43EENRUGZATKOBTGM3TQNSCGUZDGMRUIQ3TENJQGMYTOMBUHA2TSMZRGM4TGOBVIE2DKNRUGZATIQRXIEZTSNRZGUZDGMRUIU3DMNRWGQ3DOMBUHA2TSMZRGM4TGOBVHE2TKNRUGZATKOBTGM3TQNRZGUZDGMRUIQ3TENJQGMZDKMRUHA2TSNZZG4ZTERRVHEZTANRUGZATIQRTGEZTSNKBGUZDGMRUIQ3TENJQGMZDIRJUHA2TSNZZG4ZTERRVG43EENRUGZATKOBTGM3TQNSBGUZDGMRUIU3DMNRWGQ3TINRUHA2TSMZRGM4TGOBVHEZTANRUGZATIQRXIEZTSNSCGUZDGMRUIQ3TENJQGMZDIRJUHA2TSNZZG42DMNRVHEZTANRUGZATIQRXIEZTSNRZGUZDGMRUIU3DMNRWGQ3TKNRUHA2TSMZRGM4TGOBVIE2DKNRUGZATKOBTGM3TQNRYGUZDGMRUIQ3TENJQGMYTOMBUHA2TSNZZG42DMNRVG42TKNRUGZATIQRXIEZTSNSBGUZDGMRUIQ3TENJYGMYTMQZUHA2TSMZRGM4TGOBVIE2DKNRUGZATKOBTGM3TQNKBGUZDGMRUIU3DMNRWGQ3TIQJUHA2TSMZRGM4TGOBVG43EENRUGZATKOBTGM3TQNSCGUZDGMRUIQ3TENJYGMYTOMBUHA2TSMZRGM4TGOBVIE2DKNRUGZATIQRXIEZTSNRYGUZDGMRUIU3DMNRWGQ3TKNRUHA2TSNZZG4ZTERRVIE3EENRUGZATKOBTGM3TQNRYGUZDGMRUIQ3TENJQGMYTOMBUHA2TSMZRGM4TGOBVHE3EENRUGZATIQRXIEZTSNSEGUZDGMRUIU3DONCEGQ3DOMBUHA2TSNZZG4ZTERRVHE2TKNRUGZATIQRXIEZTSNSBGUZDGMRUIQ3TENJQGMYTOMBUHA2TSMZRGM4TGOBVHEZTANRUGZATKOBTGM3TQNRYGUZDGMRUIU3DMNRWGQ3TIRJUHA2TSNZZG4ZTERRVIE2DKNRUGZATIQRXIEZTSNSBGUZDGMRUIQ3TENJYGMZDINRUHA2TSMZRGM4TGOBVIE2TKNRUGZATIQRXIEZTSNRRGUZDGMRUIQ3TENJYGMZDIQJUHA2TSNZZG42DMNRVG43EENRUGZATIQRXIEZTSNSCGUZDGMRUIQ3TENJQGMZDKQJUHA2TSMZTGZDDOMRVHE2TKNRUGZATIQRXIEZTSNSEGUZDGMRUIQ3TENJQGMZDIRJUHA2TSMZRGM4TGOBVG42TKNRUGZATIQRTGEZTSNSBGUZDGMRUIQ3TENJQGMYTMQZUHA2TSMZRGM4TGOBVHEZTANRUGZATIQRTGEZTSNRRGUZDGMRUIU3DMNRWGQ3TKMRUHA2TSNZZG4ZTERRVG43EENRUGZATKOBTGM3TQNSCGUZDGMRUIQ3TENJQGMZDIQJUHA2TSMZRGM4TGOBVG43EENRUGZATKOBTGM3TQNRYGUZDGMRUIU3DMNRWGQ3DMQZUHA2TSNZZG42DMNRVHEZTANRUGZATKOBTGM3TQNRRGUZDGMRUIU3DMNRWGQ3TKNRUHA2TSNZZG4ZTERRVHEZTANRUGZATIQRXIEZTSNRRGUZDGMRUIU3DMNRWGQ3TKNRUHA2TSMZRGM4TGOBVHE2TKNRUGZATKOBTGM3TQNSEGUZDGMRUIQ3TENJYGMZDINRUHA2TSNZZG42DMNRVG42TKNRUGZATIQRXIEZTSNRZGUZDGMRUIU3DMNRWGQ3TKMRUHA2TSNZZG42DMNRVG43EENRUGZATIQRTGEZTSNRZGUZDGMRUIQ3TENJYGMYTOMBUHA2TSNZZG4ZTERRVG43EENRUGZATIQRTGEZTSNKBGUZDGMRUIQ3TENJQGMZDIRJUHA2TSNZZG42DMNRVG43EENRUGZATKOBTGM3TQNSDGUZDGMRUIU3DMNRWGQ3TIRJUHA2TSMZTGZDDOMRVG43EENRUGZATMNJWHE3TINKBGUZDGMRUIQ3TENJYGMZDINRUHA2TSNZZG42DMNRVG43EENRUGZATKOBTGM3TQNSCGUZDGMRUIQ3TENJQGMZDINRUHA2TSMZRGM4TGOBVIE2DKNRUGZATIQRTGEZTSNRZGUZDGMRUIUZTMNCCGMYTMQZUHA2TSNZZG4ZTERRVG43EENRUGZATKOBTGM3TQNSFGUZDGMRUIQ3TENJYGMZDIRJUHA2TSNZZG42DMNRVHEZTANRUGZATKOBTGM3TQNSBGUZDGMRUIQ3TENJQGMZDIRJUHA2TSNZZG4ZTERRVG42DKNRUGZATKOBTGM3TQNSBGUZDGMRUIQ3TENJQGMYTMQZUHA2TSMZRGM4TGOBVHE3EENRUGZATIQRXIEZTSNSEGUZDGMRUIQ3TENJYGMYTMQZUHA2TSMZRGM4TGOBVHE3EENRUGZATKOBTGM3TQNSDGUZDGMRUIQ3TENJYGMYTOMBUHA2TSNZZG42DMNRVHE3EENRUGZATIQRTGEZTSNRRGUZDGMRUIQ3TENJQGMYTOMBUHA2TSNZZG42DMNRVG42TKNRUGZATIQRTGEZTSNKBGUZDGMRUIQ3TENJYGMZDIQJUHA2TSNZZG4ZTERRVIE2DKNRUGZATMNJWHE3TINJZGUZDGMRUIUZTMNCCGMYTMQZUHA2TSMZTGZDDOMRVG42TKNRUGZATMNJWHE3TINJZGUZDGMRUIQ3TENJYGMYTOMBUHA2TSMZRGM4TGOBVIE2DKNRUGZATIQRXIEZTSNRRGUZDGMRUIU3DMNRWGQ3TKMRUHA2TSNZZG4ZTERRVHE3EENRUGZATKOBTGM3TQNRRGUZDGMRUIU3DMNRWGQ3TINRUHA2TSMZRGM4TGOBVG42DKNRUGZATIQRXIEZTSNSEGUZDGMRUIU3DONCEGQ3DOMBUHA2TSNZZG4ZTERRVG42TKNRUGZATIQRXIEZTSNSBGUZDGMRUIQ3TENJQGMYTMOBUHA2TSMZRGM4TGOBVHEZTANRUGZATIQRXIEZTSNKBGUZDGMRUIU3DMNRWGQ3TIQJUHA2TSNZZG4ZTERRVIE3EENRUGZATIQRTGEZTSNKBGUZDGMRUIUZTMNCCGMYTMOBUHA2TSNZZG4ZTERRVHE3EENRUGZATMNJWHE3TINRRGUZDGMRUIU3DMNRWGQ3DMQZUHA2TSNZZG42DMNRVG42TKNRUGZATMNJWHE3TINRYGUZDGMRUIQ3TENJQGMZDKNRUHA2TSMZRGM4TGOBVHEZTANRUGZATMNJWIE3TQNRYGUZDGMRUIUZTMNJQGQ3TINRUHA2TSMZTGZDDGOBVHE2TKNRUGZATMNJWIE3TQNRYGUZDGMRUIUZTMNJQGQ3TINRUHA2TSMZTGZDDGOBVHE2TCM2EGNCA====
[+] 52324D7258317048593139385A45646A4B7A396152324E66664752485979732F596B646A5833786952324E364B316848593139385A45646A4B31396152324E674D467048597974665755646A4B7A396A52324D7250317048593139385930646A6569745952324E66664756485979732F5A45646A5833786B52324E364B317048593139385A45646A4B7A395952324E6666475248593139385955646A4B7A396152324D7258316C48597974665755646A4B31396952324D7250325648593139385930646A5833786A52324E6666467048597974665955646A4B31396152324E66664752485979732F576B646A5833786C52324E6666466C48593139385955646A5833786852324E6666476448597974665930646A4B7A396A52324D7258324E485979732F5930646A4B7A396152324E6666474E4859336F725745646A5833786C52324D725032524859313938576B646A5833786C52324D7250324E485979732F5745646A6569745A52324D72503170485979732F5A45646A4B31396A52324E6666475248597974665930646A4B7A396B52324E666647464859336F725745646A4B7A396852324E666646684859797466576B646A5833786B52324D7250317048593139385A45646A4B7A396952324E6666467048593139385955646A5833786952324D72503252485979732F5930646A4B31395A52324D7250324E485979732F576B646A5833786A52324E6666474648593139385930646A4B7A396B52324D7250324E48597974665930646A4B7A396952324E6666475648593139385A45646A5833786852324D7250317048597974665755646A4B7A396A52324D7258316C48593139385A45646A5833785A52324E6666474A4859313938576B646A5833786B52324D7258317048593139385A45646A4B7A396852324E66664756485979732F5A6B646A5833786852324D725031704859313938596B646A4B7A396D52324E674D4670485979732F5955646A4B7A396A52324D7250317048593139385930646A5833786852324E6666474E485979732F5A45646A4B7A396A52324D7258324648593139385A55646A4B7A396152324D7258324A4859797466576B646A4B7A396B52324D7250325A4859336F725955646A4B7A396D52324D7250324E48593139385755646A4B31396A52324D7250316C48593139385930646A4B31396152324E66664752485979732F576B646A5833786B52324D7250324A4859313938576B646A5833786852324E6666466C48597974665930646A5833786152324E66664756485979732F5930646A4B7A396152324E6666475648593139385955646A5833786D52324D7258324648597974665755646A4B7A396952324E666647524859797466576B646A4B31396952324D72583170485979732F576B646A4B31395A52324D7250324E4859797466576B646A5833786C52324E6666474E4859336F72576B646A6569745A52324D725832464859797466576B646A5833786B52324D7250324648593139385A45646A4B31396952324E364B316C485979732F576B646A5833786E52324D7258324E48597974665930646A5833786A52324D7250324E485979732F5745646A5833786A52324D7250316C4859313938596B646A4B7A396D52324D7258316C4859313938596B646A5833786C52324D725831704859797466596B646A4B31396152324D7250317048597974665755646A4B31395A52324D7258324A485979732F5A45646A6569745952324E364B316C4859336F725755646A6569745952324D7258317048593139385A45646A4B7A396152324E66664752485979732F596B646A5833786152324E6666474648593139385745646A4B7A396D52324E674D4670485979732F5755646A4B7A396A52324D7250316848593139385930646A4B7A395A52324E6666474A485979732F5A6B646A4B31395A52324E364B3168485979732F596B646A6569746152324E6666466C48597974665755646A6569746852324D7250325648593139385930646A656A786852324E365047464859336F385955646A656A786852324E365047464859336F3859513D3D
[+] R2MrX1pHY198ZEdjKz9aR2NffGRHYys/YkdjX3xiR2N6K1hHY198ZEdjK19aR2NgMFpHYytfWUdjKz9jR2MrP1pHY198Y0djeitYR2NffGVHYys/ZEdjX3xkR2N6K1pHY198ZEdjKz9YR2NffGRHY198YUdjKz9aR2MrX1lHYytfWUdjK19iR2MrP2VHY198Y0djX3xjR2NffFpHYytfYUdjK19aR2NffGRHYys/WkdjX3xlR2NffFlHY198YUdjX3xhR2NffGdHYytfY0djKz9jR2MrX2NHYys/Y0djKz9aR2NffGNHY3orWEdjX3xlR2MrP2RHY198WkdjX3xlR2MrP2NHYys/WEdjeitZR2MrP1pHYys/ZEdjK19jR2NffGRHYytfY0djKz9kR2NffGFHY3orWEdjKz9hR2NffFhHYytfWkdjX3xkR2MrP1pHY198ZEdjKz9iR2NffFpHY198YUdjX3xiR2MrP2RHYys/Y0djK19ZR2MrP2NHYys/WkdjX3xjR2NffGFHY198Y0djKz9kR2MrP2NHYytfY0djKz9iR2NffGVHY198ZEdjX3xhR2MrP1pHYytfWUdjKz9jR2MrX1lHY198ZEdjX3xZR2NffGJHY198WkdjX3xkR2MrX1pHY198ZEdjKz9hR2NffGVHYys/ZkdjX3xhR2MrP1pHY198YkdjKz9mR2NgMFpHYys/YUdjKz9jR2MrP1pHY198Y0djX3xhR2NffGNHYys/ZEdjKz9jR2MrX2FHY198ZUdjKz9aR2MrX2JHYytfWkdjKz9kR2MrP2ZHY3orYUdjKz9mR2MrP2NHY198WUdjK19jR2MrP1lHY198Y0djK19aR2NffGRHYys/WkdjX3xkR2MrP2JHY198WkdjX3xhR2NffFlHYytfY0djX3xaR2NffGVHYys/Y0djKz9aR2NffGVHY198YUdjX3xmR2MrX2FHYytfWUdjKz9iR2NffGRHYytfWkdjK19iR2MrX1pHYys/WkdjK19ZR2MrP2NHYytfWkdjX3xlR2NffGNHY3orWkdjeitZR2MrX2FHYytfWkdjX3xkR2MrP2FHY198ZEdjK19iR2N6K1lHYys/WkdjX3xnR2MrX2NHYytfY0djX3xjR2MrP2NHYys/WEdjX3xjR2MrP1lHY198YkdjKz9mR2MrX1lHY198YkdjX3xlR2MrX1pHYytfYkdjK19aR2MrP1pHYytfWUdjK19ZR2MrX2JHYys/ZEdjeitYR2N6K1lHY3orWUdjeitYR2MrX1pHY198ZEdjKz9aR2NffGRHYys/YkdjX3xaR2NffGFHY198WEdjKz9mR2NgMFpHYys/WUdjKz9jR2MrP1hHY198Y0djKz9ZR2NffGJHYys/ZkdjK19ZR2N6K1hHYys/YkdjeitaR2NffFlHYytfWUdjeithR2MrP2VHY198Y0djejxhR2N6PGFHY3o8YUdjejxhR2N6PGFHY3o8YQ==
[+] Gc+_ZGc_|dGc+?ZGc_|dGc+?bGc_|bGcz+XGc_|dGc+_ZGc`0ZGc+_YGc+?cGc+?ZGc_|cGcz+XGc_|eGc+?dGc_|dGcz+ZGc_|dGc+?XGc_|dGc_|aGc+?ZGc+_YGc+_YGc+_bGc+?eGc_|cGc_|cGc_|ZGc+_aGc+_ZGc_|dGc+?ZGc_|eGc_|YGc_|aGc_|aGc_|gGc+_cGc+?cGc+_cGc+?cGc+?ZGc_|cGcz+XGc_|eGc+?dGc_|ZGc_|eGc+?cGc+?XGcz+YGc+?ZGc+?dGc+_cGc_|dGc+_cGc+?dGc_|aGcz+XGc+?aGc_|XGc+_ZGc_|dGc+?ZGc_|dGc+?bGc_|ZGc_|aGc_|bGc+?dGc+?cGc+_YGc+?cGc+?ZGc_|cGc_|aGc_|cGc+?dGc+?cGc+_cGc+?bGc_|eGc_|dGc_|aGc+?ZGc+_YGc+?cGc+_YGc_|dGc_|YGc_|bGc_|ZGc_|dGc+_ZGc_|dGc+?aGc_|eGc+?fGc_|aGc+?ZGc_|bGc+?fGc`0ZGc+?aGc+?cGc+?ZGc_|cGc_|aGc_|cGc+?dGc+?cGc+_aGc_|eGc+?ZGc+_bGc+_ZGc+?dGc+?fGcz+aGc+?fGc+?cGc_|YGc+_cGc+?YGc_|cGc+_ZGc_|dGc+?ZGc_|dGc+?bGc_|ZGc_|aGc_|YGc+_cGc_|ZGc_|eGc+?cGc+?ZGc_|eGc_|aGc_|fGc+_aGc+_YGc+?bGc_|dGc+_ZGc+_bGc+_ZGc+?ZGc+_YGc+?cGc+_ZGc_|eGc_|cGcz+ZGcz+YGc+_aGc+_ZGc_|dGc+?aGc_|dGc+_bGcz+YGc+?ZGc_|gGc+_cGc+_cGc_|cGc+?cGc+?XGc_|cGc+?YGc_|bGc+?fGc+_YGc_|bGc_|eGc+_ZGc+_bGc+_ZGc+?ZGc+_YGc+_YGc+_bGc+?dGcz+XGcz+YGcz+YGcz+XGc+_ZGc_|dGc+?ZGc_|dGc+?bGc_|ZGc_|aGc_|XGc+?fGc`0ZGc+?YGc+?cGc+?XGc_|cGc+?YGc_|bGc+?fGc+_YGcz+XGc+?bGcz+ZGc_|YGc+_YGcz+aGc+?eGc_|cGcz<aGcz<aGcz<aGcz<aGcz<aGcz<a
[+] 34423536343335363435353433323536344235413441343634333535333235373437353633343536343135363533343334413441344434383535353535323443344235363433353735313533353335393445343634453436343335353332353734373532353734363431333334333437344535363445343735333332343435303442353634333536343535323533353434373436344134363433353535333535343734363445343535373536353334333441343634413536353135343532353634423536343435373439353334333534343935413434343634333535353335353437343634433537343334443442343734393335343934363531344534323535344235363433353634353532353335313445353235373436343335373533353834433441343535363442344434423433344134363442353735353334333334433442353634343536344433333433353934453445353534363431353534323534343934413534353734423444344234333441344134443437333233333333333234423536343335363435353235333530343935413432343634313535343235343439344133323435333435313441333534383535334433443344334433443344
[+] 4B564356455432564B5A4A464355325747563456415653434A4A4D485555524C4B564357515353594E464E464355325747525746413343474E564E47533244504B5643564552535447464A464355535547464E45575653434A464A56515452564B56445749534354495A44464355535547464C57434D4B4749354946514E42554B564356455253514E525746435753584C4A45564B4D4B434A464B575534334C4B5644564D3343594E4E554641554254494A54574B4D4B434A4A4D47323333324B56435645525350495A424641554254494A324534514A3548553D3D3D3D3D3D
[+] KVCVET2VKZJFCU2WGV4VAVSCJJMHUURLKVCWQSSYNFNFCU2WGRWFA3CGNVNGS2DPKVCVERSTGFJFCUSUGFNEWVSCJFJVQTRVKVDWISCTIZDFCUSUGFLWCMKGI5IFQNBUKVCVERSQNRWFCWSXLJEVKMKCJFKWU43LKVDVM3CYNNUFAUBTIJTWKMKCJJMG2332KVCVERSPIZBFAUBTIJ2E4QJ5HU======
[+] UEROUVRQSV5yPVBJXzR+UEhJXiZQSV4lPlFmZihoUERFS1RQRT1ZKVBISXN5UGdHSFFQRT1Wa1FGPX44UERFPllQZWZIU1BIUjskUGVlXkhPP3Bge1BJXmozUERFOFBPP3BtNA==
[+] PDNQTPI^r=PI_4~PHI^&PI^%>Qff(hPDEKTPE=Y)PHIsyPgGHQPE=VkQF=~8PDE>YPefHSPHR;$Pee^HO?p`{PI^j3PDE8PO?pm4
[+] NEY0NzM4NzY2NjY0NzQ1RjIzNDY1NTZGNjQ2OTQzNTY2QzNCNDkyODU3NkU1ODM0MzY0NzJENDU1MzNC
[+] 4F4738766664745F2346556F646943566C3B4928576E583436472D45533B
[+] OG8vfdt_#FUodiCVl;I(WnX46G-ES;
[+] KCTF{dfs_0r_b4u7e_f04c3}
KCTF{dfs_0r_b4u7e_f04c3}

Toddler RSA (Cryptography 300)

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

・flag1: フラグの前半
・flag2: フラグの後半
・p, q: 512ビット素数
・n = p * q
・e = 0x10001
・phi = (p - 1) * (q - 1) 
・m = flag1を数値化したもの
・ct = pow(m, e, n)
・primes = [p, q]
・98回以下繰り返し
 ・primesに512ビット素数を追加
・primesをシャッフル
・primes2 = []
・以下繰り返し
 ・primes2の長さが16の場合、繰り返し終了
 ・p: 128ビット素数
 ・pがprimes2になければ、primes2にpを追加
・以下繰り返し
 ・n2 = 1
 ・primes2の各数値pについて以下を実行
  ・r: 0以上1以下のランダム整数
  ・rが1の場合、n2 *= p
 ・n2のビット長が1024より大きい場合、繰り返し終了
・m2 = flag2を数値化したもの
・ct2 = pow(m2, e, n2)
・infos.txtに以下の形式で書き込み
 [ct]
 [ct2]
 [primes[0]] [primes[1]] [primes[2]] ...
 [primes2[0]] [primes2[1]] [primes2[2]] ...

flag1の暗号化に使われているp, qは不明なので、primes1の素数のペアをブルートフォースして復号する。
flag2の暗号化に使われている素数はその積が1024bitを超えたときのものなので、それを使ってブルートフォースして復号する。

#!/usr/bin/env python3
from Crypto.Util.number import *
import itertools

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

e = 0x10001

with open('infos.txt', 'r') as f:
    params = f.read().splitlines()

ct = int(params[0])
ct2 = int(params[1])
primes = [int(p) for p in params[2].split(' ')[:-1]]
primes2 = [int(p) for p in params[3].split(' ')[:-1]]

for pq in itertools.combinations(primes, 2):
    p = pq[0]
    q = pq[1]
    phi = (p - 1) * (q - 1)
    d = inverse(e, phi)
    m = pow(ct, d, p * q)
    flag1 = long_to_bytes(m)
    if flag1.startswith(b'KCTF{'):
        break

found = False
for i in range(1, 17):
    for ps in itertools.combinations(primes2, i):
        n = 1
        for p in ps:
            n *= p
        if n.bit_length() > 1024:
            phi = 1
            for p in ps:
                phi *= p - 1
            d = inverse(e, phi)
            m2 = pow(ct2, d, n)
            flag2 = long_to_bytes(m2)
            if flag2.endswith(b'}') and is_printable(flag2):
                found = True
                break
    if found:
        break

flag = (flag1 + flag2).decode()
print(flag)
KCTF{rsa_and_only_rsa_ftw}

idekCTF 2022* Writeup

この大会は2023/1/13 9:00(JST)~2023/1/15 9:00(JST)に開催されました。
今回もチームで参戦。結果は133点で846チーム中325位でした。
参加表明の問題とアンケートの問題しか解けませんでしたが、
自分で解けた問題をWriteupとして書いておきます。

Sanity Check (Sanity)

問題にフラグが書いてあった。

idek{https://discord.gg/UDvQyHF42a}

Feedback survey (Sanity)

アンケートに答えたら、フラグが表示された。

idek{We_hope_you_enjoyed_idek2022*!}

Ugra CTF Quals 2023 Writeup

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

warmup (MISC 10)

ルールのページにフラグが書いてあるような問題文。
https://2023.ugractf.ru/qualsにフラグのサンプルが書いてあった。

ugra_ex4mpl3

depth (PPC 150)

サブディレクトリを含め、全探索し、フラグを探す。

#!/usr/bin/env python3
import requests
import re

def get_urls(url):
    pattern = '<A HREF=(\w+/)>'
    r = requests.get(url)
    paths = re.findall(pattern, r.text)
    if len(paths) == 0 and 'ugra_' in r.text:
        print('[+] url:', url)
        print('[*] content:', r.text)
    else:
        for path in paths:
            get_urls(url + path)

url = 'https://depth.q.2023.ugractf.ru/08qqtqnzzi4k1den/'

get_urls(url)

実行結果は以下の通り。

[+] url: https://depth.q.2023.ugractf.ru/08qqtqnzzi4k1den/sapphire_vacuum/threatening_mermaid/wild_mare/hunting_cartridge/coral_fairy/covert_wrench/jade_battery/untouchable_hail/hunting_transistor/onyx_chef/red_captain/unexpected_keyboard/amber_zebra/yellow_dragon/sapphire_lobster/covert_trumpet/tarnished_drum/unexpected_display/unknown_pegasus/wild_python/inconceivable_trumpet/rowdy_saxophone/warring_gelding/inconceivable_griffin/rowdy_pegasus/draconic_lion/uncanny_rhythm/urban_trumpet/draconic_pilot/killer_general/killer_general/tarnished_deer/ivory_grizzly/obsidian_cartridge/red_memory/rowdy_screwdriver/wild_battery/searching_warning/bad_horn/killer_screwdriver/sapphire_guitar/pearl_trombone/mountain_griffin/spinning_pilot/pearl_wrench/wireless_captain/draconic_clarinet/obsidian_viper/decisive_compressor/deadly_cartridge/flying_inspector/opal_cyborg/agate_beat/mountain_ink/searching_fairy/obsidian_harp/untouchable_orca/jade_drill/spinning_octopus/jet_presence/unnecessary_gelding/waning_panther/rowdy_door/unnecessary_cello/warring_general/untouchable_banjo/ivory_sidewinder/threatening_orca/green_camera/ruby_python/urban_stag/destroyed_general/amber_piccolo/unexpected_nomad/tundra_mixer/green_lightning/unknown_case/dangerous_nomad/agate_zebra/covert_display/unexpected_mixer/killer_viper/onyx_cheetah/bad_flute/field_wildebeest/urban_gelding/onyx_mixer/tundra_memory/diamond_projector/emerald_compressor/scheming_unicorn/warring_foal/insane_robot/space_wildcat/spinning_cobra/ruby_sound/blue_hail/deadly_welder/bone_commander/desert_ink/insane_trombone/waning_major/desert_jackal/tarnished_general/inconceivable_falcon/opal_beat/scheming_grizzly/ruby_flute/scheming_cougar/unknown_mill/nacre_piranha/red_griffin/beryl_android/orbiting_motherboard/warring_tiger/emerald_violin/desert_gelding/tarnished_elk/falling_mermaid/inconceivable_octopus/pearl_sidewinder/obsidian_android/orange_deer/insane_violin/jet_banjo/covert_admiral/hunting_mare/threatening_boa/hidden_elk/blue_cougar/unexpected_chain/unnecessary_mainframe/hunting_sander/nacre_rain/diamond_troll/deadly_general/ruby_drill/wireless_boa/flying_piano/rowdy_foal/agate_zebra/covert_cornet/tundra_viper/unexpected_transistor/wild_display/bone_warning/scheming_display/warring_projector/tarnished_saxophone/obsidian_cello/ruby_orca/unknown_mare/urban_deer/stalking_tape/warring_sound/violet_cyborg/glass_major/orange_admiral/destroyed_memory/explosive_colt/jet_sun/orbiting_camera/scheming_cartridge/deadly_sidewinder/decisive_wrench/explosive_thunder/stalking_commander/ruby_piranha/agate_mixer/insane_piccolo/killer_piccolo/waning_mainframe/diamond_fairy/insane_stag/decisive_piano/opal_mill/draconic_display/scheming_yearling/spinning_python/inconceivable_stag/tarnished_stag/draconic_fairy/sapphire_nomad/sapphire_chef/destroyed_rhythm/unexpected_tape/desert_storm/insane_griffin/sapphire_violin/jet_device/unexpected_screwdriver/opal_horn/inconceivable_drum/ivory_yearling/unknown_guitar/destroyed_dragon/spinning_snow/field_sloth/obsidian_motherboard/chasing_koala/flying_troll/orange_weapon/violet_cleric/amber_troll/unnecessary_wrench/hidden_yearling/inconceivable_mermaid/hidden_fairy/green_pilot/obsidian_pegasus/pearl_troll/tarnished_flute/tundra_cougar/glass_device/urban_mill/tarnished_compressor/opal_door/amber_flute/ruby_packet/desert_player/ivory_lathe/draconic_mixer/onyx_player/unexpected_door/decisive_cottonmouth/urban_motherboard/obsidian_player/falling_projector/killer_snow/stalking_door/tundra_pony/wild_troll/nacre_zebra/unexpected_lightning/threatening_sander/wild_clarinet/hunting_cleric/opal_cyborg/threatening_leopard/jet_commander/sapphire_compressor/jade_drought/decisive_rhythm/nacre_falcon/hunting_piano/ivory_clarinet/agate_fairy/ivory_sloth/unexpected_pilot/waning_dragon/red_rhythm/scheming_chef/onyx_warning/destroyed_transistor/obsidian_welder/wireless_unicorn/violet_sound/stalking_koala/jade_welder/ruby_lobster/opal_camera/draconic_inspector/insane_keyboard/desert_filly/sapphire_rhythm/pearl_hammer/opal_camera/spinning_tape/chasing_rhythm/stalking_song/ruby_welder/searching_chef/blue_lion/tarnished_rain/unexpected_gazelle/opal_leopard/bone_song/draconic_display/killer_panther/sapphire_mixer/destroyed_trumpet/unknown_mare/sapphire_horse/diamond_ink/diamond_lion/threatening_drill/decisive_zebra/hidden_unicorn/hidden_leopard/spinning_guitar/amber_horn/hunting_battery/emerald_weapon/unknown_weapon/revealing_captain/opal_storm/wild_saxophone/killer_major/decisive_leopard/red_unicorn/killer_pegasus/killer_trombone/obsidian_storm/coral_chef/nacre_leopard/hunting_drum/obsidian_lobster/draconic_griffin/pearl_lion/jet_deer/warring_sound/threatening_router/spinning_sloth/unexpected_cobra/bone_general/glass_router/threatening_horn/spinning_troll/space_elk/bone_drought/tarnished_mask/threatening_inspector/decisive_sun/stalking_cobra/wild_lightning/explosive_orca/agate_door/tarnished_cup/inconceivable_cougar/wireless_welder/agate_commander/inconceivable_lion/stalking_filly/glass_mainframe/covert_sidewinder/waning_song/killer_drill/obsidian_beat/sapphire_commander/violet_griffin/violet_trumpet/glass_cartridge/urban_welder/yellow_stag/beryl_display/falling_stallion/stalking_wildebeest/bad_captain/beryl_drought/obsidian_yearling/urban_yearling/sapphire_trumpet/orange_drought/rowdy_foal/inconceivable_cougar/hidden_beat/agate_fairy/hidden_sidewinder/hidden_packet/urban_drum/violet_griffin/red_storm/decisive_leopard/unexpected_orca/decisive_stallion/unknown_mermaid/stalking_organ/inconceivable_cottonmouth/ivory_python/flying_packet/covert_disk/red_python/desert_mill/beryl_guitar/emerald_cornet/chasing_drizzle/bad_piccolo/jet_cougar/hunting_cobra/onyx_device/destroyed_stag/hidden_foal/hidden_commander/dangerous_piano/yellow_horn/bone_troll/bad_falcon/wireless_commander/unknown_colt/dangerous_hail/jade_viper/obsidian_horse/warring_drought/inconceivable_disk/draconic_drum/threatening_tape/space_tiger/waning_cornet/orbiting_cottonmouth/dangerous_griffin/inconceivable_troll/wild_mermaid/wireless_unicorn/searching_clarinet/rowdy_horse/ivory_cello/unnecessary_storm/rowdy_beat/insane_hail/revealing_transistor/orange_tiger/stalking_beat/draconic_camera/coral_battery/unnecessary_packet/diamond_moose/violet_weapon/unnecessary_commander/decisive_android/chasing_general/wireless_mainframe/ivory_player/beryl_lobster/covert_lobster/draconic_rhythm/covert_chef/threatening_drum/bone_router/hunting_griffin/ruby_major/ruby_grizzly/jet_tape/agate_leopard/opal_violin/ivory_compressor/falling_transistor/insane_cyborg/unnecessary_lobster/agate_dragon/hidden_harp/pearl_viper/obsidian_violin/wild_jackal/jade_deer/threatening_cartridge/searching_device/jade_foal/space_nomad/destroyed_panther/flying_cobra/beryl_pony/unnecessary_banjo/covert_cyborg/waning_door/ruby_panther/pearl_saxophone/wireless_presence/tundra_wildebeest/opal_stallion/revealing_boa/decisive_cello/untouchable_grizzly/hunting_cello/urban_harp/unexpected_saxophone/chasing_moose/dangerous_pegasus/bad_foal/searching_welder/green_song/inconceivable_door/explosive_cartridge/wild_sander/sapphire_pony/threatening_router/emerald_filly/threatening_hail/onyx_guitar/inconceivable_pegasus/wild_python/insane_commander/warring_camera/jet_major/mountain_cyborg/searching_tuba/mountain_cottonmouth/bone_disk/covert_projector/revealing_cornet/waning_motherboard/agate_stallion/scheming_python/orange_general/tarnished_drill/searching_stallion/draconic_weapon/unnecessary_cartridge/searching_yeti/bone_keyboard/wild_guitar/
[*] content: ugra_i_have_always_imagined_that_paradise_will_be_a_kind_of_library_bjhlbqomhtc8
ugra_i_have_always_imagined_that_paradise_will_be_a_kind_of_library_bjhlbqomhtc8

safestr (PWN 100)

バッファとして、512バイトが確保され、後半256バイトのメモリにフラグが設定される。
任意の文字を256バイト入力すれば、フラグが表示されるはず。

$ nc safestr.q.2023.ugractf.ru 11667
Enter token: watmo5ffmja9ssy8
Enter input size: 256
Enter string: aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
You entered: aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
Bitcoin key is ugra_safe_0r_no7_5afe_u9lzsdd51x6a
ugra_safe_0r_no7_5afe_u9lzsdd51x6a

elementary (REVERSE 100)

Pythonスクリプトを読むと、フラグは以下の条件を満たすことがわかる。

・flagの長さが29バイト
・flag[17] == 'j'
・flag[:5] == 'ugra_'
・flag[9:3:-2] == 'nta'
・flag[-2:-15:-3].encode().hex() == '65646f6e67'
・int.from_bytes(flag[6:18:2].encode(), "little") == 104927802781555
・sum(ord(x) * 1000 ** i for i, x in enumerate(flag[19:-4])) == 100118104111105101
・base64.b64encode(flag[-4:].encode()) == b'eGFlNA=='
・hashlib.sha256(flag.encode()).hexdigest() == 'b6ff072c4096218622ae1f744f57c38e62edea4d9e2bcad751681c37e9fd62b1'

flag[9:3:-2] == 'nta' から以下のことが言える。

・flag[9] = 'n'
・flag[7] = 't'
・flag[5] = 'a'

flagの長さが29バイト、flag[-2:-15:-3].encode().hex() == '65646f6e67' から以下のことが言える。

・flag[-2] = flag[27] = chr(0x65) = 'e'
・flag[-5] = flag[24] = chr(0x64) = 'd'
・flag[-8] = flag[21] = chr(0x6f) = 'o'
・flag[-11] = flag[18] = chr(0x6e) = 'n'
・flag[-14] = flag[15] = chr(0x67) = 'g'

int.from_bytes(flag[6:18:2].encode(), "little") == 104927802781555 から言えることを考える。

>>> (104927802781555).to_bytes(6, "little")
b'soihn_'

・flag[6] = 's'
・flag[8] = 'o'
・flag[10] = 'i'
・flag[12] = 'h'
・flag[14] = 'n'
・flag[16] = '_'

sum(ord(x) * 1000 ** i for i, x in enumerate(flag[19:-4])) == 100118104111105101 から言えることを考える。

flag[19] + flag[20] * 1000 + flag[21] * (1000)**2 + ... + flag[24] * (1000)**5 == 100118104111105101
→この条件を満たすものを探す。
→flag[19:-4] = 'eiohvd'

base64.b64encode(flag[-4:].encode()) == b'eGFlNA==' から言えることを考える。

>>> base64.b64decode(b'eGFlNA==')
b'xae4'

flag[-4:] = 'xae4'

ここまでわかっている範囲でフラグ文字を並べていく。

          1111111111222222222
01234567890123456789012345678
ugra_astoni h ng_jneiohvdxae4

hashlib.sha256(flag.encode()).hexdigest() == 'b6ff072c4096218622ae1f744f57c38e62edea4d9e2bcad751681c37e9fd62b1' の条件を満たすよう、インデックス11と13をブルートフォースして探す。

#!/usr/bin/env python3
import hashlib

target = 100118104111105101

flag19_24 = ''
remain = target
for i in range(5, -1, -1):
    c = remain // (1000)**i
    flag19_24 += chr(c)
    remain = remain % (1000)**i

flag19_24 = flag19_24[::-1]

flag = 'ugra_astoni h ng_jneiohvdxae4'
assert flag[19:25] == flag19_24
flag_format = flag.replace(' ', '%s')

target = 'b6ff072c4096218622ae1f744f57c38e62edea4d9e2bcad751681c37e9fd62b1'

found = False
for c1 in range(33, 127):
    for c2 in range(33, 127):
       flag = flag_format % (chr(c1), chr(c2))
       if hashlib.sha256(flag.encode()).hexdigest() == target:
           found = True
           print(flag)
           break
    if found:
        break
ugra_astonishing_jneiohvdxae4

pebcak (REVERSE 150)

スクリプトの処理の概要は以下の通り。

・code = "ANEcwu4fYegObF1i2VXyvJHxKd0qBkMl36ILRaUjPG8rToSZzpCDt7n9mWsh5Q"
・flag: 入力
・s = ""
・flagの長さだけ以下繰り返し(i)
 ・numbers(i) = flagのi番目の文字のASCIIコード
 ・iが0の場合
  ・Encode(numbers(0))をsに結合
 ・iが1, 2の場合
  ・"_" + Encode(numbers(i))をsに結合
 ・iが3以上の場合
  ・numbers(i) = (numbers(i) + numbers(i-1) + numbers(i-2)) mod 179179
  ・"_" + Encode(numbers(i))をsに結合
・sを出力

Encodeの前の数値は暗号化文字列をcodeのテーブルを元に62進数のようにしてデコードすればわかる。あとはその数値からnumbers(i)を順に求めればよい。

#!/usr/bin/env python3
code = 'ANEcwu4fYegObF1i2VXyvJHxKd0qBkMl36ILRaUjPG8rToSZzpCDt7n9mWsh5Q'
b = len(code)

def get_number(s):
    n = 0
    for c in s[::-1]:
        n *= b
        n += code.index(c)
    return n

enc = '9N_GN_tN_wu_qY_xi_Md_08_zfN_ctN_HQE_O7w_vnf_xZb_1Gv_VB6_y6f_lNG_H5N_0Nr_xNo_dBG_09j_rZI_QwB_122_CHT_tE1_qDO_emd_0Za_xuV_I2Y_Bxd_WG6_Okb_sgS_7cb_GPO_cSx_v0L_ERb_b0N_mN1_5Bi_z3k_jAo_Ehq_BH0_HTf_k4I_SDG'

enc = enc.split('_')

flag = ''
numbers = []
for i in range(len(enc)):
    number = get_number(enc[i])
    assert number < 179179
    numbers.append(number)
    if i < 3:
        flag += chr(number)
    else:
        number = (number - numbers[i-1] - numbers[i-2]) % 179179
        flag += chr(number)

print(flag)
ugra_thats_not_how_access_control_works_dfqh6v7d05g9

trisection (WEB 100)

HTMLソースを見たら、コメントにこう書いてある。

<!--ugra_triangles_are_cool_-->

https://trisection.q.2023.ugractf.ru/robots.txtにアクセスしたら、こう書いてある。

User-Agent: *
Disallow: /secret-page/

https://trisection.q.2023.ugractf.ru/secret-page/にアクセスしたら、こう書いてある。

but_triflags_are_

HTTPヘッダを見てみる。

$ curl -v https://trisection.q.2023.ugractf.ru/63561fade0188a1c/ -X HEAD
Warning: Setting custom HTTP method to HEAD with -X/--request may not work the 
Warning: way you want. Consider using -I/--head instead.
*   Trying 95.217.155.244:443...
* Connected to trisection.q.2023.ugractf.ru (95.217.155.244) port 443 (#0)
* ALPN, offering h2
* ALPN, offering http/1.1
*  CAfile: /etc/ssl/certs/ca-certificates.crt
*  CApath: /etc/ssl/certs
* TLSv1.0 (OUT), TLS header, Certificate Status (22):
* TLSv1.3 (OUT), TLS handshake, Client hello (1):
* TLSv1.2 (IN), TLS header, Certificate Status (22):
* TLSv1.3 (IN), TLS handshake, Server hello (2):
* TLSv1.2 (IN), TLS header, Finished (20):
* TLSv1.2 (IN), TLS header, Supplemental data (23):
* TLSv1.3 (IN), TLS handshake, Encrypted Extensions (8):
* TLSv1.2 (IN), TLS header, Supplemental data (23):
* TLSv1.3 (IN), TLS handshake, Certificate (11):
* TLSv1.2 (IN), TLS header, Supplemental data (23):
* TLSv1.3 (IN), TLS handshake, CERT verify (15):
* TLSv1.2 (IN), TLS header, Supplemental data (23):
* TLSv1.3 (IN), TLS handshake, Finished (20):
* TLSv1.2 (OUT), TLS header, Finished (20):
* TLSv1.3 (OUT), TLS change cipher, Change cipher spec (1):
* TLSv1.2 (OUT), TLS header, Supplemental data (23):
* TLSv1.3 (OUT), TLS handshake, Finished (20):
* SSL connection using TLSv1.3 / TLS_AES_256_GCM_SHA384
* ALPN, server accepted to use h2
* Server certificate:
*  subject: CN=q.2023.ugractf.ru
*  start date: Jan  7 05:03:47 2023 GMT
*  expire date: Apr  7 05:03:46 2023 GMT
*  subjectAltName: host "trisection.q.2023.ugractf.ru" matched cert's "*.q.2023.ugractf.ru"
*  issuer: C=US; O=Let's Encrypt; CN=R3
*  SSL certificate verify ok.
* Using HTTP2, server supports multiplexing
* Connection state changed (HTTP/2 confirmed)
* Copying HTTP/2 data in stream buffer to connection buffer after upgrade: len=0
* TLSv1.2 (OUT), TLS header, Supplemental data (23):
* TLSv1.2 (OUT), TLS header, Supplemental data (23):
* TLSv1.2 (OUT), TLS header, Supplemental data (23):
* Using Stream ID: 1 (easy handle 0x558dc33b1e80)
* TLSv1.2 (OUT), TLS header, Supplemental data (23):
> HEAD /63561fade0188a1c/ HTTP/2
> Host: trisection.q.2023.ugractf.ru
> user-agent: curl/7.81.0
> accept: */*
> 
* TLSv1.2 (IN), TLS header, Supplemental data (23):
* TLSv1.3 (IN), TLS handshake, Newsession Ticket (4):
* TLSv1.2 (IN), TLS header, Supplemental data (23):
* TLSv1.3 (IN), TLS handshake, Newsession Ticket (4):
* old SSL session ID is stale, removing
* TLSv1.2 (IN), TLS header, Supplemental data (23):
* Connection state changed (MAX_CONCURRENT_STREAMS == 128)!
* TLSv1.2 (OUT), TLS header, Supplemental data (23):
* TLSv1.2 (IN), TLS header, Supplemental data (23):
* TLSv1.2 (IN), TLS header, Supplemental data (23):
< HTTP/2 200 
< server: nginx
< date: Sat, 14 Jan 2023 09:54:13 GMT
< content-type: text/html; charset=utf-8
< content-length: 5684
< x-flag-part3: way_cooler_a50d7deb2558
< 
* transfer closed with 5684 bytes remaining to read
* stopped the pause stream!
* Connection #0 to host trisection.q.2023.ugractf.ru left intact
curl: (18) transfer closed with 5684 bytes remaining to read

HTTPレスポンスヘッダのx-flag-part3にフラグの断片が設定されていた。
見つけたフラグの断片を結合すると、フラグになる。

ugra_triangles_are_cool_but_triflags_are_way_cooler_a50d7deb2558

capture (FORENSICS 100)

httpでフィルタリングする。No.32パケットで GET /hacker.jpg のレスポンスがあるので、エクスポートする。エクスポートした画像ファイルの画像にフラグが書いてあった。

ugra_traffic_extractor_53f2a01112bb

takefive (STEGO 50)

Mp3tagでカバー画像を抽出する。画像に書いてある文字を線が引いてある順番に読むとフラグになる。

ugra_we_support_local_artists_c8b5b1

bookkeeping (STEGANO 100)

Excelの各セルを見ると、数値が入っているセルと、数式が入っているセルがある。条件付き書式で以下を設定すると、文字が浮き上がってくる。

=ISFORMULA(A1)

ugra_you_asked_for_lattices_5zgp8ddrnjxe

whirlpool (CRYPTO 50)

ROT13と推測し、CyberChefで復号する。

                :

Nam rutrum ex non sapien facilisis, id cursus ante sollicitudin. Integer fringilla ugra_double_security_for_only_50_more_bucks_bu45q00imu6n consequat pellentesque. Aliquam elementum, neque in euismod luctus, nisi urna dictum ante, non imperdiet dui lorem accumsan leo. Curabitur metus arcu, vestibulum eget rhoncus a, luctus id velit. Nulla egestas libero nisi, vitae dictum risus ultricies vitae. Sed sit amet iaculis lorem. Ut mi purus, porttitor at euismod sed, porta ac massa. Nulla aliquet vel felis ac mattis. Duis erat urna, consectetur id sollicitudin at, vehicula et sapien. Morbi id mauris finibus, ullamcorper sem sed, ultrices libero.

                :

文中にフラグが含まれていた。

ugra_double_security_for_only_50_more_bucks_bu45q00imu6n

espionage (CRYPTO 150)

以下の順にデコード、解凍していく。

・base64デコード
・Zstandard compressed dataの解凍
・base32デコード
・bzip2アーカイブの解凍
・hexデコード
・xzアーカイブの解凍
・gzipアーカイブの解凍
#!/usr/bin/env python3
from base64 import *
from zstd import *
import bz2
import lzma
import uu
import gzip

with open('correspondence.txt', 'r') as f:
    enc = f.read()

enc = b64decode(enc)
enc = ZSTD_uncompress(enc)
enc = b32decode(enc)
enc = bz2.decompress(enc)
enc = bytes.fromhex(enc.decode())
enc = lzma.decompress(enc)

with open('uu.enc', 'w') as f:
    f.write(enc.decode())

uu.decode('uu.enc', 'uu.gz')

with gzip.open('uu.gz', 'rt') as f:
    enc = f.read()
print(enc)

解凍した結果以下のメッセージが表示された。

Robert A. logged on

Josh logged on
Josh: one two one two do you read?

Robert A.: Yes sir!

Josh: finally, the damn thing works
Josh: It's been what, two months?

Robert A.: That's about right.

Josh: So how secure is this device anyway?

Robert A.: Certifications are still in progress, sir, but our cryptologists say even NSA wouldn't be able to crack it.
Robert A.: Von Stierlitz confirmed that too.

Josh: ah, Stierlitz.. I'm glad our security is in good hands
Josh: alright, transmitting the key now
Josh: ugra_you_guys_are_getting_encryption_1querub3nqst

Robert A.: Roger that, I will get back to you in five minutes, sir.
Robert A. left

Josh: von Stierlitz... I totally saw that name somewhere else
Josh: reminds me of Russia for some reason
Josh: nah, looks like a german name
Josh: maybe i just need to get some sleep

Josh left

この中にフラグが含まれていた。

ugra_you_guys_are_getting_encryption_1querub3nqst

cryptoneliner (CRYPTO 200)

bash_historyの中に以下の履歴がある。

openssl enc -aes-256-cbc -pbkdf2 -in secret.data -out secret.enc -k $my_key
echo -n $my_key | base64 | tr '[A-Za-z0-9]' '[N-ZA-Mn-za-m3-90-2]' | rev | xxd -p | xargs -I {} python3 -c "import sys;print(f'{int(sys.argv[1],16)^int((sys.argv[2]*(len(sys.argv[1])//len(sys.argv[2])+1))[:len(sys.argv[1])],16):x}')" {} deadbeef | awk '{print substr($0,length/2+1) substr($0,1,length/2)}'

$my_keyがわかれば、復号できる。問題にある"bf78bb5a4fec4a3d4e9d5f9a2eae18baee"は上記の2つ目のコマンドの結果と思われる。逆にたどっていき、my_keyを求める。

#!/usr/bin/env python3
from string import *
from base64 import *

def caesar_decrypt(s):
    d = ''
    for c in s:
        if c in ascii_uppercase:
            index = (ascii_uppercase.index(c) + 13) % 26
            d += ascii_uppercase[index]
        elif c in ascii_lowercase:
            index = (ascii_lowercase.index(c) + 13) % 26
            d += ascii_lowercase[index]
        else:
            index = (digits.index(c) + 7) % 10
            d += digits[index]
    return d

enc = 'bf78bb5a4fec4a3d4e9d5f9a2eae18baee'

l = len(enc)
enc = enc[l//2:] + enc[:l//2]

key = 'deadbeef'
key = (key * (l // len(key) + 1))[:l]
enc = hex(int(enc, 16) ^ int(key, 16))[2:]
enc = bytes.fromhex(enc).rstrip().decode()
enc = enc[::-1]
enc = caesar_decrypt(enc)
key = b64decode(enc).decode()
print(key)

結果、my_keyは"baf3c67f5e98"であることがわかるので、これを鍵として復号する。

$ openssl enc -aes-256-cbc -pbkdf2 -d -in secret.enc -out secret.data -k baf3c67f5e98
$ cat secret.data
We're no strangers to love
You know the rules and so do I
A full commitment's what I'm thinking of
You wouldn't get this from any other guy

I just wanna tell you how I'm feeling
Gotta make you understand

                :

Never gonna give you up
Never gonna let you down
Never gonna run around and desert you
Never gonna make you cry
Never gonna say goodbye
Never gonna tell a lie and hurt you 
ugra_oneliners_rule_58f5596f3b02

この文末にフラグがあった。

ugra_oneliners_rule_58f5596f3b02

IrisCTF 2023 Writeup

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

Sanity Check (Welcome)

問題にフラグが書いてあった。

irisctf{w31c0m3_t0_1r15ctf_2023}

Discord (Welcome)

Discordに入り、#miscチャネルのトピックを見ると、フラグが書いてあった。

irisctf{d15c0rd_c0nn3cts_y0u_t0_0ur_0rg4n1z3rs}

Meaning of Python 1 (Reverse Engineering)

スクリプトの処理概要は以下の通り。

・flag_to_check: 第一引数
・flag_length: flag_to_checkの長さ
・flag_lengthが44より小さい場合、NGで終了
・scramble1(flag_to_check)
 ※内部の処理結果を使用していないため、何もしていないのと同じ。
・flag_compressed = zlib.compress(flag_to_check.encode("utf-8"))
・flag_compressed_length: flag_compressedの長さ
・flag_compressed_lengthが52より小さい場合、NGで終了
・scramble2(flag_compressed)
 ※内部の処理結果を使用していないため、何もしていないのと同じ。
・flag_compressedがb'x\x9c\xcb,\xca,N.I\xab.\xc9\xc8,\x8e7,\x8eOIM3\xcc3,1\xce\xa9\x8c7\x89/\xa8,\xc90\xc8\x8bO\xcc)2L\xcf(\xa9\x05\x00\x83\x0c\x10\xf9'
 と一致していたら正しい。

最後の結果をdecompressするだけでよい。

#!/usr/bin/env python3
import zlib

flag_compressed = b'x\x9c\xcb,\xca,N.I\xab.\xc9\xc8,\x8e7,\x8eOIM3\xcc3,1\xce\xa9\x8c7\x89/\xa8,\xc90\xc8\x8bO\xcc)2L\xcf(\xa9\x05\x00\x83\x0c\x10\xf9'

flag = zlib.decompress(flag_compressed).decode()
print(flag)
irisctf{this_1s_def1n1t3ly_4_pyth0n_alr1ght}

Meaning of Python 2 (Reverse Engineering)

途中まで、難読化を解除すると、以下のようになる。

from builtins import *;
WWXWXWXXXXXWWWWXXWWXXWX,lIIIlIIIllIllIIllIII,LILJIJJIJJILLIIJJLL,S2S2SS2S222S2S2SS2222,wxwwxxwwwxxxxwxxxwxx=(lambda wxxwwxwxwxxxwwxwwwwxw:wxxwwxwxwxxxwwxwwwwxw(__import__('zlib'))),(lambda wxxwwxwxwxxxwwxwwwwxw:wxxwwxwxwxxxwwxwwwwxw['decompress']),(lambda wxxwwxwxwxxxwwxwwwwxw:globals()['eval'](globals()['compile'](globals()['str']("globals()['eval'](wxxwwxwxwxxxwwxwwwwxw)"),filename=nmnnnmnnnnmnnnnnmnnmnnmnn',mode='eval'))),(lambda DDDDDDOOooOoDoDooD,wxxwwxwxwxxxwwxwwwwxw:DDDDDDOOooOoDoDooD(wxxwwxwxwxxxwwxwwwwxw)),(lambda:(lambda wxxwwxwxwxxxwwxwwwwxw:globals()['eval'](globals()['compile'](globals()['str']("globals()['eval'](wxxwwxwxwxxxwwxwwwwxw)"),filename='nmnnnmnnnnmnnnnnmnnmnnmnn',mode='eval')))('__import__('builtins').exec'));

その後、b'x\x9c\xe4'から始まるデータ部をzlib.decompressすれば良さそうという推測ができる。

#!/usr/bin/env python3
import zlib

enc = b'x\x9c\xe4\xbd\xdbn\xe3\xc8\x92.|\xbf\x9eb\xdd\xa9\nc\x0f(\x9e\xb9\x80\xba3~@・・・(省略)・・・=\xfe\xe8\xd5s\xc2@zzx\xc6"\xff\xc7\xff\x01\t\xaa\x1b!'

dec = zlib.decompress(enc).decode()
print(dec)

結果以下のようになり、再び難読化コードになる。

        :
       (省略)
        :
        elif locals()[MNMNMMMNNNNNNNNNMNMNNM]==LJJJLLLIJLLIJJLJIJJIILJ:
            locals()[XWXXXWWXXWWWWWXWX]=nmnmnnnmmmnmnnnnmmmmmnnnn(locals()[IIllIllIllIlIlIlIlIIlllll],lIlIIlIlIlIlIIllIIIlIl,nmnnmmmmnmmmmnmmmnmnm)
            locals()[XXXXWXWXWXXXXXWWWXX]=NMMNMNMNNMMMMNMMMMMMNMNNN
        elif locals()[IIllIllIllIlIIlllI]==NMMNNMMMNNMNMNMNNMNMNNN:
            locals()[IlIIlllIIIlIIIIIIllII]=nmnmnnnmmmnmnnnnmmnmmnnnn(locals()[oODDooDoOOOoDODoDoODooD],IJILIIJJJJLLILIILJLI,JIIILLLJJJJJJJILJJJJLLII)
            locals()[MNMMNNMMNMMMNMMNMNN]=S2S2SS222S2SS222SS2
        elif locals()[IIllIllIIIIlIIIIIllI]==mmmnnmmmmmnmmnnnmmmnmnm:
            return
def main():
    if len(sys.argv)<=LIJIIILJLJJILIILLILJL:
        print(S22222S22S2SSS2S22SS2S22S2)
        exit(Oo00ooO0OOO00Oo0O0)
    locals()[SSS22SS222S222SSS2S]=sys.argv[DoOoODOoooDOoODoD]
    locals()[oDDDoOOoooOOoooOoODDO]=len(locals()[wxxwxwxxxxxwwxxxwxwxwxww])
    if locals()[S2222SSS2SSS22222S]<IIlIllIIlIlIIIlIIl:
        print(ODOOOoDDDDOOoooDo)
        exit(IIILLJIIIJIIJJLIJIJLJLILI)
    SS2SS2S22S22SSSSSS2SS22S2S(locals()[lljlililiiljliiij])
    locals()[S2S2SS222S22S222S22SSSS]=zlib.compress(locals()[mnmmmnnmnmnnmnnmmmmmmm].encode(O00Oo0OO00OOOOoo00OooOo0O))
    locals()[LJLJJLJIJIIJILLJJ]=len(locals()[OoO0O000000ooo0oOo0ooOO0O0])
    if locals()[xwwxxxwxxxxwwxwwxww]<wwxwxwxxwxwxxwxxwwwwx:
        print(WWWXXWXWXWWXWXWWXWXXWX)
        exit(ljijlljjiiiljjljjjiji)
    SS2SS2S22S22SSSSSS22S22S2S(locals()[SS2SS2SS22S222SSSS22SSS2])
    if locals()[SSSS222S22SS2222SS2S2S]==OO0oOO00oOo0O000oo0OOO0:
        print(XXWWWWXXWWWWWXWXW)
    else:
        print(nmnmnmnnnnmnnmnmnmmnnm)
main()

main関数の中のパラメータを標準出力しながら、コードを書き換える。

def main():
    if len(sys.argv)<=1:
        print("Missing argument")
        exit(1)
    locals()["nmnmnmnnmmnmmnmmmm"]=sys.argv[1]
    locals()["MNNMMNMNMNNMMNNMNMN"]=len(locals()["nmnmnmnnmmnmmnmmmm"])
    if locals()["MNNMMNMNMNNMMNNMNMN"]<44:
        print("Incorrect!")
        exit(1)
    SS2SS2S22S22SSSSSS2SS22S2S(locals()["nmnmnmnnmmnmmnmmmm"])
    locals()["NMMNMNMNMNNNNNNNNNNM"]=zlib.compress(locals()["nmnmnmnnmmnmmnmmmm"].encode("utf-8"))
    locals()["NMMNMNMNMMNNNNNNNNNM"]=len(locals()["NMMNMNMNMNNNNNNNNNNM"])
    if locals()["NMMNMNMNMMNNNNNNNNNM"]<56:
        print("Incorrect!")
        exit(1)
    SS2SS2S22S22SSSSSS22S22S2S(locals()["NMMNMNMNMNNNNNNNNNNM"])
    if locals()["NMMNMNMNMNNNNNNNNNNM"]==b'x\x9c\xcb,\xca,N.I\xab\xce\xa8,H-\xca\xcc\xcf\x8b7,\x8e\xcf3(\x89O\x8c/3.\xaa\x8cO70H\x897HJ+5M6)1(R\xac\x05\x00\xce\xff\x11\xdb':
        print("Correct!")
    else:
        print("Incorrect!")

最後の比較バイト文字列をzlib.decompressする。

#!/usr/bin/env python3
import zlib

flag_compressed = b'x\x9c\xcb,\xca,N.I\xab\xce\xa8,H-\xca\xcc\xcf\x8b7,\x8e\xcf3(\x89O\x8c/3.\xaa\x8cO70H\x897HJ+5M6)1(R\xac\x05\x00\xce\xff\x11\xdb'

flag = zlib.decompress(flag_compressed).decode()
print(flag)
irisctf{hyperion_1s_n0t_a_v3ry_g00d_0bfu5c4t0r!}

babystrechy (Web Exploitation)

$ nc stretchy.chal.irisc.tf 10704
== proof-of-work: enabled ==
please solve a pow first
You can run the solver with:
    python3 <(curl -sSL https://goo.gle/kctf-pow) solve s.ADQ6.AAAusC+HkKwoFsogo/cf6NgW
===================

Solution? s.AABcs2780SHoJWB+tOoePCMgzK96TY0BKE8b7pzQ+ANGYQQ0K0eh0u+naVKDcEta+gzt8zcH81PtBMLMG+xvZFYYpDxyNLXbzhY0XWjLIB5h46/uQncyKET0SXtevB0EHf7VXBNoyMxptfXndzQyvZoMiWAdzXPuup/0cfET50vFem4r4a8Agv7fAer3jrCVlVA3LoGOfQo/2b2MzRVOKTa9
Correct
Fear my 4096 byte password!
> 

phpスクリプトの処理概要は以下の通り。

・$password: ランダム64バイト文字列の16進数表記
・$stretched_password: $passwordの各文字を64個ずつ連結した文字列
・$h = password_hash($stretched_password, PASSWORD_DEFAULT);
・$line: 入力
・password_verify(trim($line), $h)がtrueの場合にフラグを表示

password_hash関数は最大72バイトで文字列を切り、ハッシュ値を取る。このため、以下の場合のブルートフォースで条件を満たすものを探す。

xxx.....xxxyyyyyyyy(16進文字 x を64バイト+16進文字 y を8バイト)
#!/usr/bin/env python3
import socket
import subprocess
import string

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

s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect(('stretchy.chal.irisc.tf', 10704))

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

cmdline = data.splitlines()[3].lstrip()
cmd = ['python3', 'pow.py']
cmd += cmdline.split(' ')[-2:]
sol = subprocess.check_output(cmd).decode()
print(sol)

data = recvuntil(s, b'? ')
print(data + sol)
s.sendall(sol.encode() + b'\n')
data = recvuntil(s, b'!\n').rstrip()
print(data)

found = False
for c1 in string.hexdigits[:16]:
    for c2 in string.hexdigits[:16]:
        password = c1 * 64 + c2 * 8
        data = s.recv(2).decode()
        if data == '> ':
            print(data + password)
            s.sendall(password.encode() + b'\n')
        else:
            found = True
            data += recvuntil(s, b'}')
            print(data)
            break
    if found:
        break

実行結果は以下の通り。

== proof-of-work: enabled ==
please solve a pow first
You can run the solver with:
    python3 <(curl -sSL https://goo.gle/kctf-pow) solve s.ADQ6.AAAR/vy+Kp2TLgkDdHqzdh8s
===================
Solution: 

s.AAAjxFawR0RZldfC+wpq19M5XFWgOlSTEWYOMIV7gTvDGI3ObRCq/JD5vEGrSRi2TVutbh4qmfilYSp0E7eixFBH99qDXF2kj+Re1QdCVSF5UUl/uOp3W8QIdTudUkzmTXKAa/slj++ORh9Y88Q3rqO0OH+tWI7XwBpnbUvritDTG08ES83LHF9S1pJMOhgXOM0+YU0BsJOmKKgEX169aBmX

Solution? s.AAAjxFawR0RZldfC+wpq19M5XFWgOlSTEWYOMIV7gTvDGI3ObRCq/JD5vEGrSRi2TVutbh4qmfilYSp0E7eixFBH99qDXF2kj+Re1QdCVSF5UUl/uOp3W8QIdTudUkzmTXKAa/slj++ORh9Y88Q3rqO0OH+tWI7XwBpnbUvritDTG08ES83LHF9S1pJMOhgXOM0+YU0BsJOmKKgEX169aBmX
Correct
Fear my 4096 byte password!
> 000000000000000000000000000000000000000000000000000000000000000000000000
> 000000000000000000000000000000000000000000000000000000000000000011111111
> 000000000000000000000000000000000000000000000000000000000000000022222222
> 000000000000000000000000000000000000000000000000000000000000000033333333
> 000000000000000000000000000000000000000000000000000000000000000044444444
> 000000000000000000000000000000000000000000000000000000000000000055555555
> 000000000000000000000000000000000000000000000000000000000000000066666666
> 000000000000000000000000000000000000000000000000000000000000000077777777
> 000000000000000000000000000000000000000000000000000000000000000088888888
> 000000000000000000000000000000000000000000000000000000000000000099999999
> 0000000000000000000000000000000000000000000000000000000000000000aaaaaaaa
> 0000000000000000000000000000000000000000000000000000000000000000bbbbbbbb
> 0000000000000000000000000000000000000000000000000000000000000000cccccccc
> 0000000000000000000000000000000000000000000000000000000000000000dddddddd
> 0000000000000000000000000000000000000000000000000000000000000000eeeeeeee
> 0000000000000000000000000000000000000000000000000000000000000000ffffffff
> 111111111111111111111111111111111111111111111111111111111111111100000000
> 111111111111111111111111111111111111111111111111111111111111111111111111
> 111111111111111111111111111111111111111111111111111111111111111122222222
> 111111111111111111111111111111111111111111111111111111111111111133333333
> 111111111111111111111111111111111111111111111111111111111111111144444444
> 111111111111111111111111111111111111111111111111111111111111111155555555
> 111111111111111111111111111111111111111111111111111111111111111166666666
> 111111111111111111111111111111111111111111111111111111111111111177777777
> 111111111111111111111111111111111111111111111111111111111111111188888888
> 111111111111111111111111111111111111111111111111111111111111111199999999
> 1111111111111111111111111111111111111111111111111111111111111111aaaaaaaa
> 1111111111111111111111111111111111111111111111111111111111111111bbbbbbbb
> 1111111111111111111111111111111111111111111111111111111111111111cccccccc
> 1111111111111111111111111111111111111111111111111111111111111111dddddddd
> 1111111111111111111111111111111111111111111111111111111111111111eeeeeeee
> 1111111111111111111111111111111111111111111111111111111111111111ffffffff
> 222222222222222222222222222222222222222222222222222222222222222200000000
> 222222222222222222222222222222222222222222222222222222222222222211111111
> 222222222222222222222222222222222222222222222222222222222222222222222222
> 222222222222222222222222222222222222222222222222222222222222222233333333
> 222222222222222222222222222222222222222222222222222222222222222244444444
> 222222222222222222222222222222222222222222222222222222222222222255555555
> 222222222222222222222222222222222222222222222222222222222222222266666666
> 222222222222222222222222222222222222222222222222222222222222222277777777
> 222222222222222222222222222222222222222222222222222222222222222288888888
> 222222222222222222222222222222222222222222222222222222222222222299999999
> 2222222222222222222222222222222222222222222222222222222222222222aaaaaaaa
> 2222222222222222222222222222222222222222222222222222222222222222bbbbbbbb
> 2222222222222222222222222222222222222222222222222222222222222222cccccccc
> 2222222222222222222222222222222222222222222222222222222222222222dddddddd
> 2222222222222222222222222222222222222222222222222222222222222222eeeeeeee
> 2222222222222222222222222222222222222222222222222222222222222222ffffffff
> 333333333333333333333333333333333333333333333333333333333333333300000000
        :
> 9999999999999999999999999999999999999999999999999999999999999999ffffffff
> aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa00000000
> aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa11111111
> aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa22222222
> aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa33333333
> aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa44444444
> aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa55555555
> aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa66666666
> aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa77777777
> aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa88888888
> aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa99999999
> aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
> aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbb
> aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaacccccccc
> aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaadddddddd
> aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaeeeeeeee
irisctf{truncation_silent_and_deadly}
irisctf{truncation_silent_and_deadly}

babyshark (Networks)

httpでフィルタリングする。No.307のパケットで GET /babyshark.gifのレスポンスが返ってきているので、エクスポートする。エクスポートした画像にフラグが書いてあった。

irisctf{welc0m3_t0_n3tw0rks}

wi-the-fi (Networks)

WPAのキーをクラックする。

$ aircrack-ng -w dict/rockyou.txt BobertsonNet.cap 
Reading packets, please wait...
Opening BobertsonNet.cap
Read 20980 packets.

   #  BSSID              ESSID                     Encryption

   1  A0:28:ED:C3:CC:C1  BobertsonNet              WPA (1 handshake)

Choosing first network as target.

Reading packets, please wait...
Opening BobertsonNet.cap
Read 20980 packets.

1 potential targets



                               Aircrack-ng 1.6 

      [00:00:01] 13774/10303727 keys tested (23513.52 k/s) 

      Time left: 7 minutes, 17 seconds                           0.13%

                           KEY FOUND! [ billybob1 ]


      Master Key     : 84 F1 82 B2 91 7C D3 DD 26 B2 19 34 C0 7B 27 2F 
                       AC CF 32 B2 DF A3 56 01 9E FE 8C 7A 23 38 15 F8 

      Transient Key  : BA 3F 2A 6D D0 DB 35 F8 D4 60 FF BB EF CB B5 1A 
                       CE 60 FD 5F 58 D0 1F C4 3A EA 03 EA D4 DE 41 C9 
                       4B 0B 91 19 1F 47 2D E3 8A 26 19 85 69 89 37 F3 
                       2D 24 11 A4 C7 31 F9 D6 8F 6D 87 4F 17 87 01 9D 

      EAPOL HMAC     : 80 8E 84 12 3F EA 18 4E 2D 25 9A 19 10 54 E2 51 

Wiresharkの[編集]>[設定]から[Protocols]>[IEEE 802.11]の画面を表示後、Decryption keysの編集から以下を設定し、通信パケットを復号する。

・Key type: wpa-pwd
・Key     : billybob1

tcpでフィルタリングし、No.20422のパケットを見ると、TCPペイロードにフラグが入っていた。

irisctf{4ircr4ck_g0_brrrrrrrrrrrrrrr}

babyforens (Forensics)

JPEGの先頭10バイトが壊れているので、以下に書き換える。

FF D8 FF E0 00 10 4A 46 49 46


書き換えたJPG画像には以下のsecretの情報が書いてある。

exif_data_can_leak_a_lot_of_info

次にJPGのEXIFを見る。

$ exiftool IMG_0917_fix.jpg
    :
Date/Time Original              : 2022:08:27 10:04:56
Create Date                     : 2022:08:27 10:04:56
    :
Serial Number                   : 392075057288
    :
GPS Latitude                    : 37 deg 44' 49.46" N
GPS Longitude                   : 119 deg 35' 46.77" W
    :

緯度、経度は小数で小数点以下2桁に切り捨てた値が必要なので、Googleマップで以下を検索する。

37°44' 49.46" N, 119°35' 46.77" W
→37.747072, -119.596325

場所はUSのカリフォルニア州なので、GMT-8。このタイムゾーンで、2022-08-27 10:04:56 の Unixtime は 1661619896。

irisctf{37.74_-119.59_1661619896_392075057288_exif_data_can_leak_a_lot_of_info}

babynotrsa (Cryptography)

暗号は以下の計算で行っている。

encrypted = (flag * e) % n

flagは以下の計算で求めることができる。

flag = (encrypted * inverse(e, n)) % n
#!/usr/bin/env python3
from Crypto.Util.number import *

with open('output.txt', 'r') as f:
    params = f.read().splitlines()

n = int(params[0].split(' ')[-1])
e = int(params[1].split(' ')[-1])
encrypted = int(params[2].split(' ')[-1])

flag = (encrypted * inverse(e, n)) % n
flag = long_to_bytes(flag).decode()
print(flag)
irisctf{discrete_divide_isn't_hard}

babymixup (Cryptography)

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

・key: ランダム16バイト文字列
・flagの長さは16の倍数
・iv: ランダム16バイト文字列
・ivを表示
・以下の文字列をkey=iv, iv=keyでAES-CBC暗号化
 b"Hello, this is a public message. This message contains no flags."
・iv: ランダム16バイト文字列
・ivを表示
・flagをkey=key, iv=ivでAES-CBC暗号化

最初の暗号化で鍵がわかっているので、それからXORでIVを求める。あとはその情報を使ってフラグを復号する。

#!/usr/bin/env python3
from Crypto.Cipher import AES
from Crypto.Util.strxor import strxor

with open('output.txt', 'r') as f:
    params = f.read().splitlines()

IV1 = bytes.fromhex(params[0].split(' ')[-1])
CT1 = bytes.fromhex(params[1].split(' ')[-1])
IV2 = bytes.fromhex(params[2].split(' ')[-1])
CT2 = bytes.fromhex(params[3].split(' ')[-1])

PT1 = b"Hello, this is a public message. This message contains no flags."

tmp_key = b'\x00' * 16
cipher = AES.new(IV1, AES.MODE_CBC, tmp_key)
tmp_pt = cipher.decrypt(CT1)
assert tmp_pt[16:] == PT1[16:]

key = strxor(tmp_pt[:16], PT1[:16])

cipher = AES.new(key, AES.MODE_CBC, IV2)
flag = cipher.decrypt(CT2).decode()
print(flag)
irisctf{the_iv_aint_secret_either_way_using_cbc}

Nonces and Keys (Cryptography)

AES暗号OFBモードで暗号化されている。

OFBモードの場合以下のように暗号化される。
IV      --(AES暗号化)--> TMP_CT0、TMPCT0 ^ PT0 = CT0
TMP_CT0 --(AES暗号化)--> TMP_CT1、TMPCT1 ^ PT1 = CT1
TMP_CT1 --(AES暗号化)--> TMP_CT2、TMPCT2 ^ PT2 = CT2
        :

鍵が提示されているので、平文の先頭16バイト(PT1)と、暗号文(CT1, CT2, ...)がわかれば、TMP_CT1, TMP_CT2, ...も算出でき、復号できる。

#!/usr/bin/env python3
from Crypto.Cipher import AES
from Crypto.Util.strxor import strxor

key = (0x13371337133713371337133713371337).to_bytes(16, 'big')

with open('challenge_enc.sqlite3', 'rb') as f:
    enc = f.read()

pt0 = b'SQLite format 3\x00'

tmp_ct = strxor(pt0, enc[:16])

dec = pt0
for i in range(16, len(enc), 16):
    cipher = AES.new(key, mode=AES.MODE_ECB)
    tmp_ct = cipher.encrypt(tmp_ct)
    c = strxor(tmp_ct, enc[i:i+16])
    dec += c

with open('password.sqlite3', 'wb') as f:
    f.write(dec)

復号したsqlite3ファイルをDB Browser for SQLiteで見てみる。usersテーブルのnameがMichaelのレコードのpasswordにフラグが設定されていた。

irisctf{g0tt4_l0v3_s7re4mciph3rs}

SMarT 1 (Cryptography)

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

・SBOX: 256個の既知固定数値配列
・TRANSPOSE: 8個の既知固定数値配列の8個の配列
・RR: 8個の既知固定数値配列
・MASK: ランダム8バイト文字列
・KEYLEN = 12
・key: ランダム12バイト文字列
・MASK, keyを出力
・pairs = []
・以下8回繰り返し
 ・pt: ランダム8バイト文字列
 ・ptとencrypt(pt, key)の16進数表記の配列をpairsに追加
・pairsを出力
・ecb(FLAG, key).hex()を出力

鍵がわかっているので、逆算していけば、フラグを割り出せる。

#!/usr/bin/env python3
from Crypto.Util.strxor import strxor

SBOX = [99, 124, 119, 123, 242, 107, 111, 197, 48, 1, 103, 43, 254, 215, 171,
    118, 202, 130, 201, 125, 250, 89, 71, 240, 173, 212, 162, 175, 156, 164,
    114, 192, 183, 253, 147, 38, 54, 63, 247, 204, 52, 165, 229, 241, 113, 216,
    49, 21, 4, 199, 35, 195, 24, 150, 5, 154, 7, 18, 128, 226, 235, 39, 178,
    117, 9, 131, 44, 26, 27, 110, 90, 160, 82, 59, 214, 179, 41, 227, 47, 132,
    83, 209, 0, 237, 32, 252, 177, 91, 106, 203, 190, 57, 74, 76, 88, 207, 208,
    239, 170, 251, 67, 77, 51, 133, 69, 249, 2, 127, 80, 60, 159, 168, 81, 163,
    64, 143, 146, 157, 56, 245, 188, 182, 218, 33, 16, 255, 243, 210, 205, 12,
    19, 236, 95, 151, 68, 23, 196, 167, 126, 61, 100, 93, 25, 115, 96, 129, 79,
    220, 34, 42, 144, 136, 70, 238, 184, 20, 222, 94, 11, 219, 224, 50, 58, 10,
    73, 6, 36, 92, 194, 211, 172, 98, 145, 149, 228, 121, 231, 200, 55, 109,
    141, 213, 78, 169, 108, 86, 244, 234, 101, 122, 174, 8, 186, 120, 37, 46,
    28, 166, 180, 198, 232, 221, 116, 31, 75, 189, 139, 138, 112, 62, 181, 102,
    72, 3, 246, 14, 97, 53, 87, 185, 134, 193, 29, 158, 225, 248, 152, 17, 105,
    217, 142, 148,155, 30, 135, 233, 206, 85, 40, 223, 140, 161, 137, 13, 191,
    230, 66, 104, 65, 153, 45, 15, 176, 84, 187, 22]

TRANSPOSE = [
    [3, 1, 4, 5, 6, 7, 0, 2],
    [1, 5, 7, 3, 0, 6, 2, 4],
    [2, 7, 5, 4, 0, 6, 1, 3],
    [2, 0, 1, 6, 4, 3, 5, 7],
    [6, 5, 0, 3, 2, 4, 1, 7],
    [2, 0, 6, 1, 5, 7, 4, 3],
    [1, 6, 2, 5, 0, 7, 4, 3],
    [4, 5, 6, 1, 2, 3, 7, 0]
]

RR = [4, 2, 0, 6, 9, 3, 5, 7]

ROUNDS = 2

def rev_rr(c, n):
    n = n % 8
    return ((c >> (8 - n)) | (c << n)) & 0xff

def decrypt(block, key):
    block = bytearray(block)

    for r in range(ROUNDS-1, -1, -1):
        block = strxor(block, MASK)

        temp = bytearray(8)
        for i in range(8):
            for j in range(8):
                temp[i] |= ((block[j] >> i) & 1) << TRANSPOSE[i][j]
        block = temp

        for i in range(8):
            block[i] = rev_rr(block[i], RR[i])
            block[i] = SBOX.index(block[i])

        block = strxor(block, key[r*4:(r+2)*4])

    return block

def rev_ecb(ct, key):
    out = b''
    for i in range(0, len(ct), 8):
        out += decrypt(ct[i:i+8], key)
    return out.rstrip(b'\x00')

with open('output1.txt', 'r') as f:
    params = f.read().splitlines()

MASK = bytes.fromhex(params[0].split(': ')[1])
key = bytes.fromhex(params[1].split(': ')[1])
ct = bytes.fromhex(params[3].split(': ')[1])

flag = rev_ecb(ct, key).decode()
print(flag)
irisctf{ok_at_least_it_works}

Real World CTF 5th Writeup

この大会は2023/1/6 22:00(JST)~2023/1/8 22:00(JST)に開催されました。
今回もチームで参戦。結果は34点で632チーム中324位でした。
参加表明の問題しか解けませんでしたが、
自分で解けた問題をWriteupとして書いておきます。

Chat-In (check-in)

$ nc 47.89.251.203 1337
Proof your work first
PoW solve script: https://gist.github.com/WinMin/b93c7ee59a7bd0c7f2ed4aa5eed7e2d6
sha256("oqqfE"+"?") starts with 26bits of zero: 

ここで別ターミナルで、以下を実行

$ python3 solve.py oqqfE
[+] MBruteforcing: Found key: "4tg01"

このkeyを入力して、続きを行う。このとき"ls"コマンドは使えなかったので、"dir"コマンドを使った。

$ nc 47.89.251.203 1337
Proof your work first
PoW solve script: https://gist.github.com/WinMin/b93c7ee59a7bd0c7f2ed4aa5eed7e2d6
sha256("oqqfE"+"?") starts with 26bits of zero: 4tg01
Please wait for the service to start

Welcome to Chat-In challenge ( RealWorld-CTF-2023 )
ひとーつ、ひいきは绝対せず! ふたーつ、不正は见逃さず! みっつ、见事にジャッジする!

I'm **not a Linux terminal**. I'm the Chat-In robot for you! You can talk with me within 5 sentences., or until I make a mistake.
Last login: Friday, 21 January 2022, 21:00 CST

root@Chat-In:~# dir
 flag

flag
root@Chat-In:~# cat flag


rwctf{Welc0m3_to_RWCTF_2023}
rwctf{Welc0m3_to_RWCTF_2023}

niteCTF 2022 Writeup

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

4dm1n_c0ntR0L (Web)

Usernameに以下を入力して、Submitすると、ログインできる。

' or 1=1 -- - 

Get flagボタンだけあるので、押してみると、フラグがポップアップ画面に表示された。

nitectf{w3nT_1nT0_Th3_s3rV3r}

itsybitsyrsa (Cryptography)

3つのファイルに分散されているが、それぞれRSA暗号のパラメータが書いてある。nが非常に大きく、eが小さいので、Low Public-Exponent Attackで復号する。

#!/usr/bin/env python3
from Crypto.Util.number import *
import gmpy2

with open('e_value', 'r') as f:
    e = int(f.readline().rstrip().split(' ')[-1])

with open('c_value', 'r') as f:
    c = int(f.readline().rstrip().split(' ')[-1])

m = gmpy2.iroot(c, e)[0]
flag = long_to_bytes(m).decode()
print(flag)
nitectf{rsa_can_be_very_adaptable}

Basically, I locked up (Cryptography)

暗号・復号のスクリプトが提供されている。この暗号の処理概要は以下の通り。

・password: 8バイトの文字列(未知)
・plaintextにはb"HiDeteXT"が含まれている。
・add_spice関数: 左シフト
・ciphertext: plaintextの各文字について、add_spice(c) ^ ord(password[i % len(password)])のバイト文字の連結
 →ファイル書き込み

暗号の各位置から8バイトを復号した際にb"HiDeteXT"になることを前提にpasswordを求めることをブルートフォースで行う。

#!/usr/bin/env python3
add_spice = lambda b: 0xff & ((b << 1) | (b >> 7))
remove_spice = lambda b: 0xff & ((b >> 1) | (b << 7))

def decrypt(ciphertext, password):
    plaintext = bytes(remove_spice(c ^ ord(password[i % len(password)]))
        for i, c in enumerate(ciphertext))
    return plaintext

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

with open('Important_encrypted', 'rb') as f:
    ciphertext = f.read()

known = b'HiDeteXT'

with open('Important_encrypted', 'rb') as f:
    ciphertext = f.read()

for i in range(len(ciphertext) - 8 + 1):
    password = [''] * 8
    for j in range(8):
        password[(i + j) % 8] = chr(add_spice(known[j]) ^ ciphertext[i + j])
    plaintext = decrypt(ciphertext, password)
    if is_printable(plaintext):
        plaintext = plaintext.decode()
        print(plaintext)
        break

実行結果は以下の通り。

Oh, You Searching for HiDeteXT ??

NITE{BrUT3fORceD_y0uR_wAy_iN}
NITE{BrUT3fORceD_y0uR_wAy_iN}

Shoo-in (Cryptography)

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

・fn: "firstnames.py"のファイルオブジェクト
・ln: "lastnames.py"のファイルオブジェクト
・fp: "primes.py"のファイルオブジェクト
・fn_content: fnの行ごと(改行あり)の配列
・ln_content: lnの行ごと(改行あり)の配列
・prime_arr: fpの行ごと(改行あり)の配列
・N: firstnames.pyとlastnames.pyの行数の少ない方の行数(=1300)
・seed, a, b: N未満のランダム整数
・lcg = RNG(seed, a, b, N)
 ・lcg.seed = seed
 ・lcg.a = a
 ・lcg.b = b
 ・lcg.p = N
・gen=lcg.gen()
・以下10回繰り返し
 ・iが0の場合
  ・name1: "[firstnamesのリストのa番目] [lastnamesのリストのb番目]"
 ・iが0以外の場合
  ・name1: "[firstnamesのリストのnext(gen)番目] [lastnamesのリストのnext(gen)番目]"
 ・name2: "[firstnamesのリストのnext(gen)番目] [lastnamesのリストのnext(gen)番目]"
 ・name1, name2を表示
 ・winner = next(gen) % 2
 ・inp: 入力(1または2)
 ・winnerがinpと一致しない場合、終了
 ・winnerがinpと一致する場合
  ・iが9より小さい場合、正解メッセージを表示
  ・iが9の場合
   ・key=generate_keys()
    ・p: prime_arr[next(gen)]
    ・q: prime_arr[next(gen)]
    ・n = p * q
    ・g = n + 1
    ・l = (p - 1) * (q - 1)
    ・mu = gmpy2.invert(l, n)
    ・n, g, l, muを返却
   ・pallier_encrypt(key, int.from_bytes(flag, "big"), next(gen))の結果を表示
    ・(pow(g, m, n**2) * pow(next(gen), n, n**2) % n**2を返却
    ※m: フラグの数値化

最初のname1からa, bはわかる。さらにseedをブルートフォースし、name2になる条件からseedを求める。あとはnext(gen)の値がわかるので、各パラメータを算出できる。p, qがわかるので、Paillier暗号の暗号文を復号することができる。

#!/usr/bin/env python3
import socket
from Crypto.Util.number import GCD, inverse

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

class RNG:
    def __init__ (self, seed, a, b, p):
        self.seed = seed
        self.a = a
        self.b = b
        self.p = p

    def gen(self):
        out = self.seed
        while True:
            out = (self.a * out + self.b) % self.p
            self.a += 1
            self.b += 1
            self.p += 1
            yield out

def getPrime():
    prime = int(prime_arr[next(gen)].strip())
    return prime

def L(u, n):
    return (u - 1) // n

def pallier_decrypt(c):
    p = getPrime()
    q = getPrime()
    n = p * q
    g = n + 1
    l = (p - 1) * (q - 1)
    _lambda = l // GCD(p - 1, q - 1)
    m = L(pow(c, _lambda, n**2), n) * inverse(L(pow(g, _lambda, n**2), n), n) % n
    return m

fn = open(r"firstnames.py", 'r')
ln = open(r"lastnames.py", 'r')
fp = open (r"primes.py", 'r')
fn_content = fn.readlines()
ln_content = ln.readlines()
prime_arr = fp.readlines()

N = (min(len(fn_content), len(ln_content)))

s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect(('34.90.236.228', 1337))

data = recvuntil(s, b'\n').rstrip()
print(data)
data = recvuntil(s, b'\n').rstrip()
print(data)
name1 = data.split('\t')[0]
name2 = data.split('\t')[-1]

a = fn_content.index(name1.split(' ')[0] + '\n')
b = ln_content.index(name1.split(' ')[1] + '\n')
next1 = fn_content.index(name2.split(' ')[0] + '\n')
next2 = ln_content.index(name2.split(' ')[1] + '\n')

found = False
for seed in range(N):
    c1 = (a * seed + b) % N
    if c1 == next1:
        if ((a + 1) * c1 + (b + 1)) % (N + 1) == next2:
            found = True
            break

assert found

lcg = RNG(seed, a, b, N)
gen = lcg.gen()
next(gen)
next(gen)
winner = next(gen) % 2
if winner == 0:
    winner = 2

data = recvuntil(s, b'\n').rstrip()
print(data)
print(winner)
s.sendall(str(winner).encode() + b'\n')
data = recvuntil(s, b'\n').rstrip()
print(data)
data = recvuntil(s, b'\n').rstrip()
print(data)

for i in range(1, 10):
    for _ in range(4):
        next(gen)
    data = recvuntil(s, b'\n').rstrip()
    print(data)
    winner = next(gen) % 2
    if winner == 0:
        winner = 2
    data = recvuntil(s, b'\n').rstrip()
    print(data)
    print(winner)
    s.sendall(str(winner).encode() + b'\n')
    data = recvuntil(s, b'\n').rstrip()
    print(data)
    data = recvuntil(s, b'\n').rstrip()
    print(data)

data = recvuntil(s, b'\n').rstrip()
print(data)
c = int(data)
m = pallier_decrypt(c)
flag = m.to_bytes((m.bit_length() + 7) // 8, "big").decode()
print(flag)

実行結果は以下の通り。

== proof-of-work: disabled ==
Andre Reese     vs      Blaine Harmon
Choose the winner: 1 or 2
1
That's correct, here is the next round

Heath Francis   vs      Shiloh Heath
Choose the winner: 1 or 2
2
That's correct, here is the next round

Kash Avery      vs      Nikhil Lopez
Choose the winner: 1 or 2
1
That's correct, here is the next round

Margaret Schneider      vs      Bethany Selene
Choose the winner: 1 or 2
2
That's correct, here is the next round

Todd Estes      vs      Adrien Frye
Choose the winner: 1 or 2
1
That's correct, here is the next round

Rodrigo Moore   vs      Cadence Kent
Choose the winner: 1 or 2
2
That's correct, here is the next round

Jesse Beard     vs      Rodrigo Tobias
Choose the winner: 1 or 2
1
That's correct, here is the next round

Abigail Aryn    vs      Alberto Rodgers
Choose the winner: 1 or 2
2
That's correct, here is the next round

Jonathan Woods  vs      Daisy Hollyn
Choose the winner: 1 or 2
1
That's correct, here is the next round

Brice Schroeder vs      Daisy Werner
Choose the winner: 1 or 2
1
Congratulations! you made it
Can you decode this secret message?
9247144570829358924841240967295610831251025340080430839001135462876063348979941518647185296879931786766958032992008977123505926544277369888568600621632355851505450281378555399544202335817356532297136697333575081814361295223072969220560307669233249687870270873089007634771695172315068511805588870393530505136577345386776211456988815465939654026602528062989553270554332170428745622557131002272007524816246732049457089239722213074314397985724894219764450273744264907017723444907563274142874472362285638387792413075013020704951426410641101658781288819482936333414155207342080191537216025570791495740491524447242675986564
niteCTF{n0T_sO_R@nd0m}
niteCTF{n0T_sO_R@nd0m}