ホットパッチ Windowsカーネルメモリにパッチを適用する

Windows Server 2003 SP1では、ホットパッチと呼ばれる技術が導入されました。 つまり、システムを再起動する必要なく、その場でシステムを更新します。 このテクノロジーにより、個々の機能(ユーザーモードとカーネルモードの両方)にパッチをインストールできます。 バージョン8.1では、ホットパッチをインストールする機能が削除されました。 カーネルモードパッチの場合でも、ユーザーモードからこの機能を使用できることは注目に値します。



この種のパッチは、Windows Server 2003 SP1でのみ短期間リリースされたことに注意する価値があります。



特定のパッチ例を検討してください:セキュリティ更新KB914389。 この更新には、mrxsmb.sysおよびrdbss.sysドライバーからのいくつかの機能パッチが含まれています。



各ドライバーのパッチには、2つのファイルが含まれています。1つは再起動後にパッチに置き換えられるドライバー、もう1つは拡張子が* .hp.sysの不思議なファイルで、これは通常のドライバーです。 「.hotp1」という名前のセクションを含める必要があります(末尾に2つのスペースが必要です)。 パッチプロセス自体を詳しく見てみましょう。



開始するには、ホットパッチ可能な関数の概念を導入する必要があります。 これはそのような関数であり、最初の命令は2バイトの命令「mov edi、edi」であり、関数の開始前に5つのnopsがあります。

セミホットパッチ可能な関数も区別されます-最初の命令はダブルバイトですが、mov edi、ediではありません。



命令「mov edi、edi」は、マルチプロセッサシステムでホットパッチを保護するために、hotpatchable関数に導入されています。 たとえば、最初の命令がシングルバイトの場合、次の結果が得られる可能性があります。スレッドの1つがパッチを適用した関数に入り、最初のコマンドを実行します。 同時に、別のスレッドがこの関数のパッチをインストールします。その結果、最初のスレッドは2バイトの命令「jmps -5」の中間にあり、システムクラッシュを引き起こします。



ホットパッチメカニズムのかなり詳細な分析は、多くの読者にとって面白くないかもしれませんが、以下に提示されます。
テクノロジーの本質は次のとおりです。まず、MmLoadSystemImage関数を使用して* .hp.sysドライバーがメモリにロードされます。 次に、「。hotp1」セクションにあるパッチのすべての特性が読み取られます。 パッチのタイトルを表す例示的な構造を以下に示します。 構造はここから取ら 、その絶対的な正確さには確実性はありませんが、運動障害との不一致は見つかりませんでした。



