-
-
Save afish/c89d9e4de5298a135689265903123925 to your computer and use it in GitHub Desktop.
Catching unhandled exceptions in threads
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using System; | |
using System.Runtime.CompilerServices; | |
using System.Runtime.InteropServices; | |
using System.Threading; | |
namespace ThreadExceptionHandler | |
{ | |
delegate void ThreadConstructorMethod(object o, ThreadStart ts); | |
public enum Protection | |
{ | |
PAGE_NOACCESS = 0x01, | |
PAGE_READONLY = 0x02, | |
PAGE_READWRITE = 0x04, | |
PAGE_WRITECOPY = 0x08, | |
PAGE_EXECUTE = 0x10, | |
PAGE_EXECUTE_READ = 0x20, | |
PAGE_EXECUTE_READWRITE = 0x40, | |
PAGE_EXECUTE_WRITECOPY = 0x80, | |
PAGE_GUARD = 0x100, | |
PAGE_NOCACHE = 0x200, | |
PAGE_WRITECOMBINE = 0x400 | |
} | |
public static class ThreadHandler | |
{ | |
[DllImport("kernel32.dll", SetLastError = true)] | |
static extern bool VirtualProtect(IntPtr lpAddress, uint dwSize, uint flNewProtect, out uint lpflOldProtect); | |
// Function needs to change lambda and call thread | |
// Original Thread constructor is called with fastcall convention, parameters via ecx + edx, return value in eax | |
// We then jump back to original Thread constructor in order to avoid recalculating relative addresses | |
static byte[] methodCode = { | |
0x51, // push ecx | |
0xE8, // call | |
0x0, 0x0, 0x0, 0x0, // helper method call address, replaced in runtime, it returns new lambda in eax | |
0x59, // pop ecx | |
0x8B, 0xD0, // mov edx, eax | |
0x8B, 0xC1, // mov eax, ecx | |
0x55, // push ebp | |
0x8B, 0xEc, // mov ebp,esp | |
0x50, // push eax | |
0x90, // nop | |
0xE9, // jmp | |
0x0, 0x0, 0x0, 0x0, // original thread constructor call address, replaced in runtime | |
0xC3 //retn | |
}; | |
static readonly byte[] OriginalMethodCode = new byte[256]; | |
public static void HijackMethod(IntPtr sourceAddress, IntPtr targetAddress) | |
{ | |
int offset = (int)((long)targetAddress - (long)sourceAddress - 4 - 1); // 4 bytes for relative address and one byte for opcode | |
byte[] instruction = { | |
0xE9, // Long jump relative instruction | |
(byte)(offset & 0xFF), | |
(byte)((offset >> 8) & 0xFF), | |
(byte)((offset >> 16) & 0xFF), | |
(byte)((offset >> 24) & 0xFF) | |
}; | |
Marshal.Copy(instruction, 0, sourceAddress, instruction.Length); | |
} | |
public static void EnableHandling() | |
{ | |
var threadConstructorInfo = typeof (Thread).GetConstructor(new[] {typeof (ThreadStart)}); | |
var threadConstructorMethodHandle = threadConstructorInfo.MethodHandle; | |
RuntimeHelpers.PrepareMethod(threadConstructorMethodHandle); | |
var threadConstructorAddress = threadConstructorMethodHandle.GetFunctionPointer(); | |
var modifierMethodMethodHandle = typeof (ThreadHandler).GetMethod(nameof(ModifyHandler)).MethodHandle; | |
RuntimeHelpers.PrepareMethod(modifierMethodMethodHandle); | |
var modifierMethodAddress = modifierMethodMethodHandle.GetFunctionPointer(); | |
IntPtr modifierCallerMethodAddress = GetArrayPosition(methodCode); | |
Marshal.WriteInt32(modifierCallerMethodAddress, 2, (int)modifierMethodAddress - (int)(modifierCallerMethodAddress + 6)); | |
Marshal.WriteInt32(modifierCallerMethodAddress, 17, (int)threadConstructorAddress + 5 - ((int)modifierCallerMethodAddress + 21) - 1); | |
Console.WriteLine("Callback modifier caller: " + modifierCallerMethodAddress.ToString("X")); | |
Console.WriteLine("Callback modifier: " + modifierMethodAddress.ToString("X")); | |
Console.WriteLine("Constructor: " + threadConstructorAddress.ToString("X")); | |
// Unblock memory page | |
uint old; | |
VirtualProtect(threadConstructorAddress, (uint) OriginalMethodCode.Length, (uint) Protection.PAGE_EXECUTE_READWRITE, out old); | |
// Fix Thread constructor to jump | |
HijackMethod(threadConstructorAddress, modifierCallerMethodAddress); | |
} | |
private static IntPtr GetArrayPosition(byte[] array) | |
{ | |
unsafe | |
{ | |
TypedReference reference = __makeref(array); | |
IntPtr methodAddress = (IntPtr) (*(int*) *(int*) &reference + 8); | |
return methodAddress; | |
} | |
} | |
public static ThreadStart ModifyHandler(object _, ThreadStart threadStart) | |
{ | |
return () => | |
{ | |
try | |
{ | |
threadStart(); | |
} | |
catch (Exception e) | |
{ | |
Console.WriteLine(e); | |
} | |
}; | |
} | |
} | |
class Program | |
{ | |
static void Main(string[] args) | |
{ | |
ThreadHandler.EnableHandling(); | |
MakeUnhandled(); | |
Thread.Sleep(TimeSpan.FromSeconds(3)); | |
Console.WriteLine("All done"); | |
} | |
private static void MakeUnhandled() | |
{ | |
ThreadStart lambda = () => | |
{ | |
Console.WriteLine("Running in new thread"); | |
throw new Exception("This is unhandled!"); | |
}; | |
var thread = new Thread(lambda); | |
thread.Start(); | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment