2011-11-13 9 views
5

私が学校で勉強している入門用のアセンブリクラスから講義スライドを勉強しているときに、誰かが私を助けてくれるかどうかを知りたいですか?私が抱えている問題は、アセンブリを理解していないということです。アセンブリに基づいてCソースコードが正確にどのように順序付けられているかです。私は私が話しているスニペットを投稿し、私が話していることがもっと明確になるでしょう。リバースエンジニアリングアセンブリのCソースコード

Cソースは、与えられた:

int arith(int x, int y, int z) 
{ 
    int t1 = x+y; 
    int t2 = z+t1; 
    int t3 = x+4; 
    int t4 = y * 48; 
    int t5 = t3 + t4; 
    int rval = t2 * t5; 
    return rval; 
} 

アセンブリを与えられた:

arith: 
pushl %ebp 
movl %esp,%ebp 

movl 8(%ebp),%eax 
movl 12(%ebp),%edx 
leal (%edx,%eax),%ecx 
leal (%edx,%edx,2),%edx 
sall $4,%edx 
addl 16(%ebp),%ecx 
leal 4(%edx,%eax),%eax 
imull %ecx,%eax 

movl %ebp,%esp 
popl %ebp 
ret 

私はz + t1の追加その私が、たとえば見分けることができるようになっていますどのように同じくらい混乱しています(z + x + y)アセンブリ内でy * 48の後に来るアセンブリ内の2番目の行(ソース内)にリストされています。たとえば、アセンブリ内にある場合でも、x + 4は3行目です。最後のlealステートメントと混在しています。ソースがある場合は私には合っていますが、テスト用のソースを再現できるはずです。コンパイラが最適化していることを理解していますが、誰かがリバースエンジニアリングについて考える方法を持っていて、彼らが私の思考過程を歩いてもらえると大変感謝しています。

ありがとうございました。

+0

あなたがコンパイルされたとき、あなたは何の最適化レベルを使用していますか?行ごとの変換を行う場合は-O0を使用し、そうでない場合は最適化を考慮する必要があります。 – Maz

+1

...最適化されたコードを元のCステートメントの順序に戻すことは完全に可能ではありません。 –

+0

これは簡単なコンパイルですか?幾分最適化されているようです。関数が計算する代数式を書き留めて、それを発見できるかどうかを確認します。 –

答えて

9

Cソースからアセンブリがどのように生成されたかを示すために、分解を分解しました。edxeax

pushl %ebp 
movl %esp,%ebp 


移動 xy:= x

8(%ebp)、= y12(%ebp)16(%ebp) = z

arith: 

スタックフレームを作成

movl 8(%ebp),%eax 
movl 12(%ebp),%edx 


t1 = x + yleal(負荷実効アドレス) edxeaxを追加し、そして t1ecxであろう。下の2つのステップで

leal (%edx,%eax),%ecx 


int t4 = y * 48;、3を掛け、次いで16 t4によって最終的 edxであろう:

edxに2を掛け、edxを結果に加えます。 edx = edx * 3

leal (%edx,%edx,2),%edx 

左に4ビットシフトします。 16を掛け:

sall $4,%edx 


int t2 = z+t1;ecx 最初z命令 ecxの終わりに、 16(%ebp)であり、 t1を保持 t2を保持する。

addl 16(%ebp),%ecx 


int t5 = t3 + t4;t3は、単に x + 4であり、 t3を計算して保存するのではなく、 t3の式がインラインに配置されています。この指示には、 (x+4) + t4があり、これは t3 + t4と同じです。これは、 edxt4)と eaxx)を追加し、その結果を得るために オフセットとして4を加えます。

leal 4(%edx,%eax),%eax 

int rval = t2 * t5;かなりこれをストレートフォワード。 ecxt2を表し、eaxt5を表す。戻り値は、eaxを通じて呼び出し元に戻されます。

imull %ecx,%eax 


スタックフレームを破棄し、 espebpを復元:ルーチンから

movl %ebp,%esp 
popl %ebp 


戻る:

ret 


この例から、あなたがいることがわかりますレスルtは同じですが、構造は少し異なります。ほとんどの場合、このコードはある種の最適化でコンパイルされているか、誰かがそれを書いてポイントを立証しています。

他の人からも言われているように、逆アセンブリからソースに正確に戻ることはできません。アセンブリを読んでいる人が同等のCコードを思いつくのは、その解釈に依存します。あなたがLinux上で次の操作を行うことができ、学習アセンブリとあなたのCプログラムの分解を理解することを助けるために


ソースを埋め込みますデバッグ情報(-g)でコンパイルします。

gcc -c -g arith.c 

