Skip to content
KitploitKITPLOIT
ツールエクスプロイトブログ
Log in
提出
ツールエクスプロイトブログ
提出

ハッキング、侵入テスト、サイバーセキュリティツールをあなたのセキュリティアーセナルに!

Kitploitはハッキング、サイバーセキュリティ、ペネトレーションテストのツールディレクトリです。最新のプロジェクトアップデートを見つけて、脆弱性の発見、システム分析、テストの自動化、セキュリティの強化を行いましょう。

··フィード·お問い合わせ·プライバシー·© 2026 Kitploit

ツールディレクトリ

カテゴリ

すべてのカテゴリを見る
Loading categories
virtualbox_e1000_0day — VirtualBox E1000 ゲストからホストへのエスケープ | Kitploit
ツール/GitHubGitHub/mortenoir1/virtualbox_e1000_0day
脆弱性分析エクスプロイトペネトレーションテストハードウェアセキュリティバイナリエクスプロイト
GitHubmortenoir1/virtualbox_e1000_0day

virtualbox_e1000_0day

VirtualBox E1000 ゲストからホストへのエスケープ

リポジトリを見る
1.4k196227年前Kitploit レビュー済み

人気

すべて見る →

コミュニティで最も使われているツールを見つけましょう。

すべてのツールを探索

ツールコレクションを閲覧

すべてのツールを見る →
共有

なぜ

私はVirtualBoxが好きですが、0day脆弱性を公開する理由はそれとは関係ありません。その理由は、現在のインフォセック、特にセキュリティリサーチとバグバウンティの状況に対する私の不同意にあります。

  1. 脆弱性がパッチされるまで半年待つことが問題ないとされている。
  2. バグバウンティの分野では、これらが問題ないとされている:
    1. 提出された脆弱性が検証され、購入するかどうかの決定が下されるまで1ヶ月以上待つこと。
    2. 決定をその場で変更すること。今日、バグバウンティプログラムが特定のソフトウェアのバグを買うと判明し、1週間後にバグとエクスプロイトを持って行くと「興味なし」と言われる。
    3. バグバウンティがバグを買いたいソフトウェアの正確なリストがないこと。バグバウンティには便利だが、研究者には不便。
    4. 脆弱性価格の正確な下限と上限がないこと。価格に影響するものは多いが、研究者は何に取り組む価値があるかを知る必要がある。
  3. 誇大妄想とマーケティングのデタラメ:脆弱性に名前をつけてウェブサイトを作ること;1年に千ものカンファレンスを開くこと;自分自身の仕事をセキュリティ研究者として誇張すること;自分を「世界の救世主」と見なすこと。お控えください、殿下。

最初の2つにうんざりしたので、私の行動は完全開示です。インフォセック、どうか前進してください。

一般情報

脆弱なソフトウェア: VirtualBox 5.2.20およびそれ以前のバージョン。

ホストOS: 任意。バグは共有コードベースにある。

ゲストOS: 任意。

VM設定: デフォルト(唯一の要件は、ネットワークカードがIntel PRO/1000 MT Desktop(82540EM)であり、モードがNATであること)。

自分を守る方法

パッチされたVirtualBoxビルドがリリースされるまで、仮想マシンのネットワークカードをPCnet(2つのいずれか)またはParavirtualized Networkに変更できます。できない場合は、モードをNATから別のものに変更してください。前者の方法がより安全です。

はじめに

デフォルトのVirtualBox仮想ネットワークデバイスはIntel PRO/1000 MT Desktop(82540EM)で、デフォルトのネットワークモードはNATです。これをE1000と呼びます。

E1000には、ゲスト内でroot/管理者権限を持つ攻撃者がホストのring3にエスケープできる脆弱性があります。その後、攻撃者は既存の技術を使用して、/dev/vboxdrvを介してring0に特権を昇格できます。

脆弱性の詳細

E1000 101

ネットワークパケットを送信するために、ゲストは一般的なPCと同じことを行います。つまり、ネットワークカードを構成し、ネットワークパケットを供給します。パケットはデータリンク層フレームや他のより高レベルのヘッダーから構成されます。アダプタに供給されるパケットはTxディスクリプタでラップされます(Txは送信を意味します)。Txディスクリプタは、82540EMデータシート(317453006EN.PDF、リビジョン4.0)に記述されたデータ構造です。パケットサイズ、VLANタグ、TCP/IPセグメンテーション有効フラグなどのメタ情報を格納します。

