yamy (0.03) | 2009-09-19 00:52 |
検討の方向性として以下の2つを考える
日本語キーボードの場合、キーレイアウトを決めているモジュールは
C:\WINDOWS\system32\kbd106.dllと推測されるが、これのソースコードと思われるものが、WinDDK の
WinDDK/6001.18001/src/input/layout/fe_kbds/jpn/106/kbd106.cにあるので、これを調べてみる。このファイル内の
static ALLOC_SECTION_LDATA USHORT ausVK[] = {がスキャンコードからVKコードへの変換テーブルのようだ。この中で使われている Txx マクロは
WinDDK/6001.18001/inc/api/kbd.hで
#define T00 _EQ( _none_ ) #define T0D _NE(OEM_PLUS,OEM_4, OEM_PLUS,OEM_PLUS,OEM_PLUS,OEM_PLUS)などと定義されている。_EQ()/_NE()には
* _EQ() : all keyboard types have the same virtual key for this scancode * _NE() : different virtual keys for this scancode, depending on kbd typeと説明があり、その定義は KBD_TYPE ごとに異なる。日本語106の KBD_TYPE は
WinDDK/6001.18001/src/input/layout/fe_kbds/jpn/106/kbd106.hで
#define KBD_TYPE 8とされており、この場合、_EQ()/_NE()は
#define _EQ( v8 ) (VK_##v8) #define _NE(v7,v8,v16,v10,v11,v12,v13) (VK_##v8)と定義される。 これによりスキャンコード 0x1e に対応する"A"キーは
T1E
→_EQ('A')
→VK_A
→0x41(winuser.h のコメントに「VK_A - VK_Z are the same as ASCII 'A' - 'Z' (0x41 - 0x5A)」とある)
となり、スキャンコード 0x3a に対応する英数(Caps)キーは
T3A
→_NE(DBE_ALPHANUMERIC,DBE_ALPHANUMERIC,CAPITAL,CAPITAL,CAPITAL,CAPITAL,CAPITAL)
→VK_DBE_ALPHANUMERIC
→0x0f0(kbd.h で定義)
となる。今回問題となっている3キーのスキャンコードはそれぞれ 0x29(漢字), 0x3a(英数/Caps), 0x70(ひらがな) なのだが、これにあたる ausVK[] のエントリは
/* * Hankaku/Zenkaku/Kanji key must have KBDSPECIAL bit set (NLS key) */ T29 | KBDSPECIAL,
/* * Alphanumeric/CapsLock key must have KBDSPECIAL bit set (NLS key) */ T3A | KBDSPECIAL,
/* * Hiragana/Katakana/Roman key must have KBDSPECIAL bit set (NLS key) */ T70 | KBDSPECIAL,であり、KBDSPECIAL というフラグがどうも怪しい。
次に kbd106.dll のバイナリから ausVK[] の場所を探してみる。kbd106.c を見ると ausVK0から USHORT * 8 は
T00, T01, T02, T03, T04, T05, T06, T07,だがこれをバイナリで表現すると
ff 00 1b 00 31 00 32 00 33 00 34 00 35 00 36 00となる。このバイナリ列を Windows XP SP3 の kbd106.dll(バージョン5.1.2600.5512)で探すと先頭からのオフセット0x400のところに見付かる。
このデータがカーネル内でどこに配置されているのかを VMware Player 内にインストールした XP を WinDbg でデバッグして探してみる。
Scancode Map がカーネルメモリのどこに配置されるのかを探してみる。
Windows Registry Editor Version 5.00 [HKEY_CURRENT_USER\Keyboard Layout] "Scancode Map"=hex:00,00,00,00,00,00,00,00,04,00,00,00,70,00,3b,00,29,00,3c,00,7b,00,3d,00,00,00,00,00を設定して WinDbg のコマンドとして以下を繰り返し使いとしてカーネル空間全てを探すと
s 80000000 L10000000 00 00 00 00 00 00 00 00 04 00 00 00 70 00 3b 00 29 00 3c 00 7b 00 3d 00 00 00 00 00 s 90000000 L10000000 00 00 00 00 00 00 00 00 04 00 00 00 70 00 3b 00 29 00 3c 00 7b 00 3d 00 00 00 00 00 s a0000000 L10000000 00 00 00 00 00 00 00 00 04 00 00 00 70 00 3b 00 29 00 3c 00 7b 00 3d 00 00 00 00 00 s b0000000 L10000000 00 00 00 00 00 00 00 00 04 00 00 00 70 00 3b 00 29 00 3c 00 7b 00 3d 00 00 00 00 00 s c0000000 L10000000 00 00 00 00 00 00 00 00 04 00 00 00 70 00 3b 00 29 00 3c 00 7b 00 3d 00 00 00 00 00 s d0000000 L10000000 00 00 00 00 00 00 00 00 04 00 00 00 70 00 3b 00 29 00 3c 00 7b 00 3d 00 00 00 00 00 s e0000000 L10000000 00 00 00 00 00 00 00 00 04 00 00 00 70 00 3b 00 29 00 3c 00 7b 00 3d 00 00 00 00 00 s f0000000 L10000000 00 00 00 00 00 00 00 00 04 00 00 00 70 00 3b 00 29 00 3c 00 7b 00 3d 00 00 00 00 00以下の3箇所に見付かる
80677188 00 00 00 00 00 00 00 00-04 00 00 00 70 00 3b 00 ............p.;. dd3132bc 00 00 00 00 00 00 00 00-04 00 00 00 70 00 3b 00 ............p.;. e1c1c0a0 00 00 00 00 00 00 00 00-04 00 00 00 70 00 3b 00 ............p.;. e1d06478 00 00 00 00 00 00 00 00-04 00 00 00 70 00 3b 00 ............p.;.ただし、一番最初のアドレスはどのパターンを検索しても引っ掛かるのでカーネルデバッガ(KD)の作業領域と思われる。従ってScancode Mapの格納領域は後ろ3つのどれか。候補の3箇所で F1(0x3b) を「ひらがな(0x70)」に置き換えているエントリに読み込みのメモリブレークポイントを張って
ba r4 dd3132c8 ba r4 e1c1c0ac ba r4 e1d06484置き換えをしているF1キーを押してみると2つ目の 0xe1c1c0ac でブレークし、その再のコールスタックは
kd> k ChildEBP RetAddr f9a6b9b0 bf88a5dd win32k!MapScancode+0x30 f9a6b9ec bf88b34a win32k!ProcessKeyboardInputWorker+0xcb f9a6ba0c bf8855d2 win32k!ProcessKeyboardInput+0x68 f9a6ba1c 804ffb62 win32k!InputApc+0x4e f9a6ba64 80502d04 nt!KiDeliverApc+0x124 f9a6ba7c 804fbaf2 nt!KiSwapThread+0x64 f9a6bab4 bf89fc27 nt!KeWaitForMultipleObjects+0x284 f9a6bd30 bf88467b win32k!RawInputThread+0x4f3 f9a6bd40 bf8010ed win32k!xxxCreateSystemThreads+0x60 f9a6bd54 8053f648 win32k!NtUserCallOneParam+0x23 f9a6bd54 7c94e514 nt!KiFastCallEntry+0xf8 0074ffe0 764cba1a ntdll!KiFastSystemCallRet 00000000 f000ff53 winsrv!NtUserCallOneParam+0xc WARNING: Frame IP not in any known module. Following frames may be wrong. 00000000 00000000 0xf000ff53逆アセンブルリストは
win32k!MapScancode: bf925b60 8bff mov edi,edi bf925b62 55 push ebp bf925b63 8bec mov ebp,esp bf925b65 a170bd9abf mov eax,dword ptr [win32k!gpScancodeMap (bf9abd70)] bf925b6a 85c0 test eax,eax bf925b6c 8b550c mov edx,dword ptr [ebp+0Ch] bf925b6f 56 push esi bf925b70 8b7508 mov esi,dword ptr [ebp+8] bf925b73 7429 je win32k!MapScancode+0x3e (bf925b9e) bf925b75 33c9 xor ecx,ecx bf925b77 8a2a mov ch,byte ptr [edx] bf925b79 57 push edi bf925b7a 83c00c add eax,0Ch bf925b7d 8a0e mov cl,byte ptr [esi] bf925b7f 8bf9 mov edi,ecx bf925b81 eb0b jmp win32k!MapScancode+0x2e (bf925b8e) bf925b83 c1e910 shr ecx,10h bf925b86 663bcf cmp cx,di bf925b89 740b je win32k!MapScancode+0x36 (bf925b96) bf925b8b 83c004 add eax,4 bf925b8e 8b08 mov ecx,dword ptr [eax] ;;ブレーク場所 bf925b90 85c9 test ecx,ecx bf925b92 7409 je win32k!MapScancode+0x3d (bf925b9d) bf925b94 ebed jmp win32k!MapScancode+0x23 (bf925b83) bf925b96 668b00 mov ax,word ptr [eax] bf925b99 8806 mov byte ptr [esi],al bf925b9b 8822 mov byte ptr [edx],ah bf925b9d 5f pop edi bf925b9e ff7510 push dword ptr [ebp+10h] bf925ba1 33c0 xor eax,eax bf925ba3 8a02 mov al,byte ptr [edx] bf925ba5 50 push eax bf925ba6 56 push esi bf925ba7 e872fdffff call win32k!MapFlexibleKeys (bf92591e) bf925bac 5e pop esi bf925bad 5d pop ebp bf925bae c20c00 ret 0Chレジスタ値は
kd> r eax=e1c1c0ac ebx=e17dfa78 ecx=003b0070 edx=f9a6b9f4 esi=f9a6b9d0 edi=0000003b eip=bf925b90 esp=f9a6b9a8 ebp=f9a6b9b0 iopl=3 nv up ei ng nz na pe nc cs=0008 ss=0010 ds=0023 es=0023 fs=0030 gs=0000 efl=00003286 win32k!MapScancode+0x30: bf925b90 85c9 test ecx,ecxとなる。どうやら win32k!gpScancodeMap (bf9abd70) が Scancode Map を保存しているようなのでダンプしてみると
kd> dp bf9abd70 L1 bf9abd70 e1c1c0a0となり確かに一致する。HKCU の Scancode Map はログオンの度に読み込まれるはずなので、どの関数から読み込まれるかを調べるため一旦ログオフし、
kd> x win32k!gpScancodeMap bf9abd70 win32k!gpScancodeMap = <no type information> kd> ba w4 bf9abd70として win32k!gpScancodeMap に書き込みメモリブレークポイントを貼り、ログオンすると、
kd> k ChildEBP RetAddr f9a5bb48 bf899ae3 win32k!InitScancodeMap+0x9a f9a5bd44 bf8992fc win32k!xxxUpdatePerUserSystemParameters+0x88c f9a5bd54 8053f648 win32k!NtUserUpdatePerUserSystemParameters+0x13 f9a5bd54 7c94e514 nt!KiFastCallEntry+0xf8 0006e4d4 77d017b5 ntdll!KiFastSystemCallRet 0006e554 77cf88da USER32!NtUserUpdatePerUserSystemParameters+0xc 0006e5dc 77cf8bd9 USER32!GetWindowLongW+0x49 0006e5ec 77d08dac USER32!_EndUserApiHook+0x11 0006e5f0 77d08d8b USER32!DefWindowProcW+0x94 0006e628 77d09312 USER32!DefWindowProcW+0x86 0006e698 77cf8734 USER32!ValidateHwndNoRip+0x1d 0006e6c0 77d0be0a USER32!InternalCallWinProc+0x28 0006e72c 77d08ea0 USER32!UserCallWinProcCheckWow+0x103 77cf882a ffff9090 USER32!DispatchClientMessage+0xa3 WARNING: Frame IP not in any known module. Following frames may be wrong. 77cf8842 74dc5d39 0xffff9090 77cf8846 0378e805 0x74dc5d39 77cf884a 458d0000 0x378e805 77cf884e 15ff50c0 0x458d0000 77cf8852 77cf1434 0x15ff50c0 77cf8856 909090c3 USER32!_imp__RtlDeactivateActivationContextUnsafeFast kd> r eax=0000001c ebx=e2052db8 ecx=00009de9 edx=804fffad esi=00000000 edi=bf990edc eip=bf8950d8 esp=f9a5bb30 ebp=f9a5bb48 iopl=0 nv up ei pl zr na pe nc cs=0008 ss=0010 ds=0023 es=0023 fs=0030 gs=0000 efl=00000246 win32k!InitScancodeMap+0x9a: bf8950d8 e925010000 jmp win32k!InitScancodeMap+0x9a (bf895202)でブレークする。
f9a5bd44 bf8992fc win32k!xxxUpdatePerUserSystemParameters+0x88c
の逆アセンブルは
bf899a6d 0000 add byte ptr [eax],al bf899a6f 6a5b push 5Bh bf899a71 57 push edi bf899a72 ffb518ffffff push dword ptr [ebp-0E8h] bf899a78 e8ceb0ffff call win32k!FastGetProfileIntFromID (bf894b4b) bf899a7d a1d8af9abf mov eax,dword ptr [win32k!gpsi (bf9aafd8)] bf899a82 53 push ebx bf899a83 684cbf9abf push offset win32k!gcyMouseHover (bf9abf4c) bf899a88 ffb054060000 push dword ptr [eax+654h] bf899a8e 6a5c push 5Ch bf899a90 57 push edi bf899a91 ffb518ffffff push dword ptr [ebp-0E8h] bf899a97 e8afb0ffff call win32k!FastGetProfileIntFromID (bf894b4b) bf899a9c 53 push ebx bf899a9d 6848bf9abf push offset win32k!gdtMouseHover (bf9abf48) bf899aa2 ff351cb099bf push dword ptr [win32k!gdtMNDropDown (bf99b01c)] bf899aa8 6a5d push 5Dh bf899aaa 57 push edi bf899aab 8bbd18ffffff mov edi,dword ptr [ebp-0E8h] bf899ab1 57 push edi bf899ab2 e894b0ffff call win32k!FastGetProfileIntFromID (bf894b4b) bf899ab7 6a0a push 0Ah bf899ab9 58 pop eax bf899aba 390548bf9abf cmp dword ptr [win32k!gdtMouseHover (bf9abf48)],eax bf899ac0 0f82a6f3ffff jb win32k!xxxUpdatePerUserSystemParameters+0x869 (bf898e6c) bf899ac6 b8ffffff7f mov eax,7FFFFFFFh bf899acb 390548bf9abf cmp dword ptr [win32k!gdtMouseHover (bf9abf48)],eax bf899ad1 0f879ff3ffff ja win32k!xxxUpdatePerUserSystemParameters+0x87b (bf898e76) bf899ad7 57 push edi bf899ad8 e81bc2ffff call win32k!UpdatePerUserKeyboardIndicators (bf895cf8) bf899add 57 push edi bf899ade e8a7bcffff call win32k!UpdatePerUserKeyboardMappings (bf89578a) ;; ここ bf899ae3 53 push ebx bf899ae4 68f4bf9abf push offset win32k!gdwKeyboardAttributes (bf9abff4) bf899ae9 53 push ebx bf899aea 6800f898bf push offset win32k!`string' (bf98f800) bf899aef 6a19 push 19h bf899af1 57 push edi bf899af2 e88aaeffff call win32k!FastGetProfileDwordW (bf894981) bf899af7 a1f4bf9abf mov eax,dword ptr [win32k!gdwKeyboardAttributes (bf9abff4)] bf899afc c1e80f shr eax,0Fh bf899aff 83e002 and eax,2 bf899b02 57 push edi bf899b03 a3f4bf9abf mov dword ptr [win32k!gdwKeyboardAttributes (bf9abff4)],eax bf899b08 e8f5c4ffff call win32k!xxxUpdatePerUserAccessPackSettings (bf896002) bf899b0d 53 push ebx bf899b0e 6819000200 push 20019h bf899b13 6a17 push 17h bf899b15 53 push ebx bf899b16 e805810200 call win32k!OpenCacheKeyEx (bf8c1c20) bf899b1b 3bc3 cmp eax,ebx bf899b1d 7417 je win32k!xxxUpdatePerUserSystemParameters+0x8df (bf899b36) bf899b1f 8b0dd8af9abf mov ecx,dword ptr [win32k!gpsi (bf9aafd8)] bf899b25 50 push eax bf899b26 c781bc06000001000000 mov dword ptr [ecx+6BCh],1 bf899b30 ff152cd198bf call dword ptr [win32k!_imp__ZwClose (bf98d12c)] bf899b36 a1d8af9abf mov eax,dword ptr [win32k!gpsi (bf9aafd8)] bf899b3b 8388bc06000002 or dword ptr [eax+6BCh],2 bf899b42 56 push esi bf899b43 e8eee4feff call win32k!GreSetFontEnumeration (bf888036) bf899b48 6a20 push 20h bf899b4a e8e7e4feff call win32k!GreSetFontEnumeration (bf888036) bf899b4f 8b85fcfeffff mov eax,dword ptr [ebp-104h] bf899b55 a802 test al,2上記のコールスタックに
f9a5bd54 8053f648 win32k!NtUserUpdatePerUserSystemParameters+0x13という行がある。一方、user32.dll を Dependency Walker で見てみると UpdatePerUserSystemParameters という非公開 API が見付かる。これを呼び出せばログオフしなくても Scancode Map を読ませることができるかもしれない。Web で見付かる情報からの類推で UpdatePerUserSystemParameters プロトタイプは
BOOL WINAPI UpdatePerUserSystemParameters(DWORD, BOOL);と推測される(確証なし)。yamy の初期化部分に GetProcAddress() を使って UpdatePerUserSystemParameters() を呼び出す細工をしてみた。こんな感じ
typedef BOOL (WINAPI *FuncUpdatePerUserSystemParameters)(DWORD, BOOL); HMODULE hMod = GetModuleHandle(_T("user32.dll")); if (hMod != NULL) { FuncUpdatePerUserSystemParameters pUpdate; pUpdate = (FuncUpdatePerUserSystemParameters)GetProcAddress(hMod, "UpdatePerUserSystemParameters"); if (pUpdate != NULL) { BOOL ret; ret = pUpdate(0, 1); } }しかし、Scancode Map は反映されない。引数をいろいろ変えてみてもダメ。WinDbg で再度追っ掛けてみる。yamy から呼んだ場合
win32k!xxxUpdatePerUserSystemParameters
には入って来るものの
bf899ade e8a7bcffff call win32kUpdatePerUserKeyboardMappings (bf89578a)
に到達しない。ステップ実行でトレースしていくと、
bf899684 e8fd80f6ff call win32k!PsGetCurrentProcessId (bf801786) bf899689 3b05ecaf9abf cmp eax,dword ptr [win32k!gpidLogon (bf9aafec)] ds:0023:bf9aafec=00000260 bf89968f 0f85ea090000 jne win32k!xxxUpdatePerUserSystemParameters+0x3c0 (bf89a07f)の条件分岐でエラーになっているようだ。win32k!gpidLogon の中身は WinLogon.exe の PID となっている。WinLogon.exe 以外からの UpdatePerUserSystemParameters()の呼び出しはエラーとしているようだ。yamy の PID が入っている eax をデバッガで WinLogon.exe の PID に書き換えて分岐を通過させると、
bf899ade e8a7bcffff call win32kUpdatePerUserKeyboardMappings (bf89578a)
まで到達し、Scancode Map も反映される。
WinLogon.exe にコード注入して UpdatePerUserSystemParameters() を呼び出させることができれば上手くいきそう。
尚、UpdatePerUserSystemParameters で検索すると、rundll32.exe を使ってこの API を呼び出すことにより、レジストリ書き換え後、ログオフを経ないで壁紙を変更する話題が多く見付かる。自前のコードで UpdatePerUserSystemParameters を呼んだ場合も同様なので、winlogon.exe 以外から呼んだ場合でも全くのエラーになるわけではなく、部分的に処理が行われるようだ。
http://japan.internet.com/column/developer/20050830/26.html
http://ruffnex.oc.to/kenji/text/wfp/
等を参考にして、winlogon.exe にコード注入しての UpdatePerUserSystemParameters() 実行するサンプルコードを書いて試してみたが、壁紙が真っ黒になりタスクバーの挙動がおかしくなる。KD で追うと PsGetCurrentProcessId でのチェックはパスしており、UpdatePerUserKeyboardMappings も実行されている。winlogon.exe は SYSTEM ユーザ権限で動作しているため、ログオンしている実際のユーザのプロファイルが読めてないものと推測した。
そこで、winlogon.exe に注入したコード内で実際のユーザのトークンを偽装すれば良いのではないかと考えた。winlogon.exe での実際のログオン時にも同様のことをしているのではないかと推測。
http://eternalwindows.jp/security/securitycontext/securitycontext06.html
を参考にサンプルコードに ImpersonateLoggedOnUser() での偽装を追加したところ、壁紙が真っ黒になる現象はなくなった。しかし、タスクバーの挙動はやはりおかしくなる。具体的には、アプリを最小化すると、タスクバーではなくスタートボタンの真上あたりに最小化され、タスクバー上のボタンは無反応になる。また新規にアプリを起動した場合にはタスクバーにボタンが現われない。
エクスプローラを強制終了してタスクマネージャから再起動すれば元に戻るようだが、他に復旧方法がないか調べる。また他に副作用がないかも懸念事項。
エクスプローラをなるべく安全に再起動する方法を考える。エクスプローラのトップレベルウィンドウのクラス名は !progman のようだ。このウィンドウを探して !WM_QUIT をポストして終了させ、WinExec で起動するのが最も安全に思われる。サンプルコードは以下:
HWND hExplorer = FindWindow(_T("progman"), NULL); if (hExplorer != NULL) { PostMessage(hExplorer, WM_QUIT, 0, 0); WinExec("Explorer.exe", SW_SHOW); }あるいは、
HWND hExplorer = GetShellWindow(); if (hExplorer != NULL) { PostMessage(hExplorer, WM_QUIT, 0, 0); WinExec("Explorer.exe", SW_SHOW); }実際には WinExec の前に終了を待つべき。OpenProcess でプロセスハンドルを取得してそれのシグナルを待てば良いか。
エクスプローラを再起動した場合の副作用について。今のところ判明しているのは以下:
前者については「ログオン時に以前のフォルダウィンドウを表示する」を有効にしてみたが復活しない。エクスプローラ終了前にその時点で開かれているフォルダウィンドウを記憶しておき、再起動後再表示させることはできるとは思う。
上述のような制限を回避するため、エクスプローラ再起動以外の方法を検討する。
progman ウィンドウに WM_USERCHANGE を送出したが効果なし。
yamy32.dll を使って Explorer.exe に飛んでくるシェルフックメッセージ(RegisterWindowMessage(_T("SHELLHOOK"))) を調べたところ、UpdatePerUserSystemParameters()を呼び出す前は、アプリを起動する度に、wParam として HSHELL_WINDOWCREATED 等が来ていたが、UpdatePerUserSystemParameters()の呼び出し後はそれらが来なくなる。
互換シェルである bbLean のソースコードを見ると、!ShellDDEInit()/RegisterShellHook() といった非公開 API を呼び出している。また、user32.dll には関係ありそうな API RegisterShellHookWindow()が存在する。 そこで、yamy32.dll の initialize()で Explorer.exe にこれらの API を実行させてみたが効果はない。サンプルコードは以下:
if (_tcsncmp(g.m_moduleName, _T("Explorer"), sizeof(_T("Explorer"))/sizeof(_TCHAR)) == 0) { HWND hTrayWnd = FindWindow(_T("Shell_TrayWnd"), NULL); if (hTrayWnd != NULL) { HWND hReBarWindow32 = FindWindowEx(hTrayWnd, NULL, _T("ReBarWindow32"), NULL); if (hReBarWindow32 != NULL) { HWND hMSTaskSwWClass = FindWindowEx(hReBarWindow32, NULL, _T("MSTaskSwWClass"), NULL); if (hMSTaskSwWClass != NULL) { HMODULE hShell32 = GetModuleHandle(_T("shell32.dll")); HMODULE hShdocvw = GetModuleHandle(_T("shdocvw.dll")); if (hShell32 != NULL && hShdocvw != NULL) { FpRegisterShellHook pRegisterShellHook; FpShellDDEInit pShellDDEInit; pShellDDEInit = (FpShellDDEInit)GetProcAddress(hShdocvw, (LPCSTR)0x76); if (pShellDDEInit != NULL) { pShellDDEInit(TRUE); } pRegisterShellHook = (FpRegisterShellHook)GetProcAddress(hShell32, (LPCSTR)0xB5); if (pRegisterShellHook != NULL) { pRegisterShellHook(hMSTaskSwWClass, 3); RegisterShellHookWindow(hMSTaskSwWClass); } } } } } }
逆に、yamy32.dll を使って、Explorer.exe にシェルフックメッセージを無視(return 0;)させて見たが、UpdatePerUserSystemParameters()を呼んだ場合とは現象が異なり、スタートボタンの上に最小化ウィンドウが現われることがない。
GetShellWindow()は progman クラスと同じウィンドウ(恐らく Explorer のメインウィンドウ)が返される。UpdatePerUserSystemParameters()後にGetShellWindow()を呼んでも正しい値が取れる。一方、Explorer.exe を終了していれば、GetShellWindow()は NULL を返す。このことから GetShellWindow()で返されるシェルウィンドウが UpdatePerUserSystemParameters() でクリアされるわけではなさそう。
カーネル内でのシェルフックの扱いを探るために、 KD で x win32k!*ShellHook* をかけると、win32kPostShellHookMessages というのが見付かるので、ここに ba e1 すると正常時にはウィンドウ操作に際にブレークし、
ChildEBP RetAddr f78b3b08 bf846e0b win32k!PostShellHookMessages ... (A) f78b3b24 bf83abc6 win32k!xxxSetTrayWindow+0x83 ... (B) f78b3b4c bf8b1fa3 win32k!xxxUpdateTray+0x11e ... (C) f78b3b7c bf8223f3 win32k!xxxProcessEventMessage+0x161 f78b3c94 bf801ea2 win32k!xxxScanSysQueue+0x10fb f78b3ce8 bf8036cf win32k!xxxRealInternalGetMessage+0x335 f78b3d48 8053f648 win32k!NtUserPeekMessage+0x40 f78b3d48 7c94e514 nt!KiFastCallEntry+0xf8 0007ff34 7c94d80a ntdll!KiFastSystemCallRet 0007ffa0 7c956b17 ntdll!ZwQueryInformationProcess+0xc 00080058 00000000 ntdll!LdrpUpdateLoadCount3+0x68といったコールスタックが得られる。しかし、UpdatePerUserSystemParameters()実行後は同じ操作をしてもブレークしない。(A)(B)(C)それぞれに ba e1 してどこまで実行されるのかを調べると、(C)には来るが(B)には到達しないことがわかった。(C)を逆アセンブルすると
win32k!xxxUpdateTray: bf83ab05 8bff mov edi,edi bf83ab07 55 push ebp bf83ab08 8bec mov ebp,esp bf83ab0a 83ec0c sub esp,0Ch bf83ab0d 57 push edi bf83ab0e 8b7d08 mov edi,dword ptr [ebp+8] bf83ab11 f6472310 test byte ptr [edi+23h],10h bf83ab15 0f84b1000000 je win32k!xxxUpdateTray+0x124 (bf83abcc) bf83ab1b 8b473c mov eax,dword ptr [edi+3Ch] bf83ab1e 56 push esi bf83ab1f 8bf7 mov esi,edi bf83ab21 85c0 test eax,eax bf83ab23 75c5 jne win32k!xxxUpdateTray+0x1e (bf83aaea) bf83ab25 8b4608 mov eax,dword ptr [esi+8] bf83ab28 8b4030 mov eax,dword ptr [eax+30h] bf83ab2b 3b05c0c39abf cmp eax,dword ptr [win32k!gpqForeground (bf9ac3c0)] bf83ab31 0f8594000000 jne win32k!xxxUpdateTray+0x123 (bf83abcb) bf83ab37 a1d8af9abf mov eax,dword ptr [win32k!gpsi (bf9aafd8)] bf83ab3c f680a006000008 test byte ptr [eax+6A0h],8 bf83ab43 0f8482000000 je win32k!xxxUpdateTray+0x123 (bf83abcb) ...(***) bf83ab49 a198b39abf mov eax,dword ptr [win32k!gptiCurrent (bf9ab398)] bf83ab4e 8b4840 mov ecx,dword ptr [eax+40h] bf83ab51 8b490c mov ecx,dword ptr [ecx+0Ch] bf83ab54 0b8898000000 or ecx,dword ptr [eax+98h] bf83ab5a f6c508 test ch,8 bf83ab5d 750c jne win32k!xxxUpdateTray+0x71 (bf83ab6b) bf83ab5f 8b460c mov eax,dword ptr [esi+0Ch] bf83ab62 8b4004 mov eax,dword ptr [eax+4] bf83ab65 83785c00 cmp dword ptr [eax+5Ch],0 bf83ab69 7460 je win32k!xxxUpdateTray+0x123 (bf83abcb) bf83ab6b 8b460c mov eax,dword ptr [esi+0Ch] bf83ab6e 8b4004 mov eax,dword ptr [eax+4] bf83ab71 8b4e34 mov ecx,dword ptr [esi+34h] bf83ab74 3b4808 cmp ecx,dword ptr [eax+8] bf83ab77 7552 jne win32k!xxxUpdateTray+0x123 (bf83abcb) bf83ab79 f6462310 test byte ptr [esi+23h],10h bf83ab7d 744c je win32k!xxxUpdateTray+0x123 (bf83abcb) bf83ab7f 33c9 xor ecx,ecx bf83ab81 41 inc ecx bf83ab82 f6461902 test byte ptr [esi+19h],2 bf83ab86 7457 je win32k!xxxUpdateTray+0x96 (bf83abdf) bf83ab88 f6471902 test byte ptr [edi+19h],2 bf83ab8c 7443 je win32k!xxxUpdateTray+0xd9 (bf83abd1) bf83ab8e 57 push edi bf83ab8f e89243fdff call win32k!IsTrayWindow (bf80ef26) bf83ab94 85c0 test eax,eax bf83ab96 7439 je win32k!xxxUpdateTray+0xd9 (bf83abd1) bf83ab98 8bc7 mov eax,edi bf83ab9a 85c0 test eax,eax bf83ab9c 8b0d98b39abf mov ecx,dword ptr [win32k!gptiCurrent (bf9ab398)] bf83aba2 8b5128 mov edx,dword ptr [ecx+28h] bf83aba5 8955f4 mov dword ptr [ebp-0Ch],edx bf83aba8 8d55f4 lea edx,[ebp-0Ch] bf83abab 895128 mov dword ptr [ecx+28h],edx bf83abae 8945f8 mov dword ptr [ebp-8],eax bf83abb1 0f8441ffffff je win32k!xxxUpdateTray+0x112 (bf83aaf8) bf83abb7 ff4004 inc dword ptr [eax+4] bf83abba 8b700c mov esi,dword ptr [eax+0Ch] bf83abbd 6a00 push 0 bf83abbf 50 push eax bf83abc0 56 push esi bf83abc1 e8e7c10000 call win32k!xxxSetTrayWindow (bf846dad) bf83abc6 e8c365fcff call win32k!ThreadUnlock1 (bf80118e) bf83abcb 5e pop esi bf83abcc 5f pop edi bf83abcd c9 leave bf83abce c20400 ret 4 bf83abd1 56 push esi bf83abd2 e84f43fdff call win32k!IsTrayWindow (bf80ef26) bf83abd7 f7d8 neg eax bf83abd9 1bc0 sbb eax,eax bf83abdb 23c6 and eax,esi bf83abdd ebbb jmp win32k!xxxUpdateTray+0xf1 (bf83ab9a) bf83abdf f6461c80 test byte ptr [esi+1Ch],80h bf83abe3 0f8508ffffff jne win32k!xxxUpdateTray+0xe1 (bf83aaf1) bf83abe9 f6461840 test byte ptr [esi+18h],40h bf83abed 7526 jne win32k!xxxUpdateTray+0xe5 (bf83ac15) bf83abef 8a4622 mov al,byte ptr [esi+22h] bf83abf2 a80a test al,0Ah bf83abf4 740a je win32k!xxxUpdateTray+0xb3 (bf83ac00) bf83abf6 a8c0 test al,0C0h bf83abf8 751b jne win32k!xxxUpdateTray+0xe5 (bf83ac15) bf83abfa f6462320 test byte ptr [esi+23h],20h bf83abfe 7515 jne win32k!xxxUpdateTray+0xe5 (bf83ac15) bf83ac00 85c9 test ecx,ecx bf83ac02 74c7 je win32k!xxxUpdateTray+0x123 (bf83abcb) bf83ac04 8bb690000000 mov esi,dword ptr [esi+90h] bf83ac0a 85f6 test esi,esi bf83ac0c 74bd je win32k!xxxUpdateTray+0x123 (bf83abcb) bf83ac0e 33c9 xor ecx,ecx bf83ac10 e96dffffff jmp win32k!xxxUpdateTray+0x90 (bf83ab82) bf83ac15 56 push esi bf83ac16 e82e300d00 call win32k!Is31TrayWindow (bf90dc49) bf83ac1b ebba jmp win32k!xxxUpdateTray+0xeb (bf83abd7)正常時は上記の(***)で分岐しないが、UpdatePerUserSystemParameters()呼び出し後はここで分岐するため(B)に到達しない。1つ上の行で参照しているeax+6A0hを 0x8 に書き換えると、正常な動作(最小化するとタスクバーに入る・アプリを起動するとタスクバーに現われる)をするようになる。
そこで、このeax+6A0hに ba w1 を設定し、UpdatePerUserSystemParameters()を呼び出すと、
ChildEBP RetAddr f79a3b44 bf89973b win32k!SetMinMetrics+0xeb f79a3d44 bf8992fc win32k!xxxUpdatePerUserSystemParameters+0x45c f79a3d54 8053f648 win32k!NtUserUpdatePerUserSystemParameters+0x13 f79a3d54 7c94e514 nt!KiFastCallEntry+0xf8 0182ff5c 804ffb62 ntdll!KiFastSystemCallRet 0182ff90 00000000 nt!KiDeliverApc+0x124というコールスタックでブレークし、近辺の逆アセンブルは
win32k!SetMinMetrics: bf88fb98 8bff mov edi,edi bf88fb9a 55 push ebp bf88fb9b 8bec mov ebp,esp bf88fb9d 83ec14 sub esp,14h bf88fba0 8b450c mov eax,dword ptr [ebp+0Ch] bf88fba3 85c0 test eax,eax bf88fba5 56 push esi bf88fba6 7549 jne win32k!SetMinMetrics+0x59 (bf88fbf1) bf88fba8 8b7508 mov esi,dword ptr [ebp+8] bf88fbab 689a000000 push 9Ah bf88fbb0 6892000000 push 92h bf88fbb5 56 push esi bf88fbb6 e845890000 call win32k!MetricGetID (bf898500) bf88fbbb 6a00 push 0 bf88fbbd 6893000000 push 93h bf88fbc2 56 push esi bf88fbc3 8945f0 mov dword ptr [ebp-10h],eax bf88fbc6 e835890000 call win32k!MetricGetID (bf898500) bf88fbcb 6a00 push 0 bf88fbcd 6894000000 push 94h bf88fbd2 56 push esi bf88fbd3 8945f4 mov dword ptr [ebp-0Ch],eax bf88fbd6 e825890000 call win32k!MetricGetID (bf898500) bf88fbdb 6a00 push 0 bf88fbdd 6896000000 push 96h bf88fbe2 56 push esi bf88fbe3 8945f8 mov dword ptr [ebp-8],eax bf88fbe6 e815890000 call win32k!MetricGetID (bf898500) bf88fbeb 8945fc mov dword ptr [ebp-4],eax bf88fbee 8d45ec lea eax,[ebp-14h] bf88fbf1 8b5004 mov edx,dword ptr [eax+4] bf88fbf4 85d2 test edx,edx bf88fbf6 0f8e8d000000 jle win32k!SetMinMetrics+0x60 (bf88fc89) bf88fbfc 8b4808 mov ecx,dword ptr [eax+8] bf88fbff 85c9 test ecx,ecx bf88fc01 895004 mov dword ptr [eax+4],edx bf88fc04 7f02 jg win32k!SetMinMetrics+0x6e (bf88fc08) bf88fc06 33c9 xor ecx,ecx bf88fc08 894808 mov dword ptr [eax+8],ecx bf88fc0b 8b480c mov ecx,dword ptr [eax+0Ch] bf88fc0e 85c9 test ecx,ecx bf88fc10 7f02 jg win32k!SetMinMetrics+0x7a (bf88fc14) bf88fc12 33c9 xor ecx,ecx bf88fc14 8360100f and dword ptr [eax+10h],0Fh bf88fc18 89480c mov dword ptr [eax+0Ch],ecx bf88fc1b 8b0dd8af9abf mov ecx,dword ptr [win32k!gpsi (bf9aafd8)] bf88fc21 8bb1dc050000 mov esi,dword ptr [ecx+5DCh] bf88fc27 8d1472 lea edx,[edx+esi*2] bf88fc2a 8991a4060000 mov dword ptr [ecx+6A4h],edx bf88fc30 8b0dd8af9abf mov ecx,dword ptr [win32k!gpsi (bf9aafd8)] bf88fc36 8bb13c060000 mov esi,dword ptr [ecx+63Ch] bf88fc3c 8b91e0050000 mov edx,dword ptr [ecx+5E0h] bf88fc42 8d1456 lea edx,[esi+edx*2] bf88fc45 8991a8060000 mov dword ptr [ecx+6A8h],edx bf88fc4b 8b0dd8af9abf mov ecx,dword ptr [win32k!gpsi (bf9aafd8)] bf88fc51 8b91a4060000 mov edx,dword ptr [ecx+6A4h] bf88fc57 035008 add edx,dword ptr [eax+8] bf88fc5a 5e pop esi bf88fc5b 89917c060000 mov dword ptr [ecx+67Ch],edx bf88fc61 8b0dd8af9abf mov ecx,dword ptr [win32k!gpsi (bf9aafd8)] bf88fc67 8b91a8060000 mov edx,dword ptr [ecx+6A8h] bf88fc6d 03500c add edx,dword ptr [eax+0Ch] bf88fc70 899180060000 mov dword ptr [ecx+680h],edx bf88fc76 8b4010 mov eax,dword ptr [eax+10h] bf88fc79 8b0dd8af9abf mov ecx,dword ptr [win32k!gpsi (bf9aafd8)] bf88fc7f 8981a0060000 mov dword ptr [ecx+6A0h],eax <------------------ここでブレーク bf88fc85 c9 leave bf88fc86 c20800 ret 8 bf88fc89 33d2 xor edx,edx bf88fc8b e96cffffff jmp win32k!SetMinMetrics+0x62 (bf88fbfc) bf88fc90 68432a80bf push offset win32k!HeavyFreePool (bf802a43) bf88fc95 8d85c4fdffff lea eax,[ebp-23Ch] bf88fc9b 50 push eax bf88fc9c 53 push ebx bf88fc9d e8732cf7ff call win32k!PushW32ThreadLock (bf802915) bf88fca2 e9e6000000 jmp win32k!xxxClientAddFontResourceW+0x68 (bf88fd8d) bf88fca7 837e0400 cmp dword ptr [esi+4],0 bf88fcab 0f8417010000 je win32k!xxxClientAddFontResourceW+0xaf (bf88fdc8) bf88fcb1 8d7b24 lea edi,[ebx+24h] bf88fcb4 6a12 push 12h bf88fcb6 59 pop ecx bf88fcb7 f3a5 rep movs dword ptr es:[edi],dword ptr [esi] bf88fcb9 e90e010000 jmp win32k!xxxClientAddFontResourceW+0xb3 (bf88fdcc) bf88fcbe 8b00 mov eax,dword ptr [eax] bf88fcc0 e952010000 jmp win32k!xxxClientAddFontResourceW+0xfe (bf88fe17)explorer.exe を再起動すると同じ場所でブレークし、その際のコールスタックは
ChildEBP RetAddr f7b7dd60 bf8911fc win32k!SetMinMetrics+0xeb f7b7dd78 bf8911cb win32k!xxxSetAndDrawMinMetrics+0x23 f7b7dd94 bf891195 win32k!xxxSPISetMinMetrics+0x6a f7b7ddac bf81d17e win32k!xxxSetSPIMetrics+0xac f7b7e208 bf81e805 win32k!xxxSystemParametersInfo+0xfe2 f7b7e48c 8053f648 win32k!NtUserSystemParametersInfo+0x390 f7b7e48c 7c94e514 nt!KiFastCallEntry+0xf8 01a6f508 77cf8cc3 ntdll!KiFastSystemCallRet WARNING: Frame IP not in any known module. Following frames may be wrong. 01a6f550 587348e1 0x77cf8cc3 01a6f5f8 5873497c 0x587348e1 01a6f61c 77cf9f5a 0x5873497c 01a6f660 010183ce 0x77cf9f5a 01a6f690 01013f7f 0x10183ce 01a6f6a8 010185b0 0x1013f7f 01a6f760 01001b5c 0x10185b0 01a6f784 77cf8734 0x1001b5c 01a6f89c 7c94e473 0x77cf8734 01a6f89c 805016a0 ntdll!KiUserCallbackDispatcher+0x13 f7b7e764 805990ed nt!KiCallUserMode+0x4 f7b7e7c0 bf83d6c5 nt!KeUserModeCallback+0x87となる。この場合と同じパラメータで user32!SystemParametersInfo を呼べば良いのではないかと推測される。win32k!NtUserSystemParametersInfo でのスタック:
kd> dd f7b7e48c f7b7e48c f7b7e4a4 8053f648 0000002c 00000014 f7b7e49c 01a6f67c 00000000 01a6f550 7c94e514を SystemParametersInfo() のプロトタイプ
BOOL SystemParametersInfo( UINT uiAction, // 取得または設定するべきシステムパラメータ UINT uiParam, // 実施するべき操作によって異なる PVOID pvParam, // 実施するべき操作によって異なる UINT fWinIni // ユーザープロファイルの更新オプション );に当てはめると、
uiAction = 0x0000002c(SPI_SETMINIMIZEDMETRICS) uiParam = 0x00000014(sizeof(MINIMIZEDMETRICS)) pvParam = 01a6f67c(MINIMIZEDMETRICS*) fWinIni = 0となることが判る。改めて MSDN を調べてみると http://msdn.microsoft.com/en-us/library/ms644959(VS.85).aspx の「WH_SHELL Hook 」の項に
Note that custom shell applications do not receive WH_SHELL messages. Therefore, any application that registers itself as the default shell must call the SystemParametersInfo function before it (or any other application) can receive WH_SHELL messages. This function must be called with SPI_SETMINIMIZEDMETRICS and a MINIMIZEDMETRICS structure. Set the iArrange member of this structure to ARW_HIDE.との記載があり、bbLean 内でもそのような処理が行われていた。そこで、SystemParametersInfo(SPI_GETMINIMIZEDMETRICS, ...)で保存しておいた MINIMIZEDMETRICS を UpdatePerUserSystemParameters()呼び出し後に SystemParametersInfo(SPI_SETMINIMIZEDMETRICS, ...)で再設定してやると、正常な動作(最小化するとタスクバーに入る・アプリを起動するとタスクバーに現われる)をするようになった。
CreateRemoteThread のドキュメントには
lTerminal Services isolates each terminal session by design. Therefore, CreateRemoteThread fails if the target process is in a different session than the calling process.とあるが、タスクマネージャで確認したところ、幸いなことに winlogon.exe は SYSTEM 権限で動いているもののセッションはログオンしているユーザと同じもののようだ。
ただし、ユーザ切り替え機能を使うとログオンしているユーザ毎に winlogon.exe プロセスが生成されるので、ProcessIdToSessionId()を使って判別する必要がある。
Vista以降のUAC下では管理者権限でないと、
Windows7 RCで試したところ !HKEY_CURRENT_USER の Scancode Map が使えなさそう。!HKEY_LOCAL_MACHINE のほうは有効。尚、Scancode Map については !HKCU のほうはログオフ→ログオンで、!HKLM のほうは再起動で有効になるという情報が多く見られるが、少なくとも WinXP SP3 以降では、!HKLM のほうもログオフ→ログオンで有効になった。
UpdatePerUserSystemParameters()のinjectionでの実行時にハングしているプロセスがあると、inject したスレッドが制御を返さない。notepad.exe を windbg.exe でデバッグブレークさせてハング状態を作り出して確認した。この場合、Scancode Map は更新されない。
notepad.exe をハング→injection→タイムアウト→VirtualFree()→notepad.exe を復活
という手順を踏むと、winlogon.exe がクラッシュしたような挙動を示すので、ハングプロセスが復活したら処理は継続されるようだ。ただし、この「継続」でも Scancode Map は更新されていない。また、そのプロセスがハングしたままでも再度 injection すればリモートスレッドは制御を返し、Scancode Map は更新される。
WinXP の場合、クラシックテーマだとロックから復帰時(WTS_SESSION_UNLOCK受信時)に UpdatePerUserSystemParameters() を実行すると失敗する場合がある。Luna なら問題ない。WTS_SESSION_UNLOCK受信時に自分に投げたメッセージで UpdatePerUserSystemParameters() を呼ぶようにしてみたがそれでもダメ。そこで失敗した場合はWM_TIMERを使って何秒後かに UpdatePerUserSystemParameters() を呼ぶようにしたら上手くいったもののスマートなやり方ではない…