This post will cover the second part of hiding functionality with exception handlers. Unlike the technique presented in the previous post, which modified the SEH record for the local thread, the aim here is to modify the SEH record for another thread in order to better hide what is actually going on. By the end of the post, there should be enough information to put together a working application capable of modifying the SEH list of any thread (barring some exceptions) and causing it to raise an exception to execute your code. The sample application will be a DLL that is injected into a process and hijacks one of its threads to perform some task.
What is the purpose of doing all of this if you’re injecting into a process anyway? After all, you can simply spawn your own thread or likely use the one created during the injection (if CreateRemoteThread was used) and just begin executing your code. I’d argue that this technique gives more obscurity to what is happening during static analysis and is something out of the norm. Plus its fun!
The overall code is very similar to what the first part showed, but now there need to be a few steps added in order to get the TIB of another thread. There are usually a few different approaches, of varying complexity and reliability.
- Do it directly. Suspend the thread and gets its context. Change the instruction pointer to point to your code which changes the SEH list and raises an interrupt and resume. Perform your task and restore the original context in your SEH handler.
- Do it indirectly. Suspend the thread, queue an asynchronous procedure call (APC) which changes the SEH list and raises an interrupt (with QueueUserAPC), and resume the thread. The thread must be in an alertable state (waiting on something) for this to work, which is typically the case for most threads in a process.
- Take the middle ground. Suspend the thread and get the address of its FS segment directly using GetThreadSelectorEntry. Change the SEH list from within your thread and queue an APC to raise the interrupt, resume the thread.
The easiest approach is to do it indirectly with an APC. The code is really straightforward and looks like the following:
void InstallExceptionHandler(DWORD dwThreadId) { auto handle = ThreadHandleTable[dwThreadId]; DWORD dwError = SuspendThread(handle); if (dwError == -1) { fprintf(stderr, "Could not suspend thread. Error = %X.\n", GetLastError()); return; } CONTEXT ctx = { CONTEXT_ALL }; GetThreadContext(handle, &ctx); LDT_ENTRY ldtEntry = { 0 }; GetThreadSelectorEntry(handle, ctx.SegFs, &ldtEntry); const DWORD dwFSAddress = (ldtEntry.HighWord.Bits.BaseHi << 24) | (ldtEntry.HighWord.Bits.BaseMid << 16) | (ldtEntry.BaseLow); fprintf(stderr, "FS segment address of target thread should be: %X.\n", dwFSAddress); dwError = QueueUserAPC(APCProc, handle, 0); if (dwError == 0) { fprintf(stderr, "Could not queue APC to thread. Error = %X.\n", GetLastError()); } dwError = ResumeThread(handle); if (dwError == -1) { fprintf(stderr, "Could not resume thread. Error = %X.\n", GetLastError()); } } |
Here the suspend/queue/resume wording is put directly in to code (with extra debug comments). When the thread resumes, APCProc will be invoked. APCProc will be running in the context of the target thread and is responsible for modifying the SEH list to add in a new handler. Because of this, APCProc can obtain the TIB without any extra overhead code to write and the code basically becomes a copy/paste from part one.
void CALLBACK APCProc(ULONG_PTR dwParam) { fprintf(stderr, "APC callback invoked. Raising exception to trigger exception handler.\n"); EXCEPTION_REGISTRATION *pHandlerBase = (EXCEPTION_REGISTRATION *)__readfsdword(0x18); fprintf(stderr, "Segment address of target thread: %X.\n", pHandlerBase); EXCEPTION_REGISTRATION NewHandler = { pHandlerBase->pPrevHandler, (EXCEPTION_REGISTRATION::pFncHandler)(MyTestHandler) }; pHandlerBase->pPrevHandler = &NewHandler; RaiseException(STATUS_ACCESS_VIOLATION, 0, 0, nullptr); } |
The handler, NewHandler, being independent of all of this, doesn’t change much either.
EXCEPTION_DISPOSITION __cdecl MyTestHandler(EXCEPTION_RECORD *pExceptionRecord, void *pEstablisherFrame, CONTEXT *pContextRecord, void *pDispatcherContext) { if (pExceptionRecord->ExceptionCode == STATUS_ACCESS_VIOLATION) { MessageBox(0, L"Some hidden functionality can go here.", L"Test", 0); return ExceptionContinueExecution; } return ExceptionContinueSearch; } |
Below are some screenshots of this at work on a 32-bit Notepad++ instance.
Thread 5504 is chosen here.
The MessageBox in the exception handler successfully pops ups. Hitting the “OK” button resumes execution as normal.
The source for the projects (Visual Studio 2013, Update 4) presented in these parts can be found here. Thanks for reading and follow on Twitter for more updates.