82540EMデータシートは、3つのTxディスクリプタタイプを提供します:レガシー、コンテキスト、データ。レガシーは非推奨だと思います。他の2つは一緒に使用されます。私たちが気にする唯一のことは、コンテキストディスクリプタが最大パケットサイズを設定し、TCP/IPセグメンテーションを切り替えること、そしてデータディスクリプタがネットワークパケットの物理アドレスとそのサイズを保持することです。データディスクリプタのパケットサイズは、コンテキストディスクリプタの最大パケットサイズより小さくなければなりません。通常、コンテキストディスクリプタはデータディスクリプタの前にネットワークカードに供給されます。

Txディスクリプタをネットワークカードに供給するために、ゲストはそれらをTxリングに書き込みます。これは、物理メモリ内の事前定義されたアドレスにあるリングバッファです。すべてのディスクリプタがTxリングに書き込まれると、ゲストはE1000 MMIO TDTレジスタ(Transmit Descriptor Tail)を更新して、処理すべき新しいディスクリプタがあることをホストに通知します。

入力

次のTxディスクリプタの配列を考えてみましょう:``` [context_1, data_2, data_3, context_4, data_5]

それらの構造フィールドを次のように割り当てましょう(フィールド名は人間が読みやすいように仮定的ですが、82540EM仕様に直接マッピングされています):```
context_1.header_length = 0
context_1.maximum_segment_size = 0x3010
context_1.tcp_segmentation_enabled = true

data_2.data_length = 0x10
data_2.end_of_packet = false
data_2.tcp_segmentation_enabled = true

data_3.data_length = 0
data_3.end_of_packet = true
data_3.tcp_segmentation_enabled = true

context_4.header_length = 0
context_4.maximum_segment_size = 0xF
context_4.tcp_segmentation_enabled = true

data_5.data_length = 0x4188
data_5.end_of_packet = true
data_5.tcp_segmentation_enabled = true

段階的な分析を通じて、なぜそうあるべきかを学びます。

根本原因分析

[context_1, data_2, data_3] 処理

