Mono x86-32 JIT machine code hooks

Started by RawCode, December 24, 2015, 05:38:45 AM

Previous topic - Next topic

RawCode


unsafe static public int HookMethodFromTo
(
Type source_type, string source_method_name,
Type destination_type, string destination_method_name,
bool usejump)
{
MethodInfo Source_Method = source_type.GetMethod (source_method_name, (BindingFlags)(60));
MethodInfo Destination_Method = destination_type.GetMethod (destination_method_name, (BindingFlags)(60));

if (Source_Method == null || Destination_Method == null)
return 404;

int Source_Base = Source_Method .MethodHandle.GetFunctionPointer ().ToInt32();
int Destination_Base = Destination_Method.MethodHandle.GetFunctionPointer ().ToInt32();

int offset_raw = Destination_Base - Source_Base;
uint* Pointer_Raw_Source = (uint*)Source_Base;

if (usejump)
{

*(Pointer_Raw_Source+0) = 0xE9909090;
*(Pointer_Raw_Source+1)= (uint)(offset_raw-8);
}
else
{
*(Pointer_Raw_Source+0) = 0x83ec8b55;
*(Pointer_Raw_Source+1) = 0xe89008ec;
*(Pointer_Raw_Source+2) = (uint)(offset_raw-12);
*(Pointer_Raw_Source+3) = 0x9090c3c9;
}

return 0;
}


nuff said.

RawCode

Difference "usejump" is:

Entry:Main
   at System.Environment.get_StackTrace() in C:\cygwin\sources\mono\mcs\class\corlib\System\Environment.cs:line 261
   at RWX.MainClass.dummy_return_B()
   at RWX.MainClass.dummy_return_A()
   at RWX.MainClass.Main(System.String[] args)
method B internal
METHOD A RETURN 64


vs

Entry:Main
   at System.Environment.get_StackTrace() in C:\cygwin\sources\mono\mcs\class\corlib\System\Environment.cs:line 261
   at RWX.MainClass.dummy_return_B()
   at RWX.MainClass.Main(System.String[] args)
method B internal
METHOD A RETURN 64


for call

   HookMethodFromTo(typeof(MainClass),"dummy_return_A",typeof(MainClass),"dummy_return_B",false\true);

Fluffy (l2032)

omgomgomg if this is what I think it is I frigging love you. I'm too drunk/tired right now to try it out, but my family may not see much of me tomorrow if this actually works :P

1000101

The jump method would be more efficient too as there is less stack pushing/popping which has the secondary advantage of keeping more stack space available to methods called within the new method call chain.

