This is the eighth part of the .NET Inside Out series. For your convenience you can find other parts in the table of contents in Part 1 – Virtual and non-virtual calls in C#
If you are interested in the topic see the talk page
Today we are going to swallow Stack Overflow Exception in C# using Vectored Exception Handling. Let’s begin.
Table of Contents
Why?
As specified in exception’s documentation, you are not allowed to catch Stack Overflow Exception (SOE) in .NET 2.0+. Why would you even try to do that? Well, normally you should detect and avoid it but imagine that you are writing a testing framework. If some of the tested code has a bug and results in SOE your runner will fail instead of showing nice message with stacktrace. You could in theory change this by reworking shim loading CLR but today we will do it using VEH.
I hope you read the Allocating object on a stack and Generating Func from a bunch of bytes posts because I use those tricks a lot.
How?
VEH is like an external exception handler, similar to Application_Error
method. When something goes wrong you are notified with exception details. It is a Windows mechanism so don’t count on nice object which you can utilize from C#, you need to work with pointers and low level stuff. There can be multiple VEH handlers and each of them can indicate either to continue execution or continue search. Continuing execution means restoring thread to the state at the exception (or to any different state by changing CPU registers) and carrying on. Continuing search means executing next VEH handler, SEH handler or unhandled exception handler.
We are going to register new VEH handler, remember the point where we start calling method causing SOE (it is like adding catch in the code), catching the exception and redirecting the CPU to known location. Let’s go.
Implementation
We start with this faulty method:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
public static void DoWork() { try { Console.WriteLine("Preparing to throw"); // Either try to divide by zero //int a = 5; //int b = 0; //Console.WriteLine(a / b); // Or generate SOE //DoWork(); } catch(Exception e) { Console.WriteLine("I am in normal catch block with exception: " + e); } } |
You can uncomment any part you like. Dividing by zero should be caught by C# exception handler, however, SO should be ignored and crash your process. Ideally, if we get division by zero, C# handler should run, otherwise (if it is a SOE), our VEH handler should be in play.
We need the following helper methods:
P/Invoke to register VEH:
1 2 3 |
// Method for registering VEH handler [DllImport("Kernel32.dll", SetLastError = true)] static extern int AddVectoredExceptionHandler(uint first, IntPtr addres); |
Methods to get addresses of machine codes:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
private static byte[] GetAddress(string name) { var address = BitConverter.GetBytes((int)GetRawAddress(name)).ToArray(); return address; } private static IntPtr GetRawAddress(string name) { var methodHandle = typeof(Program).GetMethod(name, BindingFlags.Public | BindingFlags.Static | BindingFlags.Instance).MethodHandle; RuntimeHelpers.PrepareMethod(methodHandle); var methodAddress = methodHandle.GetFunctionPointer(); Console.WriteLine("Address: " + methodAddress.ToString("X")); return methodAddress; } |
Some space to hold registers to restore:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
// Space to hold return context public static int[] EipEspEbpEsiEdiEbxHolder = new [] { 0xBADF00D, 0xBADBEEF, 0xBADF00D, 0xBADBEEF, 0xBADF00D, 0xBADBEEF }; // Get field address (actually, first usable integer of the array, after metadata) private static int GetEipEspEbpEsiEdiEbxHolder() { unsafe { TypedReference typedReference = __makeref(EipEspEbpEsiEdiEbxHolder); int* fieldAddress = (int*)*(int*)*(int*)&typedReference; Console.WriteLine("Holder address: " + ((int)fieldAddress).ToString("X")); return (int)fieldAddress + 8; } } |
First, easy part — exception handler:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 |
// Exception info is passed in esp+8 which is fourth parameter according to fastcall calling convention // Having signature correctly like (int, int, int, IntPtr) spils stack because of calling convention - .NET will remove 2 integers from it // We need to fix ret 8 to ret 4 at the end public static int CatchingVeh(int a, int b, int c, IntPtr pExceptionInfo) { //#define EXCEPTION_EXECUTE_HANDLER 1 //#define EXCEPTION_CONTINUE_SEARCH 0 //#define EXCEPTION_CONTINUE_EXECUTION (-1) Console.WriteLine("Catching VEH!"); uint exceptionCode = (uint)Marshal.ReadInt32(Marshal.ReadIntPtr(pExceptionInfo)); Console.WriteLine("Exception code: " + exceptionCode.ToString("X")); if(exceptionCode != 0xC00000FD) { // Not Stack Overflow Exception // Return CONTINUE_SEARCH return 0; } // We restore registers here using ExceptionInfo structure Console.WriteLine("Info: " + pExceptionInfo.ToString("X")); IntPtr pContextRecord = Marshal.ReadIntPtr(pExceptionInfo + 4); Console.WriteLine("Context: " + pContextRecord.ToString("X")); Marshal.WriteIntPtr(pContextRecord + 0xB8, (IntPtr)EipEspEbpEsiEdiEbxHolder[0]); Marshal.WriteIntPtr(pContextRecord + 0xC4, (IntPtr)EipEspEbpEsiEdiEbxHolder[1]); Marshal.WriteIntPtr(pContextRecord + 0xB4, (IntPtr)EipEspEbpEsiEdiEbxHolder[2]); Marshal.WriteIntPtr(pContextRecord + 0xA0, (IntPtr)EipEspEbpEsiEdiEbxHolder[3]); Marshal.WriteIntPtr(pContextRecord + 0x9C, (IntPtr)EipEspEbpEsiEdiEbxHolder[4]); Marshal.WriteIntPtr(pContextRecord + 0xA4, (IntPtr)EipEspEbpEsiEdiEbxHolder[5]); Console.WriteLine("Going back to Eip: " + EipEspEbpEsiEdiEbxHolder[0].ToString("X")); Console.ReadLine(); // Return CONTINUE_EXECUTION return -1; } |
Lots of magic here!
First, method CatchingVeh
is an actual VEH handler. It has very weird signature – it is because of VEH handler signature. Basically, the pointer to exception structure is passed via the stack, however, C# calling convention would access it via fourth parameter so we need to ad dummy parameters. Otherwise we would need to access the exception via asm code which is tedious so let’s make a different hack (see later).
We first get exception code. You can easily traverse structures on your own using pointers so there is nothing special here. We compare exception code with value 0xC00000FD
which is a Stack Overflow Exception code. If the code is different, we just continue searching for different handler, eventually going to ordinary C# catch handler.
Next we start toying with CPU. ExceptionInfo
has a pointer to ContextRecord
structure which holds CPU registers at the time of throwing the exception. If we indicate that we want to continue execution, those registers will be restored from this structure. So we can modify EIP
here and effectively jump to some other place. We jump to our catch handler (or in this sample just to line right after calling the faulty method). We also restore other registers so we are in the same state as before calling faulty method. So: we change EIP to do jump. We change ESP and EBP to effectively drop frames from the stack. We restore ESI, EDI and EBX to be in the same state and to adhere to calling convention.
This was easy, now comes the tricky part. We need to fill EipEspEbpEsiEdiEbxHolder
with register values before calling the faulty method. And for this we need to use asembler language.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 |
public static void Veh() { var doWorkAddress = GetAddress(nameof(Program.DoWork)); var catchingAddress = GetRawAddress(nameof(Program.CatchingVeh)); // Fix ret 8 to ret 4 for CatchingVeh function IntPtr current = catchingAddress; while (true) { if(Marshal.ReadByte(current) == 0xC2 && Marshal.ReadByte(current + 1) == 0x8 && Marshal.ReadByte(current + 2) == 0) { Marshal.WriteByte(current + 1, 0x4); break; } current += 1; } // Register VEH handler AddVectoredExceptionHandler(1, catchingAddress); var holderAddress = GetEipEspEbpEsiEdiEbxHolder(); Action<int> function = FuncGenerator.Generate<Action<int>, ActionInt>( new byte[0] // -------------------------------------------- // Storing stack and instruction address // call helper for EIP .Concat(new byte[]{ 0xE8, 0x1C, 0x00, 0x00, 0x00 // call 28 }) // save EIP, ESP, EBP, ESI, EDI to temp array .Concat(new byte[]{ 0x83, 0xC0, 0x1B, // add eax,0x1B (27 bytes) 0x89, 0x02, // mov DWORD PTR [edx],eax 0x89, 0x62, 0x04, // mov DWORD PTR [edx+0x4],esp 0x89, 0x6A, 0x08, // mov DWORD PTR [edx+0x8],ebp 0x89, 0x72, 0x0C, // mov DWORD PTR [edx+0xc],esi 0x89, 0x7A, 0x10, // mov DWORD PTR [edx+0x10],edi 0x89, 0x5A, 0x14, // mov DWORD PTR [edx+0x14],ebx }) // -------------------------------------------- // -------------------------------------------- // Calling DoWork method .Concat(new byte[]{ 0xB8, // mov eax }) .Concat(doWorkAddress) .Concat(new byte[] { 0xFF, 0xD0 // call eax }) // -------------------------------------------- // -------------------------------------------- // Returning .Concat(new byte[] { 0xC3 //retn }) // -------------------------------------------- // -------------------------------------------- // Helper function for getting EIP as it is inaccessible directly on x86_32 .Concat(new byte[]{ 0x8B, 0x04, 0x24, // mov eax,DWORD PTR [esp] 0xC3 //retn }) .ToArray() ); Console.WriteLine("Calling with VEH"); Console.ReadLine(); function(holderAddress); Console.WriteLine("I'm done"); } |
Even more magic here…
First, we get addresses of methods we use.
Next, we need to fix CatchingVeh
function. If you disassembled it you would see that it uses ret 8
instruction at the end which drops two integers from the stack. To adhere to calling convention we must drop exactly one integer so we need to manually modify the code to use ret 4
Next, we register VEH handler and get address of array holding registers. We need to have machine address because we are going to fill it from asm. We pass this address via delegate parameters (see invocation at the end) so it lands in edx
register (recall that ecx
is a this
parameter).
And then we begin our magic. We want to get the eip
register but we cannot read it directly so we use a trick. At the end of machine code we have a helper function. When we call it, return address is pushed on the stack so we can read it from there and return via eax
.
So we call this method at the beginning and when we are back we have the eip
value in eax
register. We just need to calculate the actual address after the method call (yes, we just count the bytes by hand). Next, we store other registers which we can easily access.
Finally, we just do absolute call via eax
register and return. That’s it.
Summary
We can now handle the SOE easily using the VEH. We could do some more magic to analyze the reason behind the exception, capture the stack trace etc, but this is an exercise for you. And don’t forget to remove the VEH handler!