連載「OSS脆弱性ウォッチ」では、さまざまなオープンソースソフトウェアの脆弱性に関する情報を取り上げ、解説する。今回は、前回から紹介している「QEMU」の脆弱性を悪用したVMエスケープ攻撃に関する事例のうち、メモリ情報漏えいの脆弱性(CVE-2015-5165)を紹介する。
本連載「OSS脆弱性ウォッチ」では、さまざまなオープンソースソフトウェア(OSS)の脆弱(ぜいじゃく)性に関する情報を取り上げ、解説しています。
前回から数回に分けて、OSSのプロセッサエミュレータである「QEMU(キューエミュ)」の脆弱性を悪用したVM(仮想マシン)エスケープ攻撃に関する事例を、セキュリティ技術の会の佐藤が紹介しています。
前回で環境構築を終えたので、今回は、VMエスケープ攻撃に使われた2つの脆弱性のうちメモリ情報漏えいの脆弱性(CVE-2015-5165)について解説します。
前回も簡単に触れましたが、CVE-2015-5165は、RTL8139(1999年に販売されていたREALTEKのNIC)ネットワークカードデバイスエミュレータのQEMU上の脆弱性です。RTL8139ネットワークカードデバイスモデルのC+モードのオフロードエミュレーションにおけるプロセスのヒープメモリが読まれてしまいます。
後で説明するエクスプロイトでは、この脆弱性を用いて、以下の2つの情報を攻撃者にリークさせます。
それでは、脆弱性を確認してみましょう。
REALTEKのネットワークカードはCモードとC+モードの2つのreceive/transmitオペレーションモードをサポートしています。
カードがC+モードでセットアップされていると、NICデバイスエミュレータはIPパケットデータの長さを誤算してしまうので、利用可能な容量よりも多くのデータを送ってしまうことになります。
脆弱性はhw/net/rtl8139.cの「rtl8139_cplus_transmit_one function()」にあります。
/* ip packet header */ ip_header *ip = NULL; int hlen = 0; uint8_t ip_protocol = 0; uint16_t ip_data_len = 0; uint8_t *eth_payload_data = NULL; size_t eth_payload_len = 0; int proto = be16_to_cpu(*(uint16_t *)(saved_buffer + 12)); if (proto == ETH_P_IP) { DPRINTF("+++ C+ mode has IP packet\n"); /* not aligned */ eth_payload_data = saved_buffer + ETH_HLEN; eth_payload_len = saved_size - ETH_HLEN; ip = (ip_header*)eth_payload_data; if (IP_HEADER_VERSION(ip) != IP_HEADER_VERSION_4) { DPRINTF("+++ C+ mode packet has bad IP version %d " "expected %d\n", IP_HEADER_VERSION(ip), IP_HEADER_VERSION_4); ip = NULL; } else { hlen = IP_HEADER_LENGTH(ip); ip_protocol = ip->ip_p; ip_data_len = be16_to_cpu(ip->ip_len) - hlen; } }
IPヘッダは2つの領域(hlenとip->ip_len)を含んでおり、それぞれ下記を表しています。
以下の部分のソースコードで分かる通り、IPデータの長さ(ip_data_len)を計算している間は「ip->ip_len >= hlen」であると保証するための確認は行われません。
これは、ip_data_lenの領域がunsigned shortとしてエンコードされていることからも分かるように、Transmitバッファーにある利用可能な領域に対してそれ以上のデータを送信することにつながります。
ip_data_lenは後でmallocされたバッファーの中にコピーされるTCPデータの長さ(もしデータがMTUのサイズを上回るようであればチャンクごと)を計算するために使われます。
int tcp_data_len = ip_data_len - tcp_hlen; int tcp_chunk_size = ETH_MTU - hlen - tcp_hlen; int is_last_frame = 0; for (tcp_send_offset = 0; tcp_send_offset < tcp_data_len; tcp_send_offset += tcp_chunk_size) { uint16_t chunk_size = tcp_chunk_size; /* これが最後のフレームかチェック */ if (tcp_send_offset + tcp_chunk_size >= tcp_data_len) { is_last_frame = 1; chunk_size = tcp_data_len - tcp_send_offset; } memcpy(data_to_checksum, saved_ip_header + 12, 8); if (tcp_send_offset) { memcpy((uint8_t*)p_tcp_hdr + tcp_hlen, (uint8_t*)p_tcp_hdr + tcp_hlen + tcp_send_offset, chunk_size); } /* 続く */ }
従って、lengthサイズの破損した不正な形式のパケットを偽造することで(例:ip-->ip_len = hlen - 1)、QEMUのヒープメモリからおよそ64KB情報がリークされます。
単一のパケットを送信する代わりに、ネットワークカードデバイスエミュレータは43の断片化されたパケットを送信することになリます。
メモリ情報漏えいの脆弱性(CVE-2015-5165)に対するエクスプロイトコードは、「cve-2015-5165.c」で公開されています。
このエクスプロイトでは、CVE-2015-5165の脆弱性によりリークした情報から、下記を解決します。
このエクスプロイトでは、カード上のレジスタを設定します。その後、カードのMACアドレスにアドレス指定された不正なIPパケットを偽造します。
CVE-2015-5165の脆弱性に加えて、この脆弱性をエクスプロイトで利用するために、不正なパケットを送信し、漏えいしたデータを読み取るために、Rx/Txディスクリプタバッファーをカードに設定し、パケットが脆弱なコードパス(プログラムの実行経路)を通過させるようにフラグを設定する必要があります。この設定を簡単に説明します。
Rx/Txディスクリプタは、buf_loとbuf_hiがそれぞれRx/Txバッファーの下位32bitと上位32bitの物理メモリアドレスである次の構造体によって定義されます。
struct rtl8139_desc { uint32_t dw0; uint32_t dw1; uint32_t buf_lo; uint32_t buf_hi; };
これらのアドレスは、送受信されるパケットを保持しているバッファーを指しており、ページサイズの境界で整列させる必要があります。
変数dw0は、バッファーのサイズと、バッファーがカードまたはドライバによって所有されているかどうかを示す所有フラグなどをエンコードします。
ネットワークカードは「in()」「out()」プリミティブを通して設定されます(sys/io.h)。そのため、CAP_SYS_RAWIO権限が必要になります。
以下のソースコードでは、カードを設定し、単一のTxディスクリプタのセットアップを行います。
#define RTL8139_PORT 0xc000 #define RTL8139_BUFFER_SIZE 1500 struct rtl8139_desc desc; void *rtl8139_tx_buffer; uint32_t phy_mem; rtl8139_tx_buffer = aligned_alloc(PAGE_SIZE, RTL8139_BUFFER_SIZE); phy_mem = (uint32)gva_to_gpa(rtl8139_tx_buffer); memset(&desc, 0, sizeof(struct rtl8139_desc)); desc->dw0 |= CP_TX_OWN | CP_TX_EOR | CP_TX_LS | CP_TX_LGSEN | CP_TX_IPCS | CP_TX_TCPCS; desc->dw0 += RTL8139_BUFFER_SIZE; desc.buf_lo = phy_mem; iopl(3); outl(TxLoopBack, RTL8139_PORT + TxConfig); outl(AcceptMyPhys, RTL8139_PORT + RxConfig); outw(CPlusRxEnb|CPlusTxEnb, RTL8139_PORT + CpCmd); outb(CmdRxEnb|CmdTxEnb, RTL8139_PORT + ChipCmd); outl(phy_mem, RTL8139_PORT + TxAddr0); outl(0x0, RTL8139_PORT + TxAddr0 + 0x4);
これにより、設定されているRxバッファーにアクセスして、リークしたデータを読み取ることができるようになります。
リークしたデータを分析すると、以下のことが確認できます。
typedef struct ObjectProperty { gchar *name; gchar *type; gchar *description; ObjectPropertyAccessor *get; ObjectPropertyAccessor *set; ObjectPropertyResolve *resolve; ObjectPropertyRelease *release; void *opaque; QTAILQ_ENTRY(ObjectProperty) node; } ObjectProperty;
QEMUは、オブジェクトモデルに従って、デバイス、メモリ領域などを管理します。起動時、QEMUは複数のオブジェクトを作成し、それらにプロパティに割り当てます。
例として、次の呼び出しはメモリ領域オブジェクトに「may-overlap」プロパティを追加します。このプロパティには、このブール値プロパティの値を取得するための取得メソッドが用意されています。
object_property_add_bool(OBJECT(mr), "may-overlap", memory_region_get_may_overlap, NULL, /* memory_region_set_may_overlap */ &error_abort);
RTL8139ネットワークカードデバイスエミュレータは、パケットを再構成するためにヒープ上に64KBを確保しています。割り当てられたバッファーが破壊されたオブジェクトプロパティによって解放されたスペースに収まる可能性は非常に高いです。
このエクスプロイトでは、リークしたメモリ内の既知のオブジェクトプロパティを探します。正確には、少なくとも1つの関数ポインタが(「get」「set」「resolve」「release」)に設定されている80Bのメモリチャンク(解放されたオブジェクトプロパティ構造のチャンクサイズ)を探します。
これらのアドレスにASLRが適用されている場合でも、.textセクションのベースアドレスを推測できます。
それらのページオフセットは固定されています(最下位12bitまたは仮想アドレスはランダム化されていません)。QEMUの関数のアドレス得るための計算も可能です。
また「mprotect()」「system()」など、一部のLibC関数のアドレスをそれらのPLTエントリから取得することもできます。また、アドレス「PHY_MEM + 0x78」が何度かリークされています(PHY_MEMは、ゲストに割り当てられた物理メモリの開始アドレスです)。
今回は、CVE-2015-5165とエクスプロイトについて詳細に解説しました。次回はCVE-2015-7504(ヒープベースのオーバーフロー脆弱性)について解説します。
慶應義塾大学、総合政策学部2年生。中学高校はカナダのハリファックスとトロントに単身留学。将来はサイバーセキュリティの分野に進みたいと思っている。
Copyright © ITmedia, Inc. All Rights Reserved.