Process termination issues

The first technical post here is about the process of terminating applications on Windows system. I have been researching this subject for the last few days, during which a number of interesting (yet unknown) facts has appeared. Some of the solution ideas regarding particular problems are presented here, though I am sure there are many nice ways of dealing with those – feel free to post your ideas below ;>

Note: the post will be supplied with the Proof of Concept code in a few days, to present the real-life usage of the described techniques.

The PoC package link is available on the bottom of the post.

Background

OK then, let’s start with some basics. Usually, the termination process seems to be so essential in our everyday life that hardly anyone wonders what is actually happening inside the system. Programs launch, programs exit, seems like no interesting actions are performed. After a quick investigation, it turns out that it is not as simple as it could pretend to be.

We can divide the termination into four groups – 2 factors are considered here:

  • Is the application being closed GUI or console based?
  • Is the application being closed from inside or by an external process?

Let’s begin with the first division. What shouldn’t be a suprise to anyone, the nature of program interaction with the user affects the possibilities of letting it know it is supposed to exit. When it comes to the first case (graphical interface), a great number of process resources is used (dialog boxes, strings, images, sounds etc etc).

The process is able to control the usage of these resources – it has its own event dispatching loop and is being provided a really wide API choice, thus having full control over how its windows look like, behave and so on. Messages related to application termination are available in WINAPI and widely used. In the console’s case, the program doesn’t have a real control over the console object and can only customize its look and behavior using system API.

To be more precise, all the process-specific termination stuff is common for both console and GUI programs. There are, though, some subsystem-specific mechanisms concerning only one type of the application (like window events etc). I will come back to this subject in a moment, let’s take a look at the second division now. There is a tremendous difference between an application being closed by itself or by an external process. Even though Microsoft has developed proper functions for both actions, there are some important differences between what they are designed to be used to.

What should be noted here is that one process being killed by another one indicates some kind of error, in most cases. Under normal circumstances, all of the applications present on a local machine should be responsible for ending the execution at the right time (after finishing the work or being given a signal by the user). The program should never be surprised of user willing to close it – handling the WM_CLOSE and similar events as well as any other termination signals should be done by the program itself. It is actually the only way to ensure everything is cleaned up before the execution ends – not only does the process have to do the cleaning – it has a number of external modules loaded at runtime – these modules want to call their unititialization routines as well.

Notifications on process termination

The standard WINAPI function for terminating the current process is ExitProcess. The best choice would be to call it everytime we want to exit (returning directly to kernel32.dll is also fine most of the times). We are guaranteed to have the modules EntryPoint’s called with the DLL_PROCESS_DETACH dwReason value – we receive a legal termination notify so that we can save the current process state etc etc. Everything seems fine so far, ExitProcess always cares about any callback functions and stuff (which doesn’t mean everything is fine with the process, see [1]).

OK then, what about a situation when the user has no clue how to terminate a process (no GUI/console windows available on the desktop), but anyway wants to get rid of it (which is a very common situation, in fact)? Here comes the TerminateProcess function, being apparently far more brutal then the previous one. Its definition is as follows (from MSDN):

BOOL WINAPI TerminateProcess(
  HANDLE hProcess,
  UINT uExitCode
);

As one can see, it takes an already opened HANDLE to the target process, as one of the arguments. As MSDN states, the TerminateProcess function should be used to unconditionally cause a process to exit. This means that there no notifications being passed to the process about its state – all of its threads are terminated immediately and any pending I/O operations are requested to cancel. This is what actually makes this method brutal: the target has no chance to know it is being closed, thus making it impossible to recreate the process or perform any other reaction to what’s happening.

