web-dev-qa-db-ja.com

Android GraphicBuffer :: unflatten()の脆弱性(CVE-2015-1474)をリモートで悪用するにはどうすればよいですか?

私はAndroid内でDoS(サービス拒否)攻撃を読んでいますが、通常のプログラミング機能などを使用する以下のような攻撃も理解しています。

  • (Android Web Browser) Androidデバイスが特定のWebページにアクセスすると、そのWebページはJavaScriptを保持できるため、大きなループを使用してAndroidデバイスを開いてリンクを開くことができますアプリケーションなどAndroidマーケットプレイス、メール、メッセージなどが何度もクラッシュを証明しているhttp://packetstormsecurity.com/files/118539/Android-4.0.3-Browser-Crash.html

理解できない脆弱性

脆弱性の詳細

CVE-2015-1474

Androidのplatform/frameworks/native/libs/ui/GraphicBuffer.cppのGraphicBuffer :: unflatten関数の複数の整数オーバーフローにより、攻撃者が権限を取得したり、サービス拒否(メモリ破損)を引き起こしたりできる多数の(1)ファイル記述子または(2)整数値をトリガーするベクトル。

http://www.cvedetails.com/cve/CVE-2015-1474/

a

Google Android GraphicBuffer :: unflatten()での整数オーバーフローにより、リモートユーザーに任意のコードを実行させる

SecurityTrackerアラートID: 1031875

SecurityTracker URL:http://securitytracker.com/id/1031875

CVEリファレンス: CVE-2015-1474(外部サイトへのリンク)

日付: 2015年3月10日

Impact:ネットワーク経由の任意のコードの実行、ネットワーク経由のユーザーアクセス

修正可能:はいベンダー確認済み:はい

説明: Google Androidで脆弱性が報告されました。リモートユーザーは、ターゲットシステムで任意のコードを実行できます。

リモートユーザーは特別に細工したデータを送信して、GraphicBuffer :: unflatten()で整数オーバーフローをトリガーし、ターゲットシステムで任意のコードを実行する可能性があります。コードはターゲットアプリケーションの権限で実行されます。

Impact:リモートユーザーは、ターゲットシステムで任意のコードを実行できます。

ソリューション:ベンダーがソースコードの修正を発行しました。入手可能な場所はhttps://Android.googlesource.com/platform/frameworks/native/+/38803268570f90e97452cd9a30ac831661829091です。

ベンダーURL:Android.googlesource.com/(外部サイトへのリンク)

原因:境界エラー

基になるOS:

メッセージ履歴:なし。

http://www.securitytracker.com/id/1031875

a

参照:

    http://www.cvedetails.com/cve/CVE-2015-1474/
    http://www.securitytracker.com/id/1031875

上記のような問題(特定のO.Sフレームワークを使用するなど)を理解するのははるかに困難です。

上記から収集できるのは:

攻撃により、攻撃者は次のことができます。

  • 特権の獲得
  • 原因サービス拒否(メモリ破損)

しかし、私はこれらの問題に対してO.Sを正確に何が開いているのか理解できませんか?例えばこれは正確にはどういう意味ですか:リモートユーザーは特別に細工したデータを送信して、GraphicBuffer :: unflatten()で整数オーバーフローをトリガーできます

攻撃者はどのようにしてこのようなことをリモートで行うことができますか? GraphicBuffer :: unflatten()はどういう意味ですか?

または

ユーザーはこの任意のコードをどのように実行するのでしょうか、またどのようなコードになりますか?

何が欠けていますか?


回答に関する小さなクエリ:

まず、関数native_handle_createと変数native_handleはどこに宣言されていますか(使用方法はわかりますが、ローカル宣言もグローバル宣言もありません)。 。

次に、バッファの実際のサイズをどのように決定しましたか(つまり、現在のサイズがnumFdsまたはnumInts正しくありませんでした。つまり、-1)にすべきではありませんか?

3番目に、h-> dataの意味は、dataという名前の変数の宣言が見つからず、 ->の意味を知っていますか? sizeof(int)とは何ですか?

最後に、numFdsは符号なしconst size_t numFds = buf [8];であると考え、numFdsがタイプsize_tであると考えた最初に負の数を保持できないようにする必要があります(私が何かを見逃していない限り)?また、ヒープの破損や境界外の配列インデックスの作成などから、任意のコード実行に移行する方法がわかりませんか?

これらの追加のクエリが苛立たしい場合、私は申し訳ありませんが、私はそれを正しく理解しようとしています。 ありがとうございます:-)

9
user76779

脆弱性を理解する最も簡単な方法は、 diff を見て、コードを調べ、どのようにそれを悪用するかを理解することです。

脆弱なメソッドのシグネチャは次のようになります。

