Code injection development tutorial basics

Started by RawCode, January 23, 2017, 03:08:23 AM

Previous topic - Next topic

RawCode

This tutorial about "developing" code injection routines, not about using them.

1) You will need mono runtime source code, there are two options:
"staging" version provided by unity https://github.com/Unity-Technologies/mono?files=1
or mono-2 provided by mono itself https://github.com/mono/mono/tree/mono-2-0

Mono source code is "C" (without sharp) with lots and lots of pointers, you don't really need to know C to read sources, still, there are two major tricks:

Number of declarations have special versions with leading underscore, like _MonoMethod normally, such declarations do have actual code and never referenced from anywhere, expect place of declaration.

Watch for pointer sign (*), especially in case of structs.
If one struct reference other struct directly, it will be injected completely and can be typecasted both directions.
If one struct reference other struct by pointer, only 4-8 bytes reference is provided and structs cannot be typecasted directly.

Some structs allocated and passed from middle of declaration, VTABLE stuff is done by typecasting pointer to 3 different objects, many other objects created similar way.

2) You need notepad++ to navigate around source code.

3) Start from simple task, get native code of some "extern" method, like "GetFunctionPointer" nothing complicated.
There number of abstraction layer, you need to get self (without any 3rd party help) actual method that do return pointer you need.

4) You can't use number of native methods directly, due to issues with export table - some methods are not present in export table and registered only as ICALLS, some methods are removed from export table and not registered as ICALLS.
To make sure, is method available from game, use tools like DLL export viewer http://www.nirsoft.net/utils/dll_export_viewer.html

also you can test it directly, but this is time consuming task

5) About time consumption, it's viable to setup workspace and perform compilation of dll, that can run standalone and from game at same time without any modifications.

First of all, mark your main type with
[StaticConstructorOnStartup]

Then, create static constructor, similar to provided one
static Kagimono()
{
Action tx = new Action(delegate
{
Main(null);
});

Verse.LongEventHandler.QueueLongEvent (tx, "MixedMain", false, null);
}


Under normal conditions you are not allowed to invoke any platform methods, especially methods related to modification of executable memory from non main thread.
Such construction ensure, that your main will be invoked by game from main thread.

Manager do nothing outside from game, so you don't need any checks or similar constructions if you running project externally.

6) You can execute DLL if it have entry point
"C:\Program Files (x86)\Mono\bin\mono" --debug "D:\User\Desktop\MonoRuntimeTest\bin\Release\MonoRuntimeTest.dll"

Just make sure, that you have only mono-2 or reference proper version of mono, in other case, you may brew code, that will not work properly ingame.

7) Make sure that you are executing your code with mono runtime, MS runtime have completely different rules related to code injection.

8) After some time, you will need to work with x86-64 there is no way around, C# is jit based framework and all your methods are compiled on demand at moment of execution.

Reading assembler a lot more simple with services like
https://defuse.ca/online-x86-assembler.htm#disassembly

9) To extract opcodes from function pointer, you may brew something yourself, or use code provided below.
NOTE: it does not work in current version of game, without changing __Internal to mono.dll (for windows)
Multibind solution is WIP at this moment, check for updates soon.
using System.Runtime.CompilerServices;
using System.Reflection;
using System.Text;

