コース目次 / 第1章
脆弱なプログラムを作り、書き換え位置を読む
gets()を使った脆弱なプログラムを、保護を意図的に無効化してビルドします。objdumpでスタックフレームを読み、リターンアドレスまでのオフセットを計算します。
vuln.c として保存します。gets()は入力長を確認せずバッファに書き込む、教科書的に危険な関数です。
#include <stdio.h>
void win(void) {
printf("flag{ret2win_stack_smash_verified}\n");
}
void vulnerable(void) {
char buf[64];
printf("名前を入力してください: ");
fflush(stdout);
gets(buf);
printf("こんにちは、%sさん\n", buf);
}
int main(void) {
vulnerable();
printf("普通に終了しました。\n");
return 0;
}
win()は、通常の実行では一度も呼ばれません。この関数を、想定外の経路から実行させるのが今回の目標です。
保護を、意図的に無効化してビルドする
gcc -fno-stack-protector -no-pie -z execstack -o vuln vuln.c
-fno-stack-protector(スタックカナリアを付けない)、-no-pie(ロードアドレスを固定する)——どちらも、デフォルトのgccビルドでは有効になっている保護です。あえて外すことで、素朴なスタックオーバーフローが成立する状態を作っています。この意味は、第3章で確かめます。
スタックフレームを、読む
objdump -d -M intel vuln | awk '/<vulnerable>:/,/^$/'
抜粋です。
00000000004011b0 <vulnerable>:
4011b0: f3 0f 1e fa endbr64
4011b4: 55 push rbp
4011b5: 48 89 e5 mov rbp,rsp
4011b8: 48 83 ec 40 sub rsp,0x40
...
4011df: 48 8d 45 c0 lea rax,[rbp-0x40]
4011e3: 48 89 c7 mov rdi,rax
...
4011eb: e8 a0 fe ff ff call 401090 <gets@plt>
sub rsp,0x40——スタックに64バイト確保しています。lea rax,[rbp-0x40] で、getsに渡すバッファの先頭アドレスがrbp-0x40だと分かります。つまりbufは64バイトで、rbpのすぐ手前まで埋まっています。
関数プロローグの push rbp により、呼び出し元のrbpが8バイトスタックに保存されています。そのさらに先(呼び出し元アドレス側)にリターンアドレスがあります——これはcall命令が自動的に積むもので、逆アセンブルには出てきません。
オフセットを、計算する
buf(64バイト) + 保存されたrbp(8バイト) = 72バイト
72バイトを書き込んだ後の8バイトが、リターンアドレスの位置です。ここをwin()のアドレスで上書きできれば、vulnerable()から戻る際に、想定外のwin()へジャンプするはずです。次の章で、実際に試します。
こうなっていればOK
卒業まであと2章です。
この章はまだ完了していません。
保存できませんでした(プライベートブラウズ中かもしれません)。この端末に進捗は残りませんが、先へは進めます。