1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

picoCTF2025 [Rev]

Last updated at Posted at 2025-03-18

自分が解けたRevの問題です。
他のメンバーのおかげで、チームとしてRev全完:pray:

Binary Instrumentation 1

問題
I have been learning to use the Windows API to do cool stuff! Can you wake up my program to get the flag?

解法
配布されたファイルを実行してみる
image.png

ZZZ...
ということで目覚めたらフラグをくれそう。
WindowAPIのSleepの呼び出しをバイパスして無理やり起こすのが正しそう。
Sleep関数の概要はこんな感じらしい。。。

ここで、Sleepが使われていると仮定して、その動作について確認する。

使用するのが「frida-trace」

Sleepの概要から、使用されているDLLはKERNEL32.DLLなので、シェル上で

frida-trace -f .\bininst1.exe -i KERNEL32.DLL!Sleep

を実行して呼び出されているかどうかを確認する。

Started tracing 1 function. Web UI available at http://localhost:60375/
Hi, I have the flag for you just right here!
I'll just take a quick nap before I print it out for you, should only take me a decade or so!
zzzzzzzz....
           /* TID 0x4e40 */
   107 ms  Sleep()

やはりSleepが呼び出されている。

また、それに伴い、handlers\KERNEL32.DLL\Sleep.jsが自動生成される。

Sleep.js
defineHandler({
  onEnter(log, args, state) {
    log('Sleep()');
  },

  onLeave(log, retval, state) {
  }
});

これは、Sleepが呼び出されたときに実行してくれるJSらしい。
ここで、Sleepの時間を0にしてあげるとフラグが取れそう

Copilotに投げてみる。

Sleep.js
defineHandler({
  onEnter(log, args, state) {
    args[0] = ptr(0);  // Sleep を 0 に変更して即時復帰
  },

  onLeave(log, retval, state) {
  }
});

1行追加でいいらしい。
ということで、再度、

frida-trace -f .\bininst1.exe -i KERNEL32.DLL!Sleep

を行うことで、Sleep.jsが既に存在するので適用され、Sleepが呼び出されたときに、JavaScriptが実行されるようになる。

Started tracing 1 function. Web UI available at http://localhost:60494/
Hi, I have the flag for you just right here!
I'll just take a quick nap before I print it out for you, should only take me a decade or so!
zzzzzzzz....
Ok, I'm Up! The flag is: cGljb0NURnt3NGtlX20zX3VwX3cxdGhfZnIxZGFfZjI3YWNjMzh9
Process terminated

起きてくれた。
Base64されたフラグをゲット。

↓結果

picoCTF{w4ke_m3_up_w1th_fr1da_f27acc38}

Binary Instrumentation 2

問題

I've been learning more Windows API functions to do my bidding. Hmm... I swear this program was supposed to create a file and write the flag directly to the file. Can you try and intercept the file writing function to see what went wrong?

解法
ファイルを作って書き込むらしい。
ファイル作成の関数は、先ほどのAPIのサイトから、KERNEL32.DLLのCreateFileA
ファイルの書き込み関数はWriteFileということがわかる。

そこで、

frida-trace -f .\bininst2.exe -i KERNEL32.DLL!CreateFileA -i KERNEL32.DLL!WriteFile

を2回実行するとWriteFileのみトレースできる。
ファイルを書き込むWriteFileが実行されないところを見ると、

ファイルに書き込む権限がない or そもそもファイルが作られてない

のどっちかでエラーで止まってそう。

そこで、自動作成されるファイルである「handlers\KERNEL32.DLL\CreateFileA.js」の中身を編集して、CreateFileAの引数を確認する。

CreateFileA.js
defineHandler({
  onEnter(log, args, state) {
    log('CreateFileA()');
    const filename = args[0].readUtf8String();
    log('filename: ' + filename);
  },

  onLeave(log, retval, state) {
  }
});

すると、

filename: <Insert path here>

ファイルパスの指定がおかしい。
このエラーで止まってたぽい。

args[0]に正しいファイルパスを入力する。

CreateFileA.js
defineHandler({
  onEnter(log, args, state) {
    log('CreateFileA()');
    const filename = args[0].readUtf8String();
    log('filename: ' + filename);
    var pathh = "D:\\CTF\\pico\\2025\\bininst2\\ans.txt";
    this.pathmemory = Memory.allocUtf16String(pathh);
    args[0] = this.pathmemory;
    log("renew: " +Memory.readUtf16String(args[0]));
  },

  onLeave(log, retval, state) {
  }
});

もう一度、実行してみる。