上記のディスクリプタが指定された順序でTx Ringに書き込まれ、TDTレジスタがゲストによって更新されるとします。ホストはsrc/VBox/Devices/Network/DevE1000.cppファイル内のe1kXmitPending関数を実行します(ほとんどのコメントは読みやすくするために削除されています):```c static int e1kXmitPending(PE1KSTATE pThis, bool fOnWorkerThread) { ... while (!pThis->fLocked && e1kTxDLazyLoad(pThis)) { while (e1kLocateTxPacket(pThis)) { fIncomplete = false; rc = e1kXmitAllocBuf(pThis, pThis->fGSO); if (RT_FAILURE(rc)) goto out; rc = e1kXmitPacket(pThis, fOnWorkerThread); if (RT_FAILURE(rc)) goto out; }

e1kTxDLazyLoad は、Tx リングから5つの Tx ディスクリプタすべてを読み取ります。次に、初めて e1kLocateTxPacket が呼び出されます。この関数はすべてのディスクリプタを反復処理して初期状態を設定しますが、実際にはそれらを処理しません。今回の場合、最初の e1kLocateTxPacket の呼び出しで、context_1、data_2、data_3 ディスクリプタが処理されます。残りの2つのディスクリプタ、context_4 と data_5 は、while ループの2回目の反復で処理されます(2回目の反復については次のセクションで説明します)。この2つの部分への配列分割が脆弱性を発動させるために重要です。その理由を考えてみましょう。

e1kLocateTxPacket は次のようになります:```c
static bool e1kLocateTxPacket(PE1KSTATE pThis)
{
...
    for (int i = pThis->iTxDCurrent; i < pThis->nTxDFetched; ++i)
    {
        E1KTXDESC *pDesc = &pThis->aTxDescriptors[i];
        switch (e1kGetDescType(pDesc))
        {
            case E1K_DTYP_CONTEXT:
                e1kUpdateTxContext(pThis, pDesc);
                continue;
            case E1K_DTYP_LEGACY:
                ...
                break;
            case E1K_DTYP_DATA:
                if (!pDesc->data.u64BufAddr || !pDesc->data.cmd.u20DTALEN)
                    break;
                ...
                break;
            default:
                AssertMsgFailed(("Impossible descriptor type!"));
        }

最初のディスクリプタ (context_1) は E1K_DTYP_CONTEXT であるため、e1kUpdateTxContext 関数が呼び出されます。この関数は、TCP Segmentation がディスクリプタに対して有効化されている場合、TCP Segmentation Context を更新します。context_1 では有効であるため、TCP Segmentation Context が更新されます。(TCP Segmentation Context Update が実際に何を行うかは重要ではなく、単に以下のコードを参照するために使用します。)

2つ目のディスクリプタ (data_2) は E1K_DTYP_DATA であるため、この議論には不要ないくつかのアクションが実行されます。

3つ目のディスクリプタ (data_3) も E1K_DTYP_DATA ですが、data_3.data_length == 0 であるため、何のアクションも実行されません。

現時点で、3つのディスクリプタが最初に処理され、2つが残っています。ここで重要なのは、switch 文の後に、ディスクリプタの end_of_packet フィールドが設定されているかどうかのチェックがあることです。これは data_3 ディスクリプタで真です (data_3.end_of_packet == true)。コードはいくつかのアクションを実行し、関数から戻ります。```c if (pDesc->legacy.cmd.fEOP) { ... return true; }

data_3.end_of_packet が false だった場合、残りの context_4 と data_5 のディスクリプタが処理され、脆弱性が回避されることになります。以下に、その関数からのリターンがどのようにバグにつながるのかを示します。

e1kLocateTxPacket 関数の最後に、ネットワークパケットをアンラップしてネットワークに送信する準備ができた次のディスクリプタがあります: context_1, data_2, data_3。その後、e1kXmitPending の内部ループが e1kXmitPacket を呼び出します。この関数は、すべてのディスクリプタ(この場合は5つ)を反復処理して、実際に処理します。```c
static int e1kXmitPacket(PE1KSTATE pThis, bool fOnWorkerThread)
{
...
    while (pThis->iTxDCurrent < pThis->nTxDFetched)
    {
        E1KTXDESC *pDesc = &pThis->aTxDescriptors[pThis->iTxDCurrent];
        ...
        rc = e1kXmitDesc(pThis, pDesc, e1kDescAddr(TDBAH, TDBAL, TDH), fOnWorkerThread);
        ...
        if (e1kGetDescType(pDesc) != E1K_DTYP_CONTEXT && pDesc->legacy.cmd.fEOP)
            break;
    }

各記述子に対して e1kXmitDesc 関数が呼び出されます:```c static int e1kXmitDesc(PE1KSTATE pThis, E1KTXDESC *pDesc, RTGCPHYS addr, bool fOnWorkerThread) { ... switch (e1kGetDescType(pDesc)) { case E1K_DTYP_CONTEXT: ... break; case E1K_DTYP_DATA: { ... if (pDesc->data.cmd.u20DTALEN == 0 || pDesc->data.u64BufAddr == 0) { E1kLog2(("% Empty data descriptor, skipped.\n", pThis->szPrf)); } else { if (e1kXmitIsGsoBuf(pThis->CTX_SUFF(pTxSg))) { ... } else if (!pDesc->data.cmd.fTSE) { ... } else { STAM_COUNTER_INC(&pThis->StatTxPathFallback); rc = e1kFallbackAddToFrame(pThis, pDesc, fOnWorkerThread); } } ...

e1kXmitDescに渡される最初のディスクリプタはcontext_1です。この関数はコンテキストディスクリプタに対して何もしません。

e1kXmitDescに渡される2番目のディスクリプタはdata_2です。すべてのデータディスクリプタはtcp_segmentation_enable == true(上記のpDesc->data.cmd.fTSE)であるため、e1kFallbackAddToFrameを呼び出します。そこでdata_5が処理されている間に整数アンダーフローが発生します。```c
static int e1kFallbackAddToFrame(PE1KSTATE pThis, E1KTXDESC *pDesc, bool fOnWorkerThread)
{
    ...
    uint16_t u16MaxPktLen = pThis->contextTSE.dw3.u8HDRLEN + pThis->contextTSE.dw3.u16MSS;

    /*
     * Carve out segments.
     */
    int rc = VINF_SUCCESS;
    do
    {
        /* Calculate how many bytes we have left in this TCP segment */
        uint32_t cb = u16MaxPktLen - pThis->u16TxPktLen;
        if (cb > pDesc->data.cmd.u20DTALEN)
        {
            /* This descriptor fits completely into current segment */
            cb = pDesc->data.cmd.u20DTALEN;
            rc = e1kFallbackAddSegment(pThis, pDesc->data.u64BufAddr, cb, pDesc->data.cmd.fEOP /*fSend*/, fOnWorkerThread);
        }
        else
        {
            ...
        }
ツールをダウンロード