fr33f0r4ll

自分用雑記

バッファオーバーフローでeipが書き換えられない

自分の環境で脆弱性のあるプログラムを再現できなかった

pwnの練習をしようとして脆弱性のあるプログラムを書いたら、何故かその脆弱性を(幸か不幸か)exploitできなかった。 解決するまでに結構かかってしまった上、日本語の情報が見つからなかったから残しておく。 参考というか、丸パクリ元はここ

環境

自分の環境は以下の通りである。

x86_64 GNU/Linux Ubuntu LTS-16.04

やろうとしたこと

やろうとしていたことはバッファオーバーフローによるeipを書き換えだったけど、実際にやるとクラッシュしてもeipが書き換えられてなかった。 プログラム自体はfgetsしてバッファ書き換えるだけの簡単もので、32bitアーキテクチャSSPは無効にしていた。 ASLRは無効にしてなかったけど、gdbで実行していたので無効の状態のはず。

#include <stdio.h>

int main(int argc, char** argv){
  char buffer[32];
  
  fgets(buffer, 128, stdin);
  
  return 0;
}

コンパイルは、gcc -m32 -fno-stack-protector -o overflow overflow.c

gdbで調べたり、逆アセンブルして解析した結果、どうもなんらかのセキュリティが働いているような感じだった。 逆アセンブルしてみてみると、main関数の最初とret周りの処理が怪しい。

0804843b <main>:
 804843b: 8d 4c 24 04           lea    ecx,[esp+0x4] ;; 1
 804843f: 83 e4 f0                 and    esp,0xfffffff0
 8048442:  ff 71 fc                 push   DWORD PTR [ecx-0x4]
 8048445:  55                     push   ebp
 8048446:  89 e5                   mov    ebp,esp
 8048448:  51                     push   ecx
 8048449:  83 ec 24               sub    esp,0x24
 804844c: a1 20 a0 04 08         mov    eax,ds:0x804a020
 8048451:  83 ec 04               sub    esp,0x4
 8048454:  50                     push   eax
 8048455:  68 80 00 00 00         push   0x80
 804845a: 8d 45 d8              lea    eax,[ebp-0x28]
 804845d: 50                     push   eax
 804845e: e8 ad fe ff ff           call   8048310 <fgets@plt>
 8048463:  83 c4 10               add    esp,0x10
 8048466:  b8 00 00 00 00           mov    eax,0x0
 804846b: 8b 4d fc                 mov    ecx,DWORD PTR [ebp-0x4] ;; 2
 804846e: c9                       leave  
 804846f: 8d 61 fc              lea    esp,[ecx-0x4] ;; 3
 8048472:  c3                       ret    
 8048473:  66 90                 xchg   ax,ax
 8048475:  66 90                 xchg   ax,ax
 8048477:  66 90                 xchg   ax,ax
 8048479:  66 90                 xchg   ax,ax
 804847b: 66 90                 xchg   ax,ax
 804847d: 66 90                 xchg   ax,ax
 804847f: 90                     nop
  • 1で[esp+0x4]をecxに格納
  • 2でecxが復帰
  • 3のret前でespに[ecx-0x4]を格納

こんな感じの処理がされているらしい。 つまり、一番最初に格納したスタックポインタの値をleaveとretの間で復帰させることで、書き換えられたリターンアドレスがeipに格納されることを防いでいるのではないかと思われる。

また、callした後のスタックの状態は以下のようになるので、ebpを変更せずにリターンアドレスを変更することはできない(ebpの整合性を保つような値で上書きすることは可能かな?)。 したがって、リターンアドレスがバッファオーバーフローで書き換えられていると、ebpが異常値になって正しくespを復帰させられなくなる。 その結果としてeipが書き換えられなくなっている。

stack
argn
...
arg1
return-address
saved-ebp
local-variable

解決策(?)

解決というか問題の再発というか、脆弱性を仕込むことができなければ練習できないので調べた。 幸いにも参考にしたページのコメントに解決できそうなやり方が載っていたので、ありがたく参考にさせてもらう。 このespの保存と復帰はmain関数でしか行なわれないらしいので、別の関数に脆弱性のある処理を書いてmainから呼べばいいらしい。

早速試す。 プログラムを書き換えて、以下のような感じにした。 main関数の中身を丸ごと取り出しただけで、処理は同じになる。

#include <stdio.h>

void vulnerable_func(){
  char buffer[32];  
  fgets(buffer, 128, stdin);
  return;
}

int main(int argc, char** argv){
  vulnerable_func();
  return 0;
}

gdbで実行し、50文字の文字列を入力するとeipが書き変わっているのを確認できた。 これで問題なく練習できるはず。