[Code Snippet (CS)] Not very safe _FunctionHandle class

Started by RawCode, February 14, 2017, 06:04:00 AM

Previous topic - Next topic

RawCode

using System;
using System.Runtime.InteropServices;
using System.Collections;
using System.Runtime.CompilerServices;
using System.Reflection;
using System.Text;

namespace rcrml
{
unsafe public class _FunctionHandle
{
//sync with domain internals header line # 199

//void* equal to "native integer", it follows word size based on platform used automatically
public struct _MonoJitInfo
{
//should work on x64 due to variable size of void*
public RuntimeMethodHandle method; //should work properly because IntPtr host void* intenrnally
public void* next_jit_code_hash;
public IntPtr code_start; //same void* under wrappers
public uint  unwind_info;
public int    code_size;
public void* __rest_is_omitted;
//struct is longer actually, but rest of fields does not matter
}

//you may pass byte that belong to function, but is not first byte of function
//first byte will be used internally and can be accessed by ->code_start
//yes undescores violate naming convention, leave them be
private void* function_pointer;
private int malloc_size;
private byte[] function_image;    //managed pointer, managed type
private byte[][]    image_backup_stack;//managed pointer, managed type

//very unsafe
//raw (direct) pointer into VM memory structure that holds actual data
//modification to this data will cause VM wide effects
//both expected and unforseen
private _MonoJitInfo* function_jit_native; //pointer into native memory, unmanaged, unsafe

//semi constructors:

public _FunctionHandle(void* _pointer)
{
Initialize (_pointer);
}

public _FunctionHandle(MethodInfo _method_info)
{
Initialize ((void*)_method_info.MethodHandle.GetFunctionPointer ());
}

public _FunctionHandle(RuntimeMethodHandle _method_info_native)
{
Initialize ((void*)_method_info_native.GetFunctionPointer ());
}

//no platform word size checks here
public _FunctionHandle(long _unsafe_long)
{
Initialize ((void*)_unsafe_long);
}
public _FunctionHandle(int _unsafe_int)
{
Initialize ((void*)_unsafe_int);
}

private void Initialize(void* _pointer)
{
function_pointer = _pointer;
function_jit_native =
(_MonoJitInfo*)_Native.mono_jit_info_table_find (_Native.mono_domain_get (),function_pointer);

if (function_jit_native == null)
return;

malloc_size = Math.Max (function_jit_native->code_size, sizeof(void*) * 2);
}

public MethodBase GetReflectionHandle()
{
if (function_jit_native == null)
return null;
return MethodBase.GetMethodFromHandle (function_jit_native->method);
//real return is MonoMethod
}

//read and write methods are follow

private void PushImageBackup()
{
if (image_backup_stack == null)
image_backup_stack = new byte[8][];

Array.Copy (image_backup_stack, 0, image_backup_stack, 1, 7);
image_backup_stack [0] = function_image;
}

public byte[] Undo()
{
if (image_backup_stack == null)
return null;
if (image_backup_stack[0] == null)
return null;

byte[] shadow = function_image;

function_image = image_backup_stack [0];
Array.Copy (image_backup_stack, 1, image_backup_stack, 0, 7);
return shadow;
}

//read unmanaged memory into fresh byte[]
//always push value of function_image to backup stack
[MethodImpl(MethodImplOptions.NoInlining)]
public void SyncRaw2Image()
{
if (function_jit_native == null)
throw new Exception ("Pointer " + string.Format("{0:X4}",(long)function_pointer) + " does not belong to managed method");

byte[] newarray = new byte[malloc_size];
Marshal.Copy (function_jit_native->code_start, newarray, 0, malloc_size);

PushImageBackup ();
function_image = newarray;
}

//replace existing array with new one, params syntax sugar version, no safety
[MethodImpl(MethodImplOptions.NoInlining)]
public void SyncArray2Image(params byte[] _array)
{
//no safety here!
PushImageBackup ();
function_image = _array;
}

public byte[] GetFunctionImage()
{
byte[] shadow = new byte[function_image.Length];
Array.Copy (function_image, shadow, function_image.Length);
return shadow;
}

public byte[] GetFunctionImageRef()
{
return function_image;
}

//Emit data stored inside array into unmanaged RWX memory
//no safety checks, may ruin your game deeply
[MethodImpl(MethodImplOptions.NoInlining)]
public void SyncImage2Raw()
{
if (function_jit_native == null)
throw new Exception ("Pointer " + string.Format("{0:X4}",(long)function_pointer) + " does not belong to managed method");

if (function_image.Length > malloc_size)
throw new Exception ("Provided array cannot fit, check your code or use Rebase");

Marshal.Copy (function_image, 0, function_jit_native->code_start, function_image.Length);
}

public void WriteLine()
{
Console.WriteLine (ToString ());
}

[MethodImpl(MethodImplOptions.NoInlining)]
public override string ToString()
{
//this required to for one line printing of function data
if (function_image == null)
SyncRaw2Image ();
if (function_image == null)
return "Pointer " + string.Format("{0:X4}",(long)function_pointer) + " does not belong to managed method";
StringBuilder sb = new StringBuilder ();
MethodBase mb = GetReflectionHandle ();

long delta = (long)function_pointer - (long)function_jit_native->code_start;

sb.Append ("IP BEGIN " + mb + "+" + string.Format("{0:X4}",delta) +" ( "+String.Format("{0:X2}", (long)function_jit_native->code_start)+" )");

int az = -1;

foreach (byte i in function_image)
{
az++;
if (az % 4 == 0)
sb.Append ("\n" + String.Format("{0:X2}", i));
else
sb.Append (" " + String.Format("{0:X2}", i));
}
sb.Append ("\nIP END " + mb +" ( "+String.Format("{0:X2}", (int)function_jit_native->code_start)+" )");

return sb.ToString ();
}

}
}


Usage:


_FunctionHandle fh1 = new _FunctionHandle (typeof(Kagimono).GetMethod ("TEST_HOOK"));

fh1.SyncRaw2Image (); //read native memory into "image"
fh1.SyncArray2Image (0xC3); //replace "image" with solo RET sequence
TEST_HOOK (); //test invoke
fh1.SyncImage2Raw (); //apply modifications to native memory
TEST_HOOK (); //test invoke
byte[] redo = fh1.Undo (); //undo last modification - in our caseSyncArray2Image
//there is no "redo" stack.
//up to 8 modifications are tracked for each function handle
fh1.SyncImage2Raw (); //apply Undo to native memory
TEST_HOOK (); //test invoke
fh1.SyncArray2Image (redo); //redo changes to image
fh1.SyncImage2Raw (); //write image to native memory
TEST_HOOK (); //test invoke