fc2ブログ
GEEKy Script Writer [perl and more!]
You should permit the JavaScript!!
[C][ネタ] while(1)より普通な半永久ループ
以下で紹介するコードは、コンパイラに大きく依存する。
このコードの動作を確認した環境は以下の通り。
OS:WindowsXP
コンパイラ:Borland C++Compiler 5.5
オプション:最適化なし


#include <stdio.h>

void foo() {
int s1[4];
char s2[4];
s2[0] = 'a';
s2[1] = 'b';
s2[2] = 'c';
s2[3] = '\0';
puts(s2);
s1[6] = foo;
}

int main() {
foo();
return 0;
}


これで、


abc
abc
abc
..

と、資源が許す限り永久にループします。

s1[4]とs2[4]でスタックにバッファを確保すると、s1かs2からスタック内のアドレスが
わかります。そして、s1,s2より上位のアドレスには、その関数がreturnするときに戻
る場所(retアドレス)が格納されています。この2つを組み合わせれば、retアドレスを
弄って、本来戻る場所とは別のところに戻せることが分かります。
そのアドレスを自分自身のアドレス(foo)にすれば、ループするだろうってことです。
所謂 Buffer Overflowというやつですね。

foo()内のスタックはこんな感じです。(適当なアドレスを基準にしています)

0012FF70 s1[0]
0012FF74 s1[1]
0012FF78 s1[2]
0012FF7C s1[3]
0012FF80 s2[3] s2[2] s2[1] s2[0]
0012FF84 EBPレジスタ
0012FF88 retアドレス

目的は0012FF88に格納されている関数return時の戻り先(retアドレス)を書き換えること
ですから、s1かs2のアドレスにいくつ足せばretアドレスを参照できか調べます。

ここではs1のほうを使います。
0x0012ff88 - 0x0012ff70 = 0x18 なので、s1[6]で0012FF88の中身、つまりretアドスが
参照できますね。

あとは s[6] = foo; とすれば、戻り先をfooにすることでループできるでしょう。

しかし、このコードは半永久的と書いた通り問題があります。
retアドレスをfoo自身のアドレスに書き換えることによって、またfooが呼び出される
のですが、正確に言うと呼び出されるというよりは、fooのコードにジャンプするん
です。呼び出しとジャンプではどこが違うのか?

関数呼び出しは x86アセンブリで callという命令を使い、
ジャンプは普通に jmp アドレス です。

callの動作はただ、ジャンプさせるのではなく、戻り先のアドレスをスタックに格納し、
その後にjmpさせます。

call命令でスタックに格納されたretアドレスを上書きしているこのコードだと、foo()が
再び呼び出されても、retアドレスがスタックに格納されていないので、前回より1つ上位
のアドレスにretアドレスが書き込まれ、繰り返し呼び出されていくうちに、スタックを使
い切ってしまいます。スタックを使い切るとアクセス違反で止まってしまうでしょう。

この問題を回避する方法は凄く簡単です。というかむしろ、これは間違ったやり方ですね。
なのでその"正しい"方法でのコードを書いてみたかったけど長くなるので興味を持った方
は考えてみてください。すぐ分かります。しかし、その方法でも環境に依存してしまうかも
しれません。

"ネタにはなるが、実務では使えない。それがローレベルテクニック(略してローテク)というかhack"
コメント
この記事へのコメント
コメントを投稿する
URL:
Comment:
Pass:
秘密: 管理者にだけ表示を許可する
 
トラックバック
この記事のトラックバックURL
この記事へのトラックバック
copyright © 2005 GEEKy Script Writer [perl and more!] all rights reserved.
Powered by FC2ブログ.