[DreamHack] [ALL] Yorix' Light Novel Club

image.png

image.png

분야: Reversing, Web, Crypto, Pwnable

테크닉: 백도어, C++, REST API, AES-CBC IV Known Plaintext Attack, OpenSSL RSA Structure, AAR

난이도: 최상

WaRP CTF에 출제됐던 문제로, 난이도가 상당히 어려웠다.

라이트 노벨을 읽을 수 있는 웹 서비스가 주어진다. 백엔드는 C++로 만들어져 있으며, Crow 프레임워크를 사용한다.

먼저, Entrypoint가 정의되어 있는 main.cpp의 구조를 보면,

#include "crow.h"
#include "modules/consts.h"
#include <setjmp.h>

int main() {

    crow::SimpleApp app;
    app.loglevel(crow::LogLevel::CRITICAL); // As we don't want to see any logs
    auto books = readDirectory("books");

    CROW_ROUTE(app, "/")
    ([]() {
        crow::json::wvalue x;
        x["message"] = "Welcome to my club!";
        return crow::response(x);
    });

    CROW_ROUTE(app, "/list")
    ([&books]() {
        crow::json::wvalue x;
        std::vector<std::string> filenames;
        
        for(const auto& [hash, pair] : books) {
            filenames.push_back(pair.first);
        }
        
        x["message"] = std::move(filenames);
        return crow::response(x);
    });

    CROW_ROUTE(app, "/download")
    ([]() {
        crow::json::wvalue x;
        x["error"] = "Parameters required";
        return crow::response(400, x);
    });

    CROW_ROUTE(app, "/download/<string>")
    ([&books](const crow::request& req, std::string hash) {
        crow::json::wvalue x;
        auto password_raw = req.url_params.get("password");
        if (!password_raw) {
            x["error"] = "Password required";
            return crow::response(400, x);
        }
        
        std::string password = password_raw;

        auto it = books.find(hash);
        if (it == books.end()) {
            x["error"] = "Not found";
            return crow::response(404, x);
        }

        if (it->second.first == "이세계로 전생했더니 순정 모험 라이프.pdf") {
            x["error"] = "Not allowed";
            return crow::response(403, x);
        }

        try {
            // Generate yorixpub with the file content
            auto pub = generate_yorixpub(
                &public_key,
                it->first,  // hash
                "YORIX_MAGIC_HEADER" + it->second.second, // content
                password          // password value
            );

            std::string result = export_yorixpub(pub);
            x["message"] = bytes_to_hex((const unsigned char*)"YPUB", 4) + result;
            
            // Clean up
            free_yorixpub(pub);
            
            return crow::response(x);
            
        } catch (const std::exception& e) {
            x["error"] = e.what();
            return crow::response(500, x);
        }
    });

    CROW_ROUTE(app, "/view")
    .methods("POST"_method)
    ([](const crow::request& req) {
        crow::json::wvalue x;
        
        try {
            // Parse request body as JSON
            auto body = crow::json::load(req.body);
            
            // Check if yorixpub field exists
            if (!body.has("yorixpub") || !body.has("password")) {
                x["error"] = "Missing yorixpub or password field";
                return crow::response(400, x);
            }

            std::string hex_data = body["yorixpub"].s();
            std::string password = body["password"].s();
            
            // Parse yorixpub from hex
            auto pub = parse_yorixpub(&public_key, hex_data);
            if (!pub) {
                x["error"] = "Invalid yorixpub data";
                return crow::response(400, x);
            }

            // View yorixpub content
            auto content = view_yorixpub(&private_key, pub, password);
            x["message"] = content;
            
            // Clean up
            free_yorixpub(pub);
            
            return crow::response(x);
            
        } catch (const std::exception& e) {
            x["error"] = e.what();
            return crow::response(500, x);
        }
    });

    app.port(4444).run();
    return 0;
}

Warp 프레임워크랑 헷갈릴 수 있다. 왜냐하면 빌드 스크립트에 다음과 같은 문구가 있기 때문이다.

cd build
cmake ..
make

# For faking the name to [warp](<https://github.com/seanmonstar/warp>)
sed -i 's/crow/warp/gI' ./club_server

아무튼 코드에서 보이다시피

여기서 이 바이너리의 컨셉을 알아야 한다.

기본적으로 유저는 다음 프로시저를 통해 라이트 노벨을 다운로드하고 볼 수 있다.