Houseplant CTF 2020 Writeup

この大会は2020/4/25 4:00(JST)~2020/4/27 4:00(JST)に開催されました。
今回もチームで参戦。結果は 10033点で1419チーム中89位でした。
自分で解けた問題をWriteupとして書いておきます。

Beginner 1 (Beginners)

base64デコードする。

$ echo cnRjcHt5b3VyZV92ZXJ5X3dlbGNvbWV9 | base64 -d
rtcp{youre_very_welcome}
rtcp{youre_very_welcome}

Beginner 2 (Beginner)

16進数ASCIIコードをhexデコードする。

codes = '72 74 63 70 7b 62 6f 62 5f 79 6f 75 5f 73 75 63 6b 5f 61 74 5f 62 65 69 6e 67 5f 65 6e 63 6f 75 72 61 67 69 6e 67 7d'
flag = codes.replace(' ', '').decode('hex')
print flag
rtcp{bob_you_suck_at_being_encouraging}

Beginner 3 (Beginner)

8進数ASCIIコードとしてデコードする。

codes = '162 164 143 160 173 163 165 145 137 155 145 137 151 137 144 151 144 156 164 137 153 156 157 167 137 167 150 141 164 137 157 143 164 141 154 137 167 141 163 137 157 153 141 171 77 41 175'
codes = codes.split(' ')

flag = ''
for code in codes:
    flag += chr(int(code, 8))
print flag
rtcp{sue_me_i_didnt_know_what_octal_was_okay?!}

Beginner 4 (Beginner)

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

rtcp{yall_better_participate}

Beginner 5 (Beginner)

モールス信号。https://morsecode.world/international/translator.htmlでデコードする。

MANY#BEEPS#AND#BOOPS

"..--.-"がデコードできていなく"#"になっているが、"_"が正しいので、置換しフラグの形式にする。

rtcp{many_beeps_and_boops}

Beginner 6 (Beginner)

アルファベットのインデックスとして、アルファベットにしてみる(1がA、2がB、・・・26がZ)。

import string

codes = '26 26 26 26 26 26 26 26 19 12 5 5 16 9 14 7 9 14 16 8 25 19 9 3 19'
codes = map(int, codes.split(' '))

flag = ''
for code in codes:
    flag += string.lowercase[code - 1]

flag = 'rtcp{%s}' % flag
print flag
rtcp{zzzzzzzzsleepinginphysics}

Beginner 7 (Beginner)

問題文からもAtbash暗号と推測できる。

import string

enc = 'igxk{fmovhh_gsvb_ziv_nvzm}'

flag = ''
for c in enc:
    if c in string.lowercase:
        index = string.lowercase.index(c)
        flag += string.lowercase[25 - index]
    else:
        flag += c
print flag
rtcp{unless_they_are_mean}

Beginner 8 (Beginner)

問題文からベーコニアン暗号と推測できる。

bacon = {'00000': 'a', '00001': 'b', '00010': 'c', '00011': 'd', '00100': 'e',
    '00101': 'f', '00110': 'g', '00111': 'h', '01000': 'i', '01001': 'j',
    '01010': 'k', '01011': 'l', '01100': 'm', '01101': 'n', '01110': 'o',
    '01111': 'p', '10000': 'q', '10001': 'r', '10010': 's', '10011': 't',
    '10100': 'u', '10101': 'v', '10110': 'w', '10111': 'x', '11000': 'y',
    '11001': 'z'}

codes = '00110 01110 00100 00000 10011 00101 01110 01110 00011 00011 01110 01101 10011 10010 10011 00000 10001 10101 00100'
codes = codes.split(' ')

flag = ''
for code in codes:
    flag += bacon[code]

flag = 'rtcp{%s}' % flag
print flag
rtcp{goeatfooddontstarve}

Beginner 9 (Beginner)

Beginnersの集大成のような問題。以下を順に実行していく。

・base64デコード
・hexデコード
・2進数デコード
・アルファベットインデックス
・Atbash暗号の復号
・rot13
import string

with open('Beginner 10.txt', 'r') as f:
    data = f.read()

data = data.decode('base64')
print data
data = data.replace(' ', '').decode('hex')
print data
data = data.replace('-----', '0').replace('.----', '1')
print data
codes = data.replace(' ', '').split('\n')

data = ''
for code in codes:
    data += chr(int(code, 2))
print data

enc = map(int, data.split(' '))

dec = ''
for c in enc:
    dec += string.lowercase[c - 1]
