Luoingly's Space

BaseCTF 2024 新生赛 Week 1 个人题解

August 21, 2024 · Legacy Blog

只有第一周的题目,后面被拉去出题组了。

没有写题解的题不一定是不会做,很大概率是觉得不是特别有意思所以懒得做(写)了。

根本进不去啊!

悄悄告诉你: flag 在 flag.basectf.fun

进不去!怎么想都进不去吧?

直接一手 dig

$ dig -t TXT flag.basectf.fun

; <<>> DiG [REDACTED] <<>> -t TXT flag.basectf.fun
;; ... [REDACTED] ...

;; ANSWER SECTION:
flag.basectf.fun.       600     IN      TXT     "FLAG: BaseCTF{h0h0_th1s_15_dns_rec0rd}"

;; ... [REDACTED] ...

捂住 X 只耳

BaseCTF 解题指定 BGM。

题目给定一个音频文件,你会发现无论是听上去(指直接播放)、看上去(指直接看十六进制)或是莽上去(指把能想到的常见音频隐写工具全试一遍)都没有异常。事实上,该音频的左声道与右声道有着细微的差别。

将音频丢入 Auduacity,先把左右声道分开:

Screenshot-202408211402.webp

然后选中其中一个声道,应用上下反相效果:

Screenshot-202408211403.webp

接着选中两个声道,合并它们:

Screenshot-202408211404.webp

现在你应该很熟悉这是什么东西了:

Screenshot-202408211405.webp

人生苦短,我用 Python

Python 语法大杂烩,下面有请 ChatGPT 帮我写一下详细内容:

