📜 ⬆️ ⬇️

Expand the context menu of the Start button in Windows 8.1



In this article I would like to tell you about my experience with the expansion of Windows Explorer, more specifically, the context menu called “Power User Menu”. I will not say that I really need the old presentation of the “Start” button menu, but still I would like to be able to quickly and structured access to the basic functions necessary to work. The Power User Menu can be called up in two ways: 1. Right-click on the “Start” button. 2. Press the Windows Key + X key combination. Microsoft provided the ability to edit this menu, but this feature is quite limited and does not allow you to create a menu hierarchy, items with icons, and only supports shortcuts, and not all types. To implement the described functional, we will perform a dll injection into the process of Windows Explorer, as well as implement the interception of api calls that control the work of the context menu. As an experimental operating system we will use Windows 8.1 x64.

So let's start with the procedure that allows us to inject a dll into the address space of Windows Explorer. The injection method that we will use is called “Code cave dll injection” and is an injection of a previously prepared machine code into the address space of the selected process. This machine code will execute the API call to LoadLibrary with the library we need and return control to the application.

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); } 

The machine code saves the CPU registers, then loads the library we need using the LoadLibrary API call defined at the program execution stage, then restores the contents of the registers and returns control. Naturally, at the moment of injection, the process must be in a suspended state.
')
I will not consider the code of the remaining functions of the application performing the dll implementation, since they are not of much interest.
In order to be able to expand the context menu, you need to get its handle. To display the menu, use the TrackPopupMenu function, consider its prototype:

 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) 

As you can see, there is also a HWND window, which owns the menu and directly the HANDLE of the menu itself. However, before implementing interception, let's see what parameters this function receives when it is called. Let's use the API Monitor application. You can download it on the manufacturer’s website API Monitor . After configuring the breakpoint on the function in the API Monitor, we are trying to open the Power User Menu and get the following window:


From the call, you can see that Explorer opens the context menu using the TPM_RETURNCMD flag, which means that you should not try to search for WM_COMMAND messages defining the selected item. The item specified by the user will be returned by the TrackPopupMenu function itself, or 0 if the user has not selected anything.

To organize the interception of API calls, I use the Mini Hook Library . However, in the original, she pulls a Boost. Version without reference to the Boost can be found in the annex to the article.

Further I provide the code of the intercepted function:
 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; } 

As you can see here, we check that the context menu is called in the right place, namely in the window with the ImmersiveSwitchList class. The window class value was set using the Spy ++ utility provided with Visual Studio. Then we expand the context menu, call the original output function and wait for the result of the operation. When you select our menu item, MessageBox will work. The following screenshot shows what a modified Power User Menu looks like.



Conclusion

We considered the possibility of modifying the context menu of Windows Explorer with the help of injection dll and interception of the api function. In the same way, you can intercept any menu in the context of Windows Explorer or any other process. However, if the menu is called without the TPM_RETURNCMD flag, then you must also extend the window window's parent procedure to ensure that the selection of the element you created is correctly processed and does not disrupt the functionality of the existing functionality. This can be implemented using the SetWindowLongPtr API function, passing a pointer to an expanding function, and also remember to return control to the parent window procedure.

Source codes for the article are made in Visual Studio 2012 and are available at the link: DllInject.zip

Also a link to an article on editing the Power User Menu at the file system level in English: Add Shutdown, Restart options to WinKey + X Power Menu in Windows 8

PS I am not a professional in system programming, so there may be inaccuracies.

Source: https://habr.com/ru/post/194716/


All Articles