コードは次のように逆アセンブルされます。
00000000 31C0 xor eax,eax
00000002 50 push eax
00000003 682F2F7368 push dword 0x68732f2f
00000008 682F62696E push dword 0x6e69622f
0000000D 89E3 mov ebx,esp
0000000F 50 push eax
00000010 53 push ebx
00000011 89E1 mov ecx,esp
00000013 99 cdq
00000014 B00B mov al,0xb
00000016 CD80 int 0x80
の厚意によるndisasm
。これらの手順を順を追って説明し、途中でスタック フレームを分析してみましょう。
xor eax,eax
eax
オペランドとそれ自体の XOR 演算の結果は常にゼロになるため、レジスタをゼロにします。push eax
次に、値をスタックにプッシュします。したがって、スタックは現在、多かれ少なかれ次のようになっています (esp
コードの先頭にあるの値に関連して示されているオフセットは、現在指しているesp
スタック セルを意味します)。esp
+----------+
0 | 00000000 |
esp -4 | xxxxxxxx |
+----------+
次に、push dword
いくつかの即時値をスタックにプッシュする 2 つの命令があります。これらを実行すると、次のようになります。
+----------+
0 | 00000000 |
-4 | 68732f2f |
-8 | 6e69622f |
esp -12| xxxxxxxx |
+----------+
esp
現在、スタックにプッシュされた 2 番目の即値の最後のバイトを指しています。プッシュされた値を ASCII として解釈してみましょう。現在の の値から順番に開始した場合に、スタックから読み取られる順序になりますesp
。のバイト シーケンスを取得します。2f62696e2f2f7368
これは ASCII では に等しくなり/bin//sh
ます。さらに、シーケンスは 0 で終わるため、有効な C 文字列です。
esp
これが、 の現在の値がレジスタに保存される主な理由ebx
です。実行される実行可能ファイルへのパスが含まれています。POSIX は単純に複数のスラッシュを無視し、それらを 1 つのスラッシュとして扱うため、二重スラッシュは OS にとって問題ではありません。
次に、 と の現在の値をスタックeax
にebx
プッシュします。eax
にはゼロが含まebx
れており、C-string へのポインターが含まれていることがわかっています"/bin//sh"
。現在、スタックは次のようになっています。
+----------+
0 | 00000000 |
-4 | 68732f2f |
-8 | 6e69622f |
ebx -12| 00000000 |
-16| (ebxVal) |
ecx esp -20| xxxxxxxx |
+----------+
レジスタの値をスタックにプッシュした後、現在のポインタesp
が に保存されecx
ます。
cdq
この場合、非常に巧妙なトリックを実行する命令です。現在の値をレジスタペアに符号拡張しeax
ますedx:eax
。したがって、この場合、ゼロedx
の符号拡張はゼロであるため、 の値をゼロにします。もちろん、 で値をクリアすることもできますedx
がxor edx, edx
、その命令は 2 バイトでエンコードされており、1 バイトcdq
しか使用しません。
次の命令は、値0xb
(11) を の下位バイトレジスタに入れeax
ます。前のケースと同様に、 を実行することもできますがmov eax, 0xb
、即値は完全な 32 ビット値としてエンコードする必要があるため、5 バイトの命令になります。
int 0x80
Linux でシステム コール インボーカーを呼び出します。システムコールの数eax
(現在は に等しい0xb
ため、sys_execve
関数が呼び出されます) と、 、 、 、 、および の追加ebx
のecx
引数edx
がesi
必要edi
ですebp
。
それでは、そのシステム コールのプロトタイプを見てみましょう。
int execve(const char *filename, char *const argv[], char *const envp[]);
したがって、filename
引数は に配置されますebx
- それは を指し/bin//sh
ます。argv
に配置される はecx
、実行される実行可能ファイルの引数の配列であり、NULL
値で終了する必要があります。Intel アーキテクチャでNULL
は、 は に等しく0
、それをecx
指します: へのポインタ/bin//sh
、そしてNULL
値です。envp
、つまりNULL
、環境値の配列を指します。これchar*
は、形式の値として表現する必要がありますkey=value
。
実行が成功execve
すると、現在のプロセス イメージが、指定された引数で実行された、指定された実行可能ファイルのイメージに置き換えられます。この場合、/bin/sh
は (存在する場合) の引数で実行されます/bin//sh
。
これが機能しない理由については、Michael がおそらく正しかったでしょう。最近の Linux カーネルはデータ ページを実行不可としてマークしており、それらを実行しようとするとセグメンテーション フォールトが発生します。