frida-trace -f .\bininst2.exe -i KERNEL32.DLL!CreateFileA -i KERNEL32.DLL!WriteFile
・・・<中略>・・・
    12 ms  filename: <Insert path here>
    12 ms  CreateFileA() new lpFileName: D:\CTF\pico\2025\bininst2\ans.txt
    12 ms  WriteFile()

Write関数も実行されて、よさげ:relaxed:
次に、WriteFile関数の詳細を見る。

WriteFile関数の中身

0.HANDLE hFile, // ファイルハンドル
1.LPCVOID pBuffer, // バッファアドレス
2.DWORD nNumberOfBytesToWrite, // サイズ
3.LPDWORD pNumberOfBytesWritten, // 実際のサイズを格納する変数
4.LPOVERLAPPED pOverlapped // OVERLAPPED構造体

参照
https://chokuto.ifdef.jp/urawaza/api/WriteFile.html

ファイルハンドルは、CreateFileからそのまま入力されるみたい。
pBufferの中身に書き込む内容がある。

WriteFile.js
defineHandler({
  onEnter(log, args, state) {
    log('WriteFile()');
    const nakami = args[1];
    log('中身: ' + Memory.readUtf8String(nakami));
  },

  onLeave(log, retval, state) {
  }
});

実行すると...

frida-trace -f .\bininst2.exe -i KERNEL32.DLL!CreateFileA -i KERNEL32.DLL!WriteFile
    ・・・<中略>・・・
    14 ms  CreateFileA()
    14 ms  filename: <Insert path here>
    14 ms  renew: D:\CTF\pico\2025\bininst2\ans.txt
    14 ms  WriteFile()
    14 ms  中身: cGljb0NURntmcjFkYV9mMHJfYjFuX2luNXRydW0zbnQ0dGlvbiFfYjIxYWVmMzl9

Base64された何かが出てきたっぽい:dark_sunglasses:

↑元に戻してフラグゲット

picoCTF{fr1da_f0r_b1n_in5trum3nt4tion!_b21aef39}

perplexed

問題
Download the binary here.

解法
Ghidraで逆コンパイル。
main関数と、check関数が見える。check関数で、入力が正しければCollect!:)と出るっぽい。
それ以外のprintfはないから、入力がそのままフラグになりそう。


undefined8 check(char *param_1)

{
  size_t sVar1;
  undefined8 uVar2;
  size_t sVar3;
  char local_58 [36];
  uint local_34;
  uint local_30;
  undefined4 local_2c;
  int local_28;
  uint local_24;
  int local_20;
  int local_1c;
  
  sVar1 = strlen(param_1);
  if (sVar1 == 0x1b) {
    local_58[0] = -0x1f;
    local_58[1] = -0x59;
    local_58[2] = '\x1e';
    local_58[3] = -8;
    local_58[4] = 'u';
    local_58[5] = '#';
    local_58[6] = '{';
    local_58[7] = 'a';
    local_58[8] = -0x47;
    local_58[9] = -99;
    local_58[10] = -4;
    local_58[0xb] = 'Z';
    local_58[0xc] = '[';
    local_58[0xd] = -0x21;
    local_58[0xe] = 'i';
    local_58[0xf] = 0xd2;
    local_58[0x10] = -2;
    local_58[0x11] = '\x1b';
    local_58[0x12] = -0x13;
    local_58[0x13] = -0xc;
    local_58[0x14] = -0x13;
    local_58[0x15] = 'g';
    local_58[0x16] = -0xc;
    local_1c = 0;
    local_20 = 0;
    local_2c = 0;
    for (local_24 = 0; local_24 < 0x17; local_24 = local_24 + 1) {
      for (local_28 = 0; local_28 < 8; local_28 = local_28 + 1) {
        if (local_20 == 0) {
          local_20 = 1;
        }
        local_30 = 1 << (7U - (char)local_28 & 0x1f);
        local_34 = 1 << (7U - (char)local_20 & 0x1f);
        if (0 < (int)((int)param_1[local_1c] & local_34) !=
            0 < (int)((int)local_58[(int)local_24] & local_30)) {
          return 1;
        }
        local_20 = local_20 + 1;
        if (local_20 == 8) {
          local_20 = 0;
          local_1c = local_1c + 1;
        }
        sVar3 = (size_t)local_1c;
        sVar1 = strlen(param_1);
        if (sVar3 == sVar1) {
          return 0;
        }
      }
    }
    uVar2 = 0;
  }
  else {
    uVar2 = 1;
  }
  return uVar2;
}

ChatGPT大先生にC言語にしてもらう。

