
CVE-2021-40449 - Win32k 特権昇格の脆弱性 (LPE) のエクスプロイト
CVE-2021-40449 (Win32k - LPE) のエクスプロイト
CVE-2021-40449 は Win32k における use-after-free であり、ローカル権限昇格を可能にします。
この脆弱性は、Kaspersky によって実環境で発見されました。
発見されたエクスプロイトは、以下の Windows 製品をサポートするように作成されていました:
ただし、このエクスプロイトは現在、以下のバージョンでのみテストされています:
先に進む前に、Kaspersky の 技術解説 を読むことを強くお勧めします。
Kasperky による技術解説に記載されているように、この脆弱性は GreResetDCInternal に存在します。攻撃者が hdcOpenDCW の呼び出し中に呼び出されるユーザーモードコールバック DrvEnablePDEV をフックした場合、ResetDC を呼び出して元のデバイスコンテキストを破棄することができ、ユーザーモードコールバックが戻ったときにカーネル内で use-after-free が発生します。
以下の擬似コードは、流出した Windows XP のソースコードの一部と、Win32kfull.sys から (パッチ適用前の) 最新の GreResetDCInternal をリバースエンジニアリングして作成されました。不要な部分は [...] で削除されています。VULN: コメントを探してください。
BOOL GreResetDCInternal(
HDC hdc,
DEVMODEW *pdmw,
BOOL *pbBanding,
DRIVER_INFO_2W *pDriverInfo2,
PVOID ppUMdhpdev)
{
// [...]
HDC hdcNew;
{
// Create DCOBJ from HDC
DCOBJ dco(hdc);
if (!dco.bValid())
{
SAVE_ERROR_CODE(ERROR_INVALID_HANDLE);
}
else
{
// Create DEVOBJ from `dco`
PDEVOBJ po(dco.hdev());
// [...]
// Create the new DC
// VULN: Can result in a usermode callback that destroys old DC, which
// invalidates `dco` and `po`
hdcNew = hdcOpenDCW(L"",
pdmw,
DCTYPE_DIRECT,
po.hSpooler,
prton,
pDriverInfo2,
ppUMdhpdev);
if (hdcNew)
{
po->hSpooler = NULL;
DCOBJ dcoNew(hdcNew);
if (!dcoNew.bValid())
{
SAVE_ERROR_CODE(ERROR_INVALID_HANDLE);
}
else
{
// Transfer any remote fonts
dcoNew->pPFFList = dco->pPFFList;
dco->pPFFList = NULL;
// Transfer any color transform
dcoNew->pCXFList = dco->pCXFList;
dco->pCXFList = NULL;
PDEVOBJ poNew((HDEV)dcoNew.pdc->ppdev());
// Let the driver know
// VULN: Method is taken from old (possibly destroyed) `po`
PFN_DrvResetPDEV rfn = po->ppfn[INDEX_DrvResetPDEV];
if (rfn != NULL)
{
(*rfn)(po->dhpdev, poNew->dhpdev);
}
// [...]
}
}
}
}
// Destroy old DC
// [...]
}
擬似コードからわかるように、古いデバイスコンテキストは hdcOpenDCW 呼び出しからのユーザーモードコールバック内で解放され、その後、DrvResetPDEV メソッドが古いデバイスコンテキストから取得され、(po->dhpdev, poNew->dhpdev) を指定して呼び出されます。
デバイスコンテキストを作成してフックするには、次のようにします:
EnumPrinters を使用して利用可能なプリンターを見つけるOpenPrinter、GetPrinterDriver、LoadLibraryExA を使用してプリンタードライバーをメモリに読み込むGetProcAddress と DrvEnableDriver を使用してプリンタードライバーのユーザーモードコールバックテーブルを取得するVirtualProtect を使用してプリンタードライバーのユーザーモードコールバックテーブルの保護を解除するCreateDC(NULL, printerName, NULL, NULL) を使用してプリンターのデバイスコンテキストを作成するこれで、ユーザーモードコールバックがフックされたプリンターのデバイスコンテキストが得られたはずです。
私たちが関心を持つフックは1つだけ、すなわち DrvEnablePDEV です。このフックは、先に説明したように、UAF をトリガーすることと引数を制御することの2つの点で興味深いものです。UAF 脆弱性をトリガーするには、フック内で ResetDC を呼び出し、古いデバイスコンテキストを破棄します。フックから戻ると、まだ最初の GreResetDCInternal 内にいることになり、その直後に、破棄された古いデバイスコンテキストから DrvEnablePDEV が返した2つの引数(古い DHPDEV と新しい DHPDEV)を使って DrvResetPDEV の関数ポインターを取得して呼び出します。
プロセスが中整合性レベルで実行されている場合、EnumDeviceDrivers と NtQuerySystemInformation の助けを借りれば、KASLR は問題になりません。
Kaspersky によると、元のエクスプロイトは GDI パレットオブジェクトと単一のカーネル関数呼び出しを使用して任意のメモリ読み取り/書き込みを実現していました。このエクスプロイトは、ビッグプールに BitMapHeader を割り当てる手法 と RtlSetAllBits を使用して、現在のプロセストークンのすべての特権を有効にします。BitMapHeader は、現在のプロセストークンの _SEP_TOKEN_PRIVILEGES を指します。RtlSetAllBits(BitMapHeader) を呼び出すことで、単一のカーネル関数呼び出しで現在のプロセストークンのすべての特権を有効にできます。ここから、新しい特権を悪用して SYSTEM になることができます。このエクスプロイトは、SeDebugPrivilege を使用して winlogon.exe プロセスにシェルコードを注入します。
