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 に新しいバイナリを呼び出すスタブ ルーチンを持たせる) こともできます。
たとえばWindows 7でこのプログラムを動かす。
#include <windows.h> int main() { HANDLE process = ::OpenProcess(PROCESS_ALL_ACCESS, FALSE, ::GetCurrentProcessId()); ::CloseHandle(process); return 0; }
すると以下のようなことが起こる。
- 呼び出されたkernel32.dll!GetCurrentProcessIdは、kernel32.dll!GetCurrentProcessIdStubへのスタブである。
- kernel32.dll!GetCurrentProcessIdStubではkernel32.dll!GetCurrentProcessIdへJMPする。
- kernel32.dll!GetCurrentProcessIdはインポートアドレステーブル(IAT)を参照する。
- 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は面白い点がある。
- エクスポートされたすべての関数はただ単にゼロを返すコードしか持たない(IDAに食わせてみよう)
- DllMain(DLL_PROCESS_ATTACH)もゼロを返す(したがってこのDLLはイメージとしてロードできない)
- LoadLibraryやGetModuleHandleなどファイル名を受け取るローダー関数にこのファイル名を渡すと、別のDLLに置き換えられて処理される
- フルパスで指定すれば置き換えられないが、上記「2」によりロードできない
- 変換テーブルは各プロセスのアドレス0x30000にマッピングされている
- KnownDllsオブジェクトディレクトリに登録されている
- たまにカーネルがロードしている*1
なにやってんだ(笑
// 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