print dec

flag = ''
for p in dec:
    index = string.lowercase.index(p)
    flag += string.lowercase[25 - index]
print flag

flag = flag.decode('rot13')

flag = 'rtcp{%s}' % flag
print flag

実行結果は以下の通り。

2d 2d 2d 2d 2d 20 2d 2d 2d 2d 2d 20 2e 2d 2d 2d 2d 20 2e 2d 2d 2d 2d 20 2d 2d 2d 2d 2d 20 2d 2d 2d 2d 2d 20 2e 2d 2d 2d 2d 20 2d 2d 2d 2d 2d 0a 2d 2d 2d 2d 2d 20 2d 2d 2d 2d 2d 20 2e 2d 2d 2d 2d 20 2e 2d 2d 2d 2d 20 2d 2d 2d 2d 2d 20 2e 2d 2d 2d 2d 20 2e 2d 2d 2d 2d 20 2d 2d 2d 2d 2d 0a 2d 2d 2d 2d 2d 20 2d 2d 2d 2d 2d 20 2e 2d 2d 2d 2d 20 2d 2d 2d 2d 2d 20 2d 2d 2d 2d 2d 20 2d 2d 2d 2d 2d 20 2d 2d 2d 2d 2d 20 2d 2d 2d 2d 2d 0a 2d 2d 2d 2d 2d 20 2d 2d 2d 2d 2d 20 2e 2d 2d 2d 2d 20 2e 2d 2d 2d 2d 20 2d 2d 2d 2d 2d 20 2e 2d 2d 2d 2d 20 2d 2d 2d 2d 2d 20 2e 2d 2d 2d 2d 0a 2d 2d 2d 2d 2d 20 2d 2d 2d 2d 2d 20 2e 2d 2d 2d 2d 20 2d 2d 2d 2d 2d 20 2d 2d 2d 2d 2d 20 2d 2d 2d 2d 2d 20 2d 2d 2d 2d 2d 20 2d 2d 2d 2d 2d 0a 2d 2d 2d 2d 2d 20 2d 2d 2d 2d 2d 20 2e 2d 2d 2d 2d 20 2e 2d 2d 2d 2d 20 2d 2d 2d 2d 2d 20 2d 2d 2d 2d 2d 20 2e 2d 2d 2d 2d 20 2d 2d 2d 2d 2d 0a 2d 2d 2d 2d 2d 20 2d 2d 2d 2d 2d 20 2e 2d 2d 2d 2d 20 2e 2d 2d 2d 2d 20 2d 2d 2d 2d 2d 20 2e 2d 2d 2d 2d 20 2e 2d 2d 2d 2d 20 2d 2d 2d 2d 2d 0a 2d 2d 2d 2d 2d 20 2d 2d 2d 2d 2d 20 2e 2d 2d 2d 2d 20 2d 2d 2d 2d 2d 20 2d 2d 2d 2d 2d 20 2d 2d 2d 2d 2d 20 2d 2d 2d 2d 2d 20 2d 2d 2d 2d 2d 0a 2d 2d 2d 2d 2d 20 2d 2d 2d 2d 2d 20 2e 2d 2d 2d 2d 20 2e 2d 2d 2d 2d 20 2e 2d 2d 2d 2d 20 2d 2d 2d 2d 2d 20 2d 2d 2d 2d 2d 20 2e 2d 2d 2d 2d 0a 2d 2d 2d 2d 2d 20 2d 2d 2d 2d 2d 20 2e 2d 2d 2d 2d 20 2d 2d 2d 2d 2d 20 2d 2d 2d 2d 2d 20 2d 2d 2d 2d 2d 20 2d 2d 2d 2d 2d 20 2d 2d 2d 2d 2d 0a 2d 2d 2d 2d 2d 20 2d 2d 2d 2d 2d 20 2e 2d 2d 2d 2d 20 2e 2d 2d 2d 2d 20 2d 2d 2d 2d 2d 20 2d 2d 2d 2d 2d 20 2e 2d 2d 2d 2d 20 2d 2d 2d 2d 2d 0a 2d 2d 2d 2d 2d 20 2d 2d 2d 2d 2d 20 2e 2d 2d 2d 2d 20 2e 2d 2d 2d 2d 20 2d 2d 2d 2d 2d 20 2e 2d 2d 2d 2d 20 2d 2d 2d 2d 2d 20 2e 2d 2d 2d 2d 0a 2d 2d 2d 2d 2d 20 2d 2d 2d 2d 2d 20 2e 2d 2d 2d 2d 20 2d 2d 2d 2d 2d 20 2d 2d 2d 2d 2d 20 2d 2d 2d 2d 2d 20 2d 2d 2d 2d 2d 20 2d 2d 2d 2d 2d 0a 2d 2d 2d 2d 2d 20 2d 2d 2d 2d 2d 20 2e 2d 2d 2d 2d 20 2e 2d 2d 2d 2d 20 2d 2d 2d 2d 2d 20 2d 2d 2d 2d 2d 20 2e 2d 2d 2d 2d 20 2d 2d 2d 2d 2d 0a 2d 2d 2d 2d 2d 20 2d 2d 2d 2d 2d 20 2e 2d 2d 2d 2d 20 2e 2d 2d 2d 2d 20 2d 2d 2d 2d 2d 20 2d 2d 2d 2d 2d 20 2e 2d 2d 2d 2d 20 2d 2d 2d 2d 2d 0a 2d 2d 2d 2d 2d 20 2d 2d 2d 2d 2d 20 2e 2d 2d 2d 2d 20 2d 2d 2d 2d 2d 20 2d 2d 2d 2d 2d 20 2d 2d 2d 2d 2d 20 2d 2d 2d 2d 2d 20 2d 2d 2d 2d 2d 0a 2d 2d 2d 2d 2d 20 2d 2d 2d 2d 2d 20 2e 2d 2d 2d 2d 20 2e 2d 2d 2d 2d 20 2d 2d 2d 2d 2d 20 2d 2d 2d 2d 2d 20 2e 2d 2d 2d 2d 20 2d 2d 2d 2d 2d 0a 2d 2d 2d 2d 2d 20 2d 2d 2d 2d 2d 20 2e 2d 2d 2d 2d 20 2e 2d 2d 2d 2d 20 2d 2d 2d 2d 2d 20 2e 2d 2d 2d 2d 20 2e 2d 2d 2d 2d 20 2d 2d 2d 2d 2d 0a 2d 2d 2d 2d 2d 20 2d 2d 2d 2d 2d 20 2e 2d 2d 2d 2d 20 2d 2d 2d 2d 2d 20 2d 2d 2d 2d 2d 20 2d 2d 2d 2d 2d 20 2d 2d 2d 2d 2d 20 2d 2d 2d 2d 2d 0a 2d 2d 2d 2d 2d 20 2d 2d 2d 2d 2d 20 2e 2d 2d 2d 2d 20 2e 2d 2d 2d 2d 20 2d 2d 2d 2d 2d 20 2d 2d 2d 2d 2d 20 2e 2d 2d 2d 2d 20 2d 2d 2d 2d 2d 0a 2d 2d 2d 2d 2d 20 2d 2d 2d 2d 2d 20 2e 2d 2d 2d 2d 20 2e 2d 2d 2d 2d 20 2d 2d 2d 2d 2d 20 2e 2d 2d 2d 2d 20 2d 2d 2d 2d 2d 20 2e 2d 2d 2d 2d 0a 2d 2d 2d 2d 2d 20 2d 2d 2d 2d 2d 20 2e 2d 2d 2d 2d 20 2d 2d 2d 2d 2d 20 2d 2d 2d 2d 2d 20 2d 2d 2d 2d 2d 20 2d 2d 2d 2d 2d 20 2d 2d 2d 2d 2d 0a 2d 2d 2d 2d 2d 20 2d 2d 2d 2d 2d 20 2e 2d 2d 2d 2d 20 2e 2d 2d 2d 2d 20 2d 2d 2d 2d 2d 20 2d 2d 2d 2d 2d 20 2e 2d 2d 2d 2d 20 2d 2d 2d 2d 2d 0a 2d 2d 2d 2d 2d 20 2d 2d 2d 2d 2d 20 2e 2d 2d 2d 2d 20 2e 2d 2d 2d 2d 20 2d 2d 2d 2d 2d 20 2e 2d 2d 2d 2d 20 2e 2d 2d 2d 2d 20 2d 2d 2d 2d 2d 0a 2d 2d 2d 2d 2d 20 2d 2d 2d 2d 2d 20 2e 2d 2d 2d 2d 20 2d 2d 2d 2d 2d 20 2d 2d 2d 2d 2d 20 2d 2d 2d 2d 2d 20 2d 2d 2d 2d 2d 20 2d 2d 2d 2d 2d 0a 2d 2d 2d 2d 2d 20 2d 2d 2d 2d 2d 20 2e 2d 2d 2d 2d 20 2e 2d 2d 2d 2d 20 2e 2d 2d 2d 2d 20 2d 2d 2d 2d 2d 20 2d 2d 2d 2d 2d 20 2e 2d 2d 2d 2d
----- ----- .---- .---- ----- ----- .---- -----
----- ----- .---- .---- ----- .---- .---- -----
----- ----- .---- ----- ----- ----- ----- -----
----- ----- .---- .---- ----- .---- ----- .----
----- ----- .---- ----- ----- ----- ----- -----
----- ----- .---- .---- ----- ----- .---- -----
----- ----- .---- .---- ----- .---- .---- -----
----- ----- .---- ----- ----- ----- ----- -----
----- ----- .---- .---- .---- ----- ----- .----
----- ----- .---- ----- ----- ----- ----- -----
----- ----- .---- .---- ----- ----- .---- -----
----- ----- .---- .---- ----- .---- ----- .----
----- ----- .---- ----- ----- ----- ----- -----
----- ----- .---- .---- ----- ----- .---- -----
----- ----- .---- .---- ----- ----- .---- -----
----- ----- .---- ----- ----- ----- ----- -----
----- ----- .---- .---- ----- ----- .---- -----
----- ----- .---- .---- ----- .---- .---- -----
----- ----- .---- ----- ----- ----- ----- -----
----- ----- .---- .---- ----- ----- .---- -----
----- ----- .---- .---- ----- .---- ----- .----
----- ----- .---- ----- ----- ----- ----- -----
----- ----- .---- .---- ----- ----- .---- -----
----- ----- .---- .---- ----- .---- .---- -----
----- ----- .---- ----- ----- ----- ----- -----
----- ----- .---- .---- .---- ----- ----- .----
0 0 1 1 0 0 1 0
0 0 1 1 0 1 1 0
0 0 1 0 0 0 0 0
0 0 1 1 0 1 0 1
0 0 1 0 0 0 0 0
0 0 1 1 0 0 1 0
0 0 1 1 0 1 1 0
0 0 1 0 0 0 0 0
0 0 1 1 1 0 0 1
0 0 1 0 0 0 0 0
0 0 1 1 0 0 1 0
0 0 1 1 0 1 0 1
0 0 1 0 0 0 0 0
0 0 1 1 0 0 1 0
0 0 1 1 0 0 1 0
0 0 1 0 0 0 0 0
0 0 1 1 0 0 1 0
0 0 1 1 0 1 1 0
0 0 1 0 0 0 0 0
0 0 1 1 0 0 1 0
0 0 1 1 0 1 0 1
0 0 1 0 0 0 0 0
0 0 1 1 0 0 1 0
0 0 1 1 0 1 1 0
0 0 1 0 0 0 0 0
0 0 1 1 1 0 0 1
26 5 26 9 25 22 26 25 26 9
zeziyvzyzi
avarbeabar
rtcp{nineornone}
rtcp{nineornone}

