🎄teratailクリスマスプレゼントキャンペーン2024🎄』開催中!

\teratail特別グッズやAmazonギフトカード最大2,000円分が当たる!/

詳細はこちら
CentOS

CentOSは、主にRed Hat Enterprise Linux(RHEL)をベースにした、フリーのソフトウェアオペレーティングシステムです。

C

C言語は、1972年にAT&Tベル研究所の、デニス・リッチーが主体となって作成したプログラミング言語です。 B言語の後継言語として開発されたことからC言語と命名。そのため、表記法などはB言語やALGOLに近いとされています。 Cの拡張版であるC++言語とともに、現在世界中でもっとも普及されているプログラミング言語です。

Perl

Perlは多目的に使用される実用性が高い動的プログラミング言語のひとつです。

VirtualBox

VirtualBoxは、現在米オラクル社が開発している、 x86仮想化ソフトウェア・パッケージの一つです。

Q&A

解決済

2回答

1936閲覧

echo perl入力を溢れさせられない

kazuyakazuya

総合スコア193

CentOS

CentOSは、主にRed Hat Enterprise Linux(RHEL)をベースにした、フリーのソフトウェアオペレーティングシステムです。

C

C言語は、1972年にAT&Tベル研究所の、デニス・リッチーが主体となって作成したプログラミング言語です。 B言語の後継言語として開発されたことからC言語と命名。そのため、表記法などはB言語やALGOLに近いとされています。 Cの拡張版であるC++言語とともに、現在世界中でもっとも普及されているプログラミング言語です。

Perl

Perlは多目的に使用される実用性が高い動的プログラミング言語のひとつです。

VirtualBox

VirtualBoxは、現在米オラクル社が開発している、 x86仮想化ソフトウェア・パッケージの一つです。

0グッド

0クリップ

投稿2019/11/25 09:57

編集2019/12/10 10:21

Virtial Box上のCentOSで
scanf関数にecho・perlコマンドを使って
パイプを作りバッファを溢れさせる入力をさせたいです。

c

1#include <stdio.h> 2 3int sub(); 4 5int main(void){ 6sub(); 7return 0; 8} 9 10int sub(){ 11char moji[15]; 12scanf("%s\n",moji); 13int * p; 14int *subp; 15subp = main; 16printf("This is main ReturnAddress: %p\n",subp); 17printf("%d\n",sizeof(subp)); 18int *p; 19p = (int*)moji; 20int ebpofs; 21for(ebpofs=-32;ebpofs<80;ebpofs++){ 22printf("addr%p price:%x",p + count,p[count]); 23} 24return 0; 25} 26

このプログラム adr に対して

cmd

1echo -e "AAAAAAAAAAAAAAAAAAA" | .adr 2または 3perl -e 'print "A"x20' | ./adr

のように文字列を流してやると・・・

イメージ説明

41のところ
15バイト分しか受け取られていません。

なぜでしょうか?
ホストのwindows上ではechoコマンドを使ってバッファを溢れさせることができたのですが・・・
CentOSではできないようでもなっているのでしょうか?
分からないのでお願いします。
イメージ説明
イメージ説明
sub関数逆アセンブル
イメージ説明
イメージ説明

気になる質問をクリップする

クリップした質問は、後からいつでもMYページで確認できます。

またクリップした質問に回答があった際、通知やメールを受け取ることができます。

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

rubato6809

2019/11/25 10:31

そのプログラムコードをうごかしても、その実行結果の表示にはなりません。コードと結果を一致するものにしてください。
guest

回答2

0

ベストアンサー

Centos7で試しましたが、40で違反落ちしました。

objdump -dの結果

