問題概要
picoCTFの「PIE TIME」という問題の解説記事です。
- カテゴリ: Binary Exploitation
- 難易度: Easy
問題文
解説
ステップ1: picoCTFのサーバーに接続
まず、問題文に記載されているサーバーに接続します。
ncコマンドを使用して、指定されたホストとポートに接続します。$ nc rescued-float.picoctf.net xxxxx
※ポート番号は問題文に記載されているものを使用してください。
接続すると、以下のように表示されます。
$ nc rescued-float.picoctf.net 65086
Address of main: 0x5ed0810d333d
Enter the address to jump to, ex => 0x12345:
※ポートの番号やアドレスは環境に異なります。
最初に表示される
実行時に配置されているメモリアドレス を示しています。
PIE(Position Independent Executable)が有効なため、このアドレスは毎回異なりますが、
後述するアドレス計算の基準として利用できます。
Address of main は、プログラムの main 関数が実行時に配置されているメモリアドレス を示しています。
PIE(Position Independent Executable)が有効なため、このアドレスは毎回異なりますが、
後述するアドレス計算の基準として利用できます。
続いて表示される
ユーザーに対して ジャンプ先のアドレスを直接入力するよう求めている ことを意味します。
入力された値は関数のアドレスとして解釈され、そのアドレスに処理が移動します。
Enter the address to jump to, ex => 0x12345: は、ユーザーに対して ジャンプ先のアドレスを直接入力するよう求めている ことを意味します。
入力された値は関数のアドレスとして解釈され、そのアドレスに処理が移動します。
ステップ2: ソースコードの確認
次に、問題で提供されているソースコード
vuln.cを確認します。
本問題ではソースコードが提供されているため、まず全体の挙動を把握することが重要です。セグメントフォールト時の処理
void segfault_handler() {
printf("Segfault Occurred, incorrect address.\n");
exit(0);
}
この関数は不正なアドレスにジャンプしたときに呼び出されます。 セグメントフォールトが発生するとこの関数が実行され、エラーメッセージを表示してプログラムを終了します。
つまり、
- 間違ったアドレス → 失敗、エラーメッセージ表示
- 正しいアドレス → 関数が実行される
という構造になっていることがわかります。
win関数の挙動
int win() {
FILE *fptr;
char c;
printf("You won!\n");
// Open file
fptr = fopen("flag.txt", "r");
if (fptr == NULL)
{
printf("Cannot open file.\n");
exit(0);
}
// Read contents from file
c = fgetc(fptr);
while (c != EOF)
{
printf ("%c", c);
c = fgetc(fptr);
}
printf("\n");
fclose(fptr);
}
win関数は、以下の処理を行うようです。
- "You won!"と表示
flag.txtファイルを開く- ファイルの内容を1文字ずつ読み取り、表示する
- ファイルを閉じる
上記からこの関数を実行させることができれば、フラグを取得できるということが読み取れます。
main関数の挙動
int main() {
signal(SIGSEGV, segfault_handler);
setvbuf(stdout, NULL, _IONBF, 0); // _IONBF = Unbuffered
printf("Address of main: %p\n", &main);
unsigned long val;
printf("Enter the address to jump to, ex => 0x12345: ");
scanf("%lx", &val);
printf("Your input: %lx\n", val);
void (*foo)(void) = (void (*)())val;
foo();
}
以下の部分で、main関数のアドレスを表示しています。
printf("Address of main: %p\n", &main);
以下では、ユーザーからジャンプ先アドレス(16進数)を入力として受け取っています。
printf("Enter the address to jump to, ex => 0x12345: ");
scanf("%lx", &val);
printf("Your input: %lx\n", val);
そして以下で、入力されたアドレスにジャンプしています。
void (*foo)(void) = (void (*)())val;
foo();
この部分が、問題の核心です。
- 入力された値をポインタにキャスト
- その関数ポインタを実行
つまり、ユーザーが指定したアドレスにジャンプし、そのアドレスにあるコードを実行します。
このため、win関数の実行時アドレスを特定し、そのアドレスを入力すれば、win関数を実行し、フラグを取得できることがわかります。
ステップ3: win関数のアドレス特定
PIEが有効なバイナリでは、関数のアドレスは実行のたびに変化します。
そのため、main関数のアドレスを基準にして、win関数のアドレスを計算する必要があります。
そのため、main関数のアドレスを基準にして、win関数のアドレスを計算する必要があります。
PIEが有効な場合、各関数のアドレスは次のように決まります。
関数の実行時アドレス = PIEベースアドレス + 関数のオフセット
- PIEベースアドレス : 実行ごとに変化するプログラムの基準アドレス
- 関数のオフセット : 常に一定の値で、バイナリ内での関数の位置を示す
ステップ4: 関数のオフセットを調べる
ローカルにダウンロードした
vulnを使って、関数のオフセットを調べます。$ nm vuln | grep main
000000000000133d T main
s$ nm vuln | grep win
00000000000012a7 T win
この結果から、以下のことがわかります。
- main関数のオフセット: 0x133d
- win関数のオフセット: 0x12a7
ステップ5: win関数のアドレス計算
以下の接続時の出力からmain関数のアドレスが
0x5ed0810d333dであることがわかっています。$ nc rescued-float.picoctf.net 65086
Address of main: 0x5ed0810d333d
Enter the address to jump to, ex => 0x12345:
0x5ed0810d333dをもとに、PIEベースアドレスとwin関数の実行時アドレスを計算します。まず、PIEベースアドレスを計算します。
PIEベースアドレス = main関数の実行時アドレス - main関数のオフセット
= 0x5ed0810d333d - 0x133d
= 0x5ed0810d2000
次に、win関数の実行時アドレスを計算します。
win関数の実行時アドレス = PIEベースアドレス + win関数のオフセット
= 0x5ed0810d2000 + 0x12a7
= 0x5ed0810d32a7
ステップ6: win関数のアドレスを入力
最後に、リモート接続時に計算したwin関数のアドレス
0x5ed0810d32a7を入力します。Enter the address to jump to, ex => 0x12345: 0x5ed0810d32a7
Your input: 5ed0810d32a7
You won!
picoCTF{xxxxx}
※フラグはマスクされています。
これで、win関数が実行され、フラグを取得できました。
使用したコマンドの簡単な解説
nc
nc <ホスト名> <ポート番号>
nc(Netcat)は、ネットワーク接続を確立するためのコマンドラインツールです。
このコマンドは、指定されたホストとポート番号に接続します。これにより、リモートサーバーと通信を行うことができます。
nm
nm <バイナリファイル名>
nmコマンドは、バイナリファイル内のシンボル(関数や変数など)の情報を表示します。これにより、各シンボルのアドレスやオフセットを確認できます。
ctfでは、関数のオフセットを調べる際に役立ちます。
まとめ
picoCTFの「PIE TIME」問題では、PIEが有効なバイナリに対して、
関数のオフセットを利用してwin関数のアドレスを計算し、フラグを取得しました。
PIEの理解、オフセットの計算、リモート接続の基本的なスキルが求められる問題でした。
▼ポイントは以下の通りです。
関数のオフセットを利用してwin関数のアドレスを計算し、フラグを取得しました。
PIEの理解、オフセットの計算、リモート接続の基本的なスキルが求められる問題でした。
▼ポイントは以下の通りです。
- PIEが有効なバイナリでは、関数のアドレスが実行ごとに変化することを理解する。
- 関数のオフセットを調べ、PIEベースアドレスを利用して目的の関数のアドレスを計算する。
- リモート接続時に正しいアドレスを入力し、目的の関数を実行する。
閲覧ありがとうございました!
NEXT
次におすすめ
読み終わったら、そのまま次へ
【picoCTF】heap 0 - ヒープ領域の脆弱な書き込みを悪用してフラグを読む
カテゴリ: Binary Exploitation難易度: Easy#picoCTF
次の記事へ →
同じカテゴリ/難易度/picoCTFでの表示順が近い記事を優先しておすすめしています。