この記事では、Windowsエクスプローラーを展開した経験、具体的には「パワーユーザーメニュー」と呼ばれるコンテキストメニューについてお話します。 古い[スタート]メニューのプレゼンテーションが本当に必要だとは言えませんが、それでも仕事に必要な基本機能への迅速かつ構造化されたアクセスを可能にしたいと思っています。 パワーユーザーメニューは、次の2つの方法で呼び出すことができます。1. [スタート]ボタンを右クリックします。 2. Windowsキー+ Xのキーの組み合わせを押します。マイクロソフトはこのメニューを編集する機能を提供していますが、この機能は非常に制限されており、メニュー階層、アイコン付きアイテムを作成できず、ショートカットのみをサポートします。 説明した機能を実装するために、Windows Explorerプロセスへのdllインジェクションを実行し、コンテキストメニューの操作を制御するapi呼び出しをインターセプトします。 実験的なオペレーティングシステムとして、Windows 8.1 x64を使用します。
それでは、Windowsエクスプローラーのアドレススペースにdllを挿入できるようにする手順から始めましょう。 使用するインジェクション方法は「コードケーブdllインジェクション」と呼ばれ、事前に準備されたマシンコードを選択したプロセスのアドレス空間にインジェクションします。 このマシンコードは、必要なライブラリを使用してLoadLibraryにAPI呼び出しを行い、制御をアプリケーションに返します。
void InjectDLLx64( LPPROCESS_INFORMATION ppi, LPCTSTR dll ) { CONTEXT threadContext; DWORD length; LPVOID memBuf; DWORD64 loadLibApi; union { PBYTE cC; PDWORD64 cP; } ip; #define CODESIZE 92 static BYTE code[CODESIZE+SIZE_T(MAX_PATH)] = { 0,0,0,0,0,0,0,0, // original rip 0,0,0,0,0,0,0,0, // LoadLibraryW 0x9C, // pushfq 0x50, // push rax 0x51, // push rcx 0x52, // push rdx 0x53, // push rbx 0x55, // push rbp 0x56, // push rsi 0x57, // push rdi 0x41,0x50, // push r8 0x41,0x51, // push r9 0x41,0x52, // push r10 0x41,0x53, // push r11 0x41,0x54, // push r12 0x41,0x55, // push r13 0x41,0x56, // push r14 0x41,0x57, // push r15 0x48,0x83,0xEC,0x28, // sub rsp, 40 0x48,0x8D,0x0D,41,0,0,0, // lea ecx, L"path to dll" 0xFF,0x15,-49,-1,-1,-1, // call LoadLibraryW 0x48,0x83,0xC4,0x28, // add rsp, 40 0x41,0x5F, // pop r15 0x41,0x5E, // pop r14 0x41,0x5D, // pop r13 0x41,0x5C, // pop r12 0x41,0x5B, // pop r11 0x41,0x5A, // pop r10 0x41,0x59, // pop r9 0x41,0x58, // pop r8 0x5F, // pop rdi 0x5E, // pop rsi 0x5D, // pop rbp 0x5B, // pop rbx 0x5A, // pop rdx 0x59, // pop rcx 0x58, // pop rax 0x9D, // popfq 0xFF,0x25,-91,-1,-1,-1, // jmp original Rip 0, // dword alignment for loadLibApi }; length = SIZE_T(lstrlen( dll ) + 1); if (length > SIZE_T(MAX_PATH)) return; RtlCopyMemory( code + CODESIZE, dll, length ); length += CODESIZE; threadContext.ContextFlags = CONTEXT_CONTROL; GetThreadContext( ppi->hThread, &threadContext ); memBuf = VirtualAllocEx( ppi->hProcess, NULL, length, MEM_COMMIT, PAGE_EXECUTE_READWRITE ); loadLibApi = (DWORD64)LoadLibraryW; ip.cC = code; *ip.cP++ = threadContext.Rip; *ip.cP++ = loadLibApi; WriteProcessMemory( ppi->hProcess, memBuf, code, length, NULL ); FlushInstructionCache( ppi->hProcess, memBuf, length ); threadContext.Rip = (DWORD64)memBuf + 16; SetThreadContext( ppi->hThread, &threadContext); }
マシンコードはCPUレジスタを保存し、プログラム実行段階で定義されたAPI呼び出しLoadLibraryを使用して必要なライブラリをロードし、レジスタの内容を復元して制御を返します。 当然、注入時には、プロセスは一時停止状態になっている必要があります。
dllの実装を実行するアプリケーションの他の関数のコードは、あまり関心がないので考慮しません。
コンテキストメニューを展開できるようにするには、そのハンドルを取得する必要があります。 メニューを表示するには、TrackPopupMenu関数を使用します。プロトタイプを検討してください。
int WINAPI TrackPopupMenu( _In_ HMENU hMenu, _In_ UINT uFlags, _In_ int x, _In_ int y, _In_ int nReserved, _In_ HWND hWnd, _In_opt_ const RECT *prcRect)
ご覧のとおり、メニューを所有するHWNDウィンドウとメニュー自体のハンドルもあります。 ただし、インターセプトを実装する前に、呼び出されたときにこの関数が受け取るパラメーターを見てみましょう。 API Monitorアプリケーションを使用します。 メーカーのウェブサイトAPI Monitorからダウンロードできます。 APIモニターで関数のブレークポイントを構成した後、パワーユーザーメニューを開き、次のようなウィンドウを取得しようとします。
呼び出しから、エクスプローラーがTPM_RETURNCMDフラグを使用してコンテキストメニューを開くことは明らかです。つまり、選択したアイテムを決定するWM_COMMANDタイプのメッセージを検索する必要はありません。 ユーザーが指定した要素は、TrackPopupMenu関数自体によって返されます。ユーザーが何も選択していない場合は0が返されます。
API呼び出しのインターセプトを整理するには、 ミニフックライブラリを使用します 。 ただし、オリジナルでは、Boostをプルします。 Boostにバインドされていないバージョンは、記事の付録に記載されています。
以下は、インターセプトされた関数のコードです。
int WINAPI HookedTrackPopupMenu( _In_ HMENU hMenu, _In_ UINT uFlags, _In_ int x, _In_ int y, _In_ int nReserved, _In_ HWND hWnd, _In_opt_ const RECT *prcRect) { WCHAR className[250]; int command; GetClassName(hWnd,className,250); int cpount = GetMenuItemCount(hMenu); if(wcscmp(L"ImmersiveSwitchList",className) == 0 && !isInizialized) { HMENU hsubMenu = CreatePopupMenu(); InsertMenu(hsubMenu, 0, MF_BYPOSITION | MF_STRING, 23, L"Item"); InsertMenu(hMenu, 0, MF_BYPOSITION | MF_POPUP , (UINT_PTR)hsubMenu, L"Group"); isInizialized = true; } command = originalTrackMenu(hMenu, uFlags, x, y, nReserved, hWnd, prcRect); switch (command) { case 23 : { MessageBoxA(hWnd, "Test", "Test", MB_OK+MB_ICONINFORMATION); return 0; break; } default: { break; } } return command; }
ここでわかるように、コンテキストメニューが必要な場所、つまりImmersiveSwitchListクラスのあるウィンドウで呼び出されることを確認します。 ウィンドウクラスの値は、Visual Studioに付属のSpy ++ユーティリティを使用して設定されました。 次に、コンテキストメニューを展開し、元の出力関数を呼び出して、操作の結果を待ちます。 メニュー項目を選択すると、MessageBoxがトリガーされます。 次のスクリーンショットは、変更されたパワーユーザーメニューの外観を示しています。
おわりに
dllインジェクションとAPI関数のインターセプトを使用して、Windowsエクスプローラーのコンテキストメニューを変更する可能性を検討しました。 同様に、Windowsエクスプローラーまたは他のプロセスのコンテキストでメニューをインターセプトできます。 ただし、TPM_RETURNCMDフラグなしでメニューが呼び出される場合、作成した要素の選択が正しく処理され、既存の機能の操作が中断されないように、親ウィンドウのウィンドウプロシージャも展開する必要があります。 これは、SetWindowLongPtr関数APIを使用して実装し、拡張関数にポインターを渡し、親ウィンドウプロシージャに制御を戻すことも忘れないでください。
この記事のソースはVisual Studio 2012で作成され、 DllInject.zipで入手できます。
英語のファイルシステムレベルでのパワーユーザーメニューの編集に関する記事へのリンクもあります。Windows8のWinKey + Xパワーユーザーメニューにシャットダウン、再起動オプションを追加します。
PSシステムプログラミングの専門家ではないため、不正確な場合があります。