000000000040058d <main>: 40058d: 55 push %rbp 40058e: 48 89 e5 mov %rsp,%rbp 400591: 48 83 ec 20 sub $0x20,%rsp 400595: 48 8d 45 e0 lea -0x20(%rbp),%rax 400599: 48 89 c6 mov %rax,%rsi 40059c: bf a0 06 40 00 mov $0x4006a0,%edi 4005a1: b8 00 00 00 00 mov $0x0,%eax 4005a6: e8 d5 fe ff ff callq 400480 <__isoc99_scanf@plt>

からも、0x20+push %rbp分の8バイトで計40バイト必要な事がわかります。
リターンアドレスだと更に8バイト先ですかね

追記:

  • scanfに改行いれるのはやめましょう。
  • printfの方に改行いれないとSS通りになりませんが実行しているプログラムは正しいのでしょうか?

投稿2019/11/26 00:21

編集2019/11/26 00:25
asm

総合スコア15149

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

rubato6809

2019/11/26 03:01

カズヤ君もobjdumpしてみたら。スタックフレームのメモリの配置がわかるし、scanfじゃないものを呼んでるかもしれないしw
asm

2019/11/26 03:02

というか、逆汗見ないとお話にならないんですよね
kazuyakazuya

2019/11/26 03:33 編集

回答ありがとうございます。 こちらから質問を振っておいて申し訳ないのですが 試験があり、がっつりやりこめる時間を確保できないので (pc使えないので) 2週間後に結果を提示します。 すみません。
kazuyakazuya

2019/12/10 04:41

逆アセンブルした結果を載せました。 sub関数を見ると、しっかりscanf関数を読んでいるように見えます。(・・・のはず) 改行はよくないとのことなので 除きました。
asm

2019/12/10 06:44

やっぱり、実行しているプログラムが提示されてたのとは別じゃないですか・・・ ソースを画像で貼られても再現する気になれません。 main関数のアドレスを表示したとこで、ReturnAddressじゃないのは理解していますよね・・・? 逆汗みた感じ0x27+0x10くらいなので、64文字程度食わせればバグりそうな気配があります。
asm

2019/12/10 06:49

15バイト分しか受け取られていないように見える理由に答えると scanfで書き込んだ後に、ローカル変数をぐちゃぐちゃ弄ってるからです。
rubato6809

2019/12/10 08:31

> scanfで書き込んだ後に、ローカル変数をぐちゃぐちゃ弄ってるから かもしれませんね。ところがぐちゃぐちゃ弄ってるのは、scanf() 呼出しの後なのに、それが写真に写っていない。だから subp, p, ebpofs 変数がスタックのどこに割り当てられたのか手がかりが無い、よって判断しようがない。 だから、retq するまでの sub() 関数全体の逆アセンブル表示を見てみたいところ。できれば写真でなく、文字でカット&ペーストできませんかね。 或いは、scanf() で読み込んだ moji[] 配列を sub() 自身で表示するのではなく、sub() から呼び出した別の関数に表示させればぐちゃぐちゃ弄らず表示できるんですがね。scanf() したら、すかさず、他に何もせず、その関数に moji [] のアドレスを渡すようにする。
kazuyakazuya

2019/12/10 09:53

>やっぱり、実行しているプログラムが提示されてたのとは別じゃないですか・・・ 写しなおします。 >ReturnAddressじゃないのは理解していますよね? 当初、その気でいましたが 気づいた後も訂正しないままでいました。 >それが写真に写っていない。 追記いたしました。 >文字でカット&ペーストできませんかね。 ゲストマシンのほうでコピーしてもホストのほうでペーストさせることができません。 設定でなんとかなるかもしれませんが、今はできないでいます。
asm

2019/12/10 10:42

VirtualBoxの解説になってしまうので簡潔にですが ゲスト側OSにguest additionsを入れるとクリップボード等を共有できます。 また、ゲストOSはヘッドレス起動させてお好みのSSHクライアントで接続するのを検討するのもありです。
rubato6809

2019/12/10 14:04

見つけた。 lea -0x27(%rbp), %rax // 0x27 = 39 mov %rax, -0x18(%rbp) // 0x18 = 24 この2行がmoji配列のすぐ後ろを上書きしている。 Cでは p = (int*) moji; の行。
rubato6809

