All Articles

Pwnme CTF 2025 Writeup

もくじ

Back to the past(Rev)

Using the provided binary and the encrypted file, find a way to retrieve the flag contained in “flag.enc”. Note that the binary would have been run in May 2024. Note: The flag is in the format PWNME{…}

問題バイナリとして、ファイルを暗号化する機能を持った ELF ファイルと暗号化された Flag ファイルが与えられます。

この ELF を解析してみると、プログラムの実行時刻から srand で生成したシードを使用し、rand 関数で生成した乱数を用いて暗号化操作を行っていることがわかります。

image-20250301220422147

Flag ファイルが暗号化されたのはファイルのタイムスタンプから 2024 年の 5 月であることがわかるので、単純に srand と rand を使用してファイルの復号を行う Solver を実装すればよさそう、、、と思ったものの上手く Flag を復号することができませんでした。

gdb を使用して詳しく確認してみたところ、手元の環境で使用しているライブラリの srand/rand 関数を使用した場合に同じタイムスタンプから生成できる乱数が、問題バイナリに静的リンクされている srand/rand 関数が生成する乱数と一致しないことが原因でした。

そこで、問題バイナリの srand/rand 関数をリバーシングしていくことにします。

image-20250301220439451

上記のコードをカスタム関数として作成した Solver が以下の通りです。

最終的に、Thu May 09 2024 05:01:17 GMT+0900 のタイムスタンプで生成した乱数で正しい Flag を復号できる Key を取得できます。

#include <stdio.h>
#include <stdint.h>
#include <time.h>

uint64_t seed;
uint64_t result;
int32_t r;
int32_t key;
int32_t tmp;

uint64_t custom_srand(s) {
    seed = s - 1;
    return 0;
}

uint64_t custom_rand(){
    result = 0x5851f42d4c957f2d * seed + 1;
    seed = result;
    return result >> 0x21;
}

int32_t gen_key(){
    r = custom_rand();
    tmp = r / 0x7f;
    key = (r - ((int8_t)(tmp << 7) - tmp)) & 0xFF;
    return key;
}

int main(void){
    struct tm time_info = {0};  // 時刻構造体をゼロで初期化
    time_info.tm_year = 2024 - 1900;  // 年(1900年からの年数)
    time_info.tm_mon  = 5 - 1;        // 月(0 = 1月, 1 = 2月, ..., 4 = 5月)
    time_info.tm_mday = 9;            // 日
    time_info.tm_hour = 22;           // 時(24時間制)
    time_info.tm_min  = 1;            // 分
    time_info.tm_sec  = 16;           // 秒
    setenv("TZ", "Asia/Tokyo", 1);
    tzset();
    time_t epoch_time = mktime(&time_info);

    // target = 0x70 0x63 0x42 0x50 0x6a
    // for (int i = 0 ; i < 60*60*24; i++) {
    //     custom_srand(epoch_time-i);
    //     if (gen_key() == 0x70) {
    //         if (gen_key() == 0x63) {
    //             if (gen_key() == 0x42) {
    //                 printf("%d\n", epoch_time-i);
    //                 break;
    //             }
    //         }
    //     }
    // }

    epoch_time = 1715198477;
    printf("keys = [");
    for (int i = 0; i < 2; i++) {
        custom_srand(epoch_time-i);
        printf("[");
        for (int j = 0; j < 40; j++) {
            int32_t r = custom_rand();
            int32_t tmp = r / 0x7f;
            int32_t key = (r - ((int8_t)(tmp << 7) - tmp)) & 0xFF;
            
            if (j != 39) { printf("%d,", key); }
            else { printf("%d", key); }
        }
        if (i != 1) { printf("],"); }
        else { printf("]"); }
    }
    printf("]\n");
    
    return;
}

上記のコードを使用して生成した乱数を Key とした Solver は以下の通りです。

keys = [[112,99,66,80,106,86,57,41,90,9,43,34,111,95,73,32,112,54,87,46,112,74,53,81,36,29,8,83,76,20,114,97,55,89,121,109,60,22,22,56],[101,30,103,28,88,32,47,8,114,95,21,97,98,2,42,21,36,12,88,119,99,108,97,45,5,41,65,101,15,61,86,32,70,7,106,64,78,45,43,86]]

with open("flag.enc", "rb") as f:
    data = f.read()

for key in keys:
    tmp = ""
    for i,d in enumerate(data):
        tmp += chr(d^key[i])
    if "PWNME" in tmp:
        print(tmp)

これで正しい Flag を取得できました。

image-20250301220404196

まとめ

ほんとは dlopen 的な感じでスマートに元のバイナリのコードを流用したかったのですが、ライブラリファイルでない上にエクスポートされていない関数がターゲットであるためうまくいきませんでした。

方法模索中、、、