64ビットマシンを使用している場合は、-m32フラグを使用して32ビットバイナリを作成するようにコンパイラに指示することができます(以下の例ではそうしました)。

objdump -d -S arith.o 

-d =解体、-S =ディスプレイソース:それは元のインターリーブだと


使用して、objdumpのは、オブジェクトファイルをダンプします。使用している例のAT & T構文よりも好きな場合は、-M intel-mnemonicを追加してIntel ASM構文を使用できます。

出力:

arith.o:  file format elf32-i386 


Disassembly of section .text: 

00000000 <arith>: 
int arith(int x, int y, int z) 
{ 
    0: 55      push %ebp 
    1: 89 e5     mov %esp,%ebp 
    3: 83 ec 20    sub $0x20,%esp 
    int t1 = x+y; 
    6: 8b 45 0c    mov 0xc(%ebp),%eax 
    9: 8b 55 08    mov 0x8(%ebp),%edx 
    c: 01 d0     add %edx,%eax 
    e: 89 45 fc    mov %eax,-0x4(%ebp) 
    int t2 = z+t1; 
    11: 8b 45 fc    mov -0x4(%ebp),%eax 
    14: 8b 55 10    mov 0x10(%ebp),%edx 
    17: 01 d0     add %edx,%eax 
    19: 89 45 f8    mov %eax,-0x8(%ebp) 
    int t3 = x+4; 
    1c: 8b 45 08    mov 0x8(%ebp),%eax 
    1f: 83 c0 04    add $0x4,%eax 
    22: 89 45 f4    mov %eax,-0xc(%ebp) 
    int t4 = y * 48; 
    25: 8b 55 0c    mov 0xc(%ebp),%edx 
    28: 89 d0     mov %edx,%eax 
    2a: 01 c0     add %eax,%eax 
    2c: 01 d0     add %edx,%eax 
    2e: c1 e0 04    shl $0x4,%eax 
    31: 89 45 f0    mov %eax,-0x10(%ebp) 
    int t5 = t3 + t4; 
    34: 8b 45 f0    mov -0x10(%ebp),%eax 
    37: 8b 55 f4    mov -0xc(%ebp),%edx 
    3a: 01 d0     add %edx,%eax 
    3c: 89 45 ec    mov %eax,-0x14(%ebp) 
    int rval = t2 * t5; 
    3f: 8b 45 f8    mov -0x8(%ebp),%eax 
    42: 0f af 45 ec    imul -0x14(%ebp),%eax 
    46: 89 45 e8    mov %eax,-0x18(%ebp) 
    return rval; 
    49: 8b 45 e8    mov -0x18(%ebp),%eax 
} 
    4c: c9      leave 
    4d: c3      ret 

あなたは最適化せずに、コンパイラはあなたが持っている例よりも大きなバイナリを生成し、見ることができるように。コンパイル時にコンパイル最適化フラグを追加することができます(つまり、-O1-O2-O3)。最適化レベルが高いほど、分解がより抽象的に見えるようになります。例えば、単にレベル1最適化(gcc -c -g -O1 -m32 arith.c1)と、生成されたアセンブリコードは多くの短い

00000000 <arith>: 
int arith(int x, int y, int z) 
{ 
    0: 8b 4c 24 04    mov 0x4(%esp),%ecx 
    4: 8b 54 24 08    mov 0x8(%esp),%edx 
    int t1 = x+y; 
    8: 8d 04 11    lea (%ecx,%edx,1),%eax 
    int t2 = z+t1; 
    b: 03 44 24 0c    add 0xc(%esp),%eax 
    int t3 = x+4; 
    int t4 = y * 48; 
    f: 8d 14 52    lea (%edx,%edx,2),%edx 
    12: c1 e2 04    shl $0x4,%edx 
    int t5 = t3 + t4; 
    15: 8d 54 11 04    lea 0x4(%ecx,%edx,1),%edx 
    int rval = t2 * t5; 
    19: 0f af c2    imul %edx,%eax 
    return rval; 
} 
    1c: c3      ret 
6

元のソースを再現することはできません。同等のソースのみを再現することができます。

t2の計算は、t1の後の任意の場所に、retvalの前に表示されます。

ソースが1つでも表現だったかもしれない:

return (x+y+z) * ((x+4) + (y * 48)); 
1

Decompilationは完全に達成可能ではありません。コメント&名はあなたの元の手がかりを与えたソースコード(から行くときいくつかの知識の損失がありますプログラマの意図)をバイナリ・マシン・コード(命令がプロセッサによって実行される)に変換する。

5

リバースエンジニアリングでは、元のソースコードを1行ずつ気にする必要はありません。副作用は、コードが何をするのかをプログラマーが意図したものではなく、あなたが見ることです。

関連する問題