Tuesday, January 8, 2008

Signal Handling in Reverse Stack Executables

One of the challenges in reverse stack manipulation is signal handling. If a signal handler is defined for a signal, when the signal is raised, the kernel sets up a signal frame and saves the context of the process on the stack and calls the corresponding handler. Since the kernel expects normal stack growth direction, e.g. downwards in x86, the context saved by the kernel overwrites data on a reverse growing stack. To tackle this problem, we allocate a small block of memory and call sigaltstack to notify the OS that it must use this memory block as the signal stack to set up the signal frame and save the process' context.

The problem is that the handler which is defined by the programmer, is compiled for reverse stack. Now when the signal rises, the kernel saves the context on the stack and calls the handler. The handler uses the same signal handling stack and when it starts execution, the stack pointer is located just below the context saved by the OS. Therefore, a handler compiled for reverse growing stack can overwrite and destroy the context of the process, causing a crash when the handler returns.

To solve this problem, we change the interface to sigaction system call in libc. sigaction registers a new handler for a specified signal number. We change the interface to the system call so that whenever it is called, the new interface sets the new handler to a wrapper function that we have defined in libc. The wrapper function increments the stack pointer to bypass the area used for saving the process' context and then calls the user-defined function. After the user-defined function returns, the wrapper decrements the stack pointer to point back to its original location and returns. Using this method, the saved context remains intact and the kernel is able to restore it while everything remains transparent to the kernel.

As mentioned above, we allocate a block of memory to use as the alternative stack. We pass a pointer close to the beginning of the block to sigaltstack. The kernel uses this pointer as the beginning of the alternative stack and saves the context down this point and towards the beginning of the block. The pointer is far enough from the start of the block to provide adequate room for saving the context. After saving the context, our wrapper function increments the stack pointer to go past the context and uses the rest of the memory block as an upward growing stack for the signal handler.

No comments: