シェルコードとは、
コンピュータセキュリティの分野において、ソフトウェアの
脆弱性を悪用するためのペイロードとして利用されるコードの断片です。攻撃者が侵入したマシンを制御するために、
シェルを起動することが多いため「
シェルコード」と呼ばれています。
シェルコードは、通常、
機械語で記述されますが、
機械語でなくても同様のタスクを実行できるコードであれば、
シェルコードと見なされます。
シェルコードの主な役割は、攻撃者がシステムを制御するための足がかりとなることです。
シェルを起動する以外にも、様々な目的で使用されるため、
シェルコードという名称は必ずしも適切ではないという意見もありますが、他の適切な用語が定着していません。
シェルコードには、主に「ローカル型」と「リモート型」の2種類が存在します。これらは、攻撃者がどの範囲のシステムを制御しようとするかによって区別されます。
ローカル型シェルコード
ローカル型
シェルコードは、攻撃者が既にマシンへの限定的なアクセス権を持っている場合に利用されます。このタイプの
シェルコードは、より高い特権を持つプロセスの
脆弱性を悪用し、成功すると、攻撃者はそのプロセスと同等の特権を得ることができます。ローカル型
シェルコードは、比較的作成が容易で、主な目的は
シェルの実行ファイルを起動することです。
リモート型シェルコード
リモート型
シェルコードは、LANや
インターネットを介して、ネットワーク上の別のマシンで動作する
脆弱性のあるプロセスを攻撃するために使用されます。成功すると、攻撃者はネットワーク経由で対象マシンにアクセスできるようになります。リモート型
シェルコードは、通常、TCP/IPソケット接続を確立し、攻撃者が対象マシン上の
シェルにアクセスできるようにします。この接続を確立する方法として、以下の3つのタイプがあります。
- - connect-back: シェルコード自体が攻撃者のマシンに接続を試みる方式です。シェルコードが攻撃者のマシンに接続し返すため、このように呼ばれます。
- - bindshell: シェルコードが特定のポートにバインドし、攻撃者が接続を待機する方式です。攻撃者は、このバインドされたポートに接続することで、対象マシンを制御できます。
- - socket-reuse: 既存の接続を再利用する方式です。シェルコードは、実行される前にクローズされていない接続を検出し、それを利用して攻撃者と通信します。この方式は、実装が難しいため、あまり一般的ではありません。
ファイアウォールは、connect-back方式で生成される外部への接続や、bindshell方式で生成される外部からの接続を検出できます。そのため、
ファイアウォールは、
脆弱性があるマシンに対する一定の保護を提供します。しかし、socket-reuse方式は新しい接続を作成しないため、検出がより困難です。
リモート型
シェルコードの一種として、
ダウンロード実行型
シェルコードがあります。このタイプの
シェルコードは、
シェルを起動する代わりに、ネットワーク経由で特定の実行ファイルを
ダウンロードし、それを実行します。これは、
マルウェアをインストールする際によく使用され、ドライブバイ
ダウンロード攻撃として知られています。
シェルコードの実行戦略
シェルコードを対象プロセスに注入する際には、プログラムカウンタの制御を奪う
脆弱性も同時に利用されます。プログラムカウンタは、
シェルコードの開始アドレスを指すように変更され、これにより
シェルコードが実行されます。
シェルコードの注入は、ネットワーク経由でのデータ送信や、ローカルファイルへの仕込み、コマンドライン引数や環境変数への埋め込みなど、様々な方法で行われます。
シェルコードの符号化
シェルコードは、注入されるデータの制限に対応するために、符号化されることがよくあります。これらの制限には、コードサイズの最小化、ヌル文字の使用禁止、
英数字のみの使用などが含まれます。符号化技術は、これらの制限を回避するために使用され、自己書き換えコードやポリモーフィックコードなどが利用されます。
符号化方法の例
ブラウザを標的とした場合、
シェルコードは
JavaScriptの文字列として符号化されることがあります。
パーセントエンコーディング、\uXXXXエンコーディング、HTMLエンコーディングなどが使用されます。例えば、
IA-32アーキテクチャで2つのNOP命令を符号化すると、以下のようになります。
90 NOP
90 NOP
unescape("%u9090");
"\u9090";
"邐"
または
"邐"
ヌル文字の排除
シェルコードは、通常、ヌル文字で終了する文字列として扱われるため、
シェルコード内にヌル文字を含めることはできません。ヌル文字が含まれていると、そこで文字列が途切れてしまいます。ヌル文字を排除するには、同じ効果を持つ別の命令列に置き換える必要があります。例えば、
IA-32アーキテクチャでは、ヌル文字を含む命令を、ヌル文字を含まない命令に置き換えることができます。
英数字または印字可能文字のみを使用
特定の状況では、印字可能な文字や
英数字のみを使用して
シェルコードを記述する必要があります。この制限に対応するためには、自己書き換えコードなどの技術を使用します。デコーダ部分は制限されたコードで記述し、
シェルコード本体は符号化しておき、デコーダがそれを展開して実行します。
最近のプログラムでは、
Unicodeが使用されることが一般的です。
ASCII文字列は、しばしば
Unicodeに変換されます。
UTF-16では、各文字に2バイト(一部は4バイト)が使用されるため、
ASCII文字列を
UTF-16に変換すると、各バイトの後にゼロバイトが挿入されます。
UTF-16で正しく動作する
シェルコードを記述するには、自己書き換えコードを使用します。
プラットフォーム依存性
シェルコードは、通常、
機械語で記述されるため、プラットフォームに依存します。
CPUアーキテクチャ、
オペレーティングシステムのバージョン、サービスパックなど、環境に合わせて
シェルコードを調整する必要があります。また、
脆弱性によっては、使用できる
シェルコードの形式が大きく制限される場合があります。汎用的な
シェルコードを作成するには、複数のプラットフォームに対応したバージョンを作成し、実行時に適切なものを選択する必要があります。
関連項目
外部リンク