Mono x86-64 ABS hooks

Started by RawCode, June 25, 2016, 08:43:57 AM

Previous topic - Next topic

RawCode

Unlike my first and second posts about subject, this code "LIKELY"(read as "must work but untested in *live* enviroment") to work on MAC\Linux without any modifications.

Also all provided functions perform absolute adress jump, ever on 32 bit platform.
No need of short\near offset calculation.

First function, abs jump via eax\rax, obviously will damage eax\rax:
(similar to native stuff implemented by windows API, damage of EAX\RAX follow calling convention actually)

static public unsafe byte[] x64_Hook_Abs_From_To(MethodInfo From, MethodInfo Dest)
{
//will damage 12 bytes of target function
//store and return byte array of original function

if (From == null)
throw new Exception ("From method is null");

if (Dest == null)
throw new Exception ("Dest method is null");

byte* FromPTR = (byte*)From.MethodHandle.GetFunctionPointer ().ToPointer ();
void* DestPTR = Dest.MethodHandle.GetFunctionPointer ().ToPointer ();
int sovp = 0; //Size Of Void Pointer

byte[] orgcode = new byte[12];

while(sovp < orgcode.Length)
{
orgcode [sovp] = FromPTR [sovp];
sovp++;
}

sovp = sizeof(void*);
FromPTR [0]     = (byte)(sovp==4 ? 0x90 : 0x48); //sign extension flag or NOP for x86
FromPTR [1] = 0xB8; //EAX\RAX selection
*(long*)(FromPTR+2) = (long)DestPTR; //magic related to avoid pointer match auto multiplication
FromPTR [sovp+2]    = 0xFF; //JUMPF
FromPTR [sovp+3]    = 0xE0; //R-M-BYTE section 4(E) direction EAX\RAX(0)

return orgcode;
}


No register version of function:

static public unsafe byte* x64_Hook_Abs_From_To_NR(MethodInfo From, MethodInfo Dest)
{
//will not damage any register
//will allocate 20 bytes of unmanaged memory
//will damage 12 bytes of source(from) function
//store and return pointer to native memory required to recovery
//setting return value via pointer operation will alter codeflow of hooked method

if (From == null)
throw new Exception ("From method is null");

if (Dest == null)
throw new Exception ("Dest method is null");

byte* FromPTR = (byte*)From.MethodHandle.GetFunctionPointer ().ToPointer ();
void* DestPTR = Dest.MethodHandle.GetFunctionPointer ().ToPointer ();

//32-64 detection
int sovp = sizeof(void*), tmp = sizeof(void*);

//fixed size, real size is 4+sovp*2 (8 and 20)
byte* store = (byte*)Marshal.AllocHGlobal (20).ToPointer();
*(long*)store = (long)DestPTR;

while(tmp < 20)
{
store [tmp] = FromPTR [tmp-sovp];
tmp++;
}

FromPTR [0] = (byte)(sovp==4 ? 0x90 : 0xFF);
FromPTR [1] = (byte)(sovp==4 ? 0xFF : 0x24);
FromPTR [2] = 0x25;
*(long*)(FromPTR+3) = (long)store;

return store;
}


No register version store data required to perform hook inside unmanaged memory, there is no way to use heap due to garbage collection.
Very minor modifications allows to use static fields instead of unmanaged memory (static fields never moved in memory).
You likely to see this version over unmanaged buffer (or native code managed) in my not yet finished project.

After such hook is installed, it can be altered by single set operation:

byte* datax = x64_Hook_Abs_From_To_NR (ma, mb);

TEST_A ();
TEST_B ();

*(long*)datax = (long)mc.MethodHandle.GetFunctionPointer ().ToPointer ();

TEST_A ();
TEST_B ();


Some individuals demanded "short" version of hook and ever removed padding from CCL implementation.

Well, short version of abs jmp for x32 (similar hack is possible on 64 by adding sign extension flag, sadly it will have same size as *long* version (4+8) due to rm and ex flags):