2019/12/10 14:08

asmさんの予想通り!ご明察!
asm

2019/12/10 14:14

原因なんてのは正直どうでもよくて、さっさと64バイト食わせてくんないかなぁ・・・って感じです。
kazuyakazuya

2019/12/11 00:08

46バイト入力に流し込んだところでコアダンプが発生しました。
kazuyakazuya

2019/12/11 00:11

>p = (int*) moji; の行。 これが原因だったんですね。ありがとうございます。
asm

2019/12/11 00:22

なるほど、想定より短いですね おそらくrbpが壊れただけで、リターンアドレスの書き換えには至っていないのではないかと思います。 あとはデバッガで実行して調整するといいでしょう。
guest

0

その実行結果は、示されたコードで表示したものではない。別のプログラムが表示したのだから、中身も違うのだろう。
考えられることは、例えば scanf() で入力したのではなく、fgets() で入力した、とか。それなら普通は配列のサイズ分しか読み込まれない。
私は Ubuntu, GCC で

C

1 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〜-0x191515
int *p-0x18〜-0x11823
int *subp-0x10〜-0x09831
(空き)-0x08〜-0x05435
int ebpofs-0x04〜-0x01439
old %rbp0x00〜0x07847
ret address0x08〜0x0F855
  • オフセットは %rbp のオフセット
  • 累積バイト数は、その変数まで塗りつぶすために流し込むバイト数

46バイト流し込んでコアダンプしたなら old %rbp の8バイトの内、7バイトを書き換えた、ということです。old %rbp の値は sub() からリターンする時に leaveq 命令で %rbp にpopして、(一般的には)呼出し元関数のローカル変数をアクセスするので、この時不正アドレスをアクセスしてしまうのですが、、この場合はたぶんmain() の最後の leaveq 命令が不正アクセスするのでしょう。leaveq 命令はちょっとトリッキーな命令です、いつも使われるとは限らないけど覚えておかないと。

コアダンプさせるだけならテキトーな値を流しこめば済むが、乗っ取るにはリターンアドレス(オフセット+8から8バイト)を”正しい”値で上書きする必要があります。その値は普通、moji配列の先頭アドレスでしょうね。スタック上のアドレスです。表示結果を見ると 0x7fffe5320be9 でしょうか。
このように、スタック領域のメモリアドレス、変数の配置をピンポイントで確定させないと(この方式の)シェルコードは作りようがありません。この表から、この場合のシェルコードのサイズは 55バイトだとわかります。だけどこれらの変数は、コードを修正すればもちろん変化するうえに、コンパイルする都度、配置が変化する位に思ったほうが良いですよ。
ようやくこういう具体的な話ができるようになった感じですね。そうは言っても、まだ先は長そうだけどね。
Enjoy!

投稿2019/11/25 10:47

編集2019/12/11 04:53
rubato6809

総合スコア1382

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

kazuyakazuya

2019/11/25 10:57

回答ありがとうございます。 コードを訂正しました。 fgets関数ではなくscanf関数を使用しました。
kazuyakazuya

2019/11/25 13:08

windows(ホスト)でやったときは しっかりと溢れてくれるのを確認したのですが CentOS(ゲスト)ではやっぱり 溢れ出てくれません・・・ 溢れていないので、アクセス違反警告も出ませんでした。
kazuyakazuya

2019/12/11 07:39

ありがとうございます!
kazuyakazuya

2019/12/14 12:33

今更なのですが DGB出力結果のアドレスを見ると24バイト長となっています。 このプロセスは64bitで実行しているから アドレスは48ビット長になるはず・・・。 GDBの先頭は40060bと24bitですが実際は 00000040060bってことですか?
rubato6809

2019/12/14 13:34

