コース目次 / 第1章

脆弱なプログラムを作り、書き換え位置を読む

gets()を使った脆弱なプログラムを、保護を意図的に無効化してビルドします。objdumpでスタックフレームを読み、リターンアドレスまでのオフセットを計算します。

第1章 / 全4章目安 約15分この章のゴール: スタックの構造を読み、リターンアドレスまでのオフセットを計算できるようになる

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章です。

この章はまだ完了していません。