WindowsAPI呼び出しからシステムコール、カーネルへのメカニズム

  • windows
  • NT-Kernel

序章

本記事では、WindowsAPI(e.g., CreateFile, CloseHandle)がユーザープロセスで呼び出されてから、実際にシステムコールによりカーネルで処理されるまでのメカニズムをご説明します。

kernel32 と ntdll

一般的な開発者が使用するAPI群(e.g., CreateFile, CloseHandle)はkernel32.dllによってエクスポートされている関数です。

さらにそのkernel32.dllは、ユーザーモードにおけるWindowsAPIの抽象化レイヤーであるntdll.dllの高級ラッパーです。 kernel32.dllがラッパーであることにより、様々なサブシステムでの動作を実現しています。

しかし、gdi32.dllなどの一部のモジュールはラッパーを介さずに直接コールするケースもあることには注意してください。
そのモジュールがサブシステムで動作することを想定していないからです。

処理フロー

Windows x64におけるユーザーモードからのsyscallコールはカーネルモードで処理され、最終的にsysretで再びユーザーモードに復帰します。

ntdll における NtCreateFile 及び ZwCreateFile

ntdllにおけるNtCreateFileの実装

NtCreateFile及びZwCreateFileのアセンブリは上記のようになっています。

  • mov eax, 55h
    • Windows 10 1507-20H2におけるNtCreateFileのシステムコール番号である0x55eaxレジスタに代入しています。
      Windowsでは、システムコール番号はeaxレジスタに代入します。
  • test byte ptr ds:7FFE0308h, 1
    • ここでの7FFE0308SharedUserData+0x308つまりSharedUserData->SystemCallを指しています。
      32bit OSであればSharedUserData->SystemCallには0以外が入ります。
  • jnz short useInt2E
    • SharedUserData->SystemCall == 1であればuseInt2Eにジャンプします。
      64bit OSであれば、通常通りsyscallへ進みます。

syscall命令のその後

syscall命令が実行されたのち、

  1. 最終的にユーザーモードに復帰させる必要がある為、FLAGSレジスタをR11に退避させます。FLAGSレジスタはCPUの実行モードを現します。
  2. IA32_FMASK MSRの値でFLAGSレジスタをマスクします。
  3. IA32_LSTAR MSR10xC0000082にあるシステムコールハンドラであるKiSystemCall64をInvokeします。

CPUの実行モードはリングプロテクションによるセキュリティ保全の為のCPUレベルのアーキテクチャです。

IA32_LSTAR MSR

msr[c0000082]KiSystemCall64のアドレスfffff8063dbf8e80が格納されていることが確認できます。

*1MSR = Model-Specific Register

SharedUserData と 7FFE0000

The pre-set address for access from kernel mode is defined symbolically in WDM.H as KI_USER_SHARED_DATA. It helps when debugging to remember that this is 0xFFDF0000 or 0xFFFFF780`00000000, respectively, in 32-bit and 64-bit Windows. Also defined is a convenient symbol, SharedUserData, which casts this constant address to a KUSER_SHARED_DATA pointer.
The read-only user-mode address for the shared data is 0x7FFE0000, both in 32-bit and 64-bit Windows.
参照: https://www.geoffchappell.com/

SharedUserData_KUSER_SHARED_DATA構造体へのポインタです。

||0:0: kd> dt nt!_KUSER_SHARED_DATA
   ~ 
   +0x308 SystemCall       : Uint4B
   ~ 

オフセット0x308にあるSystemCallメンバーは、32bit環境では0以外の1が代入されます。
ntddk.hでは下記のように説明されています。

//
// On AMD64, this value is initialized to a nonzero value if the system
// operates with an altered view of the system service call mechanism.
//

ULONG SystemCall;

Windowsソフトウェア開発におけるトリックの一つとしても利用できます。
下記の例では、SharedUserData->SystemCallを読み取ることにより、32bitであれば0以外が代入されていることから、システムがwow64であるかを判断できます。

#include <iostream>
#include <Windows.h>

int main()
{   
    printf("%lu\n", (ULONG)(*(PVOID*)(0x7ffe0000 + 0x308)));
    return 0;
}