http://www.alexfedotov.com/articles/killproc.asp
Introduction
Developing applications for Windows, occasionally you may need to terminate a process forcibly. For example, your application might run external processes to handle certain file formats. If an external application does not return in a reasonable amount of time, you have no choice but terminate the application and report the error. Having read this acticle, you will be able to terminate any processes, each alone or all together. I assume we have the process identifier of the process we want to terminate.
The TerminateProcess Function
Win32 provides the TerminateProcess function to forcibly and unconditionally terminate a process:
BOOL TerminateProcess(
IN HANDLE hProcess, // process handle
IN DWORD dwExitCode // exit code for the process
);
This warning didn't stop you, did it? So, to make use of this function you need a handle to the process. Knowing the process identifier, obtaining a handle is an easy task to do, since the OpenProcess function returns a process handle given its identifier. Putting it all together, the process termination function might look like the following:
Listing 1. KillProcess source code
BOOL KillProcess(
IN DWORD dwProcessId
)
{
// get process handle
HANDLE hProcess = OpenProcess(PROCESS_TERMINATE, FALSE, dwProcessId);
if (hProcess == NULL)
return FALSE;
DWORD dwError = ERROR_SUCCESS;
// try to terminate the process
if (!TerminateProcess(hProcess, (DWORD)-1))
dwError = GetLastError();
// close process handle
CloseHandle(hProcess);
SetLastError(dwError);
return dwError == ERROR_SUCCESS;
}
Terminating Processes and Windows NT Security
If you try the KillProcess function, you'll discover it works perfectly on Windows 9x/Me, but on Windows NT, however, it is unable to terminate certain processes. Digging deeper, you'll find that OpenProcess fails with ERROR_ACCESS_DENIED error for those processes. In particular, this happens if you try to terminate system services or DCOM servers. As you are probably suspecting already, the cause is Windows NT security.
As any other kernel object, the process object is protected by a security descriptor, which is checked each time a handle to the process is opened. For different types of processes, the system initializes their security descriptor differently. You can use the demo application for this article to examine security descriptors of processes running on your machine. In particular, you will find that for processes started by the interactive user (that is, by you), the security descriptor's DACL (access control list) looks as follows:
| INTERACTIVE | Full Control |
| SYSTEM | Full Control |
while for services running in the system logon session, the DACL is different:
| Administrators | Read |
| SYSTEM | Full Control |
Now it should be clear why OpenProcess is unable to open a handle to a service process with PROCESS_TERMINATE access right, even if the caller is an administrator. This might seem strange why the administrator is denied from stopping any process in the system. The truth is, it isn't. A possibility to terminate any process comes from the SE_DEBUG_NAME privilege, which administrators normally have.
Any privilege that a user might have is either enabled or disabled. System administrators do have the SE_DEBUG_NAME privilege, but it is disabled by default and does not affect OpenProcess operation in any way. When this privilege is enabled, however, the calling thread can open processes with any access rights regardless of the security descriptor assigned to the process. This is exactly what we need.
Below is the modified source code of the KillProcess function that enables the SE_DEBUG_NAME privilege if this is necessary to obtain process handle.
Listing 2. KillProcess source code, modified version
The modified version of KillProcess first tries to obtain a process handle without using the SE_DEBUG_NAME privilege. If this fails, it enables the privilege, gets a handle and restores the original privilege state.
Now, using the KillProcess function you can kill almost any process in the system, if you are an administrator on the machine, of course. To see the new function in action, you can use the Process Viewer application, which is now capable of terminating processes. For example, you can terminate the Winlogon process and in a couple of seconds get a blue screen with STOP 0xC000021A message on it (don't forget to save all your work before trying that).
Terminating a Process Tree
Some time ago, my colleague asked me to write a utility equivalent to kill in Unix. He was porting some scripts from Unix to Windows NT and he needed such a utility. He could not use the utility which comes with the Windows NT Resource Kit due to licensing (or might be, religious) reasons.
I wrapped the KillProcess function in a small executable and sent it to him. The colleague was amazed by the speed with which I satisfied his request, but he noticed that my utility does not behave exactly like Unix kill does. In Unix, kill without additional arguments terminates not only the process itself, but also all child processes that it started directly or indirectly (the so-called process tree). My version acted mostly like kill -9, when only the specified process is terminated. Thus, I was posed with the task of replicating Unix kill behavior on Windows NT.
To terminate all processes in a tree, we need to somehow track the parent–child relationship between processes. On Windows 9x/Me and Windows 2000/XP this is possible with process enumeration functions from ToolHelp32 API. In particular, the th32ParentProcessID field of the PROCESSENTRY32 structure contains the identifier of the parent process. On Windows NT 4.0 this information can be obtained through the undocumented ZwQuerySystemInformation function. (You can find more detailed description of process enumeration methods in the Enumerating Windows Processes article).
Below is the source code of the KillProcessEx function that allows to terminate both a single process and a process tree. If you've read the article about process enumeration, you'll find familiar many parts of the code.
Listing 3. KillProcessEx source code
// a helper function that walks a process tree recursively // on Windows NT and terminates all processes in the tree static BOOL KillProcessTreeNtHelper( IN PSYSTEM_PROCESS_INFORMATION pInfo, IN DWORD dwProcessId ) { _ASSERTE(pInfo != NULL); PSYSTEM_PROCESSES p = pInfo; // terminate all children first for (;;) { if (p->InheritedFromProcessId == dwProcessId) KillProcessTreeNtHelper(pInfo, p->ProcessId); if (p->NextEntryDelta == 0) break; // find address of the next structure p = (PSYSTEM_PROCESS_INFORMATION)(((PUCHAR)p) + p->NextEntryDelta); } // terminate the specified process if (!KillProcess(dwProcessId)) return GetLastError(); return ERROR_SUCCESS; } // a helper function that walks a process tree recursively // on Windows 9x and terminates all processes in the tree static BOOL KillProcessTreeWinHelper( IN DWORD dwProcessId ) { HINSTANCE hKernel; HANDLE (WINAPI * pCreateToolhelp32Snapshot)(DWORD, DWORD); BOOL (WINAPI * pProcess32First)(HANDLE, PROCESSENTRY32 *); BOOL (WINAPI * pProcess32Next)(HANDLE, PROCESSENTRY32 *); // get KERNEL32.DLL handle hKernel = GetModuleHandle(_T("kernel32.dll")); _ASSERTE(hKernel != NULL); // find necessary entrypoints in KERNEL32.DLL *(FARPROC *)&pCreateToolhelp32Snapshot = GetProcAddress(hKernel, "CreateToolhelp32Snapshot"); *(FARPROC *)&pProcess32First = GetProcAddress(hKernel, "Process32First"); *(FARPROC *)&pProcess32Next = GetProcAddress(hKernel, "Process32Next"); if (pCreateToolhelp32Snapshot == NULL || pProcess32First == NULL || pProcess32Next == NULL) return ERROR_PROC_NOT_FOUND; HANDLE hSnapshot; PROCESSENTRY32 Entry; // create a snapshot of all processes hSnapshot = pCreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0); if (hSnapshot == INVALID_HANDLE_VALUE) return GetLastError(); Entry.dwSize = sizeof(Entry); if (!pProcess32First(hSnapshot, &Entry)) { DWORD dwError = GetLastError(); CloseHandle(hSnapshot); return dwError; } // terminate children first do { if (Entry.th32ParentProcessID == dwProcessId) KillProcessTreeWinHelper(Entry.th32ProcessID); Entry.dwSize = sizeof(Entry); } while (pProcess32Next(hSnapshot, &Entry)); CloseHandle(hSnapshot); // terminate the specified process if (!KillProcess(dwProcessId)) return GetLastError(); return ERROR_SUCCESS; } BOOL KillProcessEx( IN DWORD dwProcessId, // process identifier IN BOOL bTree // terminate tree flag ) { if (!bTree) return KillProcess(dwProcessId); OSVERSIONINFO osvi; DWORD dwError; // determine operating system version osvi.dwOSVersionInfoSize = sizeof(osvi); GetVersionEx(&osvi); if (osvi.dwPlatformId == VER_PLATFORM_WIN32_NT) { HINSTANCE hNtDll; NTSTATUS (WINAPI * pZwQuerySystemInformation)(UINT, PVOID, ULONG, PULONG); // get NTDLL.DLL handle hNtDll = GetModuleHandle(_T("ntdll.dll")); _ASSERTE(hNtDll != NULL); // find address of ZwQuerySystemInformation *(FARPROC *)&pZwQuerySystemInformation = GetProcAddress(hNtDll, "ZwQuerySystemInformation"); if (pZwQuerySystemInformation == NULL) return SetLastError(ERROR_PROC_NOT_FOUND), NULL; // get default process heap handle HANDLE hHeap = GetProcessHeap(); NTSTATUS Status; ULONG cbBuffer = 0x8000; PVOID pBuffer = NULL; // it is difficult to predict what buffer size will be // enough, so we start with 32K buffer and increase its // size as needed do { pBuffer = HeapAlloc(hHeap, 0, cbBuffer); if (pBuffer == NULL) return SetLastError(ERROR_NOT_ENOUGH_MEMORY), FALSE; Status = pZwQuerySystemInformation( SystemProcessesAndThreadsInformation, pBuffer, cbBuffer, NULL); if (Status == STATUS_INFO_LENGTH_MISMATCH) { HeapFree(hHeap, 0, pBuffer); cbBuffer *= 2; } else if (!NT_SUCCESS(Status)) { HeapFree(hHeap, 0, pBuffer); return SetLastError(Status), NULL; } } while (Status == STATUS_INFO_LENGTH_MISMATCH); // call the helper dwError = KillProcessTreeNtHelper( (PSYSTEM_PROCESS_INFORMATION)pBuffer, dwProcessId); HeapFree(hHeap, 0, pBuffer); } else { // call the helper dwError = KillProcessTreeWinHelper(dwProcessId); } SetLastError(dwError); return dwError == ERROR_SUCCESS; }
Based on KillProcessEx, I wrote the PKILL utility that resembles its Unix counterpart more closely. The utility is available in the source code archive for this article.
Terminating 16-bit Tasks on Windows NT
The story won't be complete if we don't mention termination of 16-bit tasks on Windows NT. The VDMDBG.DLL library provides a set of functions that deal with 16-bit tasks. In particular, to terminate a task you can use the VDMTerminateTaskWOW function:
BOOL VDMTerminateTaskWOW(
IN DWORD dwProcessId, // virtual DOS-machine identifier
IN WORD hTask16 // task identifier
);
The parameters of this function are self-describing. The Process Viewer demo application uses VDMTerminateTaskWOW to terminate 16-bit tasks on Windows NT.
Conclusion
Having read this article, you are fully armed with the knowledge needed to terminate any process. However, as with any arms, use this knowledge with care.
References
- HOWTO: How to Obtain a Handle to Any Process with SeDebugPrivilege, Q131065, Microsoft Knowledge Base.
- HOWTO: Use VDMDBG Functions on Windows NT, Q182559, Microsoft Knowledge Base.
浙公网安备 33010602011771号