Rules (Misc)

HomeのページのRulesにフラグフォーマットが記載されている。

rtcp{this_is_not_a_flag_i_think}

Spilled Milk (Misc)

Stegsolveで開き、Red plane 1を見る。
f:id:satou-y:20200429081326p:plain

rtcp{Th4nk$_f0r_h3LP1nG!}

Satan's Jigsaw (Misc)

90000個のファイルがあり、そのファイル名をlong_to_bytesで文字にすると、"100 100"というような画像の位置を表すような文字列になる。その情報から画像を復元する。

from PIL import Image
from Crypto.Util.number import *
import os

DIR = 'chall/'

output_img = Image.new('RGB', (300, 300), (255, 255, 255))

files = os.listdir(DIR)
for file in files:
    num = int(file.split('.')[0])
    place = long_to_bytes(num)
    x = int(place.split(' ')[0])
    y = int(place.split(' ')[1])
    input_img = Image.open(DIR + file)
    output_img.paste(input_img, (x, y))

output_img.save('flag.png')

f:id:satou-y:20200429081508p:plain
2つのQRコードが入った画像になったので、それぞれデコードしてみる。

左上:https://www.youtube.com/watch?v=dQw4w9WgXcQ
右下:rtcp{d1d-you_d0_7his_by_h4nd?}
rtcp{d1d-you_d0_7his_by_h4nd?}

