Skip to content

Instantly share code, notes, and snippets.

@afish

afish/Program.cs Secret

Created May 4, 2017 12:57
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save afish/c89d9e4de5298a135689265903123925 to your computer and use it in GitHub Desktop.
Save afish/c89d9e4de5298a135689265903123925 to your computer and use it in GitHub Desktop.
Catching unhandled exceptions in threads
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