static public unsafe void x86ABSHookIndirect(MethodInfo ORG, MethodInfo NEW)
{
byte* dptr = (byte*)ORG.MethodHandle.GetFunctionPointer ().ToPointer ();
dptr [0] = 0x68;
*(int*)(dptr + 1) = (int)NEW.MethodHandle.GetFunctionPointer ().ToPointer ();
dptr[5] = 0xC3;
//6 bytes abs jmp
}


Provided methods have safety stripped and will fail hard if source method less then size of injection.
Inlined methods also cannot be altered.
Solution will be provided later.

p.s. there is no problems with overloads, just use your head or provided method(s):

static public int GetHashCodeSpecial(Type[] data)
{
int hash = 31;
foreach (Type o in data)
hash = (hash * 54059) ^ (o.GetHashCode () * 76963);
return hash;
}
static public int GetHashCodeSpecial(ParameterInfo[] data)
{
int hash = 31;
foreach (ParameterInfo o in data)
hash = (hash * 54059) ^ (o.ParameterType.GetHashCode () * 76963);
return hash;
}

static public MethodInfo GetMethodWithOverloads(Type t, string s, params Type[] argx)
{
if (t == null)
throw new Exception ("Type is null");
if (t == null)
throw new Exception ("Name is null");
if (argx == null || argx.Length == 0)
throw new Exception ("No arguments?");

MethodInfo[] store = t.GetMethods ((BindingFlags)60);
int argxh = GetHashCodeSpecial (argx);
foreach (MethodInfo m in store)
{
if (m.Name.Equals (s) && GetHashCodeSpecial (m.GetParameters ()) == argxh)
{
return m;
}
}
throw new Exception ("No suitable method is found");
}


happy hooking!

Wivex

Hmm, nice to see you making progress, whatever it is, cause i don't understand anything here. Can you explain how it is different from current detouring method (not technical details, but why we (modders) should switch to it):

        public static unsafe bool TryDetourFromTo(MethodInfo source, MethodInfo destination)
        {
            // keep track of detours and spit out some messaging
            var sourceString = source.DeclaringType.FullName + "." + source.Name;
            var destinationString = destination.DeclaringType.FullName + "." + destination.Name;

            sourceMethods.Add(sourceString);
            destMethods.Add(destinationString);

            if (IntPtr.Size == sizeof (Int64))
            {
                // 64-bit systems use 64-bit absolute address and jumps
                // 12 byte destructive

                // Get function pointers
                var Source_Base = source.MethodHandle.GetFunctionPointer().ToInt64();
                var Destination_Base = destination.MethodHandle.GetFunctionPointer().ToInt64();

                // Native source address
                var Pointer_Raw_Source = (byte*) Source_Base;

                // Pointer to insert jump address into native code
                var Pointer_Raw_Address = (long*) (Pointer_Raw_Source + 0x02);

                // Insert 64-bit absolute jump into native code (address in rax)
                // mov rax, immediate64
                // jmp [rax]
                *(Pointer_Raw_Source + 0x00) = 0x48;
                *(Pointer_Raw_Source + 0x01) = 0xB8;
                *Pointer_Raw_Address = Destination_Base;
                    // ( Pointer_Raw_Source + 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09 )
                *(Pointer_Raw_Source + 0x0A) = 0xFF;
                *(Pointer_Raw_Source + 0x0B) = 0xE0;
            }
            else
            {
                // 32-bit systems use 32-bit relative offset and jump
                // 5 byte destructive

                // Get function pointers
                var Source_Base = source.MethodHandle.GetFunctionPointer().ToInt32();
                var Destination_Base = destination.MethodHandle.GetFunctionPointer().ToInt32();

                // Native source address
                var Pointer_Raw_Source = (byte*) Source_Base;

                // Pointer to insert jump address into native code
                var Pointer_Raw_Address = (int*) (Pointer_Raw_Source + 1);

                // Jump offset (less instruction size)
                var offset = Destination_Base - Source_Base - 5;

                // Insert 32-bit relative jump into native code
                *Pointer_Raw_Source = 0xE9;
                *Pointer_Raw_Address = offset;
            }

            // done!
            return true;
        }


