特定のキーが正しくフックできない問題(チケット#17575)についての調査・検討メモ

検討の方向性として以下の2つを考える

  • これらのキーがWindowsに特別扱いされないようにできないか
  • Scancode Map による置き換えをログオフや再起動なしで有効化・無効化できないか

これらのキーがWindowsに特別扱いされないようにできないか

日本語キーボードの場合、キーレイアウトを決めているモジュールは

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 による置き換えをログオフや再起動なしで有効化・無効化できないか

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 のコマンドとして以下を繰り返し使いとしてカーネル空間全てを探すと
# L?xxxxxxxx を使うと一気に探せるがどうも挙動が不安定。
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 でプロセスハンドルを取得してそれのシグナルを待てば良いか。

エクスプローラを再起動した場合の副作用について。今のところ判明しているのは以下:

  • エクスプローラのウィンドウが全て消える
  • TaskbarCreated を処理していないアプリの場合通知領域のアイコンが復活しない

前者については「ログオン時に以前のフォルダウィンドウを表示する」を有効にしてみたが復活しない。エクスプローラ終了前にその時点で開かれているフォルダウィンドウを記憶しておき、再起動後再表示させることはできるとは思う。

上述のような制限を回避するため、エクスプローラ再起動以外の方法を検討する。

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下では管理者権限でないと、

  • winlogon に対する ProcessIdToSessionId()は失敗する。
  • SeDebugPrivilege が(単に無効ではなく)割り当てられない。(たとえ当該ユーザが Administrators に属していても)

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() を呼ぶようにしたら上手くいったもののスマートなやり方ではない…