As usual, theory is theory and life is life – there are some types of software that would be particularly interested in being able to take advantage of an unkillable process cons. Yep, this could also be malware. Some ideas of how to get some kind of termination notification have been observed in the wild and described by AV companies. Here the list of found-on-the-net and discovered-by-myself btechniques follows:

  • Creating a system-wide hook to get notified – Setting up a global hook (for all the processes we can access) is nothing more but mapping our DLL library to the address space of the hooked processes. This subject is covered in more detail in [2] – the only thing the attacker should be aware of is that if we set up such a global hook, we must get notified in case of the main hooking process being terminated – it would be a pity if the process got killed but its hook would be still active in the context of other applications. Given such a situation, Windows decides to call the standard DllMain function in the context of every process we hooked (using the standard DLL_PROCESS_DETACH argument).This idea is claimed to be used by some real malware, as a way of bypassing what the TerminateProcess is supposed to provide – immediate termination without any notification that could help the process to prevent the action. Since setting up a system-wide hook requires the process to work under special privileges that a normal user doesn’t usually possess, the trick is restricted to a situation where we have the appropriate rights – the machine must have already been compromised.
  • Creating a system-wide hook to modify the TerminateProcess function – If we’re able to set up such a hook, consequently injecting our library to every possible process, we could use this library to find the processes TerminateProcess API addresses and modify the functions in such a way to make it unable to close our protected process. This technique is rather unreliable as there could still be some processes we couldn’t hook and thus would be able to kill our program (i.e. processes owned by a more privileged user).
  • Installing a hook on the ZwTerminateProcess kernel-mode function – This one presents pretty the same idea as the one described above, in fact. The difference is that the modification regards the ring0 code, making it the most reliable technique I know. See – if any process wanted to kill ours, than no matter what API functions and tricks it used, it would eventually end up in the ZwTerminateProcess system call, after all. There isn’t much code to write, as well – we just want to filter out the calls referencing our executable, which should not make a big problem. This technique has already been described in Process Invincibility [3].
  • Using standard debugger notifications – If we only want to get informed about our process being killed (in order to recreate it etc), we can use a parent-child debugging scheme. An example of how it would work in practise follows:
    1. Process debugger.exe is launched at some point.
    2. Debugger.exe launches child.exe using a DEBUG_PROCESS flag, indicating the new process is going to be debugged by its parent
    3. Debugger.exe passes the execution to its child, waiting for a debugging event
    4. The user decides to kill child.exe process and uses the TerminateProcess function
    5. Child.exe gets closed (or at least suspended), and an EXIT_PROCESS_DEBUG_EVENT signal is passed to Debugger.exe.
    6. Debugger.exe retrieves the child process state and lets the system complete the termination.
    7. Debugger.exe re-creates child.exe and writes the process state back.
  • As you can see, the whole trick takes advantage of the fact that the parent (debugger) process gets notified of what’s going on with its child. However, the problem is that another process gets involved in the whole action – debugger.exe is yet a normal process and can be killed as any other. Someone could think of creating a self-debugging process that could (maybe) get informed about its termination. This is kinda naive approach, and the following code listing taken from the WRK (ntos\dbgk\dbgkobj.c) should deprive the reader of any doubts:
    //
    // Don't let us debug ourselves or the system process.
    //
    if (Process == PsGetCurrentProcess () || Process == PsInitialSystemProcess) {
      ObDereferenceObject (Process);
      return STATUS_ACCESS_DENIED;
    }

We don’t have to create a special debugger process to keep being notified, though. We can still use some accessible system processes that are present on the machine 99% of times. In my opinion, the best idea would be to use explorer.exe, since it is the standard user shell process, running in the context of current user. The idea is to inject a special thread into the explorer, that would play the debugger.exe’s role. However, since we’re able to create a remote thread in the context of explorer.exe, some other process could remove it as well. The method is not perfect, though makes the whole termination work much harder for an average Windows user (re-creating the explorer process would also work).

As can be seen, most of the ideas require some particular process privileges. The perfect situation would occur if our process was able to get notified about being terminated, without any additional memory hacks/processes involved. New ideas are welcome!

Closing applications the nice way

OK then, having a few ways of bypassing the TerminateProcess functionality, let’s take a look at the problem from the opposite side: we want to close some application from the outside in a nice way, so as it could perform the standard uninitialization actions. As far as I know, there is no documented API that would be appropriate in such situation. There are, though, some tricks that make it possible to cleanly close an application under any conditions, in most cases (check [4]). However, these tricks are subsystem-dependent, thus have to be described separately. Let’s begin with the console applications.

Console Ctrl+C event

As have already been written, the text-mode application doesn’t have full control over the console window look and events handling. Although, there are some actions that make CSRSS (the process responsible for handling low-level console events) send specific signals back to the ‘client’ application. To be more precise, there are a few callback functions registered by kernel32.dll during console allocation/attachment, that are called by the WinSrv (one of the CSRSS components) in the context of particular process (this subject is going to be described in detail in one of the upcoming ‘CSRSS internals’ posts). A Win32 programmer can take advantage of the fact that the CTRL+C event is one of those being signalised to the process itself. Microsoft has developed and documented a

BOOL WINAPI SetConsoleCtrlHandler(
  PHANDLER_ROUTINE HandlerRoutine,
  BOOL Add
);

function [5], responsible for registering/removing a new Ctrl+C callback function. The entire mechanism is pretty easy from a programmer’s point of view: the HandlerRoutine is called by the kernel32.dll every time a Ctrl+C or Ctrl+Break combination is used (it is also being called after closing the console window manually). If our program needs to perform some additional cleanup apart from what the ExitProcess performs, we can install such a callback and do what’s supposed after someone aims to close our console window. There is one, default kernel32 handler installed at the beginning, which does nothing more but calls the ExitProcess function using a STATUS_CONTROL_C_EXIT constant as the exit code.