namespace rcrml
{
unsafe public class _FunctionHandle
{
[DllImport("__Internal")]//replace to meta mono
extern static void* mono_domain_get ();

[DllImport("__Internal")]//replace to meta mono
static extern private unsafe _MonoJitInfo* mono_jit_info_table_find(void* _MonoDomainPtr, void* _FuncPtr);
public unsafe 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
public void* function_ptr;
public _MonoJitInfo* mono_jit_info_ptr;
public byte[] function_image;//this is managed pointer, be carefull

public _FunctionHandle(void* _FuncPtr)
{
mono_jit_info_ptr = mono_jit_info_table_find (mono_domain_get (),_FuncPtr);
function_ptr = _FuncPtr;//can be non equal to code start
}

[MethodImpl(MethodImplOptions.NoInlining)]
public MethodBase GetReflectionHandle()
{
return MethodBase.GetMethodFromHandle (mono_jit_info_ptr->method);
}//return same type actually - MonoMethod

[MethodImpl(MethodImplOptions.NoInlining)]
public void SyncRaw2Image()
{//do not change size inside  jit info struct, it will affect runtime
int sizefixup = Math.Max (mono_jit_info_ptr->code_size, sizeof(void*) * 2);
byte[] tmpret = new byte[sizefixup];
Marshal.Copy (mono_jit_info_ptr->code_start, tmpret, 0, sizefixup);
function_image = tmpret;
}

[MethodImpl(MethodImplOptions.NoInlining)]
public void SyncImage2Raw()
{
Marshal.Copy (function_image, 0, mono_jit_info_ptr->code_start, mono_jit_info_ptr->code_size);
}

[MethodImpl(MethodImplOptions.NoInlining)]
public void PrepareJmpABS64AX(void* _TargetFuncPtr)//absolute 64 AX register (EAX\RAX)
{
//function_image [0] = 0x90;

//function_image [1] = ((byte*)&offset)[0];
//function_image [2] = ((byte*)&offset)[1];
//function_image [3] = ((byte*)&offset)[2];
//function_image [4] = ((byte*)&offset)[3];

}

[MethodImpl(MethodImplOptions.NoInlining)]
public void PrepareJmpREL32I(void* _TargetFuncPtr)//relative 32 bit intermediate
{
int offset = (int)_TargetFuncPtr - (int)mono_jit_info_ptr->code_start;

function_image [0] = 0x90;

function_image [1] = ((byte*)&offset)[0];
function_image [2] = ((byte*)&offset)[1];
function_image [3] = ((byte*)&offset)[2];
function_image [4] = ((byte*)&offset)[3];

}

[MethodImpl(MethodImplOptions.NoInlining)]
public void PrepareJmpABS32PR(void* _TargetFuncPtr)//absolute 32 bit push return
{
//always fits, min size is sizeof(void*)*2 (8-16)
//does not works on inlined methods
//obviously will damage function, still, you can save image
//before setting jump to rollback if you need
function_image [0] = 0x68;

function_image [1] = ((byte*)&_TargetFuncPtr)[0];
function_image [2] = ((byte*)&_TargetFuncPtr)[1];
function_image [3] = ((byte*)&_TargetFuncPtr)[2];
function_image [4] = ((byte*)&_TargetFuncPtr)[3];

function_image [5] = 0xC3;
}

[MethodImpl(MethodImplOptions.NoInlining)]
public string PrintFromShallowCopy()
{
StringBuilder sb = new StringBuilder ();
MethodBase mb = GetReflectionHandle ();
sb.Append ("IP BEGIN " + mb +" ( "+String.Format("{0:X2}", (int)function_ptr)+" )");

foreach (byte i in function_image)
{
sb.Append ("\n" + String.Format("{0:X2}", i));
}
sb.Append ("\nIP END " + mb +" ( "+String.Format("{0:X2}", (int)function_ptr)+" )");

return sb.ToString ();
}

}
}


Typical output looks like:

IP BEGIN Void TEST_BASE_NONDYNAMIC() ( 2B5C488 )
55
8B
EC
83
EC
08
8B
05
E0
3F
51
00
83
EC
0C
50
E8
57
3E
FE
FF
83
C4
10
C9
C3
IP END Void TEST_BASE_NONDYNAMIC() ( 2B5C488 )


Where "Void TEST_BASE_NONDYNAMIC()" is full name of method extracted from pointer and "( 2B5C488 )" is pointer itself.
Byte per line is opcodes of passed method, size of functions is calculated automatically (managed methods only).

Usage is:
void* dx = typeof(Kagimono).GetMethod ("TEST_BASE_NONDYNAMIC").MethodHandle.GetFunctionPointer ().ToPointer ();

_FunctionHandle x = new _FunctionHandle (dx);
x.SyncRaw2Image ();
Console.WriteLine (x.PrintFromShallowCopy ());


Just copypaste output of PrintFromShallowCopy to defuse.ca and you will get a bit more usefull representation of data
SEE ATTACHMENT #1

11) As you can see, defuse.ca can be used to compile basic assembly and provide you C# compatible array, you can use it directly with method like (not part of library due to "reasons")
[MethodImpl(MethodImplOptions.NoInlining)]
public void LoadImageParams(params byte[] data)
{
function_image = data;
}


Lets compile "mov EAX,EBP;RET;"
SEE ATTACHMENT #2


Resulting array is "{ 0x89, 0xE8, 0xC3 }" just copypaste it into LoadImageParams

x.LoadImageParams( 0x89, 0xE8, 0xC3 );

Performing "SyncImage2Raw" will result in loading new code for method, running method will invoke your code.

13) Some methods are special, they are wrapped or "gated", you can notice such conditions ever without special skills - change method, get function image, change method again, add new call, or remove one of calls, get function image again, if image does not reflect your changes you are peeking at trampoline.

