
CVE-2020-15368 のエクスプロイトおよび Proof of Concept (PoC)。ASRockはRGBコントローラー設定ツールのためにrweverythingドライバを再パッケージ化し、署名しました。彼らはioctlを暗号化することで「保護」しています...笑。私たちは昨年の夏に偶然このCVEを発見しましたが、私の知る限りこのドライバはまだパッチされていません。影響はもちろんカーネルでの任意コード実行などです。それではこの「0day」をお楽しみください。笑
もしこれが本物の正真正銘のCVEかどうかについて私と議論したいなら、お気軽にTwitterで連絡してください。公のソーシャルメディアで大喧嘩しましょう。関係者全員にとって本当にエキサイティングです! もしあなたがその気なら、このバグのためにドメインも購入します。すべてはマーケティングです!!!!
とにかく、このバグはかなりクソなので、典型的な脆弱ドライバをpwnする方法のチュートリアルとして使います。この記事は初心者向けです。 脆弱なドライバを悪用する方法を学ぶことができます。このようなクソみたいなドライバは他にもたくさんあります。世の中は思いのままです。楽しんでください。
免責事項: この出版物は教育目的のみで提供されます。適用されるすべての地域、州、連邦の法律を遵守する責任は読者にあります。この出版物の著者は一切の責任を負わず、この出版物に含まれるソフトウェアによって引き起こされた誤用や損害についても責任を負いません。
隔離生活で行き詰まり、私のルームメイト(Pear0、Codetector)と私は、Pear0の新しいASRockマザーボードで遊んでいました。明るい赤いLEDが非常に煩わしく、Linuxではそれらを設定できませんでした。そこで私たちの計画は、それを制御するWindowsドライバをリバースエンジニアリングし、Linux上でI/O操作を再現することでした。
要するに、そのドライバが文字通り何にでも任意の読み書きアクセスを許可する汎用ドライバであることに気づくのに時間はかかりませんでした。これにはCR3、CR4などの制御レジスタや物理メモリなどが含まれます。このようなドライバはデバッグツールとしての使用を目的としており、ベンダーのウェブサイトに明記されています。

私たちはこれが非常に面白いと思いました。ユーザーモードからコンピュータをトリプルフォールトさせてハードリブートさせるのは、初めてならかなりエキサイティングです。(20回目ならそれほどでもないかもしれません。)とにかく、私たちはバグを報告した後、1年間忘れていました。
カーネル初心者として、実際にドライバをロードして操作する方法が気になっていました。それは非常に簡単だと分かりました。
Process Hackerでドライバ用のサービスを作成するだけです(もちろん、ドライバをロードするには管理者権限が必要です)。あとは右クリックして開始するだけです。本当にこれだけ簡単です。

WinObjEx64でデバイスオブジェクトを表示できます。

FileTestでデバイスを操作することもできます。


これら3つのツールはすべて素晴らしく、特にPHとFileTestは最高です。それらはスイスアーミーナイフのようなもので、すべてのWindowsリバーサーのツールボックスに入れるべきです。例えば、私の理解ではJonas LはFileTestで遊んでいるだけで数え切れないほどのWindows LPE脆弱性を発見しています。Windowsには本当に遊び心満載の素晴らしいツールがあります。Linuxにもこんなものがあればいいのに。
Rweverythingには、実行する操作(メモリ読み取り、メモリ書き込み、MSR読み取りなど)を制御するioctlをパラメータとして受け取るioctlと、srcアドレス、dstアドレスなどの操作固有のパラメータのユニオンがあります。2つのドライバのコードを比較すると:

🤔🤔🤔🤔🤔🤔🤔🤔🤔
それにもかかわらず、このドライバは、すべてのioctl呼び出しがハードコードされたAESキーで適切に暗号化されることを要求することで、隠蔽によるセキュリティという粗末な試みを行っています。コード(少しクリーンアップした後)は次のようになります:
if ( IoControlCode == 0x22EC00 )
{
char enc_key[32];
memset(enc_key, 0, sizeof(enc_key));
memmove(enc_key, "C110DD4FE9434147B92A5A1E3FDBF29A", 32ui64);
memcpy(enc_key + 13, ioctl_args->key, 16);
size_t cb_decrypted = 0;
my_decrypted_cmd* decryptedCmd = NULL;
DWORD iv_size = ioctl_args->iv_size;
DWORD input_size = *(DWORD*)(ioctl_args + bufferLen - 6);
// really just calls BCrypt API to get an AES implementation
if ( (unsigned int)decrypt_ioctl_params(enc_key, 32, ioctl_args->iv, iv_size, ioctl_args + bufferLen - input_size - 6, input_size, &decryptedCmd, &cb_decrypted) )
{
// Decryption failed
if ( decryptedCmd )
ExFreePoolWithTag(decryptedCmd, 0);
irp->IoStatus.Status = 0xC000000D;
goto Fail_Out;
}
IoControlCode = decryptedCmd->opcode;
Rweverything_Args = &decryptedCmd->args;
}
else // not 0x22EC00
{
if ( IoControlCode != 0x22E858 &&
IoControlCode != 0x22E860 &&
IoControlCode != 0x22E800 &&
IoControlCode != 0x22E804 )// whitelisted control codes
IoControlCode = 0; // block everything else
}
ドライバはPMIOを行ういくつかの退屈な操作を許可しますが、「楽しい」制御コードはすべてこの復号ルーチンの背後にゲートされています。この明示的な許可リストがあるにもかかわらず、危険なRweverything機能はすべて含まれています。これらの危険な機能を隠すのではなく、完全に削除すべきだったのでしょう。
また、興味深いことに、ユーザーがキーの一部を指定することもできます(???)。その理由はまったくわかりません。コードは本当に質が悪いです。
とにかく、この奇妙な暗号化APIを使うクライアントコードを書くのは比較的簡単で、任意のioctl呼び出しを渡せます。その詳細については退屈させません。
ドライバへのハンドルを開き、DeviceIoControlを使ってioctlを呼び出します。ごく標準的なことです。
HANDLE hDevice = CreateFileA("\\\\.\\GlobalRoot\\Device\\AsrDrv104", GENERIC_READ | GENERIC_WRITE | SYNCHRONIZE, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
// ... set up the encrypted ioctl data
BOOL result = DeviceIoControl(hDevice, 0x22EC00, ioctl_data, in_buf_size, out_buf, sizeof(out_buf), &bytes_returned, NULL);
ドライバの隠されたRweverything部分と通信できるようになったので、最初にやりたかったのはクラッシュを発生させて、ドライバクライアントが動作していることを確認することでした。
これを達成する最も簡単な方法は、CR3をジャンクデータで上書きすることです。読者の中には初心者もいると思いますが、それでOKです。詳しく説明します。私もバカなので、これがあなたの学習に役立つかもしれません。自分が何をしているか分かっているなら、この部分はスキップしてください。
x86では、ページングが有効なとき(現代のほぼすべてのOSで常に有効)、CR3は最上位のページテーブルディレクトリの物理ベースアドレスを指します。それが何を意味するか分からない場合は、Wikipediaの仮想メモリの記事を読んでください。
CR3をジャンク、例えば0x0000000000000000で上書きすると、TLBがフラッシュされ、次の命令を実行しようとすると、プロセッサ(具体的にはMMU)が命令ポインタを物理アドレスに変換しようとします。アドレス変換は、基本的にCR3から始まる一連のページテーブルウォークと考えることができます。CR3は物理メモリの0番地を指すようになります。そのアドレスは確かに存在しアクセス可能ですが、それが有効なページテーブルである可能性は極めて低いです。(ページテーブルエントリ、略してPTEは、従わなければならない特定の構造を持っています。)
このとき、アドレス変換でページフォールトが発生します。通常、CPUはページフォールトハンドラのアドレスに移動します。しかし、ページフォールトハンドラ関数がどこにあるかをどうやって知るのでしょうか? これは割り込み記述子テーブル(IDT)として知られるメモリデータ構造に格納されています。プロセッサにはIDTの仮想アドレスを保持するレジスタ(sidt命令とlidt命令で読み書き)があります。問題が見えましたか? ページフォールトを処理するには、まずさらに別の仮想メモリアクセス、つまり別のアドレス変換が必要です。
もちろん、2回目のアドレス変換もフォールトします。これでダブルフォールトになります:最初のページフォールトの処理中に発生するフォールトです。これはかなり深刻ですが、まだ回復可能です---プロセッサは最後の回復のチャンスを1回与えてくれます。もちろん、その試みも3回目で最後のページフォールト、トリプルフォールトによって残酷に中断されます。この時点でCPUは諦めてマシンをハードリセットします。この手順を物理マシンで実行した場合、今頃BIOSのスプラッシュ画面が見えているでしょう。
質問がある場合、いつも私に与えられたのと同じ答えをあげます。それはIntel Manual Volume 3A(別名バイブル)を読めということです。
では、実際にどうやってドライバを悪用するのでしょうか? 見渡すと、任意の物理メモリ読み書きプリミティブが無料で手に入ります。基本的にMmMapIoSpaceを使って任意の物理アドレスをマップし、バッファをそこにコピー(またはその逆)して、アドレスをアンマップします。
小さなメモ:カーネルデバッガがアタッチされた状態で、私たちのように
MmMapIoSpaceを馬鹿げた引数で呼び出そうとすると、バグチェックが発生します。WinDbgでマジックバイトを書き込むことでこれを回避できます。詳細はexploit.cppのMiShowBadMapperを参照しているコメントを探してください。それが何なのか本当に知らないし、知りたいとも思いません。
このプリミティブを利用してカーネルでコード実行を達成するにはどうすればいいでしょうか? このプリミティブの主な問題は、物理メモリに対して動作することです。ユーザーモードプログラムとして、物理メモリのレイアウトがどうなっているかはほとんど分かりません---オペレーティングシステムがすべて処理してくれます。カーネルデータ構造やカーネル関数ポインタの仮想アドレスを取得できたとしても、それらが物理アドレス空間のどこにあるのか見当もつきません。
1つのアイデアは、CR3を読み、ページテーブルを読み、仮想アドレス変換を自分たちで行うことです。これは素晴らしいアイデアです。しかし機能しません。WindowsではMmMapIoSpaceでページテーブルをマップすることがもはやできないためです。もっと賢くなる必要があります。
私はVDMからxeroxzのテクニックを使いました。非常にシンプルですが、そのテクニックはかなり賢いです。物理メモリのレイアウトは分からないものの、探しているものを見つけるまで物理メモリ全体をスキャンすることができます。利用できる点の1つは、ページの内容は物理的にも仮想的にも常に同じであることです:ページ境界からのオフセットは常に保持されます。例えば、ページ0x7fff000000000XXXが物理フレーム0x0000000123456XXXにマップされている場合、すべてのアドレスのXXXは物理アドレスと仮想アドレスの両方で同じです。ページ内の構造はすべて保持されます。したがって、上書きしたい興味深いページをスキャンできます。
上書きする最も簡単な対象は、おそらく到達しやすいsyscallやioctlハンドラです。Windowsにはコンピュータをビープさせる標準のBeep()関数があります。信じられないかもしれませんが、これはBeepデバイスを提供するドライバBeep.sysに実装されています。(実際、先ほどのWinObjEx64のスクリーンショットで確認できます。)誰でもBeepデバイスを使用でき、めったに呼ばれません。それではBeepのioctlハンドラを上書きしましょう。
Beep.sysをIDAに入れて、DeviceIoControlハンドラを確認できます。

ページオフセット0x270に、バイト40 53 48 ...のこのコードがあります。これらのバイトはリロケートされていないため、この関数のスキャンは非常に簡単です。もしリロケートされたバイトがあれば、ワイルドカードでマスクする必要があります。これはゲームハックを書くときのシグネチャスキャンと同じ考え方です。
物理メモリをスキャンしてこのコードの場所を特定したら、それを独自のシェルコードで上書きするだけです。このページのコピーが物理メモリ内に複数存在する可能性があるので注意が必要です(!)すべてのコピーを見つけてください。
この時点で、プロセスのセキュリティトークンをシステムプロセスのものと交換することで、nt authority\system権限を簡単に取得して権限昇格できます。残念ながら、asrockドライバはそもそも開くのに管理者権限が必要なので、これはあまり面白くありません。
私たちの場合、ステージ2ペイロードを割り当ててコピーし、新しいカーネルスレッドを生成する基本的なシェルコードを書きます。上書きしたBeepハンドラだけではすべてを実行できません。なぜなら、1)1ページに制限されていること、2)Beepデバイス内の残りのコードも破壊したため、Beepデバイスへのハンドルを閉じようとするとシステムがクラッシュするからです。カーネルポインタの取得については、NtQuerySystemInformationにお願いすれば無料で教えてもらえるので、実際簡単です。
__int64 __declspec(dllexport) __fastcall MyIRPHandler(struct _DEVICE_OBJECT* a1, IRP* irp)
{
MyIrpStruct* user_data = (MyIrpStruct*)irp->AssociatedIrp.SystemBuffer;
void* my_rwx = user_data->nt_ExAllocatePoolWithTag(NonPagedPoolExecute, user_data->payload_size, 'lmao');
user_data->nt_memcpy(my_rwx, user_data->payload, user_data->payload_size);
HANDLE hThread;
user_data->nt_PsCreateSystemThread(&hThread, THREAD_ALL_ACCESS, NULL, NULL, NULL, (PKSTART_ROUTINE)my_rwx, NULL);
user_data->nt_IofCompleteRequest(irp, 0);
return 0;
}
そこで、すばやくBeepをパッチし、上書きしたioctlハンドラを呼び出し、Beepをアンパッチします。これでシステム上の他のものを壊すことなく、コードを実行するカーネルスレッドを安全に作成できました。この時点で独自のドライバなどをマップできます。
私はダメなセキュリティ研究者で、偶然に価値のないバグしか見つけられません。読んでくれてありがとう。私のOnlyFansに登録してください。