そうです。コード領域は比較的若い・低い(0番地に近い)アドレス領域だということ。デバッガはアドレス上位24bitの0の表示を省略しています。 逆アセンブルリスト中の、例えば callq 400500 <??isoc99_scanf@plt> # printf() を呼び出す movq $0x4005f6, -0x10(rbp) # main() 関数のエントリアドレス に示されたアドレスも同じメモリ領域のことだと分かる。どちらも16進数なのに、"0x" が付いたり付かなかったり、一貫性が感じられない所がナンダかなあ、とは思うけど。 一方、41414141 が読み込まれた 0x7fffe5320be9 番地はスタック中の moji[] 配列のアドレスだから、スタック領域は比較的高いアドレス領域に割り当てられていると分かる。スタック領域はアドレスの高い所から低い方向へ伸びる・・・関数呼出しの「ネストが深くなる」などと言う・・・ものなので、この配置は教科書的に合致しています。
kazuyakazuya

2019/12/14 13:45

了解です。ありがとうございます。 話はちょっと変わっちゃうけど・・・ >callq 400500 <??isoc99_scanf@plt> # printf() を呼び出す これは printf関数のアドレスを指定して処理を呼び出す命令ですよね。 (最終的に割り込みが行われる。) =============== https://teratail.com/questions/218869 =============== 上記の過去の質問では スタックに引数を積んでprintfを呼び出しています。 ただ、今回の例だと 割り込みのようにレジスターに値をセットして呼び出しています。 この違いとは一体何でしょうか?
rubato6809

2019/12/14 14:30

> スタックに引数を積んでprintfを呼び出し それは 32bit CPUの場合の呼出し方。 > レジスターに値をセットして呼び出し それは64 bit CPUの場合の呼出し方。という違いです。 32bitの場合、CPUが持つレジスタ本数が少ないのでレジスタ渡しができない、なのでスタック(==メモリ)で引数を渡すことにした。引数の個数がいくらでも対応できる。 一方、64bitの場合、CPUはレジスタをたくさん持っているので、引数6個までレジスタで渡すことにした。そのほうが効率が良い、何故ならメモリアクセスよりレジスタアクセスの方が速いから。それに引数が6個を超える関数は滅多に無い。 > 最終的に割り込みが行われる ここで言う「割込み」とは syscallとかint命令など、ソフトウェア割込命令のことだと思う。普通、割込みとは外部信号でCPUに割込みをかけるものを言う。普通の割込みとソフトウェア割込みを区別しないと誤解の元になので注意せよ。
kazuyakazuya

2019/12/14 14:52 編集

ありがとうございます。 なるほど・・・ 64bitプロセスでも、「スタックで~」って方法を使うことはできるんですか? >ここで言う「割込み」とは ・・・ ソフトウエア割り込み・・・今回で言うcall printf ですよね? 今になって混乱し始めています(笑)。
rubato6809

2019/12/14 15:31

スタック渡しで使うことはできなくないが・・・ そういうコンパイラを誰が用意するのだろう。 詳しいことは知らないが、プロセッサメーカーから、64bitCPUは引数に使うレジスタはこれこれ・・・という提案があって、世の中のコンパイラ(GCCもClangもVCも…)はそれに従っている、よってスタック渡しのコンパイラは存在しないようである。そうするとスタック渡しコンパイラは他との互換性という壁ができる。それにせっかくたくさんあるレジスタを活用できず宝の持ち腐れ、結果として性能が上がらないだろう・・・ということで64bitのスタック渡しはやめた方が良い。 > ソフトウエア割り込み・・・今回で言うcall printf call 命令は割込みじゃないよ。 printf()のなかで、最終的に write() システムコールにたどり着き、そこがたぶん syscall 命令でOSを呼ぶのである。その syscall がソフトウェア割込み命令です。
kazuyakazuya

2019/12/14 16:34

ありがとうございます。
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

15分調べてもわからないことは
teratailで質問しよう!

ただいまの回答率
85.36%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問