main関数のアセンブリコード

5014 ワード

本文は主にmain関数のコンパイル後に生成された符号化を観察し,簡単にするためにmain関数の内容は空である.(注:以下の部分は新浪ブログから)
実験方法は以下の通りである:まず異なる環境でソースコードをコンパイルし、生成した実行可能なファイルを収集する.その後、実行可能ファイルにIDA Proを使用します(バージョン5.5、ここでは強力なIDAを称賛します!)逆アセンブリを行う.最後にmain関数の符号化(すべての符号化フォーマットはIntelスタイル)を観察し,分析と比較を行った.
本文の重点はいくつかの最も基本的な概念を討論することにあり、読者が各種環境で生成された送金符号化を熟知し、よりよくバイナリ分析を行うのに役立つ.注意しなければならないのは、C言語の面では、main関数はプログラムの開始入口であるが、実際には実行可能ファイルにとって、CPUが本当に実行する最初の命令はmain関数の符号化の最初の命令ではないことが多い.ここではmain関数の符号化だけを分析し、実行可能ファイルの他の部分については無視する.
単純mainコード:
int main(){
     return 0;
}

wvin 7+vs 2008+releaseで
--- d:\coding\helloworld\testc\main.c ------------------------------------------
int main(){
	return 0;
00231000  xor         eax,eax 
}
00231002  ret          

上のmain関数の符号化は、内容が非常に簡単であることがわかります.
第1の命令xor eax、eaxはeaxに対して異或演算を行い、これはレジスタに0値を付与する一般的な形式であり、通常関数の戻り値をeaxに戻し(32ビット、16ビットをaxに置く)ことを約束しているので、return 0であることがわかる.文を準備する.第2の命令retnはプロセス近接(near)戻り命令であり、スタックから戻りアドレスがeipに押され、それに対応するまだ遠い(far)戻り命令retfがあり、まずeipがポップアップされ、そしてcsをポップアップする(実際には、現代のオペレーティングシステムでは、各プロセスには同じ論理アドレス空間があり、セグメントレジスタの値はオペレーティングシステムによって設定され固定されており、それに関連するアセンブリ命令もほとんど使用されていない)、命令retはPROC擬似命令に基づいて、自動的に近いか遠いかを判断します(もちろん、実行可能ファイルからは偽命令は見えません).
wvin 7+vs 2008+releaseでは、この場合、より複雑です.
--- d:\coding\helloworld\testc\main.c ------------------------------------------
int main(){
00331370  push        ebp  
00331371  mov         ebp,esp 
00331373  sub         esp,0C0h 
00331379  push        ebx  
0033137A  push        esi  
0033137B  push        edi  
0033137C  lea         edi,[ebp-0C0h] 
00331382  mov         ecx,30h 
00331387  mov         eax,0CCCCCCCCh 
0033138C  rep stos    dword ptr es:[edi] 
	return 0;
0033138E  xor         eax,eax 
}
00331390  pop         edi  
00331391  pop         esi  
00331392  pop         ebx  
00331393  mov         esp,ebp 
00331395  pop         ebp  
00331396  ret              
---      -----------------------------------------------------------------------

