5

こんにちは。基本的にバッファオーバーフローをテストするために書かれたCプログラムがあります。

    #include<stdio.h>
    void display()
    {
            char buff[8];
            gets(buff);
            puts(buff);
    }
    main()
    {
        display();
        return(0);
    }

GDBを使用して、ディスプレイとその主要セクションを逆アセンブルします。コード:-

関数 main のアセンブラー コードのダンプ:

    0x080484ae <+0>:    push   %ebp        # saving ebp to stack
    0x080484af <+1>:    mov    %esp,%ebp   # saving esp in ebp
    0x080484b1 <+3>:    call   0x8048474 <display>   # calling display function
    0x080484b6 <+8>:    mov    $0x0,%eax   # move 0 into eax , but WHY ????
    0x080484bb <+13>:   pop    %ebp        # remove ebp from stack
    0x080484bc <+14>:   ret                # return

アセンブラー・ダンプの終わり。

関数表示用のアセンブラ コードのダンプ:

    0x08048474 <+0>:    push   %ebp          #saves ebp to stack        
    0x08048475 <+1>:    mov    %esp,%ebp     # saves esp to ebp
    0x08048477 <+3>:    sub    $0x10,%esp    # making 16 bytes space in stack
    0x0804847a <+6>:    mov    %gs:0x14,%eax  # what does it mean ????
    0x08048480 <+12>:   mov    %eax,-0x4(%ebp) # move eax contents to 4 bytes lower in stack
    0x08048483 <+15>:   xor    %eax,%eax       # xor eax with itself (but WHY??)
    0x08048485 <+17>:   lea    -0xc(%ebp),%eax  #Load effective address of 12 bytes 
                                              lower placed value ( WHY???? )

    0x08048488 <+20>:   mov    %eax,(%esp)      #make esp point to the address inside of eax
    0x0804848b <+23>:   call   0x8048374 <gets@plt>  # calling get, what is "@plt" ????
    0x08048490 <+28>:   lea    -0xc(%ebp),%eax       # LEA of 12 bytes lower to eax
    0x08048493 <+31>:   mov    %eax,(%esp)         # make esp point to eax contained address
    0x08048496 <+34>:   call   0x80483a4 <puts@plt>  # again what is "@plt" ????
    0x0804849b <+39>:   mov    -0x4(%ebp),%eax    # move (ebp - 4) location's contents to eax
    0x0804849e <+42>:   xor    %gs:0x14,%eax         # # again what is this ????
    0x080484a5 <+49>:   je     0x80484ac <display+56> # Not known to me
    0x080484a7 <+51>:   call   0x8048394 <__stack_chk_fail@plt>  # not known to me
    0x080484ac <+56>:   leave                        # a new instruction, not known to me
    0x080484ad <+57>:   ret                          # return to MAIN's next instruction

アセンブラー・ダンプの終わり。

皆さん、私の宿題を考えてください。数行を除いて、残りのすべてのコードは私が知っています。私は大きな「なぜ????」を含めました。各行の前のコメントにさらにいくつかの質問があります。私にとっての最初のハードルは、「mov %gs:0x14,%eax」命令です。この命令の後、フローチャートを作成できません。誰かが私に説明してください、これらのいくつかの命令は何を意味し、プログラムで何をしているのでしょうか? ありがとう...

4

4 に答える 4

13
0x080484b6 <+8>:    mov    $0x0,%eax   # move 0 into eax , but WHY ????

これを持っていませんか?:

return(0);

それらはおそらく関連しています。:)

0x0804847a <+6>:    mov    %gs:0x14,%eax  # what does it mean ????

これは、アドレス gs:0x14 のメモリから eax に 4 バイトを読み取ることを意味します。gs はセグメントレジスタです。ほとんどの場合、スレッド ローカル ストレージ (AKA TLS) は、このレジスタを介して参照されます。

0x08048483 <+15>:   xor    %eax,%eax       # xor eax with itself (but WHY??)

わからない。最適化に関連している可能性があります。

0x08048485 <+17>:   lea    -0xc(%ebp),%eax  #Load effective address of 12 bytes 
                                          lower placed value ( WHY???? )

スタック上に存在するローカル変数を eax ポイントにします。sub $0x10,%espそれらにいくらかのスペースを割り当てました。

0x08048488 <+20>:   mov    %eax,(%esp)      #make esp point to the address inside of eax

違う。スタックの一番上に eax を書き込みます。これは、呼び出された関数にスタック上の引数として渡されます。

0x0804848b <+23>:   call   0x8048374 <gets@plt>  # calling get, what is "@plt" ????

知らない。名前のマングリングかもしれません。

ここまでで、それが何のローカル変数であるかを推測できたはずです。buff、他に何がありますか?

0x080484ac <+56>:   leave                        # a new instruction, not known to me

CPUの説明書で調べてみませんか?

これで、おそらく gs/TLS について説明できます...

0x08048474 <+0>:    push   %ebp          #saves ebp to stack        
0x08048475 <+1>:    mov    %esp,%ebp     # saves esp to ebp
0x08048477 <+3>:    sub    $0x10,%esp    # making 16 bytes space in stack
0x0804847a <+6>:    mov    %gs:0x14,%eax  # what does it mean ????
0x08048480 <+12>:   mov    %eax,-0x4(%ebp) # move eax contents to 4 bytes lower in stack
...
0x0804849b <+39>:   mov    -0x4(%ebp),%eax    # move (ebp - 4) location's contents to eax
0x0804849e <+42>:   xor    %gs:0x14,%eax         # # again what is this ????
0x080484a5 <+49>:   je     0x80484ac <display+56> # Not known to me
0x080484a7 <+51>:   call   0x8048394 <__stack_chk_fail@plt>  # not known to me
0x080484ac <+56>