Anyway, great job, I had tried to do similar things months ago but made no headway.  If you're open it, I'd like to incorporate a simple API into CCL to do method replacements.  Full credit for getting the code to work will go to you, of course.
(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

You can use code in CCL without any restrictions.
Suggested to omit call section and use jump only.
Call implemented for comparison.

Will need some testing with nonwindows platforms.

I will contribute to CCL when got some time by providing "standard hooks" and simple event handling.

Without event handling system similar to bukkit\forge only one mod will be allowed to hook same method.

Fluffy (l2032)

I haven't had time to experiment yet, but I wonder, will this handle methods with parameters at all? I ask because you're finding methods by name, so overloads are always going to be ambiguous?


RawCode

addition of single "s" will allow to return array of methods including all overloads...

this is not complete ready to use API, this is proof of concept.

Fluffy (l2032)

Fair enough, I get that. I was just afraid it might not work with parameters at all, and sadly because of this christmas family stuff I haven't had much time to play with it yet ;)

1000101

As far as parameters are concerned, they would have to match the original function prototype.  Since the function being replaced is simply jumping to the replacement (as opposed to using the call method which would push a new return address), there would be nothing new on the stack to invalidate stack pointers.  The first parameter will still be at esp+04, the second at esp+08, etc.
(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

TheGentlmen

Mornin' fokes... been messing around with the function and now I got dis:
        unsafe static public int HookMethodFromTo (string source_method_name, string destination_method_name)
        {
            Type source_type;
            Type destination_type;

            //Drop the brackets...
            source_method_name      = source_method_name.Remove     (source_method_name.Length      - 2);
            destination_method_name = destination_method_name.Remove(destination_method_name.Length - 2);

            //Split MethodName into its parts...
            string[] source_method_name_parts      = source_method_name     .Split('.');
            string[] destination_method_name_parts = destination_method_name.Split('.');

            //Get the name of the function
            string source_method_name_end      = source_method_name_parts     [source_method_name_parts.Length - 1];
            string destination_method_name_end = destination_method_name_parts[source_method_name_parts.Length - 1];

            //Console.WriteLine(source_method_name_end + " AND " + destination_method_name_end);

            //Get the class name...
            source_method_name       = source_method_name    .Remove(source_method_name     .Length - source_method_name_end     .Length - 1);
            destination_method_name = destination_method_name.Remove(destination_method_name.Length - destination_method_name_end.Length - 1);

            //Console.WriteLine(source_method_name + " AND " + destination_method_name);

            //Convert the string into a class type
            source_type       = Type.GetType(source_method_name);
            destination_type = Type.GetType(destination_method_name);

            //Check for errors...
            if (source_type == null || destination_type == null)
            {
                Console.WriteLine("NO Type - 404 ERROR");

                return 404;
            }

            //Grab the methodinfo...
            MethodInfo Source_Method        = source_type      .GetMethod(source_method_name_end,       (BindingFlags)(60));
            MethodInfo Destination_Method = destination_type.GetMethod(destination_method_name_end, (BindingFlags)(60));

            if (Source_Method == null || Destination_Method == null)
            {
                Console.WriteLine("NO MethodInfo - 404 ERROR");

                return 404;
            }

            //Get where the function is...
            int Source_Base       = Source_Method.MethodHandle       .GetFunctionPointer().ToInt32();
            int Destination_Base = Destination_Method.MethodHandle.GetFunctionPointer().ToInt32();

            //Calculate the diffrence between the 2 function's locations
            int offset_raw   = Destination_Base - Source_Base;

            uint* Pointer_Raw_Source  = (uint*) Source_Base;

            // [WEIRD POINTER MATH] //
            //From RawCode
            *(Pointer_Raw_Source + 0) = 0xE9909090;
            *(Pointer_Raw_Source + 1) = (uint)(offset_raw - 8);
            // [/WEIRD POINTER MATH] //

            return 0;
        }

The function can be called like this:
HookMethodFromTo("Full.Path.To.Function()",
                 "Another.Full.Path.To.Another.Function.Thingy()");


Example code:
using System;
using System.Reflection;

namespace Function_Far_Far_Away
{
    class RimAPIFunctions
    {
        static internal void AAPI()
        {
            Console.WriteLine("A API");
        }
        static public void BAPI()
        {
            Console.WriteLine("B API");
        }
    }
}

namespace Function_Jumping
{
    class Program
    {
        static void Main(string[] args)
        {

            Function_Far_Far_Away.RimAPIFunctions.AAPI();
            Function_Far_Far_Away.RimAPIFunctions.BAPI();
            C();

            Console.WriteLine("HIT ENTER!!! \n\r \n\r");
            Console.ReadLine();

            //Hook Method
            FunctionJumper.HookMethodFromTo("Function_Far_Far_Away.RimAPIFunctions.AAPI()",
                                            "Function_Jumping.RimFunctions.A()");

            FunctionJumper.HookMethodFromTo("Function_Far_Far_Away.RimAPIFunctions.BAPI()",
                                           "Function_Jumping.RimFunctions.B()");

            FunctionJumper.HookMethodFromTo("Function_Jumping.Program.C()",
                                            "Function_Jumping.RimFunctions.C()");

            Function_Far_Far_Away.RimAPIFunctions.AAPI();
            Function_Far_Far_Away.RimAPIFunctions.BAPI();
            C();

            Console.WriteLine("HIT ENTER!!! \n\r \n\r");
            Console.ReadLine();
        }

        static private void C()
        {
            Console.WriteLine("C Override");
        }
    }

    class RimFunctions
    {
        static public void A()
        {
            Console.WriteLine("A NORM");
        }
        static protected void B()
        {
            Console.WriteLine("B NORM");
        }
        static private void C()
        {
            Console.WriteLine("C NORM");
        }
    }

    class FunctionJumper
    {
        unsafe static public int HookMethodFromTo (string source_method_name, string destination_method_name)
        {
            Type source_type;
            Type destination_type;

            //Drop the brackets...
            source_method_name      = source_method_name.Remove     (source_method_name.Length      - 2);
            destination_method_name = destination_method_name.Remove(destination_method_name.Length - 2);

            //Split MethodName into its parts...
            string[] source_method_name_parts      = source_method_name     .Split('.');
            string[] destination_method_name_parts = destination_method_name.Split('.');

            //Get the name of the function
            string source_method_name_end      = source_method_name_parts     [source_method_name_parts.Length - 1];
            string destination_method_name_end = destination_method_name_parts[source_method_name_parts.Length - 1];

            //Console.WriteLine(source_method_name_end + " AND " + destination_method_name_end);

            //Get the class name...
            source_method_name      = source_method_name     .Remove(source_method_name     .Length - source_method_name_end     .Length - 1);
            destination_method_name = destination_method_name.Remove(destination_method_name.Length - destination_method_name_end.Length - 1);

            //Console.WriteLine(source_method_name + " AND " + destination_method_name);

            //Convert the string into a class type
            source_type      = Type.GetType(source_method_name);
            destination_type = Type.GetType(destination_method_name);

            //Check for errors...
            if (source_type == null || destination_type == null)
            {
                Console.WriteLine("NO Type - 404 ERROR");

                return 404;
            }

            //Grab the methodinfo...
            MethodInfo Source_Method      = source_type     .GetMethod(source_method_name_end,      (BindingFlags)(60));
            MethodInfo Destination_Method = destination_type.GetMethod(destination_method_name_end, (BindingFlags)(60));

            if (Source_Method == null || Destination_Method == null)
            {
                Console.WriteLine("NO MethodInfo - 404 ERROR");

                return 404;
            }

            //Get where the function is...
            int Source_Base      = Source_Method.MethodHandle     .GetFunctionPointer().ToInt32();
            int Destination_Base = Destination_Method.MethodHandle.GetFunctionPointer().ToInt32();

            //Calculate the diffrence between the 2 function's locations
            int offset_raw = Destination_Base - Source_Base;

            uint* Pointer_Raw_Source  = (uint*) Source_Base;

            // [WEIRD POINTER MATH] //
            //From RawCode
            *(Pointer_Raw_Source + 0) = 0xE9909090;
            *(Pointer_Raw_Source + 1) = (uint)(offset_raw - 8);
            // [/WEIRD POINTER MATH] //

            return 0;
        }
    }
}


Output:

A API
B API
C Override
HIT ENTER!!!



A NORM
B NORM
C NORM
HIT ENTER!!!


Full credit to RawCode for idea...

RawCode

Jump work with parameters and return just fine from the box.

TheGentlmen

I've been interested in what your doing, so I got the code to print out what your inserting using the pointers, and the next 8 value and the 8 values before it (cause context is everything).

The output was:
A API
B API
C Override
HIT ENTER!!!



2567422952
24176
19270860
0
2567418856
24176
19270988
0
are the 8 aresses before it...
3918565520
64
Were inserted at:
23727360
and
2210793
17063680
2567410664
548464
19271392
0
0
0
are the next 8 adresses...

19270860
0
2567418856
24176
19270988
0
3918565520
64
are the 8 aresses before it...
3918565520
64
Were inserted at:
23727368
and
2567410664
548464
19271392
0
0
0
2239465
16801536
are the next 8 adresses...

1683298488
3924658433
7236
1683264696
3924658433
1889078296
2194409
33578752
are the 8 aresses before it...
3918565520
288
Were inserted at:
23727152
and
2567465960
548464
19269720
0
0
0
2567459816
24176
are the next 8 adresses...

A NORM
B NORM
C NORM
HIT ENTER!!!


So then I arranged the address and converted to Hex (a very painful procces):

Atmp 1: At 16A0D00
9907C3E8
5E70
1260CCC
0
9907B3E8
5E70
1260D4C
0
E9909090
40
21BBE9
1045F00
990793E8
85E70
1260EE0
0
0
0

Atmp 2: At 16A0D08
1260CCC
0
9907B3E8
5E70
1260D4C
0
E9909090
40
E9909090
40
990793E8
85E70
1260EE0
0
0
0
222BE9
1005F00

Atmp 3: At 16A0C30
645518B8
E9ED8901
1C44
645494B8
E9ED8901
70990C18
217BE9
2005F00
E9909090
120
99086BE8
85E70
1260858
0
0
0
990853E8
5E70


Well... that's utter gibberish to me... so I got an disassembler and did it on all 3 parts... i got dis:
https://www.onlinedisassembler.com/odaweb/f1x3G0GS/0
https://www.onlinedisassembler.com/odaweb/xmmXha51/0
https://www.onlinedisassembler.com/odaweb/9pFiKhmn/0 (Odd, E9909090 wasn't split into 'jmp 99090' then 'NOP', I assume its faulty disassembling)

The common thing between the 3 is that you jump to 9090 (where either that is), then do nothing (NOP literately means do nothing), then add a random function (the difference between the 2 pointers).

Of course, my question is that if you jump to 9090 then why insert something else? NOP does nothing, and inserting a random function is sure to crash the program. I'm I missing something?

RawCode

1) Endianness
2)

