Windows 7でのリファクタリング

Microsoft Partoner Program - Windows 7にある「Windows 7 アプリケーション品質(互換性 信頼性 パフォーマンス)を高めるための開発者ガイド」には、Windows 7へ移行するにあたっての開発者向け変更点が記述されている。Windowsプラットフォームの開発者は必読である。


人によってこの文書の見所はいろいろかと思うが、個人的には「新しいバイナリ: リファクタリング」が面白い。kernel32.dllとadvapi32.dllの実装はkernelbase.dllに集約された。

新しいバイナリ: リファクタリング

(中略)

具体的な影響や現象

新しい低レベルのバイナリへ機能面の再配置を行いました。たとえば、kernel32.dll および advapi32.dll の機能は、kernelbase.dll に集約されます。今後、既存のバイナリは、呼び出しを直接処理するのではなく、低レベルの新しいバイナリに転送することになります。転送は静的に行う (エクスポート テーブルにリダイレクトが含まれるようにする) ことも、実行時に行う (Dll に新しいバイナリを呼び出すスタブ ルーチンを持たせる) こともできます。

問題の軽減策と対処策

既存の機能に対する影響の解決策

影響があるのは、ウイルス対策アプリケーションなど、メモリ内の kernel32.dll または advapi32.dll のエクスポート テーブルを参照する際に、何らかの推測を行うコードだけです。
実装の深い部分ではなく、公開されている API を使用するようにしてください。これは、リファクタリングによって、API の詳細な実装を覆い隠すように再配置されている 1 つの例です。


たとえばWindows 7でこのプログラムを動かす。

#include <windows.h>
int main()
{
  HANDLE process = ::OpenProcess(PROCESS_ALL_ACCESS, FALSE, ::GetCurrentProcessId());
  ::CloseHandle(process);
  return 0;
}


すると以下のようなことが起こる。

  1. 呼び出されたkernel32.dll!GetCurrentProcessIdは、kernel32.dll!GetCurrentProcessIdStubへのスタブである。
  2. kernel32.dll!GetCurrentProcessIdStubではkernel32.dll!GetCurrentProcessIdへJMPする。
  3. kernel32.dll!GetCurrentProcessIdはインポートアドレステーブル(IAT)を参照する。
  4. IATはkernelbase.dll!GetCurrentProcessIdを指し示しており、これが呼び出される。
; メモリレイアウト
;   kernel32.dll(winmain_win7beta.081212-1400)    76CB0000-76D6F000
;   KernelBase.dll(winmain_win7beta.081212-1400)  75AD0000-75B29000

; 「1」
_GetCurrentProcessIdStub@0:
76D04BB0  jmp         _GetCurrentProcessId@0 (76D04B96h)  ; 「2」
76D04BB2  nop         

; 「3」
_GetCurrentProcessId@0:
76D04B96  jmp         dword ptr [__imp__GetCurrentProcessId@0 (76CB17F0h)]  ; 0x76CB17F0 => 20 6e ad 75
76D04B9C  nop   

; 「4」
_GetCurrentProcessId@0:
75AD6E20  mov         eax,dword ptr fs:[00000018h] 
75AD6E26  mov         eax,dword ptr [eax+20h] 
75AD6E29  ret         

OpenProcessやCloseHandleでも一連の流れはほぼ同じである。


GetProcAddressはスタブ関数へのポインタを返す。

ptr = GetProcAddress(GetModuleHandle(_T("kernel32")), "GetCurrentProcessId");
// ptr == GetCurrentProcessIdStub

ほとんどのアプリケーションはこの変更を意識する必要はないだろう。


実際、Detoursフックを行うプログラムにさえ影響しない。MS Detoursライブラリは(確か)関数の先頭アドレスがJMPになっている場合は機能しないように実装されているが、GetCurrentProcessIdStubのように、すべての関数で先頭アドレスがJMPになっているわけではない。/hotpatch用コードが挿入されていたり、引数のある関数はプロローグ/エピローグ文が残っていることが多い。

_OpenProcessStub@12:
76CF0B79 8B FF            mov         edi,edi   ; hotpatch用
76CF0B7B 55               push        ebp  
76CF0B7C 8B EC            mov         ebp,esp 
76CF0B7E 5D               pop         ebp  
76CF0B7F EB 05            jmp         _OpenProcess@12 (76CF0B86h) 


この変更の目的は

  • 内部的なエンジニアリングの効率を高め、将来的な作業基盤をしっかりと整えておく
  • 将来の Windows 環境の総合的な "表面積" (ディスクやメモリ要件、サービス、攻撃にさらされる領域など) をより小さく抑える

ためらしい。そういえばsechost.dllのリソースには "for minwin" と書かれている。

ApiSet Stub DLL

関連するのか知らないが、system32にはmicrosoft-windows-system-*-l1-1-0.dllという小さなDLLが複数存在する。これはいくつかの関数をエクスポートするDLLで、たとえばmicrosoft-windows-system-handle-l1-1-0.dllならば、CloseHandle, DuplicateHandle, GetHandleInformation, SetHandleInformation をエクスポートするなど、ファイル名の通り分類されている。