If we want to terminate a console application in a nice way, we can generate a Ctrl+C event in the context of the application – it would result in its immediate termination in 99% cases. This can be achieved using a GenerateControlCtrlEvent function, which description I encourage you to read. Despite the presence of default kernel32 handler, we cannot be 100% sure the process will terminate after receiving a Ctrl+C signal – some console hacks can be used inside the program not to let it get closed using any keyboard-combinations and related techniques. I think it would be a reasonable solution to generate a Ctrl+C event, wait a few seconds to see whether the target closes itself or not, and take harsher actions in case the process is still alive.

CreateRemoteThread(ExitProcess)

When it comes to GUI applications, we have no chance to use the CSRSS console features unless the program is both graphical and text-mode (which isn’t a very common situation, anyway). One way or another, we can always try to create a remote thread that would do the job in the context of our target. CreateRemoteThread takes a function address as one of its parameters – the new thread’s EntryPoint. The given function is supposed to be of the following type:

DWORD WINAPI ThreadProc(
  LPVOID lpParameter
);

It means that knowing the address of an API function in the target’s address space, and provided the function takes exactly one argument, we can make the new thread start directly inside a system function. What must be remembered is that the CreateRemoteThread function creates a thread using the given address argument – the address MUST point to a valid memory area (simply a valid function) in the target’s addressing. If we wanted to launch our own function in the context of another process, we would have to allocate some executable memory at first,copy the desired code inside, and eventually use the new allocation address as the thread API argument.

What makes the whole trick so easy is that the ExitProcess definitions matches ThreadProc:

VOID WINAPI ExitProcess(
  UINT uExitCode
);

It takes one argument (which can be passed to the thread using a CreateRemoteThread parameter), so the stack frame would not get damaged if we started the thread in the function itself. What could be a little confusing is that ExitProcess does not return any value, while ThreadProc is supposed to. Yeah, this is generally right, but what should be noted is that the function never returns, thus making the return type/value irrevelant.

DebugActiveProcess + ExitProcess

There is one more particular issue actually worth being mentioned. In spite of the standard TerminateProcess brutal termination technique, one can use a “DebugActiveProcess + Quit-the-debugger” method. Let’s assume we have two running processes: A and B. Process A calls the DebugActiveProcess function using a HANDLE to the second program, thus becoming its debugger. There’s a default setting present in every debugger process – everytime the dbg is shutdown, so are the debugged processes (this characteristic can be easily changed using the DebugSetProcessKillOnExit API function). So, if the A process suddenly decides to quit, B gets (silently, just like TerminateProcess) terminated, too. This little technique is claimed to be a good alternative for standard termination API, sometimes even more effective (according to deus).

Conclusion

To be honest, the simple-and-friendly process termination mechanism is way more complex and advanced than my first thoughts were. A number of other, different issues (not even mentioned here) have already been discussed (check out the References section).

The termination itself is connected very tightly with some of the CSRSS functionalities that are going to be described soon – stay up to date!;)

The post will be updated with some Proof of Concept code snippets in a few days.

Proof of Concept code can now be downloaded from here.

Take care.

References & Links

  1. Quick overview of how processes exit on Windows XP
  2. Techniques of Adware and Spyware
  3. Process Invincibility
  4. How To Terminate an Application “Cleanly” in Win32
  5. SetConsoleCtrlHandler Function
  6. The arms race between programs and users
  7. Why do some process stay in Task Manager after they’ve been killed?
  8. The old-fashioned theory on how processes exit

8 thoughts on “Process termination issues”

  1. Good “first real” post. I think I’m going to enjoy this blog. (But please turn off snapshots on links, your readers will thank you) And thanks to Gynvael for sending me here in the first place

  2. It is a wonderfull article, i recently came in this type of programming and i understand a lot. kindly can you tell me how to build the project for “system hook terminate process hack” in your zip folder. please i know this is a stupid question. but…. i m really getting a lot of errors, i am building a dll project in which i include hookdll.c and hookdll.def… am i doing something wrong. i am using VC8.

    regards,
    farhan

  3. @farhan:
    Thanks a lot! When it comes to your problem, it’s because of the fact that the Proof of Concept code is supposed to be compatible with the MinGW gcc, only (specific in-line assembler code format etc). Furthermore, its main purpose is to illustrate some mechanisms rather than be a 100% usable application ;-)

    Anyway, I think it isn’t really hard to port the provided PoC to a valid VC8 project ;) Let me know, if you decide to do so.

    Regards,
    j00ru//vx

  4. I think one other method should work in some 32-bit app cases, where you know that the process-to-be-terminated goes periodically into some kind of wake-able sleeps. The idea is to use QueueUserAPC(TerminateProcess, hRemoteThreadHandle, 0);
    Haven’t tried it out though.

Comments are closed.