例外がユーザー空間に落ちてくるまで

しばらくカーネル空間で例外ハンドラをいじって遊んでいたのだけど、ユーザー空間でも似たような遊びができる気がしたので下調べのメモ。常に以下のフローをたどるわけではなく、あくまで発生した例外がユーザー空間に伝播するときのフロー。x86Windows XP SP3、x64は7を見ている。

  1. 何らかの理由で割込みもしくは例外が発生する。
  2. ハードウェアによりIDTが参照され、発生した例外の番号に対応する例外ハンドラ*1にEipが設定される。
  3. 例外ハンドラのアクションはアーキテクチャ別。
    1. x64
      1. 例外ハンドラはKiExceptionDispatchを呼び出す
      2. KiExceptionDispatchはKiDispatchExceptionを呼び出す
      3. KiDispatchExceptionはKTRAP_FRAME.Ripに大域変数KeUserExceptionDispatcherをセットする
mov     rax, cs:KeUserExceptionDispatcher
mov     [rsi+168h], rax
    1. x86
      1. 例外ハンドラはCommonDispatchExceptionを呼び出す
      2. CommonDispatchExceptionは_KiDispatchExceptionを呼び出す
      3. _KiDispatchExceptionははKTRAP_FRAME.Eipに大域変数KeUserExceptionDispatcherをセットする
mov     eax, ds:_KeUserExceptionDispatcher
mov     [ebx+68h], eax

KTRAP_FRAMEは例外ハンドラの開始直後に必要なレジスタを保存することで構築される。

大域変数KeUserExceptionDispatcherにはあらかじめntdll!KiUserExceptionDispatcherがセットされている。以下はx86での初期化コード。x64では初期化コードを見つけられなかったが同じだと思う。

INIT:005DB168                         ; int __stdcall PspLookupKernelUserEntryPoints()
INIT:005DB168                         _PspLookupKernelUserEntryPoints@0 proc near
INIT:005DB168 68 90 B4 48 00                          push    offset _KeUserExceptionDispatcher
INIT:005DB16D 68 FC B1 5D 00                          push    offset aKiuserexceptio ; "KiUserExceptionDispatcher"
INIT:005DB172 E8 D2 FF FF FF                          call    _PspLookupSystemDllEntryPoint@8 ; PspLookupSystemDllEntryPoint(x,x)

その後、

  1. KiExceptionDispatchもしくはCommonDispatchExceptionまで戻る。
  2. KiExceptionDispatchもしくはCommonDispatchExceptionは先ほど構築したKTRAP_FRAMEを引数にKiDeliverApcを使いユーザー空間にAPC*2をスケジュールする。
  3. KiExceptionDispatchもしくはCommonDispatchExceptionは iret しカーネルにおける一連の処理を終了する。


いずれ、

  1. ユーザープロセスにおいてAPCが実行されるとntdll!KiUserExceptionDispatcherに制御が移る。
  2. ntdll!KiUserExceptionDispatcherはntdll!RtlDispatchExceptionを呼び出す。
  3. ntdll!RtlDispatchExceptionはループ構造を持っており、例外ハンドラを検索し引数としてntdll!RtlpExecuteHandlerForExceptionを呼び出す。
  4. ntdll!RtlpExecuteHandlerForExceptionは引数として与えられた例外ハンドラを呼び出す。


もう少し枝葉もあるが、ユーザー空間への例外の伝播はAPCを使いntdll!KiUserExceptionDispatcherに行くのは共通。プロトタイプはこんな感じ(のはず)。

VOID STDCALL
KiUserExceptionDispatcher(PEXCEPTION_RECORD ExceptionRecord,
                          PCONTEXT Context);

この関数はエクスポートされた関数なので簡単にフックすることができる。機会があればこの辺の情報を元になんかする。

*1:例外ハンドラは !idt -a コマンドで確認できる。

*2:Asynchronous Procedure Calls。Armored Personal Carrierの略じゃないです。。。