Taskbar Modfication to Kill Windows NT/2000 Processes

The application lets you terminate any kind of running processes by
1) modifying Task Manager,
2) through a popup menu that comes up by right clicking on a tray icon.


Click here for larger image

By selecting a process from the menu, the process will be terminated. By selecting "Task Manager..." from the menu, a new Task Manager will be spawned, if one is not running yet. Then, Task Manager will be modified so that it allows the user to use "End Process" (right click on the process name in the list box) to terminate services.

There are 2 binaries involved in the operation:

killer.exe: main application
privi.dll: process privilege manipulator dll

Here is how it works:

1. First the application creates a tray icon for itself. For this, I used Chris Maunder's CSystemTray class. Thanks Chris!
(Unfortunately, couldn't find the link to his submission.)

2. On Right click on the icon, the running processes are collected and their are added to an stl vector. It is done using PSAPI.DLL:

int CKillerDlg::UpdateProcessList()
{
 typedef DWORD (WINAPI *PEnumProcesses)( DWORD*, \
                                         UINT, \
                                         DWORD* );

 typedef DWORD (WINAPI *PEnumProcessModules)( HANDLE, \
                                              HMODULE*, \
                                              UINT, \
                                              DWORD* );

 typedef DWORD (WINAPI *PGetModuleBaseName)( HANDLE, \
                                             HMODULE, \
                                             LPTSTR, \
                                             UINT );

 _TCHAR   szProcessName[MAX_PATH] = _T("unknown");
 DWORD    processID[1024], cbNeeded, cProcesses;
 UINT i;
 int result = -1;


 HINSTANCE hPsApi = LoadLibrary( _T("PSAPI.DLL") );

 if ( hPsApi == NULL )
  return result;

 PEnumProcessModules EnumProcessModules = 
 (PEnumProcessModules)GetProcAddress( hPsApi, 
                                      "EnumProcessModules" );

#ifdef UNICODE
 PGetModuleBaseName GetModuleBaseName = 
 (PGetModuleBaseName)GetProcAddress( hPsApi, 
                                     "GetModuleBaseNameW" );
#else
 PGetModuleBaseName GetModuleBaseName = 
 (PGetModuleBaseName)GetProcAddress( hPsApi, 
                                     "GetModuleBaseNameA" );
#endif

 PEnumProcesses EnumProcesses = 
 (PEnumProcesses)GetProcAddress( hPsApi, 
                                 "EnumProcesses" );

 if ( EnumProcessModules == NULL 
 || GetModuleBaseName == NULL 
 || EnumProcesses == NULL )
  return result;


 // Get the list of process identifiers.

 if ( !EnumProcesses( processID, 
                      sizeof(processID), 
                      &cbNeeded ) )
  return result;

 // Calculate how many process identifiers were returned.
 cProcesses = cbNeeded / sizeof(DWORD);

 // Print the name and process identifier for each process.

 ClearProcessList();

 for ( i = 0; i < cProcesses; i++ )
 {
  // Get a handle to the process.

  HANDLE hProcess = OpenProcess( PROCESS_QUERY_INFORMATION 
                                 | PROCESS_VM_READ,
                                 FALSE, 
                                 processID[i] );

  if ( hProcess )
  {
   HMODULE hMod;

   if ( EnumProcessModules( hProcess, &hMod, 
                            sizeof(hMod), &cbNeeded) )
   {
    GetModuleBaseName( hProcess, hMod, szProcessName, 
                       sizeof(szProcessName) );

    ProcItemStruct *procItem = new ProcItemStruct;
    procItem->name = szProcessName;
    procItem->id = processID[i];
    this->m_processList.push_back(procItem);
   }
  }

  CloseHandle( hProcess );
 }

 return result;
}

3. Afterwards, the the vector is sorted and used to create the popup menu.

void CKillerDlg::ShowTrayPopupMenu( CWnd* pWnd, CPoint pt )
{
 CMenu menu;
 CMenu* pSubMenu = NULL;

 menu.LoadMenu( IDR_TRAYPOPUPMENU );

 pSubMenu = menu.GetSubMenu(0);

 if ( pSubMenu != NULL )
 {
  UpdateMenu( pSubMenu );

  ::SetMenuDefaultItem( pSubMenu->m_hMenu, 0, TRUE );

  UpdateProcessList();

  // Function object for sorting
  ProcItemLess pl;

  std::sort(m_processList.begin(), m_processList.end(), pl);

  for (UINT i = 0; i < m_processList.size(); i++)
  {
   // arrange items into columns
   if (i%20 == 0)
    pSubMenu->AppendMenu( MF_MENUBARBREAK | MF_STRING, 
                          KILLER_WINDOWID_FIRST + i , 
                          m_processList[i]->name );
   else
    pSubMenu->AppendMenu( MF_STRING, 
                          KILLER_WINDOWID_FIRST + i , 
                          m_processList[i]->name ); 
  }

  if ( pWnd != NULL )
   pWnd->SetForegroundWindow();

  pSubMenu->TrackPopupMenu( TPM_LEFTALIGN, 
                            pt.x, pt.y, 
                            pWnd );

  if ( pWnd != NULL )
   pWnd->SetForegroundWindow();
 }
}

4. On clicking on process names, the KillProcess function is activated:

void CKillerDlg::OnKillProcess( UINT nID )
{
 ProcItemStruct *procItem;
 procItem = m_processList[nID - KILLER_WINDOWID_FIRST];
 // Below, 3 lines are commented out, so that no 
 // annoying "Are you sure?" messagebox is shown.

 //CString str(procItem->name);
 //str.Format(_T("Name: %s \nPID: %d"), 
 //           str, 
 //           procItem->id);   
 
 //if (IDOK == ::MessageBox(NULL, 
 //                         str, 
 //                         _T("Kill?"), 
 //                         MB_OKCANCEL))

 theApp.KillProcess(procItem->id, 9);	
}

5. If the user select "Task Manager...", first there is a test for running instance. If running TaskMgr is found, the process ID is obtained, if not, a new instance is spawnd using CreateProcess(), then the process ID is stored. After this, LoadDllForRemoteThread() is called:

if (pId != 0)
 LoadDllForRemoteThread( pId, 
                         TRUE, 
                         TRUE, 
                         L"privi.dll", 
                         L"Func" );

This function was published by Zoltan Csizmadia. Thanks Zoltan!

Minor modification was made to his code. If debug code is compiled, function pointer point to a relative jump instruction (E9), and the real function code starts from where the jump is made to. Between the two addresses, variable amount of "other stuff" is present. If MAXINJECTSIZE (the size of memory that is allocated in the remote process to hold the code copied from our process) is not big enough, problems arise. By testing for the relative jump in debug mode, MAXINJECTSIZE can be reduced to the size of the function code. The following simple function is the implementation:

PVOID GetFuncAddress(PVOID addr)
{
#ifdef _DEBUG
 //check if instruction is relative jump (E9)
 if (0xE9 != *((UCHAR*)addr))
  return addr;

 // calculate base of relative jump
 ULONG base = (ULONG)((UCHAR*)addr + 5);

 // calculate offset 
 ULONG *offset = (ULONG*)((UCHAR*)addr + 1);

 return (PVOID)(base + *offset);
#else
 // in release, don't have to mess with jumps
 return addr;
#endif
}

Then it is used as follows:

WriteProcessMemory( hProcess, 
                    p, 
                    GetFuncAddress(RemoteThread), 
                    MAXINJECTSIZE, 
                    0 );

What the Func() does in privi.dll is merely enables the DEBUG process privilege:

// Set SE_DEBUG privilige for TaskMgr process
// This will make it able to kill services as well
ULONG Func()
{
 HANDLE hToken;

 if ( ! OpenProcessToken( GetCurrentProcess(), 
                          TOKEN_ADJUST_PRIVILEGES 
                          | TOKEN_QUERY, 
                          &hToken ))

  return 1;


 if ( ! SetPrivilege( hToken, SE_DEBUG_NAME, TRUE ))
 {
  CloseHandle(hToken);
  return 2;
 }

 CloseHandle(hToken);
 return 0;
}

Here is how SetPrivilege works:

// Set/Unset specified privilige for given handle
BOOL SetPrivilege(HANDLE hToken, // token handle
 LPCTSTR Privilege, // Privilege to enable/disable
 BOOL bEnablePrivilege // TRUE to enable. FALSE to disable)
{
 TOKEN_PRIVILEGES tp;
 LUID luid;
 TOKEN_PRIVILEGES tpPrevious;
 DWORD cbPrevious=sizeof(TOKEN_PRIVILEGES);

 if(!LookupPrivilegeValue( NULL, Privilege, &luid )) 
  return FALSE;

 // 
 // first pass.  get current privilege setting
 // 
 tp.PrivilegeCount           = 1;
 tp.Privileges[0].Luid       = luid;
 tp.Privileges[0].Attributes = 0;

 AdjustTokenPrivileges(hToken, 
                       FALSE,
                       tp,
                       sizeof(TOKEN_PRIVILEGES),
                       &tpPrevious,
                       &cbPrevious);

 if (GetLastError() != ERROR_SUCCESS) 
  return FALSE;

 // 
 // second pass.  set privilege based on previous setting
 // 
 tpPrevious.PrivilegeCount       = 1;
 tpPrevious.Privileges[0].Luid   = luid;

 if(bEnablePrivilege) 
 {
  tpPrevious.Privileges[0].Attributes |= (SE_PRIVILEGE_ENABLED);
 }
 else 
 {
  tpPrevious.Privileges[0].Attributes ^= (SE_PRIVILEGE_ENABLED &
  tpPrevious.Privileges[0].Attributes);
 }

 AdjustTokenPrivileges(hToken,
                       FALSE,
                       &tpPrevious,
                       cbPrevious,
                       NULL,
                       NULL);

 if (GetLastError() != ERROR_SUCCESS) 
  return FALSE;

 return TRUE;
}

The same adjustment of privileges takes place in the killer process to allow the termination of services.

Downloads

Download executables - 12 Kb
Download source - 26 Kb