このDLLは面白い点がある。

  1. エクスポートされたすべての関数はただ単にゼロを返すコードしか持たない(IDAに食わせてみよう)
  2. DllMain(DLL_PROCESS_ATTACH)もゼロを返す(したがってこのDLLはイメージとしてロードできない)
  3. LoadLibraryやGetModuleHandleなどファイル名を受け取るローダー関数にこのファイル名を渡すと、別のDLLに置き換えられて処理される
    1. フルパスで指定すれば置き換えられないが、上記「2」によりロードできない
    2. 変換テーブルは各プロセスのアドレス0x30000にマッピングされている
  4. KnownDllsオブジェクトディレクトリに登録されている
  5. たまにカーネルがロードしている*1


なにやってんだ(笑


とりあえずマッピングだけ調べてみた*2

// test.cpp
#include <windows.h>
#include <tchar.h>
#include <iostream>

int _tmain(int argc, TCHAR* argv[])
{
  if (argc != 2)
  {
    return 0;
  }

  HMODULE sechost = ::LoadLibrary(_T("sechost"));  // SCM/SDDL/LSA Lookup APIs DLL for minwin
  HMODULE apiset_stub = ::GetModuleHandle(argv[1]);//                              ~~~~~~~~~~
  TCHAR path[MAX_PATH];
  if (apiset_stub)
  {
    ::GetModuleFileName(apiset_stub, path, RTL_NUMBER_OF(path));
  }
  else
  {
    path[0] = _T('\0');
  }
  _tprintf(_T("%-60s -> %s\n"), argv[1], path);
  return 0;
}
PS C:\Windows\System32> Get-ChildItem microsoft-windows-system-*.dll -name | foreach { test.exe $_ }
microsoft-windows-system-console-l1-1-0.dll                  -> C:\Windows\system32\kernel32.dll
microsoft-windows-system-datetime-l1-1-0.dll                 -> C:\Windows\system32\KERNELBASE.dll
microsoft-windows-system-debug-l1-1-0.dll                    -> C:\Windows\system32\KERNELBASE.dll
microsoft-windows-system-delayload-l1-1-0.dll                -> C:\Windows\system32\kernel32.dll
microsoft-windows-system-errorhandling-l1-1-0.dll            -> C:\Windows\system32\KERNELBASE.dll
microsoft-windows-system-fibers-l1-1-0.dll                   -> C:\Windows\system32\KERNELBASE.dll
microsoft-windows-system-file-l1-1-0.dll                     -> C:\Windows\system32\kernel32.dll
microsoft-windows-system-handle-l1-1-0.dll                   -> C:\Windows\system32\kernel32.dll
microsoft-windows-system-heap-l1-1-0.dll                     -> C:\Windows\system32\KERNELBASE.dll
microsoft-windows-system-interlocked-l1-1-0.dll              -> C:\Windows\system32\KERNELBASE.dll
microsoft-windows-system-io-l1-1-0.dll                       -> C:\Windows\system32\KERNELBASE.dll
microsoft-windows-system-libraryloader-l1-1-0.dll            -> C:\Windows\system32\KERNELBASE.dll
microsoft-windows-system-localization-l1-1-0.dll             -> C:\Windows\system32\KERNELBASE.dll
microsoft-windows-system-memory-l1-1-0.dll                   -> C:\Windows\system32\KERNELBASE.dll
microsoft-windows-system-misc-l1-1-0.dll                     -> C:\Windows\system32\KERNELBASE.dll
microsoft-windows-system-namedpipe-l1-1-0.dll                -> C:\Windows\system32\KERNELBASE.dll
microsoft-windows-system-process-environment-l1-1-0.dll      -> C:\Windows\system32\KERNELBASE.dll
microsoft-windows-system-process-processthreads-l1-1-0.dll   -> C:\Windows\system32\KERNELBASE.dll
microsoft-windows-system-profile-l1-1-0.dll                  -> C:\Windows\system32\KERNELBASE.dll
microsoft-windows-system-registry-localregistry-l1-1-0.dll   -> C:\Windows\system32\KERNELBASE.dll
microsoft-windows-system-rtlsupport-l1-1-0.dll               -> C:\Windows\SYSTEM32\ntdll.dll
microsoft-windows-system-security-base-l1-1-0.dll            -> C:\Windows\system32\KERNELBASE.dll
microsoft-windows-system-security-lsalookup-l1-1-0.dll       -> C:\Windows\system32\sechost.DLL
microsoft-windows-system-security-sddl-l1-1-0.dll            -> C:\Windows\system32\sechost.DLL
microsoft-windows-system-services-l1-1-0.dll                 -> C:\Windows\system32\sechost.DLL
microsoft-windows-system-string-l1-1-0.dll                   -> C:\Windows\system32\KERNELBASE.dll
microsoft-windows-system-synch-l1-1-0.dll                    -> C:\Windows\system32\kernel32.dll
microsoft-windows-system-sysinfo-l1-1-0.dll                  -> C:\Windows\system32\KERNELBASE.dll
microsoft-windows-system-threadpool-l1-1-0.dll               -> C:\Windows\system32\KERNELBASE.dll
microsoft-windows-system-util-l1-1-0.dll                     -> C:\Windows\system32\KERNELBASE.dll
microsoft-windows-system-xstate-l1-1-0.dll                   -> C:\Windows\SYSTEM32\ntdll.dll

*1:Windows 7カーネルはuser32やgdi32などなど、複数のDLLをユーザーモード空間にロードすることがある。

*2:Windows 7と2008 R2からはPowerShellが標準で実装されてます。運用系の技術者は早めに学習しておきませう。