If multiple methods provide same function pointer, you are peeking at shared trampoline and actual method is passed as argument.
Noticing same pointer condition only possible at same run, different runs always result in different pointers, this is due to memory allocation.

[attachment deleted by admin due to age]

Brrainz

Excellent start RawCode!
I had started a similar approach last night but yours got much further. I will use this to analyze how to get through the trampoline code to the real code. Or I try to call mono's compile method - I have seen it to be invoked with the method token in the generic trampoline.

More info on that here: https://monoruntime.wordpress.com/2009/04/13/magic-of-trampolines/

My assumption here is that it is ok to overwrite a trampoline with a jump to the replacement code but you need to make sure that the pointer to the replacement code does not point to a trampoline itself. Correct me if this is a wrong assumption.

RawCode

"mov EAX,EBP;RET;"
is not random set of values, running such code from inside of dynamic method allows to calculate it's real pointer without reading source code of mono, basically this is dirty shortcut but it's "works".

outdated still viable as generic reference
https://ludeon.com/forums/index.php?topic=28176.msg285219#msg285219

Brrainz

So you are saying that if I "prerun" my dynamic method it could use a method prepared with this assembler to find out its own address and report it back. Then I could patch the original with an ordinary detour jump to that address. Is that what your are saying?

RawCode

#4
saying? no, it actually do works:
static unsafe public void Main (string[] args)
{
Console.WriteLine ("Entry:start");

//section related to creation of dynamic method

//take nothing return nothing
DynamicMethod dynmethod = new DynamicMethod ("dynmethod", typeof(void), new Type[0]);
ILGenerator dynmethodilgen = dynmethod.GetILGenerator ();

//just compile your project and copypaste IL from itself with dnspy or similar tools
//we going to reimplement DynamicMethodTest1

dynmethodilgen.EmitCall (OpCodes.Call, typeof(Kagimono).GetMethod ("__rejit_ReturnCallSitePTR"), null);
dynmethodilgen.Emit (OpCodes.Stsfld, typeof(Kagimono).GetField ("sfield_for_dmethod_test_1"));
dynmethodilgen.Emit (OpCodes.Ret);
dynmethod.Invoke (null, null);

Console.WriteLine ("basic return");
Console.WriteLine ("{0:X2}",(int)sfield_for_dmethod_test_1);
//basic invoke test ends here


void* nativeptrrejit = typeof(Kagimono).GetMethod ("__rejit_ReturnCallSitePTR").
MethodHandle.GetFunctionPointer().ToPointer();

_FunctionHandle x1 = new _FunctionHandle (nativeptrrejit);

x1.SyncRaw2Image ();//read function data into array
x1.SyncParams2Image( 0x8B, 0x44, 0x24, 0x00, 0xC3 );//modify array, if you need rollback data, save before this
x1.SyncImage2Raw ();//write array into function data, this do actual modification

dynmethod.Invoke (null, null);
Console.WriteLine ("return after modification");
Console.WriteLine ("{0:X2}",(int)sfield_for_dmethod_test_1);

_FunctionHandle dynstuff = new _FunctionHandle (sfield_for_dmethod_test_1);
dynstuff.SyncRaw2Image ();

Console.WriteLine (dynstuff);

Console.WriteLine ("Entry:complete");
}


result for dynamic method is proper code:
0:  55                      push   ebp
1:  8b ec                   mov    ebp,esp
3:  83 ec 08                sub    esp,0x8
6:  e8 15 13 f4 fe          call   0xfef41320
b:  8b c8                   mov    ecx,eax
d:  b8 e0 3f 5e 00          mov    eax,0x5e3fe0
12: 89 08                   mov    DWORD PTR [eax],ecx
14: c9                      leave
15: c3                      ret


this does not require to read wall of text related to magic trampolines and in addition, does essential part of actually compiling dynamic method.

RawCode

updated lib code:

static public void* __rejit_ReturnCallSitePTR()
{
//padding is required if you going to write something big
//returning pointer stored on top of stack does not need any padding
Console.WriteLine ("PADDING");
Console.WriteLine ("PADDING");
Console.WriteLine ("PADDING");
Console.WriteLine ("PADDING");
return (void*)0xDEAD;
}

