カーネルブリッジフレームワーク:Ring0のブリッジ

オペレーティングシステムの内部を見て、そのメカニズムの内部構造を見て、ネジをねじって、開かれた機会を見てみたいと思ったことはありませんか? おそらく、彼らはハードウェアを直接操作したかったのですが、ドライバーはロケット科学だと思っていたのでしょうか?



橋に沿ってコアまで歩いて、ウサギの穴の深さを確認することを提案します。



そこで、C ++ 17で記述されたカーネルハッキング用のドライバーフレームワークを紹介し、可能であれば、カーネルとユーザーモードの間の障壁を取り除くか、それらの存在を可能な限り滑らかにするように設計しました。 また、初心者と上級プログラマーの両方を対象とした、Ring0での迅速かつ便利な開発のためのユーザーモードおよびカーネルAPIとラッパーのセット。



主な機能:





...などなど。



そして、フレームワークをC ++プロジェクトにロードして接続することから始めます。



Github



アセンブリには、最新バージョンのVisual Studioおよび最新の利用可能なWDK(Windows Driver Kit)を使用することを強くお勧めします。これは、 公式のMicrosoft Webサイトからダウンロードできます。



テストには、Windows 7以上のWindowsがインストールされた無料のVMware Playerが最適です。



アセンブリは簡単で、質問は発生しません。



  1. Kernel-Bridge.slnを開きます
  2. 必要なビット深度を選択してください
  3. Ctrl + Shift + B


その結果、ドライバー、ユーザーモードライブラリ、および関連するユーティリティファイル(手動インストール用の* .inf 、Microsoft Hardware Certification Publisherでドライバーに署名するための* .cabなど)を取得します。



ドライバーをインストールするには(対応するEV証明書であるx64にデジタル署名が必要ない場合)、ドライバーのデジタル署名を無視して、システムをテストモードにする必要があります。 これを行うには、管理者としてコマンドラインを実行します。



bcdedit.exe /set loadoptions DISABLE_INTEGRITY_CHECKS

bcdedit.exe /set TESTSIGNING ON







...マシンを再起動します。 すべてが正しく実行されると、Windowsがテストモードになっていることを示す右下隅に碑文が表示されます。



テスト環境のセットアップが完了したら、プロジェクトでAPIの使用を開始しましょう。



フレームワークには次の階層があります。



/ Kernel-Bridge / API-ドライバーとカーネルモジュールで使用するための一連の関数。外部依存関係はなく、サードパーティのプロジェクトで自由に使用できます。

/ユーザーブリッジ/ API -PEファイル、PDBキャラクターなどを操作するためのドライバーおよびユーティリティ関数のユーザーモードラッパーのセット

/ SharedTypes / -必要な共通タイプを含むユーザーモードヘッダーとニュークリアスヘッダーの両方



ドライバーは、通常のドライバーとミニフィルターの2つの方法でロードできます。 2番目の方法が好ましい システムイベントのフィルターおよびユーザーモードコールバックの高度な機能へのアクセスを開きます。



それでは、C ++でコンソールプロジェクトを作成し、必要なヘッダーファイルを接続して、ドライバーをロードしましょう。



 #include <Windows.h> #include "WdkTypes.h" //    x32/x64    WDK #include "CtlTypes.h" //   IOCTL-   #include "User-Bridge.h" // API,   int main() { using namespace KbLoader; BOOL Status = KbLoadAsFilter( L"X:\\Folder\\Path\\To\\Kernel-Bridge.sys", L"260000" //       ); if (!Status) return 0; //   ! //     API ... // : KbUnload(); return 0; }
      
      





いいね! これで、APIを使用してカーネルと対話できるようになりました。