What a Sight to See! (OSINT)

問題はこうなっている。

What Google Search Operator can I use to find results from a single website?

Googleで検索するときに、特定のウェブサイトを対象とする場合に指定するコマンドを答えればよい。

site:

The Drummer who Gave all his Daughters the Same Name (OSINT)

問題はこうなっている。

What is the value stored in the first registry key created by the virus Anna Kournikova?

調べてみると、例えば、以下のページにレジストリに登録される情報が載っている。

http://www.iwar.org.uk/comsec/resources/virus/vbsvirus.htm
Worm made with Vbswg 1.50b

Neko Hero (Forensics)

Stegsolveで開き、Green plane 0にすると、フラグが見えた。
f:id:satou-y:20200429082220p:plain

rtcp{s4vE_@LL_7h3m_c4tG1Rl$}

I don't like needles (Web)

HTMLソースを見ると、以下のコメントがある。

<!-- ?sauce -->

http://challs.houseplant.riceteacatpanda.wtf:30001/?sauceにアクセスする。

<?php

    // error_reporting(0);

    if (isset($_GET['sauce'])) {
        show_source("index.php");
        die();
    }

?>

<!DOCTYPE html>
<html>
<head>
    <title>Super secure login portal</title>

    <style>
        .container {
            position: absolute;
            left: 50%;
            top: 50%;
            transform: translate(-50%, -50%);
        }

        body {
            font-family: sans-serif;
        }

    </style>

