

분야: 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
아무튼 코드에서 보이다시피
/list 에서 현재 볼 수 있는 (서버에 저장되어 있는, readDirectory("books") 의 반환값인) 라이트 노벨의 목록을 확인할 수 있다,/download 에서 원하는 책을 다운로드 받을 수 있다./view 에서 원하는 책을 업로드하여 볼 수 있다.여기서 이 바이너리의 컨셉을 알아야 한다.
기본적으로 유저는 다음 프로시저를 통해 라이트 노벨을 다운로드하고 볼 수 있다.