チート開発者の間で最も人気のある機能から始めましょう-別のプロセスのメモリの読み取りと書き込み:



 using namespace Processes::MemoryManagement; constexpr int Size = 64; BYTE Buffer[Size] = {}; BOOL Status = KbReadProcessMemory( //  KbWriteProcessMemory,   ProcessId, 0x7FFF0000, //     ProcessId &Buffer, Size );
      
      





複雑なことはありません! 1つ下のレベルに進みましょう-核メモリの読み取りと書き込み:



 using namespace VirtualMemory; constexpr int Size = 64; BYTE Buffer[Size]; //  "",  ""    , //       : BOOL Status = KbCopyMoveMemory( reinterpret_cast<WdkTypes::PVOID>(Buffer), //  0xFFFFF80000C00000, //  Size, FALSE //  ,     );
      
      





鉄と相互作用する機能はどうですか? たとえば、I / Oポート。



EFlagsレジスタの2 IOPLビットをコッキングしてユーザーモードに転送します。これは、 in / out / cli / sti命令が使用可能な特権レベルを担当します。



したがって、特権命令エラーなしでユーザーモードで実行できます。



 #include <intrin.h> using namespace IO::Iopl; //  ,   ! KbRaiseIopl(); //  in/out/cli/sti   ! ULONG Frequency = 1000; // 1 kHz ULONG Divider = 1193182 / Frequency; __outbyte(0x43, 0xB6); //     //      : __outbyte(0x42, static_cast<unsigned char>(Divider)); __outbyte(0x42, static_cast<unsigned char>(Divider >> 8)); __outbyte(0x61, __inbyte(0x61) | 3); //   (   ) for (int i = 0; i < 5000; i++); //   Sleep(),  IOPL      ! __outbyte(0x61, __inbyte(0x61) & 252); //   KbResetIopl();
      
      





しかし、本当の自由はどうですか? 結局のところ、カーネル特権で任意のコードを実行したいことがよくあります。 すべてのカーネルコードをユーザーモードで作成し、カーネルから制御をカーネルに転送します(ドライバーを呼び出す前にFPUコンテキストが保存され、 try..exceptブロック内で呼び出し自体が発生する前に、SMEPは自動的にオフになります)。



 using namespace KernelShells; //    KeStallExecutionProcessor: ULONG Result = 1337; KbExecuteShellCode( []( _GetKernelProcAddress GetKernelProcAddress, PVOID Argument ) -> ULONG { //      Ring0 //     : using _KeStallExecutionProcessor = VOID(WINAPI*)(ULONG Microseconds); auto Stall = reinterpret_cast<_KeStallExecutionProcessor>( GetKernelProcAddress(L"KeStallExecutionProcessor") ); Stall(1000 * 1000); //      ULONG Value = *static_cast<PULONG>(Argument); return Value == 1337 ? 0x1EE7C0DE : 0; }, &Result, // Argument &Result // Result ); //   Result = 0x1EE7C0DE
      
      





しかし、シェルを甘やかすだけでなく、ファイル、オブジェクト、およびプロセスフィルターのサブシステムに基づいて簡単なDLPを作成できる深刻な機能もあります。



このフレームワークでは、 CreateFile / ReadFile / WriteFile / DeviceIoControlのフィルタリング、ハンドルを開く/複製するイベント( ObRegisterCallbacks )、プロセス/スレッドの開始イベントおよびモジュールのロード( PsSet *** NotifyRoutine )のイベントを許可します。 これにより、たとえば、任意のファイルへのアクセスをブロックしたり、ハードディスクのシリアル番号に関する情報を置き換えたりすることができます。



動作原理:



  1. ドライバーはファイルフィルターを登録し、 Ob *** / Ps ***コールバックをインストールします
  2. ドライバーは、イベントにサブスクライブしたいクライアントが接続する通信ポートを開きます
  3. ユーザーモードアプリケーションは、ポートに接続し、発生したイベントに関するデータをドライバーから受信し、フィルター処理(権限の切り捨て、ファイルへのアクセスのブロックなど)を実行し、イベントをカーネルに返します。
  4. ドライバーは受信した変更を適用します。


ObRegisterCallbacksにサブスクライブし、現在のプロセスへのアクセスを切断する例:



 #include <Windows.h> #include <fltUser.h> #include "CommPort.h" #include "WdkTypes.h" #include "FltTypes.h" #include "Flt-Bridge.h" ... //   ObRegisterCallbacks: CommPortListener<KB_FLT_OB_CALLBACK_INFO, KbObCallbacks> ObCallbacks; //        PROCESS_VM_READ: Status = ObCallbacks.Subscribe([]( CommPort& Port, MessagePacket<KB_FLT_OB_CALLBACK_INFO>& Message ) -> VOID { auto Data = static_cast<PKB_FLT_OB_CALLBACK_INFO>(Message.GetData()); if (Data->Target.ProcessId == GetCurrentProcessId()) { Data->CreateResultAccess &= ~PROCESS_VM_READ; Data->DuplicateResultAccess &= ~PROCESS_VM_READ; } ReplyPacket<KB_FLT_OB_CALLBACK_INFO> Reply(Message, ERROR_SUCCESS, *Data); Port.Reply(Reply); //   });
      
      





そのため、フレームワークのユーザーモード部分の要点について簡単に説明しましたが、コアAPIは舞台裏のままです。



すべてのAPIとラッパーは、対応するフォルダーにあります: / Kernel-Bridge / API /

これには、メモリ、プロセス、文字列とロックなどの操作が含まれ、さらに多くの場合、独自のドライバの開発が大幅に簡素化されます。 APIとラッパーはそれ自体にのみ依存し、外部環境には依存しません。独自のドライバーで自由に使用できます。



カーネルで文字列を操作する例は、すべての初心者向けのつまずきブロックです。



 #include <wdm.h> #include <ntstrsafe.h> #include <stdarg.h> #include "StringsAPI.h" WideString wString = L"Some string"; AnsiString aString = wString.GetAnsi().GetLowerCase() + " and another string!"; if (aString.Matches("*another*")) DbgPrint("%s\r\n", aString.GetData());
      
      





IOCTLコードに独自のハンドラーを実装する場合、次のスキームに従ってこれを非常に簡単に実行できます。



  1. /Kernel-Bridge/Kernel-Bridge/IOCTLHandlers.cppにハンドラーを記述します
  2. 同じファイルで、 DispatchIOCTL関数のHandlers配列の最後にハンドラーを追加します
  3. アイテム2のHandlers配列のように、 同じ位置にあるCtlTypes.hのCtls :: KbCtlIndices列挙にクエリインデックスを追加します。
  4. User-Bridge.cppでラッパーを作成し、 KbSendRequest関数を使用して呼び出しを行うことにより、ユーザーモードからハンドラーを呼び出します。


3種類すべてのI / O(METHOD_BUFFERED、METHOD_NEITHERおよびMETHOD_IN_DIRECT / METHOD_OUT_DIRECT)がサポートされています。デフォルトでは、METHOD_NEITHERが使用されます。



以上です! この記事では、すべての可能性のほんの一部しか取り上げていません。 このフレームワークがカーネルコンポーネントの初心者開発者、リバースエンジニア、チート、アンチチート、保護の開発者に役立つことを願っています。



また、誰もが開発に参加するよう招待されています。 将来の計画では:





ご清聴ありがとうございました!



All Articles