</head>
<body>

    <div class="container">
    <h1>Super secure login portal</h1>

    <!-- ?sauce -->

    <?php

        if ($_SERVER['REQUEST_METHOD'] == "POST") {
            
            require("config.php");

            if (isset($_POST["username"]) && isset($_POST["password"])) {

                $username = $_POST["username"];
                $password = $_POST["password"];

                if (strpos($password, "1") !== false) {
                    echo "<p style='color: red;'>Auth fail :(</p>";
                } else {

                    $connection = new mysqli($SQL_HOST, $SQL_USER, $SQL_PASS, $SQL_DB);
                    $result = mysqli_query($connection, "SELECT * FROM users WHERE username='" . $username . "' AND password='" . $password . "'", MYSQLI_STORE_RESULT);
                    
                    if ($result === false) {
                        echo "<p style='color: red;'>I don't know what you did but it wasn't good.</p>";
                    } else {
                        if ($result->num_rows != 0) {
                            if (mysqli_fetch_array($result, MYSQLI_ASSOC)["username"] == "flagman69") {
                                echo "<p style='color: green;'>" . $FLAG . " :o</p>";
                            } else {
                                echo "<p style='color: green;'>Logged in :)</p>";
                            }
                        } else {
                            echo "<p style='color: red;'>Auth fail :(</p>";
                        }
                    }
                    
                }
            }
        }

    ?>

    <form method="POST">
        <span>Username: </span><input type="text" name="username">
        <br>
        <br>
        <span>Password: </span><input type="password" name="password">
        <br>
        <br>
        <input type="submit">
    </form>
    </div>

</body>
</html>

ユーザが"flagman69"でSQLで該当するデータを結果として得られれば、フラグが表示される。以下を入力してログインすると、フラグが表示された。

