Introduction
As we all know, Task Manager, whatever the reasons are, does not allow the user to run multiple instances. This article explains a simple application that allows the user to start as many instances of the Task Manager as he/she wishes to. The technique described here is only valid for Windows 7 (and maybe Vista); XP's Task Manager uses a different technique.
Background
To do so, it employs simple tactics. When it begins execution, it tries to create a named kernel object (in this case, a mutex), and if there is already an object with the name Task Manager, it assumes that another instance of it is already active and it closes the current process.
The nme of the offending mutex is TASKMGR.879e4d63-6c0e-4544-97f2-1244bd3f6de0
.
Here's an example of how it can be done:
if( ::CreateMutex( NULL, FALSE,
TEXT( "TASKMGR.879e4d63-6c0e-4544-97f2-1244bd3f6de0" ) ) == NULL )
{
if( ::GetLastError() == ERROR_ALREADY_EXISTS )
{
}
}
Code
But first, we must find out the full name of the mutex, which is in a format like this: \Sessions\<SESSION_ID>\BaseNamedObjects\TASKMGR.879e4d63-6c0e-4544-97f2-1244bd3f6de0, and we can obtain the session ID using the ProcessIdToSessionId
API call.
DWORD sid;
if( !::ProcessIdToSessionId( ::GetCurrentProcessId(), &sid ) )
{
wprintf( L"Unable to get session ID. Error code: %d\n", ::GetLastError() );
return 2;
}
WCHAR mutexName[ MAX_PATH ];
mutexName[ 0 ] = 0;
wcscat( mutexName, L"\\Sessions\\" );
_itow( sid, mutexName + wcslen( mutexName ), 10 );
wcscat( mutexName, L"\\BaseNamedObjects\\" );
wcscat( mutexName, MUTEX_NAME );
The basic idea is to destroy the mutex object before we start another instance of the Task Manager. To destroy it, we need to find all the handles to the object and close them. In the process, we need to use these undocumented API calls: NtQuerySystemInformation
, NtQueryObject
, and DuplicateHandle
.
We use NtQuerySystemInformation
to get the list of all open handles in the system, and then we iterate through the list, duplicating handles, to gain access to the object, and call NtQueryObject
to get the name of the object. When we find the handle, we make a duplicate using the DuplicateHandle
API call, but this time, we pass the DUPLICATE_CLOSE_SOURCE
flag that instructs the system to close the original handle after the copy is made (effectively taking the ownership of the object), and immediately after that, we also close the new handle, and the end result is destroying the object.
INT SeekAndDestory(WCHAR* handleName)
{
INT found = 0;
DWORD size = 0;
PSYSTEM_HANDLE_INFORMATION handles =
(PSYSTEM_HANDLE_INFORMATION)malloc( sizeof( SYSTEM_HANDLE_INFORMATION ) );
if( !NT_SUCCESS( ::pNtQuerySystemInformation(
(SYSTEM_INFORMATION_CLASS)SystemHandleInformation, handles,
sizeof( SYSTEM_HANDLE_INFORMATION ), &size ) ) )
{
free( handles );
if( size == 0 )
return -1;
DWORD newSize = size + sizeof(HANDLE) * 512;
handles = (PSYSTEM_HANDLE_INFORMATION)malloc( newSize );
if( !NT_SUCCESS( ::pNtQuerySystemInformation(
(SYSTEM_INFORMATION_CLASS)SystemHandleInformation, handles,
newSize, &size ) ) )
{
free( handles );
return -1;
}
}
for( DWORD i = 0; i < handles->dwCount; i++ )
{
HANDLE process = ::OpenProcess( PROCESS_ALL_ACCESS, FALSE,
handles->Handles[ i ].dwProcessId );
if( process )
{
HANDLE myHandle;
if( ::DuplicateHandle( process,
(HANDLE)handles->Handles[ i ].wValue, ::GetCurrentProcess(),
&myHandle, DUPLICATE_SAME_ACCESS, FALSE, 0 ) )
{
PPUBLIC_OBJECT_TYPE_INFORMATION nameInfo =
(PPUBLIC_OBJECT_TYPE_INFORMATION)malloc(
sizeof( PUBLIC_OBJECT_TYPE_INFORMATION ) );
if( !NT_SUCCESS( pNtQueryObject( myHandle, ObjectNameInformation,
nameInfo, sizeof( PUBLIC_OBJECT_TYPE_INFORMATION ), &size ) ) )
{
free( nameInfo );
if( (int)size <= 0 )
{
::CloseHandle( myHandle );
continue;
}
DWORD newSize = size;
nameInfo = (PPUBLIC_OBJECT_TYPE_INFORMATION)malloc( newSize );
if( !NT_SUCCESS( pNtQueryObject( myHandle, ObjectNameInformation,
nameInfo, newSize, &size ) ) )
{
::CloseHandle( myHandle );
continue;
}
}
::CloseHandle( myHandle );
if( lstrcmp( handleName, nameInfo->TypeName.Buffer ) == 0 )
{
if( ::DuplicateHandle( process,
(HANDLE)handles->Handles[ i ].wValue, ::GetCurrentProcess(),
&myHandle, 0, FALSE,
DUPLICATE_SAME_ACCESS | DUPLICATE_CLOSE_SOURCE ) )
{
::CloseHandle( myHandle );
found = 1;
}
}
free( nameInfo );
}
::CloseHandle( process );
}
}
free( handles );
return found;
}
Note: To get access to NtQuerySystemInformation
and NtQueryObject
, we need to use GetProcAddress
because they are not available to the linker during the building process. The NtLib.h file contains the definitions required for using the undocumented API.
pfnNtQuerySystemInformation pNtQuerySystemInformation = NULL;
pfnNtQueryObject pNtQueryObject = NULL;
BOOL LoadNtLib()
{
pNtQuerySystemInformation = (pfnNtQuerySystemInformation)::GetProcAddress(
GetModuleHandle( TEXT( "ntdll" ) ), "NtQuerySystemInformation" );
pNtQueryObject = (pfnNtQueryObject)::GetProcAddress(
GetModuleHandle( TEXT( "ntdll" ) ), "NtQueryObject" );
return pNtQuerySystemInformation && pNtQueryObject;
}
Some more information about NtQuerySystemInformation
can be found in Naveen's article: Listing Used Files.