题目给定的代码实际上是在定义一组验证规则,我们可以通过逆向思考,逐步构造满足所有条件的字符串。下面是针对每个条件的解析,以及在这个条件下可以确定的字符串部分。

  1. 长度检查if len(flag) != 38

    • 字符串长度必须是 38。
    • ??????????????????????????????????????
  2. 前缀检查if not flag.startswith('BaseCTF{')

    • 字符串必须以 BaseCTF{ 开头。
    • BaseCTF{??????????????????????????????
  3. 特定位置字符检查if flag.find('Mp') != 10

    • 在索 10 的地方必须找到 Mp,因此索 10 开始的地方应该是 Mp
    • BaseCTF{??Mp??????????????????????????
  4. 后缀检查if flag[-3:] * 8 != '3x}3x}3x}3x}3x}3x}3x}3x}'

    • 最后三个字符必须是 3x}
    • BaseCTF{??Mp???????????????????????3x}
  5. 最后一个字符 ASCII 检查if ord(flag[-1]) != 125

    • 最后一个字符的 ASCII 值应该是 125,即 }
  6. 字符计数检查if flag.count('_') // 2 != 2

    • 下划线字符的个数除以 2 后必须等于 2,也就是说总共必须有 4 个下划线。
  7. 分段长度检查if list(map(len, flag.split('_'))) != [14, 2, 6, 4, 8]

    • 按照下划线分割后,每段的长度分别应该是 14, 2, 6, 4, 8。
    • BaseCTF{??Mp??_??_??????_????_?????3x}
  8. 间隔字符检查if flag[12:32:4] != 'lsT_n'

    • 从索引 12 到 32,每隔 4 个字符取一次,这些字符应该拼成 lsT_n
    • BaseCTF{??Mpl?_?s_??T???_???n_?????3x}
  9. Emoji 连接的大写检查if '😺'.join([c.upper() for c in flag[:9]]) != 'B😺A😺S😺E😺C😺T😺F😺{😺S'

    • 前 9 个字符转换为大写后应该是 BASECTF{S
  10. 数值幂运算检查if not flag[-11].isnumeric() or int(flag[-11]) ** 5 != 1024

    • 倒数第 11 个字符必须是数字且其五次方等于 1024,因此这个数字是 4
    • BaseCTF{??Mpl?_?s_??T???_??4n_?????3x}
  11. Base64 编码检查if base64.b64encode(flag[-7:-3].encode()) != b'MG1QbA=='

    • 倒数第 7 到第 3 个字符的 Base64 编码结果必须是 MG1QbA==
    • BaseCTF{??Mpl?_?s_??T???_??4n_?0mPl3x}
  12. 倒序切片检查if flag[::-7].encode().hex() != '7d4372733173'

    • 字符串每隔 7 个字符倒序排列后,其十六进制结果必须是 7d4372733173
    • BaseCTF{?1Mpl?_?s_??T??r_??4n_C0mPl3x}
  13. 集合检查if set(flag[12::11]) != {'l', 'r'}

    • 从索引 12 开始每隔 11 个字符取一次,这些字符的集合必须是 {'l', 'r'}
  14. 子串编码检查if flag[21:27].encode() != bytes([116, 51, 114, 95, 84, 104])

    • 索引 21 到 27 的子串必须是编码后的 [116, 51, 114, 95, 84, 104],即 t3r_Th
    • BaseCTF{?1Mpl?_?s_??Tt3r_Th4n_C0mPl3x}
  15. 加权和检查if sum(ord(c) * 2024_08_15 ** idx for idx, c in enumerate(flag[17:20])) != 41378751114180610

    • 计算从索 17 到 20 的字符加权和,要求结果等于 41378751114180610
    • BaseCTF{?1Mpl?_?s_BeTt3r_Th4n_C0mPl3x}
  16. 字符类型检查if not all([flag[0].isalpha(), flag[8].islower(), flag[13].isdigit()])

    • 第 1 个字符必须是字母,第 9 个字符必须是小写字母,第 14 个字符必须是数字。
    • BaseCTF{s1Mpl?_?s_BeTt3r_Th4n_C0mPl3x}
  17. 字符串替换检查if '{whats} {up}'.format(whats=flag[13], up=flag[15]).replace('3', 'bro') != 'bro 1'

    • 字符替换后应该得到 bro 1
    • BaseCTF{s1Mpl3_1s_BeTt3r_Th4n_C0mPl3x}
  18. SHA-1哈希检查if hashlib.sha1(flag.encode()).hexdigest() != 'e40075055f34f88993f47efb3429bd0e44a7f479'

    • 整个字符串的 SHA-1 哈希值必须等于 e40075055f34f88993f47efb3429bd0e44a7f479

基于这些条件,可以逐步构造出目标字符串,可以直接运行脚本来验证它是否符合所有条件。

Ez Xor

ChatGPT 是优秀的解混淆工具。

#include <bits/stdc++.h>

using namespace std;

int64_t KeyStream(int64_t key, int64_t stream, int length) {
    for (int i = 0; i < length; ++i) {
        *reinterpret_cast<uint8_t*>(stream + i) = i ^ *reinterpret_cast<uint8_t*>(key + i % 3);
    }
    return 1;
}

int64_t encrypt(int64_t input, int64_t output, int length) {
    for (int i = 0; i < length; ++i) {
        *reinterpret_cast<uint8_t*>(output + i) ^= *reinterpret_cast<uint8_t*>(input + length - i - 1);
    }
    return 1;
}

int64_t CheckFlag(int64_t original, int64_t encrypted, int length) {
    for (int i = 0; i < length; ++i) {
        if (*reinterpret_cast<uint8_t*>(original + i) != *reinterpret_cast<uint8_t*>(encrypted + i)) {
            return 0;
        }
    }
    return 1;
}

int main(int argc, const char **argv, const char **envp) {
    int64_t keyStreamBuffer[3] = {0};
    char inputBuffer[8] = {0};
    char expectedFlag[24] = {0};
    char key[] = "\"@;%"; 
    unsigned int inputLength = 0;
    unsigned int flagLength = strlen(expectedFlag);

    // 初始化预期的 flag
    *reinterpret_cast<int64_t*>(expectedFlag) = 0x1D0B2D2625050901;
    *reinterpret_cast<int64_t*>(expectedFlag + 8) = 0x673D491E20317A24;
    *reinterpret_cast<int64_t*>(expectedFlag + 16) = 0x34056E2E2508504D;

    cout << "Please input your answer: ";
    cin >> inputBuffer;

    inputLength = strlen(inputBuffer);

    if (inputLength == 28) {
        int64_t key = 7499608;
        KeyStream(reinterpret_cast<int64_t>(&key), reinterpret_cast<int64_t>(keyStreamBuffer), 28);
        encrypt(reinterpret_cast<int64_t>(keyStreamBuffer), reinterpret_cast<int64_t>(inputBuffer), inputLength);

        if (CheckFlag(reinterpret_cast<int64_t>(inputBuffer), reinterpret_cast<int64_t>(expectedFlag), flagLength)) {
            cout << "You are good!" << endl;
        } else {
            cout << "It's not the flag!" << endl;
        }
    } else {
        cout << "The length was wrong." << endl;
    }

    return 0;
}

ChatGPT 这写法… 算了,也不是不能看。

from Crypto.Util.number import long_to_bytes

def keystream(key, length):
    stream = [0] * length
    for i in range(length):
        stream[i] = i ^ key[i % 3]
    return bytes(stream)

def encrypt(input_data, key, length):
    encrypted = list(input_data)
    for i in range(length):
        encrypted[i] ^= key[length - i - 1]
    return bytes(encrypted)

cipher = \
    long_to_bytes(0x1D0B2D2625050901)[::-1] +\
    long_to_bytes(0x673D491E20317A24)[::-1] +\
    long_to_bytes(0x34056E2E2508504D)[::-1] +\
    b"\"@;%"

key = long_to_bytes(7499608)[::-1]
key_stream = keystream(key, 28)
flag = encrypt(cipher, key_stream, 28)

print(flag)

ez_maze

真就一迷宫。

#include <bits/stdc++.h>

using namespace std;

const char maze[] = "x$$$$$$$$$$$$$$"
                    "&&&&&&$$$$$$$$$"
                    "&$&$$&$$&&&&&$$"
                    "&$&$$$&&$$$$&$$"
                    "&$$$&&&$$$$$&$$"
                    "&$$$&$&&$&$$$$$"
                    "&$$$&$&$$&&&$$$"
                    "&&&&&$&&&&$&$$$"
                    "$$$$$$&&&&&&$$$"
                    "$$$$$$&$$$$$$$$"
                    "$$$&&&&$$&&&$$$"
                    "$$$&&&&&&&$$$$$"
                    "$$$$$$$$$&$$&$$"
                    "$$$$$$$$$&$&$$$"
                    "$$$$$$&&&&&&&&y";

int err_out_of_bounds()
{
    cout << "Invalid move: out of bounds!" << endl;
    return -1;
}

int err_invalid_input()
{
    cout << "Invalid input!" << endl;
    return -1;
}

int main()
{
    cout << "Can you walk the maze???" << endl;
    cout << "Take the shortest path to the finish line.OK?" << endl;
    cout << "Show your time!!!" << endl;

    char path[35];
    memset(path, 0, sizeof(path));

    // scanf("%34s", path);
    cin >> path;

    int pos = 0;
    for (int i = 0; path[i]; ++i)
    {
        char move = path[i];
        if (move == 'd')
        {
            if (pos % 15 == 14)
                return err_out_of_bounds();
            ++pos;
        }
        else if (move > 'd')
        {
            if (move == 's')
            {
                if (pos > 209)
                    return err_out_of_bounds();
                pos += 15;
            }
            else
            {
                if (move != 'w')
                    return err_invalid_input();
                if (pos <= 14)
                    return err_out_of_bounds();
                pos -= 15;
            }
        }
        else
        {
            if (move != 'a')
                return err_invalid_input();
            if (!(pos % 15))
                return err_out_of_bounds();
            --pos;
        }
        if (maze[pos] == '$')
        {
            cout << "Invalid move: hit a wall!" << endl;
            return -1;
        }
        if (maze[pos] == 'y')
        {
            cout << "You win!" << endl;
            cout << "plz BaseCTF{lower.MD5{your path}} by 32bit" << endl;
            return 0;
        }
    }
    cout << "You didn't reach the end." << endl;
    return 0;
}

x 是起点 y 是终点,$ 是墙,& 是路。自己看着迷宫走一下就行了。

BasePlus

Base64,但是自己指定了字符表并且还加了个 XOR:

#include <iostream>
#include <string>
#include <cstring>

using namespace std;

const string Secret = "/128GhIoPQROSTeUbADfgHijKLM+n0pFWXY456xyzB7=39VaqrstJklmNuZvwcdEC";

size_t Encode(const char *input, char *output)
{
    int inputLength = strlen(input);
    if (inputLength <= 0) return 0;

    size_t outputIndex = 0;
    int inputIndex = 0;

    while (inputIndex < inputLength)
    {
        unsigned char temp[3] = {0};
        int tempLength = 0;

        for (int i = 0; i < 3 && inputIndex < inputLength; ++i)
        {
            temp[i] = input[inputIndex++];
            ++tempLength;
        }

        output[outputIndex++] = Secret[(temp[0] >> 2) & 0x3F];
        output[outputIndex++] = Secret[((temp[0] & 0x03) << 4) | ((temp[1] >> 4) & 0x0F)];
        output[outputIndex++] = Secret[((temp[1] & 0x0F) << 2) | ((temp[2] >> 6) & 0x03)];
        output[outputIndex++] = Secret[temp[2] & 0x3F];

        for (int i = 0; i < 4; ++i)
            output[outputIndex - 4 + i] ^= 0xE;
    }

    output[outputIndex] = '\0';
    return outputIndex;
}

size_t Decode(const char *input, char *output)
{
    size_t inputLength = strlen(input);
    if (inputLength % 4 != 0) return 0;

    size_t outputIndex = 0;
    char decoded[4];

    for (size_t i = 0; i < inputLength; i += 4)
    {
        for (int j = 0; j < 4; ++j)
            decoded[j] = input[i + j] ^ 0xE;

        int index1 = Secret.find(decoded[0]);
        int index2 = Secret.find(decoded[1]);
        int index3 = Secret.find(decoded[2]);
        int index4 = Secret.find(decoded[3]);

        if (index1 == string::npos || index2 == string::npos || index3 == string::npos || index4 == string::npos) return 0;

        output[outputIndex++] = (index1 << 2) | (index2 >> 4);
        if (decoded[2] != Secret[64])
            output[outputIndex++] = ((index2 & 0x0F) << 4) | (index3 >> 2);
        if (decoded[3] != Secret[64]) 
            output[outputIndex++] = ((index3 & 0x03) << 6) | index4;
    }

    output[outputIndex] = '\0';
    return outputIndex;
}

int main()
{
    char decodedOutput[100];
    const char *input = "lvfzBiZiOw7<lhF8dDOfEbmI]i@bdcZfEc^z>aD!";
    size_t encodedLength = Decode(input, decodedOutput);

    cout << "Decoded: " << decodedOutput << endl;
    return 0;
}

麻烦 ChatGPT 写一下 Decoder 又不是什么难事。

Tags: #CTF #Writeup #Misc #Reverse #BaseCTF

This article is authored by luoingly and licensed under CC BY-NC 4.0

Permalink: https://luoy.ing/posts/basectf-2024-week1-writeup/