自分が解けたRevの問題です。
他のメンバーのおかげで、チームとしてRev全完
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?
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が自動生成される。
defineHandler({
onEnter(log, args, state) {
log('Sleep()');
},
onLeave(log, retval, state) {
}
});
これは、Sleepが呼び出されたときに実行してくれるJSらしい。
ここで、Sleepの時間を0にしてあげるとフラグが取れそう
Copilotに投げてみる。
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の引数を確認する。
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]に正しいファイルパスを入力する。
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関数も実行されて、よさげ
次に、WriteFile関数の詳細を見る。
WriteFile関数の中身
0.HANDLE hFile, // ファイルハンドル
1.LPCVOID pBuffer, // バッファアドレス
2.DWORD nNumberOfBytesToWrite, // サイズ
3.LPDWORD pNumberOfBytesWritten, // 実際のサイズを格納する変数
4.LPOVERLAPPED pOverlapped // OVERLAPPED構造体
ファイルハンドルは、CreateFileからそのまま入力されるみたい。
pBufferの中身に書き込む内容がある。
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された何かが出てきたっぽい
↑元に戻してフラグゲット
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言語にしてもらう。
#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 ビットの比較をして、間違っていたら即終了。
比較をなくして、比較元を出力するように書き換える。
#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;
}
いつも通りCyberChef↓
フラグゲット
picoCTF{0n3_bi7_4t_a_7im3}