Library for creating Delegate to any machine code provided as a byte array
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
// Created by Adam Furmanek | |
using System; | |
using System.Collections.Generic; | |
using System.Linq; | |
using System.Reflection; | |
using System.Runtime.CompilerServices; | |
using System.Runtime.InteropServices; | |
namespace ByteToFunc | |
{ | |
// Flags for VirtualProtect method | |
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 | |
} | |
// Base type for stubbing to have runtime type checking | |
public class BaseStub | |
{ | |
public int Target; | |
} | |
public class FuncGenerator | |
{ | |
// Method to unlock page for executing | |
[DllImport("kernel32.dll", SetLastError = true)] | |
static extern bool VirtualProtect(IntPtr lpAddress, uint dwSize, uint flNewProtect, out uint lpflOldProtect); | |
// Unlocks page for executing | |
private static void UnlockPage(int address) | |
{ | |
uint old; | |
VirtualProtect((IntPtr)address, 6, (uint)Protection.PAGE_EXECUTE_READWRITE, out old); | |
} | |
// Some internal storage for pinning | |
private static IList<object> memory = new List<object>(); | |
private static IList<GCHandle> handles = new List<GCHandle>(); | |
// Pins array with code and returns address to the beginning of the array | |
private static IntPtr Pin(object data) | |
{ | |
memory.Add(data); | |
var handle = GCHandle.Alloc(data); | |
handles.Add(handle); | |
return Marshal.ReadIntPtr(GCHandle.ToIntPtr(handle)); | |
} | |
// Returns delegate of type T using class U for stubbing | |
public static T Generate<T, U>(byte[] data) where U : BaseStub, new() | |
{ | |
// Address of machine code in array | |
// We omit first 8 bytes (array type and size) | |
var arrayCodeAddress = ((int)Pin(data)) + 8; | |
Console.WriteLine("Machine code in array address: " + arrayCodeAddress.ToString("X")); | |
// Unlock page so we can execute code from it | |
UnlockPage(arrayCodeAddress); | |
// Fixing code of stub method Stub | |
// Target points to the actual place with the code | |
var oldTarget = new U { | |
Target = arrayCodeAddress | |
}; | |
var emitDelegate = Delegate.CreateDelegate(typeof(T), oldTarget, "Stub"); | |
var stubMethodHandle = typeof(U).GetMethod("Stub").MethodHandle; | |
RuntimeHelpers.PrepareMethod(stubMethodHandle); | |
var stubCodeAddress = stubMethodHandle.GetFunctionPointer(); | |
Console.WriteLine("Stub address: " + stubCodeAddress.ToString("X")); | |
// Modifies stub method to do the absolute jump | |
Marshal.WriteInt32(stubCodeAddress, unchecked((int)0xc30471ff)); // push dword ptr [ecx+4] ; retn | |
// Returns delegate of correct type so we have runtime type checking | |
return (T)(object)emitDelegate; | |
} | |
} | |
// Stub for Action<int> | |
// Each stub must have a non-static method called Stub with correct signature and at least 4 bytes of body | |
public class ActionInt : BaseStub | |
{ | |
public void Stub(int x) | |
{ | |
Console.WriteLine("Original action stub"); | |
} | |
} | |
// Stub for Func<int, int> | |
public class FuncInt : BaseStub | |
{ | |
public int Stub(int x) | |
{ | |
Console.WriteLine("Original func stub"); | |
return 0; | |
} | |
} | |
public class Program | |
{ | |
// Helper method just to show jumping | |
public void MyWriteLine(int text) | |
{ | |
// Type is ByteToFunc.ActionInt because we are jumping around in the code! | |
Console.WriteLine("I'm in the MyWriteLine! And I am:\t\t" + GetType()); | |
Console.WriteLine(text); | |
Console.WriteLine(); | |
} | |
static void ActionTest() | |
{ | |
var methodHandle = typeof(Program).GetMethod(nameof(Program.MyWriteLine), BindingFlags.Public | BindingFlags.Instance).MethodHandle; | |
RuntimeHelpers.PrepareMethod(methodHandle); | |
var methodAddress = methodHandle.GetFunctionPointer(); | |
Console.WriteLine("MyWriteLine address: " + methodAddress.ToString("X")); | |
var address = BitConverter.GetBytes((int)methodAddress).ToArray(); | |
// Should jump to MyWriteLine and do some job | |
Action<int> function = FuncGenerator.Generate<Action<int>, ActionInt>( | |
new byte[]{ | |
0x68, // push | |
} | |
.Concat(address) | |
.Concat(new byte[] | |
{ | |
0xC3 //retn | |
}).ToArray() | |
); | |
function(5); | |
} | |
static void FuncTest() | |
{ | |
Func<int, int> function = FuncGenerator.Generate<Func<int, int>, FuncInt>( | |
new byte[]{ | |
0x89, 0xD0, // mov eax, edx | |
0x83, 0xC0, 0x04, // add eax, 4 | |
0xc3 // retn | |
} | |
); | |
// Should print 23 + 4 == 27 | |
Console.WriteLine(function(23)); | |
} | |
static void Main(string[] args) | |
{ | |
ActionTest(); | |
FuncTest(); | |
// To figure out machine code you can use this: https://defuse.ca/online-x86-assembler.htm | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment