2017-01-23 1 views
6

私は、学生のためのバッファオーバーフロー演習をcに書こうとしています。ebxが単純な関数のスタックフレームに保存されるのはなぜですか?

通常、スタックフレームは、関数パラメータ、リターンアドレス、ベースポインタ、およびローカル変数で構成されます。しかし、私は、時には追加のレジスタがベースポインタと共に保存されることを検出しました。私はクラスから、保存されたレジスタを使用する前に保存しなければならないことを覚えています。しかし、Cコードをコンパイルするとアセンブラが生成され、目的を持たずにレジスタを節約して使用するケースがあります。この行動を私に説明してください。

私はgdbの

break func 
run 
info frame 

で生成される実行可能ファイルをデバッグする場合はすべてがうまくされ、スタックフレームのみEBPが含まれている主な機能

int main (int argc, char** argv) { 
    func(); 

    return 0; 
} 

と機能

void func() { 
    char buf[5]; 
    strcpy(buf,"AAAA"); 
    strcpy(buf,"BBBB"); 
} 

を想定およびeip。

私はそうEBXは、さらに、スタックフレームに保存されている

Saved registers: 
    ebx at 0xffffd1cc, ebp at 0xffffd1d0, eip at 0xffffd1d4 

を取得し、私は

void func() { 
    char buf[5]; 
    gets(buf); 
} 

を使用している場合は?どうして?私は

disas func 

を実行すると、私はそうEBXが保存されている

Dump of assembler code for function func: 
    0x56555730 <+0>: push %ebp 
    0x56555731 <+1>: mov %esp,%ebp 
    0x56555733 <+3>: push %ebx 
    0x56555734 <+4>: sub $0x8,%esp 
    0x56555737 <+7>: call 0x5655576e <__x86.get_pc_thunk.ax> 
    0x5655573c <+12>: add $0x18c4,%eax 
=> 0x56555741 <+17>: lea -0x9(%ebp),%edx 
    0x56555744 <+20>: push %edx 
    0x56555745 <+21>: mov %eax,%ebx 
    0x56555747 <+23>: call 0x56555590 <[email protected]> 
    0x5655574c <+28>: add $0x4,%esp 
    0x5655574f <+31>: nop 
    0x56555750 <+32>: mov -0x4(%ebp),%ebx 
    0x56555753 <+35>: leave 
    0x56555754 <+36>: ret  
End of assembler dump. 

を取得します。 OK。しかし、それは何のために使われていますか? eaxはgets()を呼び出す前にebxで移動します。しかし、その後は使用されません。古いebxはスタックから復元され、退室して戻ってきます。それは役に立たないようです。 Btw。 call get_pc_thunkものは何ですか?

同等の振る舞い私はprintfの使用の代わりになれば、:

void func() { 
    char buf[5]; 
    strcpy(buf, "AAAA"); 
    printf("%s",buf); 
} 

gdbの出力:

(gdb) info frame 
Stack level 0, frame at 0xffffd1d8: 
eip = 0x56555741 in func (/home/mischa/stuff/test/test.c:35); saved eip = 0x56555779 
called by frame at 0xffffd1e0 
source language c. 
Arglist at 0xffffd1d0, args: 
Locals at 0xffffd1d0, Previous frame's sp is 0xffffd1d8 
Saved registers: 
    ebx at 0xffffd1cc, ebp at 0xffffd1d0, eip at 0xffffd1d4 
(gdb) disas func 
Dump of assembler code for function func: 
    0x56555730 <+0>: push %ebp 
    0x56555731 <+1>: mov %esp,%ebp 
    0x56555733 <+3>: push %ebx 
    0x56555734 <+4>: sub $0x8,%esp 
    0x56555737 <+7>: call 0x56555780 <__x86.get_pc_thunk.ax> 
    0x5655573c <+12>: add $0x18c4,%eax 
=> 0x56555741 <+17>: movl $0x41414141,-0x9(%ebp) 
    0x56555748 <+24>: movb $0x0,-0x5(%ebp) 
    0x5655574c <+28>: lea -0x9(%ebp),%edx 
    0x5655574f <+31>: push %edx 
    0x56555750 <+32>: lea -0x17f0(%eax),%edx 
    0x56555756 <+38>: push %edx 
    0x56555757 <+39>: mov %eax,%ebx 
    0x56555759 <+41>: call 0x565555a0 <[email protected]> 
    0x5655575e <+46>: add $0x8,%esp 
    0x56555761 <+49>: nop 
    0x56555762 <+50>: mov -0x4(%ebp),%ebx 
    0x56555765 <+53>: leave 
    0x56555766 <+54>: ret  
End of assembler dump. 

を誰かが私にこれを説明していただけますか?

私は、次のCMakeLists.txtをコンパイルするためにcmakeを使用します。

cmake_minimum_required (VERSION 2.8) 

# projectname is the same as the main-executable 
project(test) 

# compile with 32 bit 
add_definitions('-m32') 

# Disable compiler optimization 
add_definitions('-O0') 

# include debugging information 
add_definitions('-g') 

# Align items on the stack to 4 bytes. This makes stuff easier. 
# See https://stackoverflow.com/questions/1061818/stack-allocation-padding-and-alignment 
add_definitions('-mpreferred-stack-boundary=2') 

# disable compiler buffer overflow protection 
add_definitions('-z execstack -z norelro -fno-stack-protector') 

# executable source code 
add_executable(test test.c) 

cmakeのは、GCCを使用しているようです。

+1

最適化を無効にしたためですか? –

+3

'ebx'はGOTへのポインタとして使われますが、実際には位置独立コードにのみ関係します。それは、それをあなたがそれほどコンパイルしたかどうか、あなたのcmakeのものからは明らかではありません。振る舞いを再現するためには、 '-fPIC'コマンドラインオプションを' gcc'に渡さなければなりませんでした。 – Jester

+0

'strcpy(buf、" AAAA ");は' movl'の後ろに 'movb'がインライン化されているので、実行する関数はまったく呼び出されません。 (最後の分解で指摘されたコードを参照してください) – rici

答えて

10

あなたのコンパイラツールチェーンは、デフォルトで位置独立実行可能ファイル(PIE)を生成するように(おそらくあなたのディストリビューションによって)設定されています。 32ビットx86では、位置に依存しないコードが呼び出し側とは別のライブラリに存在する関数を呼び出すためには、呼び出し時に呼び出し側モジュールのGOTのアドレスをebxにロードする必要があります。これはABI要件です。 ebxはx86 ABIのコールセーブされたレジスタなので、発信者は自分の発信者に戻る前に保存して後で復元する必要があります。

私はバックが有益かもしれないが、トピックAに書いたこの記事:GCCの非常に最近のバージョンで

https://ewontfix.com/18/

、新しい-fno-pltオプションはGOTというから負荷をインライン化することによって、この問題を回避することができるが、 ebxに依存するPLTを使用するよりも

+0

ありがとう! PIEの作成を無効にするにはどうすればよいですか?作成したコードを比較したいと思います。 –

+1

@MichaelPalm:コンパイル時には '-fno-PIE'を、リンク時には' -no-pie'または '-nopie'を使います。 '-no-pie'と' -nopie'のどちらが必要なのかは、GCC 6+をデフォルトのPythonでサポートしているのか(以前のもの)、Gentooの古いgccのデフォルトのパッチを使っているのか、後者の形式)。 –

+0

私のために働いた:-) –

関連する問題