A library to modify MSIL/Native code at runtime.
It's a library built in .NET Standard 2.0, works on all version >=.NET Core 2.0.
.NET Core (2.1 ~ 3.1) | .NET 5~8 | .NET Framework | Mono | |
---|---|---|---|---|
Windows (x86/x64/ARM64) | ✔️ | ✔️ | ❌ Not supported | ❌ Not supported |
Linux (x86/x64/ARM64) | ✔️ | ✔️ | ❌ Not supported | ❌ Not supported |
MacOS (x86/x64/ARM64) | ✔️ | ✔️ | ❌ Not supported | ❌ Not supported |
Jitex can help you replace code at runtime easily.
using System;
using Jitex;
JitexManager.MethodResolver += context =>
{
if (context.Method.Name.Contains("Sum"))
context.ResolveMethod(Mul); //Replace Sum by Mul
};
int result = Sum(5, 5);
Console.WriteLine(result); //Output is 25
static int Sum(int n1, int n2) => n1 + n2;
static int Mul(int n1, int n2) => n1 * n2;
- Intercept call method
- Modify normal and generic methods
- Detour method
- Replace MSIL code (IL)
- Replace native code
- Execute custom metadatatoken
- Replace content string
- Modules
using System;
using Jitex;
JitexManager.MethodResolver += context =>
{
if (context.Method.Name.Contains("Sum"))
context.InterceptCall();
};
//Every call from Sum, will be pass here.
JitexManager.Interceptor += async context =>
{
//Get parameters passed in call
int n1 = context.GetParameterValue<int>(0);
int n2 = context.GetParameterValue<int>(1);
n1 *= 10;
n2 *= 10;
//Override parameters value
context.SetParameterValue(0, n1);
context.SetParameterValue(1, n2);
//Or we can just set return value
context.SetReturnValue(100);
};
int result = Sum(5, 5); //Output is 100
Console.WriteLine(result);
int Sum(int n1, int n2) => n1 * n2;
/// <summary>
/// Take sum of 2 random numbers
/// </summary>
/// <returns></returns>
public static int SumReplace ()
{
const string url = "https://www.random.org/integers/?num=2&min=1&max=999&col=2&base=10&format=plain&rnd=new";
using HttpClient client = new HttpClient ();
using HttpResponseMessage response = client.GetAsync (url).Result;
string content = response.Content.ReadAsStringAsync ().Result;
string[] columns = content.Split ("\t");
int num1 = int.Parse (columns[0]);
int num2 = int.Parse (columns[1]);
return num1 + num2;
}
private static void MethodResolver (MethodContext context)
{
if (context.Method.Name == "Sum") {
//Replace Sum to our SumReplace
MethodInfo replaceSumMethod = typeof (Program).GetMethod (nameof (SumReplace));
context.ResolveMethod (replaceSumMethod);
}
}
private static void MethodResolver (MethodContext context) {
if (context.Method.Name == "Sum") {
//Detour by MethodInfo
MethodInfo detourMethod = typeof (Program).GetMethod (nameof (SumDetour));
context.ResolveDetour (detourMethod);
//or
context.ResolveDetour<Action> (SumDetour);
//Detour by Action or Func
Action<int, int> detourAction = (n1, n2) => {
Console.WriteLine ("Detoured");
Console.WriteLine (n1 + n2);
};
context.ResolveDetour (detourAction);
//Detour by Address
IntPtr addressMethod = default; //Address of method to execute.
context.ResolveDetour (addressMethod);
}
}
private static void MethodResolver (MethodContext context)
{
if (context.Method.Name == "Sum") {
//num1 * num2
byte[] newIL = {
(byte) OpCodes.Ldarg_0.Value, //parameter num1
(byte) OpCodes.Ldarg_1.Value, //parameter num2
(byte) OpCodes.Mul.Value,
(byte) OpCodes.Ret.Value
};
MethodBody body = new MethodBody (newIL, context.Method.Module);
context.ResolveBody (body);
}
}
private static void MethodResolver (MethodContext context)
{
if (context.Method.Name == "Sum") {
Assembler assembler = new Assembler (64);
//Replace with fatorial number:
//int sum = num1+num2;
//int fatorial = 1;
//for(int i = 2; i <= sum; i++){
// fatorial *= i;
//}
//return fatorial;
assembler.add (edx, ecx);
assembler.mov (eax, 1);
assembler.mov (ecx, 2);
assembler.cmp (edx, 0x02);
assembler.jl (assembler.@F);
assembler.AnonymousLabel ();
assembler.imul (eax, ecx);
assembler.inc (ecx);
assembler.cmp (ecx, edx);
assembler.jle (assembler.@B);
assembler.AnonymousLabel ();
assembler.ret ();
using MemoryStream ms = new MemoryStream ();
assembler.Assemble (new StreamCodeWriter (ms), 0);
byte[] asm = ms.ToArray ();
context.ResolveNative (asm);
}
}
You can inject a custom metadata too, in this way, you can "execute" metadatatoken not referenced in compile-time:
/// <summary>
/// Example of a external library to replace Sum.
/// </summary>
/// <remarks>
/// We replace Sum to return the PID of process running. To do this, normally we need
/// reference assembly (System.Diagnostics.Process) and class Process.
/// In this case, the original module, dont have any reference to namespace System.Diagnostics.Process.
/// As we pass the MetadataToken from Process.GetCurrentProcess().Id, its necessary resolve that manually,
/// because CLR dont have any information about that in original module.
/// </remarks>
public static class ExternLibrary
{
private static MethodInfo _getCurrentProcess;
private static MethodInfo _getterId;
static ExternLibrary()
{
LoadAssemblyDiagnostics();
}
public static void Initialize()
{
JitexManager.AddMethodResolver(MethodResolver);
JitexManager.AddTokenResolver(TokenResolver);
}
private static void LoadAssemblyDiagnostics()
{
string pathAssembly = Path.Combine(Directory.GetCurrentDirectory(), "../../../../", "System.Diagnostics.Process.dll");
Assembly assemblyDiagnostics = Assembly.LoadFrom(pathAssembly);
Type typeProcess = assemblyDiagnostics.GetType("System.Diagnostics.Process");
_getCurrentProcess = typeProcess.GetMethod("GetCurrentProcess");
_getterId = _getCurrentProcess.ReturnType.GetProperty("Id", BindingFlags.Public | BindingFlags.Instance).GetGetMethod();
}
private static void TokenResolver(TokenContext context)
{
if (context.TokenType == TokenKind.Method && context.Source.Name == "Sum")
{
if (context.MetadataToken == _getCurrentProcess.MetadataToken)
{
context.ResolveMethod(_getCurrentProcess);
}
else if (context.MetadataToken == _getterId.MetadataToken)
{
context.ResolveMethod(_getterId);
}
}
}
private static void MethodResolver(MethodContext context)
{
if (context.Method.Name == "Sum")
{
List<byte> newIl = new List<byte>();
newIl.Add((byte)OpCodes.Call.Value);
newIl.AddRange(BitConverter.GetBytes(_getCurrentProcess.MetadataToken));
newIl.Add((byte)OpCodes.Call.Value);
newIl.AddRange(BitConverter.GetBytes(_getterId.MetadataToken));
newIl.Add((byte)OpCodes.Ret.Value);
MethodBody methodBody = new MethodBody(newIl.ToArray(), _getCurrentProcess.Module);
context.ResolveBody(methodBody);
}
}
}
static void Main (string[] args) {
ExternLibrary.Initialize ();
int result = Sum (1, 7);
Console.WriteLine (result); //output is PID
}
private static void TokenResolver (TokenContext context) {
if (context.TokenType == TokenKind.String && context.Content == "Hello World!")
context.ResolveString ("H3110 W0RLD!");
}
static void Main (string[] args) {
ExternLibrary.Initialize ();
HelloWorld (); //output is H3110 W0RLD!
}
static void HelloWorld () {
Console.WriteLine ("Hello World!");
}
Jitex can support modules. To create your own module, just extend JitexModule:
public class ModuleJitex : JitexModule
{
protected override void MethodResolver(MethodContext context)
{
//...
}
protected override void TokenResolver(TokenContext context)
{
//...
}
}
And load module:
JitexManager.LoadModule<ModuleJitex>();
//or...
JitexManager.LoadModule(typeof(ModuleJitex));
To load module in ASP.NET Core, just call UseModule in Configure (Startup.cs):
app.UseModule<ModuleJitex>();
app.UseModule<ModuleJitex1>();
app.UseModule<ModuleJitex2>();
//or
app.UseModule(typeof(ModuleJitex);
Nitter - A easy mocker for .NET
AutoMapper Patcher - A simple remover AutoMapper at runtime.
InAsm - Run assembly directly from a method.
Replace methods was an idea to increase performance in .NET Applications. Searching a way to do that, i've found this hook implementation from @xoofx Writing a Managed JIT in C# with CoreCLR, which became core of Jitex.