質問: (int*)moji; int ebpofs; for(ebpofs=-32;ebpofs<80;ebpofs++){ printf("addr%p price:%x",p + count,p[count]); } return 0; } ``` このプログラム adr に対して ``` echo -e "AAAAAAAAAAAAAAAAAAA" | .adr または perl -e 'print "A"x20' | ./adr ``` のように文字列を流してやると・・・ ![イメージ説明](https://teratail-v2.storage.googleapis.com/uploads/contributed_images/3dab663340f228351337d721b35737c0.png) 41のところ 15バイト分しか受け取られていません。 なぜでしょうか? ホストのwindows上ではechoコマンドを使ってバッファを溢れさせることができたのですが・・・ CentOSではできないようでもなっているのでしょうか? 分からないのでお願いします。 ![イメージ説明](https://teratail-v2.storage.googleapis.com/uploads/contributed_images/911e6c812618bb27f7ab248e6e06f784.png) ![イメージ説明](https://teratail-v2.storage.googleapis.com/uploads/contributed_images/8751c449be0900293932375f7a093bae.png) sub関数逆アセンブル ![イメージ説明](https://teratail-v2.storage.googleapis.com/uploads/contributed_images/f3270cf963d884a718b11d9a21f0b85f.png) ![イメージ説明](https://teratail-v2.storage.googleapis.com/uploads/contributed_images/739ed9191f08e630acbf0dbaf55e9398.png)
回答: ~~その実行結果は、示されたコードで表示したものではない。別のプログラムが表示したのだから、中身も違うのだろう。~~ 考えられることは、例えば scanf() で入力したのではなく、fgets() で入力した、とか。それなら普通は配列のサイズ分しか読み込まれない。 私は Ubuntu, GCC で ``` printf("addr%p price:%08x\n", p + count, p[count]); ``` で表示、"A"x30 辺りでコアダンプに至りました。リターンアドレスを上書きされたからに他なりません。 CentOSを使ってないので断言はしないけど、scanf() はCライブラリ関数にふくまれ、仕様が違うとは考えにくい。scanf() を使う限り溢れると思いますが。 ~~そうですか。溢れないとすると、私には事情がわかりかねます。~~ sub() 内の変数の割当がわかり、すっきり原因がわかった。asm さんと違って私は原因を特定することに興味があるので、こちらに書かせてもらう。 15バイトの配列に echo -e "AAAAAAAAAAAAAAAAAAA" | .adr と20バイトほど送り込んでいるが * 20バイトだと溢れたメモリは sub() の中の p ~~と subp~~ 変数に相当する。この変数に代入した時点で上書きされるので、溢れたようにみえない。20バイトでは慎ましすぎた。 * 40バイトほど送り込めば、溢れた痕跡が見えるだろう。変数と変数の間に4バイトの未使用領域があるので、そこは上書きされずに溢れた値が残るはずだから。 * 60バイトほど送り込めばリターンアドレスを上書きするので、sub() からリターンした時点でセグメントフォールトを起こすだろう。 sub() が使う変数の割当は次のようです。 | 変数名 | オフセット | サイズ | 累積バイト数 | | --- | --- | --- | --- | | char moji[15] | -0x27〜-0x19 | 15 | 15 | | int *p | -0x18〜-0x11 | 8 | 23 | | int *subp | -0x10〜-0x09 | 8 | 31 | | (空き) | -0x08〜-0x05 | 4 | 35 | | int ebpofs | -0x04〜-0x01 | 4 | 39 | | old %rbp | 0x00〜0x07 | 8 | 47 | | ret address | 0x08〜0x0F | 8 | 55 | * オフセットは %rbp のオフセット * 累積バイト数は、その変数まで塗りつぶすために流し込むバイト数 46バイト流し込んでコアダンプしたなら old %rbp の8バイトの内、7バイトを書き換えた、ということです。old %rbp の値は sub() からリターンする時に leaveq 命令で %rbp にpopして、(一般的には)呼出し元関数のローカル変数をアクセスするので、この時不正アドレスをアクセスしてしまうのですが、、この場合はたぶんmain() の最後の leaveq 命令が不正アクセスするのでしょう。leaveq 命令はちょっとトリッキーな命令です、いつも使われるとは限らないけど覚えておかないと。 コアダンプさせるだけならテキトーな値を流しこめば済むが、乗っ取るにはリターンアドレス(オフセット+8から8バイト)を”正しい”値で上書きする必要があります。その値は普通、moji配列の先頭アドレスでしょうね。スタック上のアドレスです。表示結果を見ると 0x7fffe5320be9 でしょうか。 このように、スタック領域のメモリアドレス、変数の配置をピンポイントで確定させないと(この方式の)シェルコードは作りようがありません。この表から、この場合のシェルコードのサイズは 55バイトだとわかります。だけどこれらの変数は、コードを修正すればもちろん変化するうえに、**コンパイルする都度、配置が変化する位に思ったほうが良い**ですよ。 ようやくこういう具体的な話ができるようになった感じですね。そうは言っても、まだ先は長そうだけどね。 Enjoy!