В этой статье мне хотелось бы рассказать о своем опыте расширения Windows Explorer, если конкретнее, контекстного меню, называемого «Power User Menu». Не скажу, что очень нуждаюсь в старом представлении меню кнопки «Пуск», но все таки хотелось бы иметь возможность быстрого и структурированного доступа к основным функциям необходимым в работе. Power User Menu можно вызвать двумя способами: 1. Щелкнуть правой кнопкой мыши на кнопке «Пуск». 2. Нажать комбинацию клавиш Windows Key + X. Корпорация Майкрософт предоставила возможность редактирования этого меню, однако эта возможность достаточно ограниченна и не позволяет создавать иерархию меню, пункты с пиктограммами, и поддерживает только ярлыки, да и то не всех типов. Для реализации описываемого функционала мы выполним dll инъекцию в процесс Windows Explorer, а так же осуществим перехват api вызовов управляющих работой контекстного меню. В качестве подопытной операционной системы будем использовать Windows 8.1 x64.
Итак начнем с процедуры позволяющей нам выполнить инъекцию dll в адресное пространство Windows Explorer. Способ инъекции, который мы будем использовать называется «Code cave dll injection» и представляет из себя инъекцию заранее подготовленного машинного кода в адресное пространство выбранного процесса. Данный машинный код выполнит API вызов LoadLibrary с нужной нам библиотекой и вернет управление приложению.
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); }
Машинный код выполняет сохранение регистров ЦП, затем загрузку нужной нам библиотеки при помощи API вызова LoadLibrary определенного на стадии выполнения программы, далее восстанавливает содержимое регистров и возвращает управление. Естественно, что в момент инъекции процесс должен быть в приостановленном состоянии.
Код остальных функций приложения выполняющего внедрение dll рассматривать не буду, так как они не представляет большого интереса.
Для того что бы иметь возможно расширять контекстное меню, необходимо получить его handle. Для вывода меню используется функция 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 окна, которому принадлежит меню и непосредственно HANDLE самого меню. Однако прежде чем реализовывать перехват посмотрим какие параметры получает эта функция при вызове. Воспользуемся приложением API Monitor. Скачать его можно на сайте производителя API Monitor. После конфигурации точки останова на функции в API Monitor, пытаемся открыть Power User Menu и получаем окно следующего вида:
Из вызова видно что Explorer открывает контекстное меню используя флаг TPM_RETURNCMD, это значит, что не нужно пытаться искать сообщения типа WM_COMMAND определяющие выбранный элемент. Элемент указанный пользователем вернет сама функция TrackPopupMenu, либо 0 если пользователь ничего не выбрал.
Для организации перехвата API вызовов я использую библиотеку Mini Hook Library. Однако в оригинале она тянет за собой 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. Значение класса окна было установлено при помощи утилиты Spy++ поставляемой вместе с Visual Studio. Далее производим расширение контекстного меню, вызываем оригинальную функцию вывода и ожидаем результата выполнения операции. При выборе нашего пункта меню сработает MessageBox. На следующем скриншоте показано как выглядит модифицированное Power User Menu.
Заключение.
Мы рассмотрели возможность модификации контекстного меню Windows Explorer при помощи dll инъекции и перехвата api функции. Таким же образом можно перехватить любое меню в контексте Windows Explorer или любого другого процесса. Однако если меню вызывается без флага TPM_RETURNCMD, то нужно так же выполнить расширение оконной процедуры окна родителя дабы обеспечить корректную обработку выбора созданного Вами элемента и не нарушить работу уже существующего функционала. Это можно реализовать при помощи API функции SetWindowLongPtr, передав указатель на расширяющую функцию, а так же не забыть вернуть управление родительской оконной процедуре.
Исходники к статье выполнены в Visual Studio 2012 и доступны по ссылке: DllInject.zip
Так же ссылка на статью о редактировании Power User Menu на уровне файловой системы на английском: Add Shutdown, Restart options to WinKey+X Power User Menu in Windows 8
P.S Не являюсь профессионалом в системном программировании, так что могут быть не точности.
ссылка на оригинал статьи http://habrahabr.ru/post/194716/
Добавить комментарий