vc 2010 debug次にvc 2010 debugモードで生成された実行可能ファイルの符号化を観察し、上図を参照するとreleaseモードよりも複雑であることがわかります.このような違いがあるのは、debugモードにデバッグ情報が含まれており、最適化されていないため、releaseモードがいくつかの実行プロセスを最適化しているからです.
コードの意味を簡単に説明します.
main関数も関数なので、関数の実行手順と同じです.呼び出し前に関数パラメータを渡し(この例ではパラメータがありません)、入ると関数の局所変数に空間を割り当て、終了するとこれらの空間を解放します.ここでは、渡されたパラメータ、サブルーチンの戻りアドレス、ローカル変数、および保存されたレジスタのために保持されるスタック空間であるスタックフレーム(stack frame)の概念を紹介する.スタックフレームの両端は2つのポインタで境界され、レジスタebpはフレームポインタとして、スタックフレームの底部を表し、関数呼び出し前にスタックを実行するスタックトップポインタの値に等しく、関数呼び出し中にその値を変更せず、関数呼び出しが終了すると、フレームポインタの値によってスタックフレーム空間を解放することができる.レジスタespは、スタックを実行するスタックトップポインタであり、スタックフレームのトップも表し、実行時に変更可能である(下図参照).
第1の符号化push ebpは、すぐにフレームポインタとして使用されるため、まずebpの値を保存する.第2の符号化mov ebpは、espがebpを現在のスタックトップポインタ、すなわちフレームポインタに割り当て、このときからebpはすべてのサブルーチンパラメータをアドレスするベースポインタとして使用される.3番目の符号化sub espは、0 C 0 hはスタック(スタックフレームでもある)を0 C 0 hサイズに拡大するものであるが、この場合はその中に内容が埋め込まれていないため、通常は局所変数に空間を残すためであり、ここでは局所変数は何もないのに、0 C 0 hサイズはどのように走っているのか、後でこの問題を説明する.慣例によれば、eax、edx、ecxの値は呼び出し元が保存する.すなわち、関数内部の3つのレジスタは勝手に使用できる.ebx,esi,ediの値は呼び出し元が保存し,使用前に元の値をスタックに保存しなければならないのも,次の3つのコードがこの3つのレジスタをそれぞれスタックに押し込む理由である.次のいくつかのコマンドは、デバッグに特化しています.lea edi,[ebp+var_C 0]は実際にはediにアドレスを格納し、アドレスの値はさっき0 C 0 hの大きさを残した領域の最低位置である.次にecxに30 hの値を付与する.eaxに0 CCCCCCCChを割り当てる.最後にコマンドrep stosdを実行し、このコマンドはstosdというコマンドをecx(すなわち30 h)回繰り返すことを意味し、stosdコマンドはeaxの値(0 CCCCCCCCCCh)をメモリにコピーすることを意味し、メモリのアドレスはes:ediであり、実行するたびにediが変化し、このコマンドを合わせるとes:ediを開始アドレスとし、サイズecx*4のメモリのすべてのバイトを0 CChとすることを意味する.先ほど残した0 C 0 h(30 h*4=0 C 0 h)のスペースをすべて0 CChにします.したがって、デバッグを容易にするためです.0 CChはアセンブリ命令int 3のバイナリコードです.この命令は3番割り込みサービスプログラムを呼び出すことを意味し、ブレークポイントが発生します.実際の実行効果を感じたい場合は、int main(){_asmint 3;return 0;}大きな領域をブレークポイントとして設定する意味は、プログラムに脆弱性がある場合、実行時にこの領域の内容を誤って実行する可能性があります.この領域の内容はすべて0 CChなので、実行時にすぐにエラーを報告し、脆弱性を発見しやすくなります.つまり、スタックに役立つデータのそばに罠が付着し、正しいプログラムの実行は決して罠に足を踏み入れません.このプロセスが終了すると、前に紹介したxor eax,eaxです.このmain関数に他の文があれば、アセンブリされたコードはrep stosdとxoe eax,eaxの間にあります.次にedi,esi,ebxレジスタの値を復元する.このとき、関数の実行はほぼ終了し、以前に開かれたスタックフレームの使命は終了し、mov esp、ebpはespを関数呼び出し前の状態に回復し、次いでebpを回復し、最後に戻り、プロセス全体が終了する.さらに、intelは、スタックフレームの作成と解放が非常に一般的であるため、2つの簡略化されたアセンブリ命令enterおよびleaveを提供する.ここで、enter imm、0はpush ebpと;mov ebp,esp; sub esp,imm相等価;leaveとmov esp,ebp;pop ebpは等価である.
GCCモードでは次はGCCモードでのアセンブリであり,同様に簡単なmain関数で作られている[lrao@localhost unix]$ gcc test5.c -S -o t.s
        .file   "test5.c"
        .text
.globl main
        .type   main, @function
main:
        pushl   %ebp
        movl    %esp, %ebp
        movl    $0, %eax
        popl    %ebp
        ret
        .size   main, .-main
        .ident  "GCC: (GNU) 4.4.6 20120305 (Red Hat 4.4.6-4)"
        .section        .note.GNU-stack,"",@progbits

この文章を総括していくつかのよく見られる環境の下でmain関数の符号化を示して、そして簡単に分析して、内容は比較的に太いです.スタックフレームの概念を完全に理解すれば,将来どのような関数符号化に遭遇しても,様々な混乱した操作を簡単に突破し,その重要な内容を見つけることができるのも本論文の初志である.今後さらに学習して、空のmain関数だけでなく、各オペレーティングシステムの実行可能なファイルの内容を完全に解析してみます.
ネット上で1つのwindowsの下の小さいツールを探し当てて、バイナリコードの対応する送金コード(Droplr)を見て、悪くありません
参照先:http://blog.sina.com.cn/s/blog_55a8a96d0100lib3.html