int TMP = 0;
int I = 0;
bool resflag = false;
byte* POINTER_1x = (byte*)POINTER_1;
for (;;)
{
TMP = *(POINTER_1x + I);
I++;
Console.WriteLine (TMP.ToString ("X2"));

if (TMP == 0xC3)
if (resflag)
break;
resflag = false;

if (TMP == 0xC9 && !resflag)
{
resflag = true;
continue;
}
}


3) You need assembly and c knowledge to play with mono internals (basic level will be fine, but you need it)

4) E9909090 is

DO NOTHING (0x90)
DO NOTHING (0x90)
DO NOTHING (0x90)
JUMP NEAR (0xE9)

5) Why nops? answer is alignment.

TheGentlmen

Hohoho, todays Christmas New years present is EVENTS...

GET IT HERE:
https://files.slack.com/files-pri/T07L8SKEY-F0HJM0NNN/download/code_example_followed_by_function_itself___.txt

You'll need to copy the whole class ONCE FOR EVERY FUNCTION in RW... enjoy the fun...
Please note that functionToOverrideName needs to be the name of the function you'll override BEFORE ya run the Init() function... after that your too late too change it.

Since you, RawCode, are the master of convoluted (no offence :) ) code I'd be joyful if you could find some sort of convoluted (no offense once again) method to insure that we some unlucky poor sod doesn't have to copy it 8bil ^ 9bil times...

Ty in advanced...

RawCode

slack force registration.

use pastebin instead.