In general, what i personally would like to have is a good wrapper or simple universal method to detour from-to without specifting the binding flags each time (maybe you did that just here, but i need an example then), so that detouring will be easier then:

            // added new def generators and removed redundant
            var vanillaGenerateImpliedDefs_PreResolve = typeof(DefGenerator).GetMethod("GenerateImpliedDefs_PreResolve",
                BindingFlags.Static | BindingFlags.Public);
            var newGenerateImpliedDefs_PreResolve = typeof(RA_DefGenerator).GetMethod("GenerateImpliedDefs_PreResolve",
                BindingFlags.Static | BindingFlags.Public);
            TryDetourFromTo(vanillaGenerateImpliedDefs_PreResolve, newGenerateImpliedDefs_PreResolve);

1000101

You'd still need the binding flags to get the method info, that has nothing to do with the detouring or anything else but with reflection.  There is no way around that.
(2*b)||!(2*b) - That is the question.
There are 10 kinds of people in this world - those that understand binary and those that don't.

Powered By

Wivex

#3
I thought there is a way to determine them from the source method somehow.  :(
Anyway, if previous detouring method is still working, why switch to the new one? What are the benefits?

1000101

From the looks of this, it's just using a different jump instruction but otherwise does the exact same thing.  Although, he did add a bit of code to copy the original bytes which are overwritten.  It would be easy enough to add that to CCL and I have considered doing something similar to undo the detours of a mod that fails on any detouring but see little point as there are probably other things that would need to be "undone" which would just be too numerous to practicably track.
(2*b)||!(2*b) - That is the question.
There are 10 kinds of people in this world - those that understand binary and those that don't.

Powered By

RawCode

Quotewithout specifting the binding flags each time
magical binding flag is 60, (it just like 42 but 16 higher!), you can see it on 13th line from botton.

QuoteCan you explain how it is different from current detouring method
hmmm...
provided functions perform absolute adress jump, ever on 32 bit platform
one of methods store hooking data offheap.

Quotebut why we (modders) should switch to it
i will push commit to CCL when research is done and method is ready for safe and flawless use on all platforms in all cases and will not fail, ever if used improperly.
if commit is approved, you will switch and won't ever notice that.


@1000101
Sadly, mono is unable to properly unload assembly objects, there is no way to recover from loading issues.
Original bytes harvesting is useless waste of resources and provided as reference implementation, just in case someone will need it.

Wivex

Quote from: RawCode on July 22, 2016, 04:51:58 AM
magical binding flag is 60, (it just like 42 but 16 higher!), you can see it on 13th line from botton.

Ehh... What code part out of those you posted are you talking about? In any case, i'm not using CCL, so i need to know which detouring Method() i should use now. Let's say i'm dumb, so could you please re-post the proper method again. And an example of it's use without binding flags specified.


1000101

What he's refering to is that the enums (BindingFlags included) are just a byte value.  60 is the value for "return any public, non-public, static, instance".

...
    Instance = 4,
    Static = 8,
    Public = 16,
    NonPublic = 32,
...


RimWorld itself just sets up it's own mask for a similar purpose:
public const Verse.GenGeneric.BindingFlagsAll = BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic;

As to the detouring itself, I already said that the code presented in the first post is the same as CCLs current method, it's just injecting a slightly different set of instructions.
(2*b)||!(2*b) - That is the question.
There are 10 kinds of people in this world - those that understand binary and those that don't.

Powered By

uilmas

Hi, I hope Method(NEW) can run before or after Method(ORG). Could it possible by modifying memory? Thanks.

1000101

The original method is destroyed, it can never be run.  You need to duplicate all functionality into the method which replaces it.  This has been standard for detouring from the beginning.
(2*b)||!(2*b) - That is the question.
There are 10 kinds of people in this world - those that understand binary and those that don't.

Powered By

RawCode

saving original method "alive" require multiple complicated actions, including, but not limited to, parsing x86 opcodes and allocating RWX memory.

compared to single pointer operation on "classical" code injection technique.