Username: flagman69' #
Password: a
rtcp{y0u-kn0w-1-didn't-mean-it-like-th@t}

Fragile (Reverse Engineering)

$ cat fragile.java
import java.util.*;

public class fragile
{
    public static void main(String args[]) {
        Scanner scanner = new Scanner(System.in);
        System.out.print("Enter flag: ");
        String userInput = scanner.next();
        String input = userInput.substring("rtcp{".length(),userInput.length()-1);
        if (check(input)) {
            System.out.println("Access granted.");
        } else {
            System.out.println("Access denied!");
        }
    }
    
    public static boolean check(String input){
        boolean h = false;
        String flag = "h1_th3r3_1ts_m3";
        String theflag = "";
        if(input.length() != flag.length()){
            return false;
        }
        for(int i = 0; i < flag.length(); i++){
            theflag += (char)((int)(flag.charAt(i)) + (int)(input.charAt(i)));
        }
        return theflag.equals("ÐdØÓ™§å’ÍaèÒÁ¡");
    }
}

flagと入力文字列の各文字を足した結果がわかっているので、逆算してフラグを算出する。

public class solve
{
    public static void main(String args[]) {
        String flag = "h1_th3r3_1ts_m3";
        String theflag = "DdOO?§a?IaeOA!?";

        String input = "";
        for(int i = 0; i < flag.length(); i++){
            input += (char)((int)(theflag.charAt(i)) - (int)(flag.charAt(i)));
        }
        System.out.println(String.format("rtcp{%s}", input));
    }
}
rtcp{h3y_1ts_n0t_b4d}

EZ (Reverse Engineering)

Pythonコードのコメントにフラグが書いてあった。

rtcp{tH1s_i5_4_r3aL_fL4g_s0_Do_sUbm1T_1t!}

PZ (Reverse Engineering)

Pythonコードのcheckpass()がTrueになるuserinputを確認すればよい。

rtcp{iT5_s1mPlY_1n_tH3_C0d3}

LEMON (Reverse Engineering)

Pythonコードのcheckpass()がTrueとなるよう文字列を結合していけばよい。

rtcp{y34H_tHiS_a1nT_sEcuR3}

SQUEEZY (Reverse Engineering)

Pythonコードのcheckpass()でTrueになるようにする。XORで暗号化しているが、keyと暗号化データがわかっているので、復号する。

import base64

def woah(s1, s2):
    return ''.join(chr(ord(a) ^ ord(b)) for a, b in zip(s1, s2))

key = 'meownyameownyameownyameownyameownya'
result = 'HxEMBxUAURg6I0QILT4UVRolMQFRHzokRBcmAygNXhkqWBw='

flag = woah(key, base64.b64decode(result))
print flag
rtcp{y0u_L3fT_y0uR_x0r_K3y_bEh1nD!}

thedanzman (Reverse Engineering)

Pythonコードのcheckpass()でTrueになるようにする。XOR暗号をしたり順序を逆にしたりしているので、逆順に復号していく。

import base64

def nope(s1, s2):
    return ''.join(chr(ord(a) ^ ord(b)) for a, b in zip(s1, s2))

def wow(x):
  return x[::-1]

key = 'nyameowpurrpurrnyanyapurrpurrnyanya'
key = key.encode('rot13')

result = '\'=ZkXipjPiLIXRpIYTpQHpjSQkxIIFbQCK1FR3DuJZxtPAtkR\'o'
d = wow(result)
c = d.decode('rot13')
c = c[2:-1]
b = base64.b64decode(c)
flag = nope(key, b)
print flag
rtcp{n0w_tH4T_w45_m0r3_cH4lL3NgiNG}

CH₃COOH (Crypto)

Vigenere暗号と推測し、https://www.guballa.de/vigenere-solverで復号する。

Vinegar is an aqueous solution of acetic acid and trace chemicals that may include flavorings.
Vinegar typically contains five to eight percent acetic acid by volume.
Usually the acetic acid is produced by the fermentation of ethanol or sugars by acetic acid bacteria.
There are many types of vinegar, depending on source materials.
Vinegar is now mainly used in the culinary arts: as a flavorful, acidic cooking ingredient, or in pickling.
As the most easily manufactured mild acid, it has historically had a wide variety of industrial and domestic uses (such as its use as a household cleaner).
While vinegar making may be as old as alcoholic brewing, the first documented evidence of vinegar making and use was by the Ancient Babylonians around 3000 BC.
They primarily made vinegar from dates, figs, and beer and used it for both culinary and medicinal purposes.
Traces of it also have been found in Egyptian urns.
In East Asia, the Chinese began professionalizing vinegar production in the Zhou Dynasty.
In the book Zhou Li, it mentions many noble or royal households had a “vinegar maker” as a specialized position.
Most vinegar making then was concentrated in what is now Shanxi province near the city Taiyuan which remains a famous vinegar making region today.
Many Chinese vinegars and their uses for culinary and medicinal purposes were written down in the agricultural manual Qimin Yaoshu.
rtcp{vinegar_on_my_fish_and_chips_please}

Key は "tony"で復号できた。

rtcp{vinegar_on_my_fish_and_chips_please}

”fences are cool unless they're taller than you” - tida (Crypto)

問題文からRail Fence cipherと推測できる。https://www.geocachingtoolbox.com/index.php?lang=en&page=railFenceCipherで復号する。
rail数は3で、少し調整する。

.t...a...t..._...u...i...w...i...
r.c.{.s._.i.a.a.o.t.r.c._.a.h.n.}
...p...k...d...b..._...e...s...g.
rtcp{ask_tida_about_rice_washing}

Returning Stolen Archives (Crypto)

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

with open('intercepted.txt', 'r') as f:
    n = eval(f.readline().rstrip().split(' = ')[1])
    e = eval(f.readline().rstrip().split(' = ')[1])
    ct = eval(f.readline().rstrip().split(' = ')[1])

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

print flag
rtcp{cH4r_bY_Ch@R!}

Rivest Shamir Adleman (Crypto)

RSA暗号の問題。コードを読むと、pの値がcomponent.txtに書かれている。素因数分解ができて、そのまま復号できる。

import json
from Crypto.Util.number import *

with open('public-key.json', 'r') as f:
    pubkey = json.load(f)

n = pubkey['n']
e = pubkey['e']

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

with open('component.txt', 'r') as f:
    p = int(f.read())

assert n % p == 0

q = n / p

phi = (p - 1) * (q - 1)
d = inverse(e, phi)
m = pow(c, d, n)
flag = long_to_bytes(m)
print flag

実行結果は以下の通り。

VERIFICATION-UpTheCuts-END
 .--.
/.-. '----------.
\'-' .--"--""-"-'
 '--'

rtcp{f1xed_pr*me-0r_low_e?}
rtcp{f1xed_pr*me-0r_low_e?}

Broken Yolks (Crypto)

スキュタレー暗号。https://www.dcode.fr/scytale-cipherで復号する。

SCRAMBLEDORFRIED
rtcp{scrambled_or_fried}

Parasite (Crypto)

モールス信号をデコードする。

JDUMEKKDFPUFPTKJEFLUGEUKCHKKUWFUBE

意味が通らない。問題文の不自然に大文字になっているところを見ると、SKATSになる。SKATSを調べると、韓国のモールス信号のようなものについても説明が記載されているサイトも多い。以下のURLを見ると、initialとmiddleとlastの3つの信号で、1つの文字になることがわかった。

https://github.com/cs09g/morsecode

スペースが3バイト以上の箇所が区切り文字と考えられる。

JDU/MEK/KDF/PUF/PTK/JEF/LU/GEUK/CHK/KUW/FU/BE

該当するハングル文字の部品のようなものを組み合わせる。上記のURLに記載されているコードを参考にコードにする。エンコードとデコードの計算の概要は以下のようになっている。

エンコード
・last = code % 28
・mid = (code - last) / 28 % 21
・init = ((code - last) / 28 - mid) / 21

デコード
・0xac00 + init * 21 * 28 + mid * 28 + last
init_list = ['.-..', '.-.. .-..', '..-.', '-...', '-... -...', '...-', '--',
    '.--', '.-- .--', '--.', '--. --.', '-.-', '.--.', '.--. .--.', '-.-.',
    '-..-', '--..', '---', '.---']

mid_list = ['.', '--.-', '..', '.. ..-', '-', '-.--', '...', '... ..-', '.-',
    '.- .', '.- --.-', '.- ..-', '-.', '....', '.... -', '.... .-..',
    '.... ..-', '.-.', '-..', '-.. ..-', '..-']

last_list = ['', '.-..', '.-.. .-..', '.-.. --.', '..-.', '..-. .--.',
    '..-. .---', '-...', '...-', '...- .-..', '...- --', '...- .--',
    '...- --.', '...- --..', '...- ---', '...- .---', '--', '.--', '', '--.', 
    '--. --.', '-.-', '.--.', '-.-.', '-..-', '--..', '---', '.---', '..-']

with open('Parasite.txt', 'r') as f:
    codes = f.read().split('   ')

msg = ''
for code in codes:
    words = code.strip().split('  ')
    index1 = init_list.index(words[0])
    if len(words) < 4:
        index2 = mid_list.index(words[1])
        if len(words) == 2:
            index3 = 0
        elif len(words) == 3:
            index3 = last_list.index(words[2])
    else:
        index2 == 1 # composit of word[1] and word[2] 
        index3 = last_list.index(words[3])
    uni = 0xac00 + index1 * 21 * 28 + index2 * 28 + index3
    msg += unichr(uni)

with open('flag.txt', 'wb') as f:
    f.write(msg)

コードを実行し復号すると、こうなる。

희망은진정한기싱충입니다

4つの部品を組み合わせるのがうまくいかなかった。以下の4つの部品を組み合わせハングル文字にしてみる。

・ㅅ
・ㅏ
・ㅣ
・ㅇ
→생

正しい復号結果は以下の通り。

희망은진정한기생충입니다

フラグはアルファベットなので、翻訳する。

Hope is a true parasite
rtcp{hope_is_a_true_parasite}

Sizzle (Crypto)

モールス信号にも見えるが、全部.-の記号が5文字ずつに区切られている。ベーコニアン暗号として復号してみる。
試した結果バージョン1の方で復号する必要がある。iとj、uとvの区別はつかないことにだけ気を付ける。

bacon = {'00000': 'a', '00001': 'b', '00010': 'c', '00011': 'd', '00100': 'e',
    '00101': 'f', '00110': 'g', '00111': 'h', '01000': 'i', '01001': 'k',
    '01010': 'l', '01011': 'm', '01100': 'n', '01101': 'o', '01110': 'p',
    '01111': 'q', '10000': 'r', '10001': 's', '10010': 't', '10011': 'u',
    '10100': 'w', '10101': 'x', '10110': 'y','10111': 'z'}

with open('encoded.txt', 'r') as f:
    codes = f.read()

codes = codes.replace('.', '0').replace('-', '1')
print codes
codes = codes.split(' ')

flag = ''
for code in codes:
    flag += bacon[code]

print flag

復号結果は以下の通り。

baconbutgrilledandmorsified
rtcp{bacon_but_grilled_and_morsified}

Rainbow Vomit (Crypto)

Hexahueというエンコードがあるらしい。http://kryptografie.de/kryptografie/chiffre/hexahue.htmにある換字表を元に復号する。

from PIL import Image

colors = {
    (255, 255, 255): '0', # white
    (255, 255,   0): '1', # yellow
    (255,   0, 255): '2', # purple
    (  0, 255, 255): '3', # light blue
    (255,   0,   0): '4', # red
    (  0, 255,   0): '5', # green
    (  0,   0, 255): '6', # blue
    (  0,   0,   0): '7', # black
    (128, 128, 128): '8', # gray
}

codes = {
    '000000': ' ',
    '245163': 'a',
    '425163': 'b',
    '452163': 'c',
    '451263': 'd',
    '451623': 'e',
    '451632': 'f',
    '541632': 'g',
    '514632': 'h',
    '516432': 'i',
    '516342': 'j',
    '516324': 'k',
    '156324': 'l',
    '165324': 'm',
    '163524': 'n',
    '163254': 'o',
    '163245': 'p',
    '613245': 'q',
    '631245': 'r',
    '632145': 's',
    '632415': 't',
    '632451': 'u',
    '362451': 'v',
    '326451': 'w',
    '324651': 'x',
    '324561': 'y',
    '324516': 'z',
    '700770': '.',
    '077007': ',',
    '780780': '0',
    '870780': '1',
    '807780': '2',
    '807870': '3',
    '807807': '4',
    '087807': '5',
    '078807': '6',
    '078087': '7',
    '078078': '8',
    '708078': '9',
}

def hexahue_decode(img, left, top):
    code = ''
    for y in range(3):
        for x in range(2):
            rgb = img.getpixel((left + x, top + y))
            code += colors[rgb]
    return codes[code]

img = Image.open('output.png').convert('RGB')
w, h = img.size

msg = ''
for y in range(2, h - 2, 3):
    for x in range(2, w - 2, 2):
        msg += hexahue_decode(img, x, y)

print msg

実行結果は以下の通り。

there is such as thing as a tomcat but have you ever heard of a tomdog. this is the most important question of our time, and unfortunately one that may never be answered by modern science. the definition of tomcat is a male cat, yet the name for a male dog is max. wait no. the name for a male dog is just dog. regardless, what would happen if we were to combine a male dog with a tomcat. perhaps wed end up with a dog that vomits out flags, like this one rtcp should,fl5g4,b3,st1cky,or,n0t
rtcp{should,fl5g4,b3,st1cky,or,n0t}

Survey (Misc)

アンケートに答えたら、暗号化文字列が表示された。

F(K6"+A-'QAKWC8DBNV(EZdbEF"&5>EbT#p?Z]jf?XmMd?Z9FkA78j

base85デコードする。

>>> import base64
>>> a = 'F(K6"+A-\'QAKWC8DBNV(EZdbEF"&5>EbT#p?Z]jf?XmMd?Z9FkA78j'
>>> base64.a85decode(a)
b'send Jade (in her DMs) `rice_tea_cat_panda`'

DicordでJadeボットに以下のメッセージを投げたら、フラグの返答があった。

rice_tea_cat_panda
rtcp{awaken_winged_sun_dragon_of_ra}