picoCTF2025 [Rev]

:::note info
自分が解けた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](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3810599/6067d0e6-541b-4ecd-a6f9-d0046e4b36fc.png)

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

https://chokuto.ifdef.jp/urawaza/api/Sleep.html

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

使用するのが「frida-trace」

https://blogs.blackberry.com/ja/jp/2021/05/malware-analysis-with-dynamic-binary-instrumentation-frameworks

Sleepの概要から、使用されているDLLはKERNEL32.DLLなので、シェル上で
```sh
frida-trace -f .\bininst1.exe -i KERNEL32.DLL!Sleep
```
を実行して呼び出されているかどうかを確認する。
```sh
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行追加でいいらしい。
ということで、再度、
```sh
frida-trace -f .\bininst1.exe -i KERNEL32.DLL!Sleep
```
を行うことで、Sleep.jsが既に存在するので適用され、Sleepが呼び出されたときに、JavaScriptが実行されるようになる。

```sh
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されたフラグをゲット。

↓結果

https://gchq.github.io/CyberChef/#recipe=From_Base64('A-Za-z0-9%2B/%3D',true,false)&input=Y0dsamIwTlVSbnQzTkd0bFgyMHpYM1Z3WDNjeGRHaGZabkl4WkdGZlpqSTNZV05qTXpoOQ

> 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ということがわかる。

そこで、
```sh
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) {
  }
});
```
すると、
```sh
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) {
  }
});
```

もう一度、実行してみる。
```sh
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関数の詳細を見る。

:::note info
WriteFile関数の中身

０．HANDLE   hFile,                  // ファイルハンドル
１．LPCVOID  pBuffer,                // バッファアドレス
２．DWORD    nNumberOfBytesToWrite,  // サイズ
３．LPDWORD  pNumberOfBytesWritten,  // 実際のサイズを格納する変数
４．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) {
  }
});
```
実行すると...
```sh
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:

https://gchq.github.io/CyberChef/#recipe=From_Base64('A-Za-z0-9%2B/%3D',true,false)&input=Y0dsamIwTlVSbnRtY2pGa1lWOW1NSEpmWWpGdVgybHVOWFJ5ZFcwemJuUTBkR2x2YmlGZllqSXhZV1ZtTXpsOQ

↑元に戻してフラグゲット
> picoCTF{fr1da_f0r_b1n_in5trum3nt4tion!_b21aef39}

## perplexed
**問題**
Download the binary here.

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

```c

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](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3810599/16207598-6721-48ac-9ff4-a55acaa2897c.png)

いつも通りCyberChef↓

https://gchq.github.io/CyberChef/#recipe=From_Binary('Space',7)&input=MTExMDAwMDExMDEwMDExMTAwMDExMTEwMTExMTEwMDAwMTExMDEwMTAwMTAwMDExMDExMTEwMTEwMTEwMDAwMTEwMTExMDAxMTAwMTExMDExMTExMTEwMDAxMDExMDEwMDEwMTEwMTExMTAxMTExMTAxMTAxMDAxMTEwMTAwMTAxMTExMTExMDAwMDExMDExMTExMDExMDExMTExMDEwMDExMTAxMTAxMDExMDAxMTExMTExMDEwMA

フラグゲット
> picoCTF{0n3_bi7_4t_a_7im3}