static public void* sfield_for_dmethod_test_1 = (void*)0;
static public void DynamicMethodTest1()
{
sfield_for_dmethod_test_1 = __rejit_ReturnCallSitePTR ();
}



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
{
[DllImport("__Internal")]//replace to meta mono
extern static void* mono_domain_get ();

[DllImport("__Internal")]//replace to meta mono
static extern private unsafe _MonoJitInfo* mono_jit_info_table_find(void* _MonoDomainPtr, void* _FuncPtr);
public unsafe 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
public void* function_ptr;
public _MonoJitInfo* mono_jit_info_ptr;
public byte[] function_image;//this is managed pointer, be carefull

public _FunctionHandle(void* _FuncPtr)
{
mono_jit_info_ptr = mono_jit_info_table_find (mono_domain_get (),_FuncPtr);
function_ptr = _FuncPtr;//can be non equal to code start
}

[MethodImpl(MethodImplOptions.NoInlining)]
public MethodBase GetReflectionHandle()
{
return MethodBase.GetMethodFromHandle (mono_jit_info_ptr->method);
}//return same type actually - MonoMethod

[MethodImpl(MethodImplOptions.NoInlining)]
public void SyncRaw2Image()
{//do not change size inside  jit info struct, it will affect runtime
int sizefixup = Math.Max (mono_jit_info_ptr->code_size, sizeof(void*) * 2);
byte[] tmpret = new byte[sizefixup];
Marshal.Copy (mono_jit_info_ptr->code_start, tmpret, 0, sizefixup);
function_image = tmpret;
}

[MethodImpl(MethodImplOptions.NoInlining)]
public void SyncImage2Raw()
{
Marshal.Copy (function_image, 0, mono_jit_info_ptr->code_start, mono_jit_info_ptr->code_size);
}

[MethodImpl(MethodImplOptions.NoInlining)]
public void SyncParams2Image(params byte[] _array)
{
Array.Copy (_array, function_image, _array.Length);
}

[MethodImpl(MethodImplOptions.NoInlining)]
public void PrepareJmpABS64AX(void* _TargetFuncPtr)//absolute 64 AX register (EAX\RAX)
{
//function_image [0] = 0x90;

//function_image [1] = ((byte*)&offset)[0];
//function_image [2] = ((byte*)&offset)[1];
//function_image [3] = ((byte*)&offset)[2];
//function_image [4] = ((byte*)&offset)[3];

}

[MethodImpl(MethodImplOptions.NoInlining)]
public void PrepareJmpREL32I(void* _TargetFuncPtr)//relative 32 bit intermediate
{
int offset = (int)_TargetFuncPtr - (int)mono_jit_info_ptr->code_start;

function_image [0] = 0x90;

function_image [1] = ((byte*)&offset)[0];
function_image [2] = ((byte*)&offset)[1];
function_image [3] = ((byte*)&offset)[2];
function_image [4] = ((byte*)&offset)[3];

}

[MethodImpl(MethodImplOptions.NoInlining)]
public void PrepareJmpABS32PR(void* _TargetFuncPtr)//absolute 32 bit push return
{
//always fits, min size is sizeof(void*)*2 (8-16)
//does not works on inlined methods
//obviously will damage function, still, you can save image
//before setting jump to rollback if you need
function_image [0] = 0x68;

function_image [1] = ((byte*)&_TargetFuncPtr)[0];
function_image [2] = ((byte*)&_TargetFuncPtr)[1];
function_image [3] = ((byte*)&_TargetFuncPtr)[2];
function_image [4] = ((byte*)&_TargetFuncPtr)[3];

function_image [5] = 0xC3;
}

[MethodImpl(MethodImplOptions.NoInlining)]
public override string ToString()
{
StringBuilder sb = new StringBuilder ();
MethodBase mb = GetReflectionHandle ();

int delta = (int)function_ptr - (int)mono_jit_info_ptr->code_start;

sb.Append ("IP BEGIN " + mb + "+" + string.Format("{0:X4}",delta) +" ( "+String.Format("{0:X2}", (int)function_ptr)+" )");

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_ptr)+" )");

return sb.ToString ();
}

}
}

Brrainz

Just what the doctor ordered. Thank you a million times, RawCode! I will build this into Harmony - finally someone who thinks outside the box.

:-D

/Andreas

PS: I will also try to use parts of this code to locate dlls dynamically: http://dimitry-i.blogspot.se

RawCode

actually i hit some issues with dynamic methods, i never tested dynamic methods directly, just assumed that direct reading of function pointer result in magic trampoline code, but, looks like this is not true.

in my testing case, reading function pointer directly and fetching it via stackwalk resulted in same value.
will perform additional checks later, for now, do not rely on provided code "blindly" check it before use, probably something is wrong.

Brrainz

Oh, I don't trust your code for 5 seconds. I am trying to replicate and test on my own. Peer review! But I was impressed by some parts of it and I got inspired to do more research & coding myself. It's nice to know that someone else also looks into this and that I am not alone here.

