Introduction
This article shows how to extend the Windows Task Manager application by using DLL injection. There are plenty of articles already that show how to use DLL injection but this article is intended to show you what you can do with it. It also gives you some nice features for Task Manager.
This application consists of three separate projects:
- TaskExApp
This is an application that sits in the background waiting for instances of Windows Task Manager to popup. It loops through the top level windows every 1 second looking for instances. When an instance is detected, it will inject the InstallTaskHook
DLL into the process. This application has a system tray icon that will show and hide the application dialog box when double-clicked. To end the process, just click the "Exit" button.
- InstallTaskHook
This is a DLL that is injected into the Windows Task Manager application. On injection, it creates an extra menu on the application's main menu called "Extensions". In this menu there is a sub menu "Get Extended Info" which will display a dialog box of extended data for the application that is selected in the processes list. You must select an item in the processes list and the PID field must be visible for this to work correctly.
- TaskExHook
This DLL is injected into the application by the Task Manager hook to get the extended information. Currently the only extra information that it gets is the command line parameter that the application was started with.
The following extended information is given on the display dialog:
- Command Line
The command line parameters that the application was started with.
- File
The full path of the executable file.
- Modules
All modules loaded by the application.
It's important to note that when this application builds, the DLLs are copied to the System32 directory, this is so that they can be found by the Task Manager application. If they are not copied there then the application won't work correctly, so if it doesn't work, first check to see if these DLLs were copied correctly by the build process.
Background
I have done extensive work in the past with Windows hooks on many projects, that was my intent when I started this project as well. Then I found the method of using CreateRemoteThread
to inject a DLL into a process. I decided to use this method both for a learning exercise and because this is only intended to work on Windows 2000 and XP anyway. The main reason that I created this application was that I constantly need to know the command line parameters of an application, especially java.exe. There are applications out there that show this, but I would like to just have it work from within Windows Task Manager.
Screen shots
This is the main application window, it's very simple. You can double click the system tray icon to hide it without exiting:
Here are the menu items that are added to the Windows Task Manager application:
Here is the dialog box that is used to display the extended information:
A look at the code
The code is fairly light-weight, here are some of the more interesting aspects of the code.
From TaskExApp, here is how it installs the InstallTaskHook
into the Windows Task Manager:
void TaskExDlg::Install(HWND hWnd, DWORD pid)
{
m_taskManagers.insert(pid);
HANDLE hProcess =
OpenProcess(PROCESS_CREATE_THREAD | PROCESS_QUERY_INFORMATION
| PROCESS_VM_OPERATION | PROCESS_VM_WRITE | PROCESS_VM_READ,
FALSE, pid);
if (hProcess != NULL)
{
HANDLE hThread;
char szLibPath [_MAX_PATH];
void* pLibRemote = 0;
DWORD hLibModule = 0;
HMODULE hKernel32 = ::GetModuleHandle("Kernel32");
if( !::GetSystemDirectory(szLibPath, _MAX_PATH))
return;
strcat(szLibPath, "\\InstallTaskHook.dll");
pLibRemote = ::VirtualAllocEx( hProcess, NULL,
sizeof(szLibPath), MEM_COMMIT, PAGE_READWRITE );
if( pLibRemote == NULL )
return;
::WriteProcessMemory(hProcess, pLibRemote,
(void*)szLibPath,sizeof(szLibPath),NULL);
hThread = ::CreateRemoteThread( hProcess, NULL, 0,
(LPTHREAD_START_ROUTINE)::GetProcAddress(hKernel32,
"LoadLibraryA"),
pLibRemote, 0, NULL );
if( hThread != NULL )
{
::WaitForSingleObject( hThread, INFINITE );
::GetExitCodeThread( hThread, &hLibModule );
::CloseHandle( hThread );
}
}
}
The code for the InstallTaskHook
to inject the TaskExHook
is fairly similar, except that it also un-injects the DLL as well, the DLL is only there during the load process:
std::string GetCmdLineData(DWORD pid)
{
HANDLE hProcess = OpenProcess(PROCESS_CREATE_THREAD |
PROCESS_QUERY_INFORMATION | PROCESS_VM_OPERATION |
PROCESS_VM_WRITE | PROCESS_VM_READ,
FALSE, pid);
if (hProcess != NULL)
{
HANDLE hThread;
char szLibPath [_MAX_PATH];
void* pLibRemote = 0;
DWORD hLibModule = 0;
HMODULE hKernel32 = ::GetModuleHandle("Kernel32");
::GetSystemDirectory(szLibPath, _MAX_PATH);
strcat(szLibPath, "\\TaskExHook.dll");
pLibRemote = ::VirtualAllocEx( hProcess, NULL,
sizeof(szLibPath),
MEM_COMMIT, PAGE_READWRITE );
if( pLibRemote == NULL )
return "Failed to get command line information...\r\n\r\n";
::WriteProcessMemory(hProcess, pLibRemote,
(void*)szLibPath,sizeof(szLibPath),NULL);
hThread = ::CreateRemoteThread( hProcess, NULL, 0,
(LPTHREAD_START_ROUTINE) ::GetProcAddress(hKernel32,
"LoadLibraryA"),
pLibRemote, 0, NULL );
if( hThread != NULL )
{
::WaitForSingleObject( hThread, INFINITE );
::GetExitCodeThread( hThread, &hLibModule );
::CloseHandle( hThread );
::VirtualFreeEx( hProcess, pLibRemote,
sizeof(szLibPath), MEM_RELEASE );
if( hLibModule != NULL )
{
hThread = ::CreateRemoteThread( hProcess,
NULL, 0,
(LPTHREAD_START_ROUTINE) ::GetProcAddress(hKernel32,
"FreeLibrary"),
(void*)hLibModule,
0, NULL );
if( hThread != NULL )
{
::WaitForSingleObject( hThread, INFINITE );
::GetExitCodeThread( hThread, &hLibModule );
::CloseHandle( hThread );
}
}
}
CloseHandle(hProcess);
return "Command Line:\r\n\t" + std::string(g_szCmdLine);
}
return "Failed to get command line information...\r\n\r\n";
}
This code sets the debug privileges so that the application can inject in all other applications:
void GetDebugPrivs()
{
HANDLE hToken;
LUID sedebugnameValue;
TOKEN_PRIVILEGES tp;
if (::OpenProcessToken(GetCurrentProcess(),
TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, &hToken))
{
if ( !::LookupPrivilegeValue( NULL, SE_DEBUG_NAME, &sedebugnameValue ) )
{
::CloseHandle( hToken );
}
tp.PrivilegeCount = 1;
tp.Privileges[0].Luid = sedebugnameValue;
tp.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;
if ( !::AdjustTokenPrivileges( hToken,
FALSE, &tp, sizeof(tp), NULL, NULL ) )
{
::CloseHandle( hToken );
}
::CloseHandle( hToken );
}
}
Here is where the InstallTaskHook
DLL actually creates the menu items and subclasses the main window to capture the menu events:
BOOL APIENTRY DllMain(HANDLE hModule,
DWORD ul_reason_for_call, LPVOID lpReserved)
{
if( (ul_reason_for_call == DLL_PROCESS_ATTACH) )
{
EnumWindows(EnumProc, GetCurrentProcessId());
if (g_hWnd)
{
char sz[256];
SetWindowText(g_hWnd, "Extended Task Manager");
HMENU hMenu = GetMenu(g_hWnd);
int numMenus = GetMenuItemCount(hMenu);
HMENU hCheck = GetSubMenu(hMenu, numMenus - 1);
GetMenuString(hMenu, numMenus - 1, sz,
sizeof(sz), MF_BYPOSITION);
if (strcmp(sz, "Extensions"))
{
HMENU hPopup = CreatePopupMenu();
AppendMenu(hPopup, MF_STRING, 2112, "Get Extended Info");
AppendMenu(hMenu, MF_STRING | MF_ENABLED | MF_POPUP,
(UINT_PTR)hPopup, "Extensions");
wndProcOriginal = (WNDPROC)SetWindowLong(g_hWnd,
GWL_WNDPROC, (LONG)(WNDPROC)FilterProc);
DrawMenuBar(g_hWnd);
GetDebugPrivs();
}
}
}
return TRUE;
}
Issues
For some reason, some times the Windows Task Manager application will freeze during the injection process. It doesn't happen very often so it's hard to debug. When this does happen, just bring up another instance of Task Manager and kill the previous instance. If anyone can figure out why this happens, please let me know. Also, the application was only tested on Windows XP, but it should work on 2000/XP/2003.
Conclusion
I hope that this article gave you a good idea on how you can use DLL injection to your advantage. Being able to control another application can give you a lot of power on the system and allow you to make simple modifications to a large application without needing the code for that application.
I would also like to acknowledge the Winspy article for introducing me to the CreateRemoteThread
method of DLL injection.