check.c
#include <stdio.h>
#include <string.h>
#include <stdint.h>
#include <stdbool.h>
int check(char *param_1) {
    size_t len = strlen(param_1);
    if (len != 0x1b) {
        return 1;
    }
    // マジック値の定義(元のバイナリと同じ値)
    unsigned char magic_bytes[27];
    uint64_t part1 = 0x617b2375f81ea7e1;
    uint64_t part2 = 0x69df5b5afc9db9;
    uint8_t part3 = 0xd2;
    uint64_t part4 = 0xf467edf4ed1bfe;
    // マジック値をバイト配列に変換
    memcpy(magic_bytes, &part1, 8);
    memcpy(magic_bytes + 8, &part2, 7);
    magic_bytes[15] = part3;
    memcpy(magic_bytes + 16, &part4, 7);
    int char_index = 0;  // 入力文字列の現在の位置
    int bit_position = 0;  // 現在のビット位置
    // 0x17 (23) バイト分ループ
    for (uint32_t i = 0; i < 0x17; i++) {
        // 各バイトの8ビットをループ
        for (int j = 0; j < 8; j++) {
            if (bit_position == 0) {
                bit_position = 1;
            }
            // ビットマスクの計算
            uint32_t magic_bit_mask = 1 << (7 - j);
            uint32_t input_bit_mask = 1 << (7 - bit_position);
            // マジックバイトのビットと入力のビットを比較
            bool magic_bit_set = (magic_bytes[i] & magic_bit_mask) > 0;
            bool input_bit_set = (param_1[char_index] & input_bit_mask) > 0;
            // ビットが一致しなければ失敗
             if (magic_bit_set != input_bit_set) {
                 return 1;
            }
            // 次のビットへ
            bit_position++;
            if (bit_position == 8) {
                bit_position = 0;
                char_index++;
            }
            // 入力の終わりに達した場合は成功
            if ((size_t)char_index == len) {
                return 0;
            }
        }
    }
    return 0;  // すべてのチェックに合格
}
int main(){
    char input[100];
    printf("Input: ");
    scanf("%s", input);
    if (check(input) == 0) {
        printf("Correct!\n");
    } else {
        printf("Incorrect!\n");
    }
    return 0;
}

パスワードが27文字以外 or ビットの比較をして、間違っていたら即終了。
比較をなくして、比較元を出力するように書き換える。

check2.c
#include <stdio.h>
#include <string.h>
#include <stdint.h>
#include <stdbool.h>
int check(char *param_1) {
    size_t len = strlen(param_1);
    if (len != 0x1b) {
        printf("ながさちがい");
        return 1;
    }
    // マジック値の定義(元のバイナリと同じ値)
    unsigned char magic_bytes[27];
    uint64_t part1 = 0x617b2375f81ea7e1;
    uint64_t part2 = 0x69df5b5afc9db9;
    uint8_t part3 = 0xd2;
    uint64_t part4 = 0xf467edf4ed1bfe;
    // マジック値をバイト配列に変換
    memcpy(magic_bytes, &part1, 8);
    memcpy(magic_bytes + 8, &part2, 7);
    magic_bytes[15] = part3;
    memcpy(magic_bytes + 16, &part4, 7);
    int char_index = 0;  // 入力文字列の現在の位置
    int bit_position = 0;  // 現在のビット位置
    // 0x17 (23) バイト分ループ
    for (uint32_t i = 0; i < 0x17; i++) {
        // 各バイトの8ビットをループ
        for (int j = 0; j < 8; j++) {
            if (bit_position == 0) {
                bit_position = 1;
            }
            // ビットマスクの計算
            uint32_t magic_bit_mask = 1 << (7 - j);
            uint32_t input_bit_mask = 1 << (7 - bit_position);
            // マジックバイトのビットと入力のビットを比較
            bool magic_bit_set = (magic_bytes[i] & magic_bit_mask) > 0;
            bool input_bit_set = (param_1[char_index] & input_bit_mask) > 0;
            // ビットが一致しなければ失敗
            // if (magic_bit_set != input_bit_set) {
            //     return 1;
            // }
            printf("%d", magic_bit_set);
            // 次のビットへ
            bit_position++;
            if (bit_position == 8) {
                bit_position = 0;
                char_index++;
            }
            // 入力の終わりに達した場合は成功
            if ((size_t)char_index == len) {
                return 0;
            }
        }
    }
    return 0;  // すべてのチェックに合格
}
int main(){
    char input[100];
    printf("Input: ");
    scanf("%s", input);
    if (check(input) == 0) {
        printf("Correct!\n");
    } else {
        printf("Incorrect!\n");
    }
    return 0;
}

実行してみる。
image.png

いつも通りCyberChef↓

フラグゲット

picoCTF{0n3_bi7_4t_a_7im3}

1
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?