Extending the context menu of the Start button in Windows 8.1



In this article, I would like to talk about my experience of expanding Windows Explorer, more specifically, a context menu called the “Power User Menu”. I can’t say that I really need the old presentation of the Start menu, but still I would like to be able to quickly and structured access to the basic functions needed in the work. The Power User Menu can be called up in two ways: 1. Right-click on the "Start" button. 2. Press the key combination Windows Key + X. Microsoft has 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 supports only shortcuts, and even then not all types. To implement the described functionality, we will perform a dll injection into the Windows Explorer process, as well as intercept api calls that control the operation of the context menu. As an experimental operating system, we will use Windows 8.1 x64.

So, let's start with a procedure that allows us to inject dlls 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 pre-prepared machine code into the address space of the selected process. This machine code will make an 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 API call to LoadLibrary defined at the program execution stage, then restores the contents of the registers and returns control. Naturally, at the time of injection, the process should be in a suspended state.

I will not consider the code of other functions of the application performing the dll implementation, since they are not of great interest.
In order to be able to expand the context menu, you need to get its handle. To display the menu, the TrackPopupMenu function is used, 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 an HWND window, which owns the menu and HANDLE of the menu itself. However, before implementing the interception, let's see what parameters this function receives when called. We use the API Monitor application. You can download it on the manufacturer’s website API Monitor . After configuring the breakpoint on the functions in the API Monitor, we try to open the Power User Menu and get a window that looks like this:
Explorer shows that it opens the context menu using the TPM_RETURNCMD flag, which means that you don’t have to try to look for WM_COMMAND messages that identify the selected item. The element 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, it pulls Boost. The version without reference to Boost can be taken in the appendix to the article.

The following is the code for 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 up in the place we need, namely, in the window with the ImmersiveSwitchList class. The window class value was set using the Spy ++ utility supplied with Visual Studio. Next, we expand the context menu, call the original output function, and wait for the result of the operation. Selecting our menu item will trigger a MessageBox. The following screenshot shows how the modified Power User Menu looks.



Conclusion

We examined the possibility of modifying the Windows Explorer context menu using dll injection and interception of api functions. 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 expand the window procedure of the parent window in order to ensure the correct processing of the selection of the element you created and not disrupt the operation of an existing functional. This can be implemented using the SetWindowLongPtr function API by passing a pointer to an extension function, and also remember to return control to the parent window procedure.

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

There is also a link to an article about editing Power User Menu at the file system level in English: Add Shutdown, Restart options to WinKey + X Power User Menu in Windows 8
PS I am not a professional in system programming, so there may be inaccuracies.