<< Hide left pane
Leave feedback
Terminating Windows Processes
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,
IN DWORD dwExitCode
);
Now I have to make a
Warning
The TerminateProcess function should only be used in extreme
cases when all other possibilities to exit the process are already taken,
because this function does not allow the process to clean up properly
and save its data, neither it notifies DLLs loaded into the process
address space.
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:
BOOL KillProcess(
IN DWORD dwProcessId
)
{
HANDLE hProcess = OpenProcess(PROCESS_TERMINATE, FALSE, dwProcessId);
if (hProcess == NULL)
return FALSE;
DWORD dwError = ERROR_SUCCESS;
if (!TerminateProcess(hProcess, (DWORD)-1))
dwError = GetLastError();
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.
BOOL KillProcess(
IN DWORD dwProcessId
)
{
HANDLE hProcess;
DWORD dwError;
hProcess = OpenProcess(PROCESS_TERMINATE, FALSE, dwProcessId);
if (hProcess == NULL)
{
if (GetLastError() != ERROR_ACCESS_DENIED)
return FALSE;
OSVERSIONINFO osvi;
osvi.dwOSVersionInfoSize = sizeof(osvi);
GetVersionEx(&osvi);
if (osvi.dwPlatformId != VER_PLATFORM_WIN32_NT)
return SetLastError(ERROR_ACCESS_DENIED), FALSE;
TOKEN_PRIVILEGES Priv, PrivOld;
DWORD cbPriv = sizeof(PrivOld);
HANDLE hToken;
if (!OpenThreadToken(GetCurrentThread(),
TOKEN_QUERY|TOKEN_ADJUST_PRIVILEGES,
FALSE, &hToken))
{
if (GetLastError() != ERROR_NO_TOKEN)
return FALSE;
if (!OpenProcessToken(GetCurrentProcess(),
TOKEN_QUERY|TOKEN_ADJUST_PRIVILEGES,
&hToken))
return FALSE;
}
_ASSERTE(ANYSIZE_ARRAY > 0);
Priv.PrivilegeCount = 1;
Priv.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;
LookupPrivilegeValue(NULL, SE_DEBUG_NAME,
&Priv.Privileges[0].Luid);
if (!AdjustTokenPrivileges(hToken, FALSE, &Priv, sizeof(Priv),
&PrivOld, &cbPriv))
{
dwError = GetLastError();
CloseHandle(hToken);
return SetLastError(dwError), FALSE;
}
if (GetLastError() == ERROR_NOT_ALL_ASSIGNED)
{
CloseHandle(hToken);
return SetLastError(ERROR_ACCESS_DENIED), FALSE;
}
hProcess = OpenProcess(PROCESS_TERMINATE, FALSE, dwProcessId);
dwError = GetLastError();
AdjustTokenPrivileges(hToken, FALSE, &PrivOld, sizeof(PrivOld),
NULL, NULL);
CloseHandle(hToken);
if (hProcess == NULL)
return SetLastError(FALSE), NULL;
}
if (!TerminateProcess(hProcess, (UINT)-1))
{
dwError = GetLastError();
CloseHandle(hProcess);
return SetLastError(dwError), FALSE;
}
CloseHandle(hProcess);
return TRUE;
}
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.
static BOOL KillProcessTreeNtHelper(
IN PSYSTEM_PROCESS_INFORMATION pInfo,
IN DWORD dwProcessId
)
{
_ASSERTE(pInfo != NULL);
PSYSTEM_PROCESSES p = pInfo;
for (;;)
{
if (p->InheritedFromProcessId == dwProcessId)
KillProcessTreeNtHelper(pInfo, p->ProcessId);
if (p->NextEntryDelta == 0)
break;
p = (PSYSTEM_PROCESS_INFORMATION)(((PUCHAR)p)
+ p->NextEntryDelta);
}
if (!KillProcess(dwProcessId))
return GetLastError();
return ERROR_SUCCESS;
}
static BOOL KillProcessTreeWinHelper(
IN DWORD dwProcessId
)
{
HINSTANCE hKernel;
HANDLE (WINAPI * pCreateToolhelp32Snapshot)(DWORD, DWORD);
BOOL (WINAPI * pProcess32First)(HANDLE, PROCESSENTRY32 *);
BOOL (WINAPI * pProcess32Next)(HANDLE, PROCESSENTRY32 *);
hKernel = GetModuleHandle(_T("kernel32.dll"));
_ASSERTE(hKernel != NULL);
*(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;
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;
}
do
{
if (Entry.th32ParentProcessID == dwProcessId)
KillProcessTreeWinHelper(Entry.th32ProcessID);
Entry.dwSize = sizeof(Entry);
}
while (pProcess32Next(hSnapshot, &Entry));
CloseHandle(hSnapshot);
if (!KillProcess(dwProcessId))
return GetLastError();
return ERROR_SUCCESS;
}
BOOL KillProcessEx(
IN DWORD dwProcessId,
IN BOOL bTree
)
{
if (!bTree)
return KillProcess(dwProcessId);
OSVERSIONINFO osvi;
DWORD dwError;
osvi.dwOSVersionInfoSize = sizeof(osvi);
GetVersionEx(&osvi);
if (osvi.dwPlatformId == VER_PLATFORM_WIN32_NT)
{
HINSTANCE hNtDll;
NTSTATUS (WINAPI * pZwQuerySystemInformation)(UINT, PVOID,
ULONG, PULONG);
hNtDll = GetModuleHandle(_T("ntdll.dll"));
_ASSERTE(hNtDll != NULL);
*(FARPROC *)&pZwQuerySystemInformation =
GetProcAddress(hNtDll, "ZwQuerySystemInformation");
if (pZwQuerySystemInformation == NULL)
return SetLastError(ERROR_PROC_NOT_FOUND), NULL;
HANDLE hHeap = GetProcessHeap();
NTSTATUS Status;
ULONG cbBuffer = 0x8000;
PVOID pBuffer = NULL;
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);
dwError = KillProcessTreeNtHelper(
(PSYSTEM_PROCESS_INFORMATION)pBuffer,
dwProcessId);
HeapFree(hHeap, 0, pBuffer);
}
else
{
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,
IN WORD hTask16
);
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.
This arctile was first published on
RSDN.ru.
Copyright (c) 2001-2003 The RSDN Group. All rights reserved.