I think what you describe is basically what I experience when my original code did work for 1 dynamic method, but not with 2.

Brrainz

I am at work, so I only have access to Visual Studio Preview with mono 2.11.4 installed on my Mac. I took your original code and ran it:


// patchMethod is an instance field set to the result of getDynamicMethod()
patchMethod.Invoke(null, null);
void* dx = patchMethod.MethodHandle.GetFunctionPointer().ToPointer();
SetFunctionPtr(dx);
SyncRaw2Image();
Console.WriteLine(PrintFromShallowCopy());


and I get three times different pointers and different asm code containing the actual code that matches the dynamic methods. Here is the way I generate my dynamic method with:


public static MethodInfo getDynamicMethod(int intValue, Type parent)
{
var m = typeof(Console).GetMethod("WriteLine", new Type[] { typeof(int) });
var d = new DynamicMethod("", typeof(void), new Type[] { }, parent);
var il = d.GetILGenerator();
il.DeclareLocal(typeof(int));
il.Emit(OpCodes.Ldc_I4, intValue);
il.Emit(OpCodes.Call, m);
il.Emit(OpCodes.Ret);
return d;
}


And this is the output for three different runs within the same launch:


287454020
Void () (0x14C9008)
55 8B EC 83 EC 08 83 EC 0C 68 44 33 22 11 90 E8 9C 31 FE FF 83 C4 10 C9 C3
1146447479
Void () (0x14C9030)
55 8B EC 83 EC 08 83 EC 0C 68 77 66 55 44 90 E8 74 31 FE FF 83 C4 10 C9 C3
305419896
Void () (0x14C9058)
55 8B EC 83 EC 08 83 EC 0C 68 78 56 34 12 90 E8 4C 31 FE FF 83 C4 10 C9 C3


That part seems to work fine.

RawCode

#10
0:  55                      push   ebp
1:  8b ec                   mov    ebp,esp
3:  83 ec 08                sub    esp,0x8
6:  83 ec 0c                sub    esp,0xc
9:  68 44 33 22 11          push   0x11223344
e:  90                      nop
f:  e8 9c 31 fe ff          call   0xfffe31b0
14: 83 c4 10                add    esp,0x10
17: c9                      leave
18: c3                      ret


vs

0:  55                      push   ebp
1:  8b ec                   mov    ebp,esp
3:  83 ec 08                sub    esp,0x8
6:  83 ec 0c                sub    esp,0xc
9:  68 77 66 55 44          push   0x44556677
e:  90                      nop
f:  e8 74 31 fe ff          call   0xfffe3188
14: 83 c4 10                add    esp,0x10
17: c9                      leave
18: c3                      ret


call takes relative offset, for this reason, two absolutely same methods calling same method will result in different offsets, due to different base of methods.

post is changed completely due to missread.

Brrainz

But those two dynamic methods are not the same. They routine that builds them adds a constant that is different in IL code and that shows as well in the assembler (the two push 0x.....)

Right now, I look into why my dynamic methods that I generate in RW are generating trampoline assembler instead of the test application that i start with mono.exe

RawCode

First, emit and compile
dynmethodilgen.EmitCall (OpCodes.Call, typeof(Kagimono).GetMethod ("__rejit_ReturnCallSitePTR"), null);
dynmethodilgen.Emit (OpCodes.Stsfld, typeof(Kagimono).GetField ("sfield_for_dmethod_test_1"));
dynmethodilgen.Emit (OpCodes.Ret);


then emit and compile

dynmethodilgen.EmitCall (OpCodes.Call, typeof(Kagimono).GetMethod ("__rejit_ReturnCallSitePTR"), null);
dynmethodilgen.Emit (OpCodes.Stsfld, typeof(Kagimono).GetField ("sfield_for_dmethod_test_1"));
dynmethodilgen.EmitCall (OpCodes.Call, typeof(Kagimono).GetMethod ("__rejit_ReturnCallSitePTR"), null);
dynmethodilgen.Emit (OpCodes.Stsfld, typeof(Kagimono).GetField ("sfield_for_dmethod_test_1"));
dynmethodilgen.Emit (OpCodes.Ret);


check resulting ASM in both cases at same session, if resulting bytecode is equal - trampoline no questions, if different - it's not trampoline.

also you MUST invoke or cunstruct delegate or invoke native constructor for dynamic method, in other case function pointer is invalid.

RawCode

checked in game and externally, i am getting SAME x86 in both cases, probably we are talking about different things or it's platform related.