連載
» 2019年05月13日 05時00分 公開

OSS脆弱性ウォッチ(13):QEMU脆弱性を利用したVMエスケープ攻撃の検証:メモリ情報漏えいの脆弱性編

連載「OSS脆弱性ウォッチ」では、さまざまなオープンソースソフトウェアの脆弱性に関する情報を取り上げ、解説する。今回は、前回から紹介している「QEMU」の脆弱性を悪用したVMエスケープ攻撃に関する事例のうち、メモリ情報漏えいの脆弱性(CVE-2015-5165)を紹介する。

[佐藤琳音,OSSセキュリティ技術の会]

 本連載「OSS脆弱性ウォッチ」では、さまざまなオープンソースソフトウェア(OSS)の脆弱(ぜいじゃく)性に関する情報を取り上げ、解説しています。

 前回から数回に分けて、OSSのプロセッサエミュレータである「QEMU(キューエミュ)」の脆弱性を悪用したVM(仮想マシン)エスケープ攻撃に関する事例を、セキュリティ技術の会の佐藤が紹介しています。

 前回で環境構築を終えたので、今回は、VMエスケープ攻撃に使われた2つの脆弱性のうちメモリ情報漏えいの脆弱性(CVE-2015-5165)について解説します。

メモリ情報漏えいの脆弱性(CVE-2015-5165)の概要

 前回も簡単に触れましたが、CVE-2015-5165は、RTL8139(1999年に販売されていたREALTEKのNIC)ネットワークカードデバイスエミュレータのQEMU上の脆弱性です。RTL8139ネットワークカードデバイスモデルのC+モードのオフロードエミュレーションにおけるプロセスのヒープメモリが読まれてしまいます。

 後で説明するエクスプロイトでは、この脆弱性を用いて、以下の2つの情報を攻撃者にリークさせます。

  • シェルコードをビルドするための.textセグメントのベースアドレス
  • インジェクトされた幾つかのダミー構成の正確なアドレスを得るための、ゲストに割り当てられた物理メモリのベースアドレス

 それでは、脆弱性を確認してみましょう。

脆弱性の含まれているソースコードの確認

 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)を含んでおり、それぞれ下記を表しています。

  • hlen:IPヘッダの長さ(オプションなしのパケットも含めて20B)
  • ip_len:IPヘッダを含んだトータルの長さ

 以下の部分のソースコードで分かる通り、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)に対するエクスプロイトコードは、「cve-2015-5165.c」で公開されています。

 このエクスプロイトでは、CVE-2015-5165の脆弱性によりリークした情報から、下記を解決します。

  • .textセグメントのベースアドレス
  • 物理メモリのベースアドレス

 このエクスプロイトでは、カード上のレジスタを設定します。その後、カードのMACアドレスにアドレス指定された不正なIPパケットを偽造します。

カードのレジスタの設定

 CVE-2015-5165の脆弱性に加えて、この脆弱性をエクスプロイトで利用するために、不正なパケットを送信し、漏えいしたデータを読み取るために、Rx/Txディスクリプタバッファーをカードに設定し、パケットが脆弱なコードパス(プログラムの実行経路)を通過させるようにフラグを設定する必要があります。この設定を簡単に説明します。

RTL8130レジスタの図(今回のエクスプロイトに関連する部分のみ)
  • TxConfig:Txループバック、TxCRC(TxパケットにCRCをアペンドしない)などのTxフラグを有効/無効にする
  • RxConfig:AcceptBroadcastやAcceptMulticast(マルチキャストパケットを受け入れる)などのRxフラグを有効/無効にする
  • CpCmd:C+コマンドレジスタ。CplusRxEnd(受信を可能にする)やCplusTxEnd(送信を可能にする)などの幾つかの機能を有効にするために使われる
  • TxAddr0:Txディスクリプタテーブルの物理メモリ
  • RxRingAddrLO:Rxディスクリプタのロー物理メモリアドレステーブル(32B)
  • RxRingAddrHI:Rxディスクリプタのハイ物理メモリアドレステーブル(32B)
  • TxPoll:カードに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バッファーにアクセスして、リークしたデータを読み取ることができるようになります。

リークしたデータの分析

 リークしたデータを分析すると、以下のことが確認できます。

  • 関数ポインタが存在している
  • これらの関数ポインタは全て同じQEMU内部構造の一部である
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.

RSSについて

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

メールマガジン登録

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