typedef struct _HOTPATCH_HEADER { DWORD Signature;//"HOT1" DWORD Version;//   = 0x00010000 DWORD FixupRgnCount;//  x86 ,       RtlpApplyRelocationFixups DWORD FixupRgnRva;//RVA   DWORD ValidationCount;//   RtlpValidateTargetRanges DWORD ValidationArrayRva;//RVA   DWORD HookCount;//,  .  ,    DWORD HookArrayRva;//   ,    RtlReadHookInformation ULONGLONG OrigHotpBaseAddress;// 86 .       ULONGLONG OrigTargetBaseAddress;//  ,     DWORD TargetNameRva;//,     ,    DWORD ModuleIdMethod;//  union { ULONGLONG Quad; GUID Guid; struct { GUID Guid; DWORD Age; } PdbSig; BYTE Hash128[16]; BYTE Hash160[20]; } TargetModuleIdValue; } HOTPATCH_HEADER, *PHOTPATCH_HEADER;
      
      





パッチがx86システムのホットパッチ可能な関数に重ねられている場合、パッチされた関数の最初の命令は短いjmp-jmps -5(opcode ebf9)に置き換えられます。 制御フローを5バイト戻します。5バイトの命令jmp m32が配置されます。つまり、遠いjmpはホットパッチで指定されたアドレスに転送されます。

その他の場合、パッチを適用する関数のタイプに関係なく、ターゲットモジュールとロードされた* .hp.sysのアドレスの違いがチェックされます。 パッチは、モジュールがターゲットモジュールの+ -2GB以内でブートした場合にのみインストールされます(オペランド「ff 25」jmpのサイズによって制限されます)。 最初の命令は6バイトの命令「jmp m32」に置き換えられ、ターゲット関数への相対アドレスを持ちます。



次に、ホットパッチプロセスを開始する方法を見てみましょう。



ntdll.dllから、NtSetSystemInformation関数がエクスポートされます。これは、当時よく使用されるNtQuerySystemInformation関数と同様に機能します。つまり、関数のさらなる動作を決定する引数SystemInformationClassの1つを取ります。 SystemInformationClass = 69関数を渡すと、syscallを使用してカーネルモードで失敗した場合、制御はMmHotPatchRoutine関数に転送されます。



そこで、* .hpファイルがメモリにロードされ、さらに制御がMiPerformHotpatch関数に転送されます。

特に、ロードされたモジュールの「.hotp1」セクションを検索し、RtlFindRtlPatchHeader関数を呼び出します。また、システム内のすべてのセッションを列挙して、メモリ内のターゲットモジュールを検索します。 次に、制御はRtlInitializeHotpatch関数に転送されます。







RtlpApplyRelocationFixupsおよびRtlpValidateTargetRangesの機能については詳しく説明しません。後者の助けを借りて、ターゲット関数がホットパッチ可能であることを確認できるとのみ言います。







RtlReadHookInformation関数では、パッチのインストールが行われます。



各パッチの構造を以下に示します。



 typedef struct _HOTPATCH_HOOK { WORD HookType;//  HOTPATCH_HOOK_TIPE WORD HookOptions; DWORD HookRva; DWORD HotpRva; DWORD ValidationRva; } HOTPATCH_HOOK, *PHOTPATCH_HOOK; typedef enum _HOTPATCH_HOOK_TYPE { HOTP_Hook_None = 0, HOTP_Hook_VA32 = 1, HOTP_Hook_X86_JMP = 2, HOTP_Hook_PCREL32 = 3, //not yet implemented HOTP_Hook_X86_JMP2B = 4, HOTP_Hook_VA64 = 16, HOTP_Hook_IA64_BRL = 32, HOTP_Hook_IA64_BR = 33, //not yet implemented HOTP_Hook_AMD64_IND = 48, HOTP_Hook_AMD64_CNT = 49 } HOTPATCH_HOOK_TYPE;
      
      





次に、RtlpReadSingleHookInformation関数が2回呼び出されます。1回目はスプリングボードのサイズ(「jmp」コマンドの形式とサイズ)で、2回目はパッチが直接インストールされます。







また、この関数では、ロードされたモジュールとターゲットモジュールの間の距離がチェックされます。 2GBを超える場合、パッチはインストールされません。





Windows 7 x64にパッチをインストールするとします。 いくつかの機能のパッチを実装してみましょう。 たとえば、fastfatサブシステムのFatCommonWrite関数を選択できます。これは、fat32フラッシュドライブにデータが書き込まれたときに呼び出されます。 最初に、塗りつぶされた「.hotp1」セクションと新しい関数を含むドライバーを作成する必要があります。



 #pragma section (".hotp1 ") __declspec(allocate(".hotp1 ")) struct Hotp_Header { ULONG Signature; ULONG Version; ULONG FixupRgnCount; ULONG FixupRgnRva; ULONG ValidationCount; ULONG ValidationArrayRva; ULONG HookCount; ULONG HookArrayRva; ULONGLONG OrigHotpBaseAddress; ULONGLONG OrigTargetBaseAddress; ULONG TargetNameRva; ULONG ModuleIdMethod; union { ULONGLONG Quad; GUID Guid; struct { GUID guid; ULONG Age; } PdbSig; UCHAR Hash128[16]; UCHAR Hash160[20]; } TargetModuleIdValue; CHAR TagretName[13]; struct { USHORT HookType; USHORT HookOptions; ULONG HookRva; ULONG HotpRva; ULONG ValidationRva; } Hook; } hpHeader = { 0x31544F48, // "1TOH" 0x00010000, // 1.0 0x00000000, // FixupRgn 0x00000000, // FixupRgn Rva 0x00000000, // Validations 0x00000000, // Validation Rva 0x00000001, // 1 Hook 0x00005060, // HookRva 0x0000000000010000, // HotpBase 0x0000000000010000, // TargetBase 0x00005050, // Targetname Rva 0x00000000, // ModuleID 0x0000000000000000, // Quad "fastfat.sys", { 0x0030, // hook type HOTP_Hook_AMD64_IND 0x8000, // hook option +- 2GB 0x0002B6F0, // hook rva 0x0004392A, // hotp rva 0x00000000 // valid rva } }; NTSTATUS FatCommonWrite() { PINT32 p = 0; INT32 a = *p;//  ,     (: return a; }
      
      





次に、ホットパッチプロセスを呼び出すアプリケーションを作成する必要があります。 これを行うには、通常のWin32アプリケーションを作成します。



 typedef struct _SYSTEM_HOTPATCH_CODE_INFORMATION { ULONG Flags; ULONG InfoSize; USHORT NameOffset; USHORT NameLength; } SYSTEM_HOTPATCH_CODE_INFORMATION; // //... //  PatchInfo  ,   ,   KB914389 // // //  . OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES, &hToken); SetPrivilege(hToken, SE_DEBUG_NAME, TRUE); SetPrivilege(hToken, SE_LOAD_DRIVER_NAME, TRUE); ZwSetSystemInformation(69, PatchInfo, PatchInfo->InfoSize);
      
      





このアプリケーションは、ホットパッチプロセスを開始します。 あとは、fat32フラッシュドライブをコンピューターに挿入し、何かを書き込んで、簡潔なBSODを確認するだけです。



パッチの前のFatCommonWrite:







パッチ後のFatCommonWrite:







結論として、この技術は潜在的な脆弱性ではありませんが、それでもWindowsカーネルに属するメモリにパッチを当てる興味深い方法です。



All Articles