したがって、このコードは TLS (gs:0x14) から値を取得し、保存された ebp 値 (ebp-4) のすぐ下に格納します。次に、とのあなたのものがget()ありput()ます。次に、このコードは、TLS からの値のコピーが変更されていないかどうかを確認します。xor %gs:0x14,%eax比較を行います。

XOR された値が同じ場合、XOR の結果は 0 で、flags.zf は 1 です。それ以外の場合、結果は 0 ではなく、flags.zf は 0 です。

je 0x80484ac <display+56>flags.zf をチェックしcall 0x8048394 <__stack_chk_fail@plt>、flags.zf = 1 の場合はスキップします。IOW、TLS からの値のコピーが変更されていない場合、この呼び出しはスキップされます。

それは一体何ですか?これは、バッファ オーバーフローをキャッチしようとする方法です。バッファの末尾を超えて書き込むと、TLS からスタックにコピーされた値が上書きされます。

この値を TLS から取得する理由 オーバーフローをより頻繁にキャッチするために、ハードコーディングされていない別の値を使用する必要があるでしょう (そのため、TLS の値はプログラムの実行ごとに変化し、プログラムのスレッドごとに異なります)。また、プログラムが実行されるたびに値がランダムに選択される場合、攻撃者がバッファ オーバーフローを悪用する可能性も低くなります。

最後に、値のコピーがバッファ オーバーフローのために上書きされたことが判明した場合、 はcall 0x8048394 <__stack_chk_fail@plt>、問題の報告やプログラムの終了など、必要なことを行う専用の特別な関数を呼び出します。

于 2012-09-02T10:09:42.653 に答える
8
0x0804849e <+42>:   xor    %gs:0x14,%eax         # # again what is this ????
0x080484a5 <+49>:   je     0x80484ac <display+56> # Not known to me
0x080484a7 <+51>:   call   0x8048394 <__stack_chk_fail@plt>  # not known to me
0x080484ac <+56>:   leave                        # a new instruction, not known to me
0x080484ad <+57>:   ret                          # return to MAIN's next instruction

gsセグメントは、スレッド ローカル ストレージに使用できます。たとえばerrno、マルチスレッド プログラムの各スレッドが効果的に独自の errno 変数を持つように、 for に使用されます。

上記の関数名は大きな手がかりです。これはスタック カナリアである必要があります。

leave実際のretの前に必要なすべてを実行するCISC命令です。詳細はわかりません)。

于 2012-09-02T09:48:55.490 に答える
4

他の人はすでにGSのことを説明しました(スレッドに関係しています)。

0x08048483 <+15>:   xor    %eax,%eax       # xor eax with itself (but WHY??)

これを説明するには、X86 アーキテクチャの歴史が必要です。

xor eax, eax 命令は、レジスタ eax のすべてのビットをクリアします (ゼロをロードします)。

ただし、xor eax, eax は x86 でも別のことを行います。al、ah、および ax を使用して、レジスタ eax の一部にアクセスできることはおそらくご存じでしょう。それは 386 以来のやり方であり、eax が本当に単一のレジスターだった当時は問題ありませんでした。

しかし、これはもうありません。コードで表示および使用するレジスタは、単なるプレースホルダーです。CPU の内部では、より多くの内部レジスタと完全に異なる命令セットが使用されています。記述した命令は、この内部命令セットに変換されます。

たとえば、AL、AH、および EAX を使用する場合、CPU の観点からは 3 つの異なるレジスタを使用しています。

AL または AH を使用した後に EAX にアクセスする場合、CPU はこれらの異なるレジスタをマージして有効な EAX 値を構築する必要があります。

この線:

0x08048483 <+15>:   xor    %eax,%eax       # xor eax with itself (but WHY??)

レジスタ eax をクリアするだけではありません。また、名前が変更されたすべてのサブレジスタ (AL、AH、および AX) が無効化されている (ゼロに設定されている) と見なされ、CPU がサブレジスタのマージを行う必要がないことも CPU に通知します。

コンパイラがこの命令を発行するのはなぜですか?

コンパイラは、どのコンテキストで display() が呼び出されるかを知らないためです。AL と AH を使用して多くのバイト演算を行うコードから呼び出すことができます。XOR を介して EAX レジスタをクリアしない場合、CPU は多くのサイクルを必要とするコストのかかるレジスタ マージを実行する必要があります。

したがって、関数の開始時にこの余分な作業を行うと、パフォーマンスが向上します。あなたの場合は不要ですが、コンパイラーは確実に命令を発行することを認識できないためです。

于 2012-09-02T10:06:08.673 に答える
2

stack_check_fail は、gcc バッファー オーバーフロー チェックの一部です。これは libssp (stack-smash-protection) を使用し、最初の動きはスタックのガードを設定し、xor %gs:0x14... はガードがまだ問題ないかどうかのチェックです。問題がなければ、leave にジャンプし (アセンブラーのドキュメントを確認してください。スタック処理のヘルパー命令です)、stack_chk_fail へのジャンプをスキップします。これにより、プログラムが中止され、エラー メッセージが出力されます。

gcc オプションを使用して、このオーバーフロー チェックの送信を無効にすることができます-fno-stack-protector

コメントで既に述べたように、xor x,x は x をクリアする簡単なコマンドであり、最後の mov 0, %eax はメインの戻り値です。

于 2012-09-02T09:54:32.163 に答える