_status_t GraphicBuffer::unflatten(
    void const*& buffer, size_t& size, int const*& fds, size_t& count) {
_

ここで重要な引数は、フラット化を解除するためのデータのバッファーである_void const*& buffer_と、バッファーのサイズを指定する_size_t& size_です。

_size_t_はtypedef _unsigned int_であり、符号なし整数であることを覚えておいてください。つまり、負の値を表すことはできません。

メソッドは次のように開始します。

_// if the size of the buffer is less than 8 integers, return because it's too small
if (size < 8*sizeof(int)) return NO_MEMORY;

// cast the buffer to an integer pointer type (think "array of ints")
// remember that the size argument is NOT an int, but a size_t - therefore unsigned int.
int const* buf = static_cast<int const*>(buffer);

// check that the buffer contains the right magic number at the start
if (buf[0] != 'GBFR') return BAD_TYPE;

// populate two size_t variables - these are the ones which cause the integer overflow later!
// note that this data is supplied by the calling application, so it can control it.
const size_t numFds  = buf[8];
const size_t numInts = buf[9];
_

ご覧のとおり、バッファが適切なサイズかどうかを確認するための大まかなチェックがあります。次に、バッファーからさらにデータを読み取り、2つのカウント(FdsとInts)を入力します。これらの数値は、ヘッダーデータの後に続く値の数を指定します。

続けましょう...

_// check that the buffer is large enough to contain the number of ints specified.
// notice that 10 is the number of ints read so far (buf[9] was our last access)
// so in order to store numInts ints, the buffer needs to be 10 + numInts ints long.
const size_t sizeNeeded = (10 + numInts) * sizeof(int);
// if the buffer isn't big enough, fail
if (size < sizeNeeded) return NO_MEMORY;

// if the count argument is less than zero, fail
// this is meaningless because count is size_t, so can't be negative
size_t fdCountNeeded = 0;
if (count < fdCountNeeded) return NO_MEMORY;

if (handle) {
    // free previous handle if any
    free_handle();
}
_

これまでのところ、ほとんどが良好です。バッファが、ヘッダーが要求する「Int」の数を保持するのに十分な大きさであることが確認されます。負の数の無意味なチェックがありますが、それはとにかく不可能です。最後に、万が一のために古いハンドルをクリーンアップして、ハンドルのリークを防ぎます。

今度は脆弱性が来ます...

_// if either numFds or numInts is nonzero...
if (numFds || numInts) {
    // read a bunch of fields...
    width  = buf[1];
    height = buf[2];
    stride = buf[3];
    format = buf[4];
    usage  = buf[5];

    // native_handle_create is just a wrapper for malloc'ing a struct which
    // contains a few metadata fields and the fds/ints data.
    // the function takes two ints as its parameters, so it can take negatives.
    // the underlying code does malloc(...+((numFds+numInts)*sizeof(int)))
    // and since nothing has checked if numFds < 0, this will make a buffer
    // which is smaller than expected!
    //
    // example: if numFds is -1 and numInts is 20, the allocated data buffer
    // will be 19*sizeof(int) bytes long.
    //
    // this is potentially bad already, but it gets worse.
    native_handle* h = native_handle_create(numFds, numInts);

    // this will copy the list of file descriptors (fds) from the fds pointer
    // to the h->data pointer. the number of bytes to copy is numFds*sizeof(int).
    // it is important to note that the third argument of memcpy takes a size_t,
    // which is unsigned. numFds is a signed int, but when you multiply it with
    // sizeof(int), which is type size_t, you get a size_t, which is interpreted
    // as an UNsigned integer. as such, -1 (0xFFFFFFFF) in signed becomes
    // 4294967295 when translated over to size_t. so, it attempts to copy way
    // too much data from fds to h->data, leading to heap corruption.
    memcpy(h->data,          fds,     numFds*sizeof(int));

    // <-- CRASH HERE, HEAP IS CORRUPTED

    memcpy(h->data + numFds, &buf[10], numInts*sizeof(int));

    handle = h;
} else {
    width = height = stride = format = usage = 0;
    handle = NULL;
}
_

基本的に問題は、ファイル記述子の数(numFds)が符号付き整数として読み取られるが、memcpyの符号なし整数に変換され、小さな負の値が大きな正の値に変換され、ヒープバッファーがオーバーランすることです。これはすべて、numIntsnumFdsの値のチェックが欠落していることが原因です。

The FullDisclosure post によると、BufferQueueunflatten引数に最終的に渡されるデータにアクセスできるbufferから派生するクラスを作成することで、これを利用できます。

この脆弱なコードはすべて、systemユーザーの下で実行される_system_server_プロセス内に存在するため、ユーザーアプリケーション(アプリなど)からのヒープの破損は大きな問題です。ヒープ上にある構造体またはクラス内で(たとえば、コールバックまたはイベントから)関数ポインターを上書きできる場合、_system_server_内で任意のコードを実行し、特権を取得できます。


説明のリクエストに答えるために...

最初に、関数native_handle_createと変数native_handleが宣言されています(私はそれらの使用を見ることができますが、ローカル宣言もグローバル宣言もありません)。

Androidクロスリファレンス は、この種のものを見つけるのに役立ちます。関数は native_handle.c で宣言され、_native_handle_構造体(変数ではない-hは_native_handle_型のポインター変数です)は native_handleで宣言されます。 h

次に、バッファの実際のサイズをどのように決定しましたか(つまり、numFdsまたはnumIntsの現在のサイズが正しくなかったかどうか、つまり-1にすべきではないかどうかを判断しました)?

まあ、リストに-1項目はあり得ないので、それは無効です。 the patch diff を見ると、いくつかのチェックが追加されています。

_// compute the maximum possible number of ints that you can store in a buffer
// whose size is represented by a size_t.
const size_t maxNumber = UINT_MAX / sizeof(int);
// if numFds exceeds the max number of possible ints (e.g. if 0xFFFFFFFF is passed)
// this check will fail. the second check is the same, except it accounts for the
// fact that 10 ints in the buffer are already used for other purposes.
if (numFds >= maxNumber || numInts >= (maxNumber - 10)) {
    // clear data and fail
    width = height = stride = format = usage = 0;
    handle = NULL;
    ALOGE("unflatten: numFds or numInts is too large: %d, %d",
            numFds, numInts);
    return BAD_VALUE;
}
_

後でさらにチェックを見ることができます...

_ // originally this was set to 0, which essentially did nothing.
 size_t fdCountNeeded = numFds;
 if (count < fdCountNeeded) return NO_MEMORY;
_

_native_handle_create_呼び出しの後の最後のチェックは、割り当ての失敗が問題を引き起こすのを防ぎます-memcpyがnullであってもhを_h->data_に実行する前に、null逆参照バグ(DoS条件)につながります。

したがって、これらのチェックにより、入力値が範囲外にならず、以前のように悪用されないことが保証されます。

3番目に、dataという名前の変数の宣言が見つからず、->の意味がわからないため、h-> dataの意味は何ですか? sizeof(int)とは何ですか?

_->_演算子は逆参照です。左側のオペランドは、構造体へのポインターです。したがって、_h->data_は、hを逆参照します。これは、_native_handle_型の構造体へのポインターであり(これについては前に説明しました)、その構造体のdataフィールドにアクセスします。対照的に、hがポインターではなく単なる構造体である場合は、代わりに_h.data_を使用します。

sizeofマクロは、型のサイズをバイト単位で示します。したがって、sizeof(int)は、コンパイラの実装とターゲットプラットフォームのネイティブのintサイズを返します。ほとんどの場合、32ビットまたは64ビット整数を指定するには、4または8を返します。

最後に、numFdsは符号なしのconst size_t numFds = buf [8];であると思いました。numF​​dsがタイプsize_tである場合、最初に負の数を保持することはできないと思いました(何かを見逃していない限り)?

はい、numFdsは_size_t_ですが、それは、その値内のrawバイトが符号なし整数として解釈されることを意味します。 0xFFFFFFFFが含まれている場合、直接解釈される10進値は4294967295になります。ただし、_size_t_をintにキャストすると問題が発生し、その時点で内部の生のバイトがsigned整数。この場合、0xFFFFFFFFは突然-1になります。

また、ヒープの破損や境界外の配列インデックスの作成などから、任意のコード実行に移行する方法がわかりませんか?

これは複雑なトピックですが、概要を説明します。ヒープは、ほとんどのアプリケーションデータが送られる場所です。つまり、クラスやその他のバッファやインスタンスを作成する場合、そのデータのほとんどはヒープにあります。ここで、イベントを持つクラスがあるとします。これをDogと呼び、イベントにBarkという名前を付けます。特定の条件が発生すると、そのクラスのインスタンスがBarkイベントを発生させ、他のオブジェクトがその樹皮に反応できるようにします。イベントは単なるポインタです。他のオブジェクトは、メソッドへのポインター、つまりいくつかのコードへのポインターを割り当てることによって、そのイベントを処理します。したがって、DogインスタンスがBarkを発生させたい場合、Barkポインターを調べて、そこでコードの実行を開始します。アセンブリでは、これは_call [eax+0x0...]_のようになります。

Dogインスタンスはヒープ内のメモリに存在するため、そのBarkポインターも存在します。 Barkポインターを上書きして、一部のother実行可能メモリを指すようにし、Dogインスタンスでイベントを発生させると、次の場所でコードが実行されます。その別の場所。その場所でコードを制御する場合(たとえば、バッファーをコードで満たし、そのアドレスをポインターに使用することにより)、任意のコードが実行されます。

ターゲットデータを意味のある方法で上書きする可能性が高いデータ構造でヒープをフラッディングするだけで、これを悪用することができます。常に100%の信頼性があるわけではありませんが、それがときどき起こることです。

NXとASLRでは、ポインターリークとret2libc/ROPを使用してそれを悪用する必要があるため、事態はさらに複雑になりますが、それははるかに大きなトピックです。

エクスプロイトライティングを始めたい場合は、 Corelanのエクスプロイトライティングチュートリアル を強くお勧めします。それらは少し古くなっています-WinXP VMハードウェアNXを無効にしたいですが、それでも強力なリソースです。

8
Polynomial