コツ? 開発者の立場になって考えることさ!リバースエンジニアリング入門(2)(2/2 ページ)

» 2011年07月19日 00時00分 公開
[岩村誠日本電信電話株式会社]
前のページへ 1|2       

シェルコード内で使えないバイト値

 攻撃の過程で脆弱な個所にたどり着くために、思わぬ制約がシェルコードに課せられる場合があります。ここではその例として、strcpyによるスタックバッファオーバーフローを見ていきましょう。オーバーフローさせる書き込み先バッファ内にシェルコードを配置しつつ、リターンアドレスを上書きすることで、配置したシェルコードに制御を移す攻撃を考えてみます(図4)。

図4 strcpyによるスタックバッファオーバーフロー 図4 strcpyによるスタックバッファオーバーフロー

 ここで注意が必要なのは、strcpyにおいて0x00が文字列の終端記号として解釈されることです。つまりシェルコードをバイト列として見たときに0x00がその中に入っていると、strcpyによる文字列コピーが0x00の個所で止まってしまいます。その結果、0x00の後ろに控えているシェルコードやポインタを書き込むことができなくなってしまいます(図5)。

図5 0x00によるコピーの中断 図5 0x00によるコピーの中断

 こうした使えないバイト値を避けるには、バイト列としてのシェルコードを意識する必要があります。例として、eaxレジスタに0を代入する方法を考えてみましょう。単純に考えると

mov eax,0

というアセンブリになると思います。しかしながら、このアセンブリのバイト列表現は0xB8、0x00、0x00、0x00、0x00となり、0x00が機械語命令内に出てきてしまいます。

 そこで、同じ値の排他的論理和は0になることに着目すると、次のようなアセンブリでもeaxレジスタを0にすることができます。

xor eax, eax

 このバイト列表現は0x33、0xC0となり、0x00を取り除くことができました。シェルコード全体に対してこうした工夫を行うことで、使えないバイト値を避けつつシェルコードを作成することができます。また、こうしたバイト値をシェルコード内から取り除くために、前述のXORエンコーダが利用されることもあります。

 他にも興味深い例として、対象脆弱性にたどり着くまでにASCII文字列以外は通さないというチェックルーチンが存在する場合があります。つまり攻撃者は、シェルコードをASCII文字列で表現しなくてはなりません。

 ここまでくると、もはや曲芸のように思えますが、これでも何とかなるのがシェルコード開発の面白いところです。先ほど紹介したエンコーダの中には、元のシェルコードをASCII文字列に変形するモジュールが用意されています。

use payload/windows/download_exec
set URL http://hoge.com
generate -t raw -e x86/alpha_mixed

 x86/alpha_mixedは対象となるシェルコードを英数字のみで表現するモジュールです。この出力結果は以下のようになります。

 この英数字の羅列を機械語として実行すると、payload/windows/download_execとまったく同じ振る舞いをします。

 こうした制約条件を満たすコードは、一見回りくどい処理をしているように感じることがあります。ただ、その回りくどさの背後にシェルコード内で使えないバイト値があると知っていれば、解析時のストレスも少しは和らぐのではないでしょうか。

APIアドレスの解決

 一般的な実行ファイルは、ローダと呼ばれるOSの機能により起動されます。

 ローダは実行ファイルで使われる(静的リンクされている)Win32 APIを調べ、関連するDLLをロードし、Win32 APIのアドレスが決まったところで、実行ファイル内のIAT(Import Address Table)と呼ばれる領域にそのアドレス情報を書き込みます。実行ファイルはビルド時にIAT経由でWin32 APIを呼ぶように構成されますので、プログラム開発者としてはWin32 APIがどのアドレスに位置しているかを意識することなく、Win32 APIを呼び出すことができます。

 一方、起動済みのプロセス内で動作を開始するシェルコードには、Win32 APIのアドレスを解決してくれるローダ役がいません。このため、ローダの処理であるアドレス解決を自前で行う必要があります。

 Win32 APIも呼べない状態で、DLLをロードし、アドレス解決することは難しいように感じるかもしれませんが、いくつかの方法が知られています。詳細は今後の連載で紹介していくことにしましょう。

 少し話が脱線しますが、Windowsにおけるシステムコール番号は、バージョンによって大きく変わります。

 例えば、仮想メモリを確保するNtAllocateVirtualMemoryというシステムコールの番号は、XPの場合は0x0011でしたが、Windows Vistaでは0x0012になっています。このため、Windowsのバージョンに依存しないシェルコードは、たいていWin32 APIを利用しようとします。

 ただ、Linuxでは少し事情が異なります。筆者の記憶では、Linuxのシステムコール番号はこれまでに追加されることはあっても、すでに割り当てられた番号に別のシステムコールが割り当てられたことはなかったと記憶しています。

 このため、直接システムコールを呼び出したとしても、Linuxのバージョンによって挙動が変わってしまうことはありません。一般的にglibcなどのAPIのアドレスを解決するよりも、システムコールを直接呼び出す方が手間も少なく済むため、Linuxではシステムコールを直接呼び出すアプローチが利用される傾向にあります。

シェルコードサイズの制限

 攻撃対象とする脆弱性によっては、シェルコードのサイズが制限される場合があります。こうした制限は、コーディングの工夫で何とかなればいいのですが、それだけでは解決できない場合もあります。

 このときに使われる技術の1つに、Stager/Stageと呼ばれる構成があります(図6)。

図6 Stage/Stager構成 図6 Stage/Stager構成

 最初にStagerと呼ばれるシェルコードが実行(図6-【1】)され、このStagerが本体となるシェルコード(Stage)を受信(図6-【2】)し、実行(図6-【3】)します。

 一般的にStagerは、単一のシェルコードよりもコンパクトに作られています。例えば、Metasploitに含まれるpayload/linux/x86/shell_reverse_tcpは単一のシェルコードであり、そのサイズは71バイトです。

 一方、同じ機能を持つpayload/linux/x86/shell/reverse_tcp(shellとreverse_tcpの間が/になっていることに注意してください)はStager/Stage構成を取り、そのStagerのサイズは50バイトとなっています。この例では大した差ではないように見えますが、シェルコードの機能が豊富になればなるほど、その効果は大きくなります。

 ネットワーク経由で受け取ったデータに命令ポインタを移す、そんなシェルコードを見たときは、このStager/Stage構成のことを思い出してみましょう。

快適なシェルコード解析ライフのために

 今回は普通のプログラム開発では縁のない、シェルコードに課せられる典型的な制約をいくつか紹介してきました。

 こうした知識なく、例えば数十KBの無意味なスライディングコードを1つ1つ読んでいたら、それこそ途中で心が折れてしまいます。今回の内容はシェルコードで使われるテクニックの一部に過ぎませんが、今後の皆様のシェルコード解析ライフ(?)を少しでも楽にする材料になればと思います。

 次回以降は、シェルコードの解析ツールや、実際のアセンブリを紹介していく予定です。お楽しみに。

筆者紹介

NTT情報流通プラットフォーム研究所

岩村 誠(いわむら まこと)

2002年 日本電信電話株式会社入社。学生時代にセキュリティホール対策に魅せられ、現在は新たなマルウェア対策技術の研究開発を推進。「アナライジング・マルウェア」執筆の言い出しっぺ、らしいが、当時は酔っぱらっていて正直あまり覚えていない(^^;



前のページへ 1|2       

Copyright © ITmedia, Inc. All Rights Reserved.

RSSについて

アイティメディアIDについて

メールマガジン登録

@ITのメールマガジンは、 もちろん、すべて無料です。ぜひメールマガジンをご購読ください。