Click here to Skip to main content
15,867,704 members
Articles / Web Development / HTML

.NET CLR Injection: Modify IL Code during Run-time

Rate me:
Please Sign up or sign in to vote.
4.98/5 (240 votes)
7 Aug 2014LGPL310 min read 591.3K   18.3K   352   105
Modify methods' IL codes on runtime even if they have been JIT-compiled, supports release mode / x64 & x86, and variants of .NET versions, from 2.0 to 4.5.

 

Introduction   

You can download the demo and have a try, it demonstrates modifying .Net method code during runtime.

  • Supports variants of .NET versions from 2.0 to 4.5 
  • Supports variants of methods to be modified, including dynamic methods and generic methods.
  • Supports release mode .NET process.   
  • Supports both x86 & x64.    

Modifying .NET methods' MSIL codes during run-time is very cool, it helps to implement hooking, software protection, and other amazing stuff. That's why I want it, but there is a big challenge on the road -- the MSIL code could have been complied to native code by JIT-complier before we have a chance to modify; also the .NET CLR implementation is not documented and it changes during each version, we need a reliable and stable way without dependency to the exact memory layout. 

Anyway, after more than one week research, finally I made it! Here is a simple method in the demo problem:

C#
protected string CompareOneAndTwo()
{
    int a = 1;
    int b = 2;
    if (a < b)
    {
        return "Number 1 is less than 2";
    }
    else
    {
        return "Number 1 is greater than 2 (O_o)";
    }
}

Certainly it returns "Number 1 is less than 2"; let's try to make it return the incorrect result "Number 1 is greater than 2 (O_o)".

Looking at the MSIL code for this method, we can do it by changing the opcode from Bge_S to Blt_S. And then the jump works in a different logic which returns in a wrong result, that is what I need.

Image 1

And if you try in the demo application, it shows a wrong answer as below.

Image 2

Here is the code replacing the IL, I assume there are enough comments between the lines.

Image 3

Hook .NET Method 

According to the explaination in ECMA-335, OpCode jmp is used to transfer control to destination method. Unlike OpCode call, current parameters are transferred to destination method -- this is much simpler.

Image 4

For example, there is a method declared in the sample app.

C#
string TargetMethod(string a, string b)
{
      return a + "," + b;
}

To hook above method, first declare the destination method with the same parameters and return type.

C#
string ReplaceMethod(string a, string b)
{
    return string.Format( "This method is hooked, a={0};b={1};", a, b);
}

Then, prepare the IL code to be inserted into TargetMethod.

C#
MethodInfo replaceMethod = type.GetMethod("ReplaceMethod", BindingFlags.NonPublic | BindingFlags.Instance);

byte[] ilCodes = new byte[5];
ilCodes[0] = (byte)OpCodes.Jmp.Value;
ilCodes[1] = (byte)(replaceMethod.MetadataToken & 0xFF);
ilCodes[2] = (byte)(replaceMethod.MetadataToken >> 8 & 0xFF);
ilCodes[3] = (byte)(replaceMethod.MetadataToken >> 16 & 0xFF);
ilCodes[4] = (byte)(replaceMethod.MetadataToken >> 24 & 0xFF);

The first byte is the OpCode jmp, and then follows the destination method's token. The IL codes are updated to the TargetMethod.

C#
MethodInfo targetMethod = type.GetMethod("TargetMethod", BindingFlags.NonPublic | BindingFlags.Instance);
InjectionHelper.UpdateILCodes(targetMethod, ilCodes);

Finally TargetMethod is hooked.

Image 5

Using the code 

Copy the InjectionHelper.cs file into your project, it contains several methods.

C#
public static class InjectionHelper
{
    // Load the unmanaged injection.dll, the initlaization happens in a background thread
    public static void Initialize()
 
    // Unload the unmanaged injection.dll
    public static void Uninitialize()
 
    // Update the IL Code of a Method.
    public static void UpdateILCodes(MethodInfo method, byte[] ilCodes, int nMaxStack = -1) 
 
    // The method returns until the initialization is completed
    public static Status WaitForIntializationCompletion()
}

The InjectionHelper.Initialize method loads the unmanaged injection.dll from the directory of the current assembly directory, so all the related files need to be there, or you can modify the code to change the location.

Here is the file list. 

File Name Description
Injection32.dll Unmanaged DLL to do the work in this article (x86 version) 
Injection64.dll Unmanaged DLL to do the work in this article (x64 version) 

 

Background  

Replace the IL code

First, take a look at how the CLR and JIT works.

Image 6

The JIT implementation DLLs (clrjit.dll for .Net4.0+ / mscorjit.dll for .NET 2.0+) exports a _stdcall method getJit, which returns the ICorJitCompiler interface. 

The CLR implementation DLLs (clr.dll for .NET 4.0+ / mscorwks.dll for .NET 2.0+) invokes the getJit method to obtain the ICorJitCompiler interface, then calls its compileMethod method to compile MSIL code to native code.

C++
CorJitResult compileMethod(ICorJitInfo * pJitInfo, CORINFO_METHOD_INFO * pMethodInfo, 
   UINT nFlags, LPBYTE * pEntryAddress, ULONG * pSizeOfCode);

This part is quite easy, just find the location of the compileMethod method, replace the entry via EasyHook.

C++
// ICorJitCompiler interface from JIT dll
class ICorJitCompiler 
{
public:
	typedef CorJitResult (__stdcall ICorJitCompiler::*PFN_compileMethod)(ICorJitInfo * pJitInfo, CORINFO_METHOD_INFO * pMethodInfo, UINT nFlags, LPBYTE * pEntryAddress, ULONG * pSizeOfCode);

	CorJitResult compileMethod(ICorJitInfo * pJitInfo, CORINFO_METHOD_INFO * pMethodInfo, UINT nFlags, LPBYTE * pEntryAddress, ULONG * pSizeOfCode)
	{
		return (this->*s_pfnComplieMethod)( pJitInfo, pMethodInfo, nFlags, pEntryAddress, pSizeOfCode);
	}
private:
	static PFN_compileMethod s_pfnComplieMethod;
};

// save the real address
LPVOID pAddr = tPdbHelper.GetJitCompileMethodAddress();
LPVOID* pDest = (LPVOID*)&ICorJitCompiler::s_pfnComplieMethod;
*pDest = pAddr;

// and this is my compileMethod
CorJitResult __stdcall CInjection::compileMethod(ICorJitInfo * pJitInfo , CORINFO_METHOD_INFO * pCorMethodInfo , UINT nFlags , LPBYTE * pEntryAddress , ULONG * pSizeOfCode )
{
	ICorJitCompiler * pCorJitCompiler = (ICorJitCompiler *)this;
	
	// TO DO: Replace IL code before invoking the real compileMethod
	CorJitResult result = pCorJitCompiler->compileMethod( pJitInfo, pCorMethodInfo, nFlags, pEntryAddress, pSizeOfCode);
	return result;
}

// hook and replace JIT's compileMethod with my own
NTSTATUS ntStatus = LhInstallHook( (PVOID&)ICorJitCompiler::s_pfnComplieMethod 
	, &(PVOID&)CInjection::compileMethod
	, NULL
	, &s_hHookCompileMethod
	);
 

Modify IL code for JIT-complied methods

Now we are here, the compileMethod method above won't be called by CLR for the JIT-compiled method. To solve this problem, my idea is to restore the data structures in CLR to the previous status before JIT-compliation. And in this case, complileMethod will be called again and we can replace the IL.

Thus we have to look into the implementation of CLR a bit, SSCLI (Shared Source Common Language Infrastructure) is a good reference from Microsoft although it is quite out of date and we can't use it in our code.

Image 7

The above diagram is a bit out of date, but the primary structure is the same. For each "class" in .NET, there is at least one MethodTable structure in memory. And each MethodTable is related to a EEClass, which stores the runtime type information for Reflection and other use.  

For each "method", there is at least one corresponding MethodDesc data structure in memory containing the information of this method like flags / slot address / entry address / etc. 

Before a method is JITted-complied, the slot is pointed to a JMI thunk (prestub), which triggers JIT compliation; when the IL code is complied, the slot is rewritten to point to the JMI thunk, which jumps to complied native code directly.

To restore the data structure, first clear the flags, then modify the entry address back to a temporary entry address, and so on. I successfully did that in the debugger by modifying the memory directly. But this is messy, it depends on the layout of the data structures, and the code is unreliable for different versions of .NET.

I was seeking a reliable manner, and luckily, I found the MethodDesc::Reset method in SSCLI source code (vm/method.cpp). 

C++
void MethodDesc::Reset()
{
    CONTRACTL
    {
        THROWS;
        GC_NOTRIGGER;
    }
    CONTRACTL_END
 
    // This method is not thread-safe since we are updating
    // different pieces of data non-atomically.
    // Use this only if you can guarantee thread-safety somehow.

    _ASSERTE(IsEnCMethod() || // The process is frozen by the debugger
             IsDynamicMethod() || // These are used in a very restricted way
             GetLoaderModule()->IsReflection()); // Rental methods                                                                 

    // Reset any flags relevant to the old code
    ClearFlagsOnUpdate();
 
    if (HasPrecode())
    {
        GetPrecode()->Reset();
    }
    else
    {
        // We should go here only for the rental methods
        _ASSERTE(GetLoaderModule()->IsReflection());
 
        InterlockedUpdateFlags2(enum_flag2_HasStableEntryPoint | enum_flag2_HasPrecode, FALSE);
 
        *GetAddrOfSlotUnchecked() = GetTemporaryEntryPoint();
    }
 
    _ASSERTE(!HasNativeCode());
}

As you can see above, it is doing the same thing for me. Hence I just need to invoke this method to reset the MethodDesc status to pre-JITted.  

Certainly I can't use the MethodDesc from SSCLI, and the MethodDesc is internally used by MS, whose exact implementation and layout are unknown to everyone except Microsoft. 

After endless mountains and rivers that leave doubt whether there is a path out, suddenly one encounters the shade of a willow, bright flowers, and a lovely village.

Fortunately the address of this internal method exists in the PDB symbol from Microsoft Symbol Server, and it solves my problem. The Reset() method's address in the CLR DLL can be known by parsing the PDB file! 

Now only one mandatory parameter is left -- the this pointer of MethodDesc. It is not hard to obtain this pointer. Actually MethodBase.MethodHandle.Value == CORINFO_METHOD_HANDLE == MethodDesc address == this pointer of MethodDesc .

Thus, I have my MethodDesc class below defined in unmanaged code.

C++
class MethodDesc
{
	typedef void (MethodDesc::*PFN_Reset)(void);
	typedef BOOL (MethodDesc::*PFN_IsGenericMethodDefinition)(void);
	typedef ULONG (MethodDesc::*PFN_GetNumGenericMethodArgs)(void);
	typedef MethodDesc * (MethodDesc::*PFN_StripMethodInstantiation)(void);
	typedef BOOL (MethodDesc::*PFN_HasClassOrMethodInstantiation)(void);
	typedef BOOL (MethodDesc::*PFN_ContainsGenericVariables)(void);	
	typedef MethodDesc * (MethodDesc::*PFN_GetWrappedMethodDesc)(void);
	typedef AppDomain * (MethodDesc::*PFN_GetDomain)(void);
	typedef Module * (MethodDesc::*PFN_GetLoaderModule)(void);

public:
	void Reset(void) { (this->*s_pfnReset)(); }
	BOOL IsGenericMethodDefinition(void) { return (this->*s_pfnIsGenericMethodDefinition)(); }
	ULONG GetNumGenericMethodArgs(void) { return (this->*s_pfnGetNumGenericMethodArgs)(); }
	MethodDesc * StripMethodInstantiation(void) { return (this->*s_pfnStripMethodInstantiation)(); }
	BOOL HasClassOrMethodInstantiation(void)  { return (this->*s_pfnHasClassOrMethodInstantiation)(); }
	BOOL ContainsGenericVariables(void) { return (this->*s_pfnContainsGenericVariables)(); }
	MethodDesc * GetWrappedMethodDesc(void) { return (this->*s_pfnGetWrappedMethodDesc)(); }
	AppDomain * GetDomain(void) { return (this->*s_pfnGetDomain)(); }
	Module * GetLoaderModule(void) { return (this->*s_pfnGetLoaderModule)(); }
	
private:
	static PFN_Reset s_pfnReset;
	static PFN_IsGenericMethodDefinition s_pfnIsGenericMethodDefinition;
	static PFN_GetNumGenericMethodArgs s_pfnGetNumGenericMethodArgs;
	static PFN_StripMethodInstantiation s_pfnStripMethodInstantiation;
	static PFN_HasClassOrMethodInstantiation s_pfnHasClassOrMethodInstantiation;
	static PFN_ContainsGenericVariables s_pfnContainsGenericVariables;
	static PFN_GetWrappedMethodDesc s_pfnGetWrappedMethodDesc;
	static PFN_GetDomain s_pfnGetDomain;
	static PFN_GetLoaderModule s_pfnGetLoaderModule;
}; 

The static variables above store the addresses of the internal methods from the MethodDesc implementation from the CLR DLL. And they are initialized when my unmanaged DLL is loaded. And the public members just call the internal method with the this pointer. 

Now it becomes quite easy to invoke Microsoft's internal methods. Like:

C++
MethodDesc * pMethodDesc = (MethodDesc*)pMethodHandle;
pMethodDesc->Reset();

Find internal methods' addresses from the PDB Symbol file

The internal method's virtual addresses can be known from PDB symbol file. With the virtual address, we can know the method real address by plus the base address of the DLL.

Method Address = Method Virtual Address + base address of dll.  

In previous version, the PDB file is downloaded and parsed locally with Microsoft symcheck.exe.

In the current version, I have made a web service to parse the addresses on the server and return the virtual addresses to clients. This will reduce the initialization time.

Further, after collecting most of the virtual addresses, the virtual addresses for different binaries are stored in the DLL resource. And during initialization the injection.dll will first lookup the virtual addresses locally, and only request the web service if the virtual addresses for current binaries are not found. In this case,  the web service will only be a backup when virtual addresses can not be found.

Reset the MethodDesc to pre-JITted status 

Now everything is ready. The unmanaged DLL exports a method for managed codes, accepts the IL codes and MethodBase.MethodHandle.Value from the managed code.

C++
// structure to store the IL code for replacement
typedef struct _ILCodeBuffer
{
	LPBYTE						pBuffer;
	DWORD						dwSize;
} ILCodeBuffer, *LPILCodeBuffer;


// method to be called by managed code
BOOL CInjection::StartUpdateILCodes( MethodTable * pMethodTable
	, CORINFO_METHOD_HANDLE pMethodHandle
	, mdMethodDef md
	, LPBYTE pBuffer
	, DWORD dwSize
	)
{
	MethodDesc * pMethodDesc = (MethodDesc*)pMethodHandle;

	// reset this MethodDesc
	pMethodDesc->Reset();

	ILCodeBuffer tILCodeBuffer;
	tILCodeBuffer.pBuffer = pBuffer;
	tILCodeBuffer.dwSize = dwSize;
	tILCodeBuffer.bIsGeneric = FALSE;

	// save the IL code for the method
	s_mpILBuffers.insert( std::pair< CORINFO_METHOD_HANDLE, ILCodeBuffer>( pMethodHandle, tILCodeBuffer) );

	return TRUE;
}

The code above just calls the Reset() method, and stores the IL codes in a map, which will be used by complieMethod when the method gets complied.

And in complieMethod, just replaces the ILCode, with code like below.

C++
CorJitResult __stdcall CInjection::compileMethod(ICorJitInfo * pJitInfo
	, CORINFO_METHOD_INFO * pCorMethodInfo
	, UINT nFlags
	, LPBYTE * pEntryAddress
	, ULONG * pSizeOfCode
	)
{
	ICorJitCompiler * pCorJitCompiler = (ICorJitCompiler *)this;
	LPBYTE pOriginalILCode = pCorMethodInfo->ILCode;
	unsigned int nOriginalSize = pCorMethodInfo->ILCodeSize;

	
	ILCodeBuffer tILCodeBuffer = {0};

	MethodDesc * pMethodDesc = (MethodDesc*)pCorMethodInfo->ftn;

	// find the method to be replaced
	std::map< CORINFO_METHOD_HANDLE, ILCodeBuffer>::iterator iter = s_mpILBuffers.find((CORINFO_METHOD_HANDLE)pMethodDesc);

	if( iter != s_mpILBuffers.end() )
	{
		tILCodeBuffer = iter->second;
		pCorMethodInfo->ILCode = tILCodeBuffer.pBuffer;
		pCorMethodInfo->ILCodeSize = tILCodeBuffer.dwSize;
	}

	CorJitResult result = pCorJitCompiler->compileMethod( pJitInfo, pCorMethodInfo, nFlags, pEntryAddress, pSizeOfCode);

	return result;
}  

Generic method 

 

A generic method is mapped to a MethodDesc in memory . But calling the generic method with different type parameter may cause the CLR to create different instantiations of the definition method. (The instantiation may be shared, you can see the types of generic method instantiation below). 

  1. shared generic method instantiations  
  2. unshared generic method instantiations 
  3. instance methods in shared generic classes
  4. instance methods in unshared generic classes 
  5. static methods in shared generic classes 
  6. static methods in unshared generic classes  

 

The following line is a simple generic method defined from the demo program 

C#
string GenericMethodToBeReplaced<T, K>(T t, K k)  

Calling GenericMethodToBeReplaced<string, int>("11", 2) for the first time,  CLR creates an    InstantiatedMethodDesc instance( sub-class of MethodDesc and its flag is marked with mcInstantiated ), which is stored in InstMethodHashTable data structure of the method's corresponding module. 

And calling GenericMethodToBeReplaced<long, int>(1, 2) leads to another  InstantiatedMethodDesc instance creation. 

Hence, we need find and reset all of the  InstantiatedMethodDesc of the generic method so that we can replace the IL code without missing. 

From SSCLI source code (vm/proftoeeinterfaceimpl.cpp), there is a class named LoadedMethodDescIterator can be used.  It accepts 3 parameters, and search the instantiated methods in given AppDomain and Module by MethodToken.  

 

C++
LoadedMethodDescIterator MDIter(ADIter.GetDomain(), pModule, methodId);
while(MDIter.Next())
{
    MethodDesc * pMD = MDIter.Current();
    if (pMD)
    {
        _ASSERTE(pMD->IsIL());
        pMD->SetRVA(rva);
    }
} 

Looking at the methods from CLR's PDB symbol file,  the constructor / Next / Current methods' addresses can be retrieved for LoadedMethodDescIterator class. so we can take use of this class from CLR. 

Image 8 

It doesn't matter that we don't know the exact size of the LoadedMethodDescIterator instance,  just define a big enough memory block to hold the actual instance data. 

C++
class LoadedMethodDescIterator
{
private:
	BYTE dummy[10240]; 
}; 

 

Actually there are some changes from .Net2.0 to .Net4.5 for the Next() method and the constructor. 

C++
// .Net 2.0 & 4.0
LoadedMethodDescIterator(AppDomain * pAppDomain, Module *pModule,	mdMethodDef md)

// .Net 4.5
LoadedMethodDescIterator(AppDomain * pAppDomain, Module *pModule,	mdMethodDef md,enum AssemblyIterationMode mode)  
C++
// .Net 2.0
BOOL LoadedMethodDescIterator::Next(void)
// .Net 4.0 / 4.5
BOOL LoadedMethodDescIterator::Next(CollectibleAssemblyHolder<DomainAssembly *> *)  

So, we need detect the current .Net Framework version and invoke the correct method. The primary problem comes from .Net 4.5,  which is an in-place upgrade of .Net4.0.  Hence, in the demo code, this is done by detecting the CLR binary version number. 

C++
// detect the version of CLR
BOOL DetermineDotNetVersion(void)
{
	WCHAR wszPath[MAX_PATH] = {0};
	::GetModuleFileNameW( g_hClrModule, wszPath, MAX_PATH);
	CStringW strPath(wszPath);
	int nIndex = strPath.ReverseFind('\\');
	if( nIndex <= 0 )
		return FALSE;
	nIndex++;
	CStringW strFilename = strPath.Mid( nIndex, strPath.GetLength() - nIndex);
	if( strFilename.CompareNoCase(L"mscorwks.dll") == 0 )
	{
		g_tDotNetVersion = DotNetVersion_20;
		return TRUE;
	}

	if( strFilename.CompareNoCase(L"clr.dll") == 0 )
	{
		VS_FIXEDFILEINFO tVerInfo = {0};
		if ( CUtility::GetFileVersion( wszPath, &tVerInfo) &&
			 tVerInfo.dwSignature == 0xfeef04bd)
		{
			int nMajor = HIWORD(tVerInfo.dwFileVersionMS);
			int nMinor = LOWORD(tVerInfo.dwFileVersionMS);
			int nBuildMajor = HIWORD(tVerInfo.dwFileVersionLS);
			int nBuildMinor = LOWORD(tVerInfo.dwFileVersionLS);

			if( nMajor == 4 && nMinor == 0 && nBuildMajor == 30319 )
			{
				if( nBuildMinor < 10000 )
					g_tDotNetVersion = DotNetVersion_40;
				else
					g_tDotNetVersion = DotNetVersion_45;
				return TRUE;
			}
		}
		return FALSE;
	}

	return FALSE;
} 

Now, we can define our LoadedMethodDescIterator class which will be attached to the CLR implementation. 

C++
enum AssemblyIterationMode { AssemblyIterationMode_Default = 0 };

class LoadedMethodDescIterator
{
	typedef void (LoadedMethodDescIterator::*PFN_LoadedMethodDescIteratorConstructor)(AppDomain * pAppDomain, Module *pModule,	mdMethodDef md);
	typedef void (LoadedMethodDescIterator::*PFN_LoadedMethodDescIteratorConstructor_v45)(AppDomain * pAppDomain, Module *pModule,	mdMethodDef md, AssemblyIterationMode mode);
	typedef void (LoadedMethodDescIterator::*PFN_Start)(AppDomain * pAppDomain, Module *pModule, mdMethodDef md);
	typedef BOOL (LoadedMethodDescIterator::*PFN_Next_v4)(LPVOID pParam);
	typedef BOOL (LoadedMethodDescIterator::*PFN_Next_v2)(void);
	typedef MethodDesc* (LoadedMethodDescIterator::*PFN_Current)(void);
public:
	LoadedMethodDescIterator(AppDomain * pAppDomain, Module *pModule, mdMethodDef md)
	{
		memset( dummy, 0, sizeof(dummy));
		memset( dummy2, 0, sizeof(dummy2));
		if( s_pfnConstructor )
			(this->*s_pfnConstructor)( pAppDomain, pModule, md);
		if( s_pfnConstructor_v45 )
			(this->*s_pfnConstructor_v45)( pAppDomain, pModule, md, AssemblyIterationMode_Default);
	}

	void Start(AppDomain * pAppDomain, Module *pModule, mdMethodDef md) 
	{ 
		(this->*s_pfnStart)( pAppDomain, pModule, md); 
	}
	BOOL Next() 
	{
		if( s_pfnNext_v4 )
			return (this->*s_pfnNext_v4)(dummy2); 

		if( s_pfnNext_v2 )
			return (this->*s_pfnNext_v2)(); 

		return FALSE;
	}
	MethodDesc* Current() { return (this->*s_pfnCurrent)(); }
private:
	// we don't know the exact size of LoadedMethodDescIterator, so add enough memory here
	BYTE dummy[10240]; 

	// class CollectibleAssemblyHolder<class DomainAssembly *> parameter for Next() in .Net4.0 and above	
	BYTE dummy2[10240]; 

	// constructor for .Net2.0 & .Net 4.0
	static PFN_LoadedMethodDescIteratorConstructor s_pfnConstructor;

	// constructor for .Net4.5
	static PFN_LoadedMethodDescIteratorConstructor_v45 s_pfnConstructor_v45;

	static PFN_Start s_pfnStart;
	static PFN_Next_v4 s_pfnNext_v4;
	static PFN_Next_v2 s_pfnNext_v2; 
	static PFN_Current s_pfnCurrent;

public:
	static void MatchAddress(PSYMBOL_INFOW pSymbolInfo)
	{
		LPVOID* pDest = NULL;
		if( wcscmp( L"LoadedMethodDescIterator::LoadedMethodDescIterator", pSymbolInfo->Name) == 0 )
		{
			switch(g_tDotNetVersion)
			{
			case DotNetVersion_20:
			case DotNetVersion_40:
				pDest = (LPVOID*)&(LoadedMethodDescIterator::s_pfnConstructor);
				break;

			
			case DotNetVersion_45:
				pDest = (LPVOID*)&(LoadedMethodDescIterator::s_pfnConstructor_v45);
				break;

			default:
				ATLASSERT(FALSE);
				return;
			}
		}
		else if( wcscmp( L"LoadedMethodDescIterator::Next", pSymbolInfo->Name) == 0 )
		{
			switch(g_tDotNetVersion)
			{
			case DotNetVersion_20:
				pDest = (LPVOID*)&(LoadedMethodDescIterator::s_pfnNext_v2);
				break;

			case DotNetVersion_40:
			case DotNetVersion_45:
				pDest = (LPVOID*)&(LoadedMethodDescIterator::s_pfnNext_v4);
				break;

			default:
				ATLASSERT(FALSE);
				return;
			}
		}
		else if( wcscmp( L"LoadedMethodDescIterator::Start", pSymbolInfo->Name) == 0 )
			pDest = (LPVOID*)&(LoadedMethodDescIterator::s_pfnStart);
		else if( wcscmp( L"LoadedMethodDescIterator::Current", pSymbolInfo->Name) == 0 )
			pDest = (LPVOID*)&(LoadedMethodDescIterator::s_pfnCurrent);
		
		if( pDest )
			*pDest = (LPVOID)pSymbolInfo->Address;
	}

};

Finally,  using LoadedMethodDescIterator to reset all the instantiated MethodDesc for generic method as below. 

C++
// find out all the instantiations of this generic method
Module * pModule = pMethodDesc->GetLoaderModule();
AppDomain * pAppDomain = pMethodDesc->GetDomain();
if( pModule )
{
	LoadedMethodDescIterator * pLoadedMethodDescIter = new LoadedMethodDescIterator( pAppDomain, pModule, md);
	while(pLoadedMethodDescIter->Next())
	{
		MethodDesc * pMD = pLoadedMethodDescIter->Current();
		if( pMD )
			pMD->Reset();
	}
	delete pLoadedMethodDescIter;
}
 

Points of interest    

Compilation optimization   

I found that if the method is too simple and the IL codes are only several bytes, the method may be complied as inline mode. And in this case, Reset MethodDesc does not help anything because the execution even doesn't reach there. More details can be found in CEEInfo::canInline,  (vm/jitinterface.cpp in SSCLI) 

Dynamic method 

To update the IL code of a dynamic method we need to be very careful. Filling incorrect IL code for other kinds of methods only causes an InvalidProgramException; but incorrect IL code in a dynamic method can crash the CLR and the whole process! And IL code for a dynamic method is different from that for others. Better to generate the IL code from another dynamic method and then copy and update.

Inject a running .Net process 

To modify a running .Net process without source code, you can first inject your own .Net assembly into the target process via using RhInjectLibrary from EasyHook. Then after the .Net assembly is loaded in the target process, call the InjectionHelper.UpdateILCodes to update the target method. More information about EasyHook can be found in its documentation

History   

 

 

  • 2012 Sep 22 - First Version 
  • 2012 Oct 5 - Added the LoadedMethodDescIterator for generic method
  • 2012 Oct 8 - Added x64 support
  • 2012 Oct 10 - Added support for .Net4.5 
  • 2012 Oct 11 - Added cache feature to speedup the initialization procedure 
  • 2012 Oct 13 - Embed the address offset cache in the resourse.   
  • 2012 Oct 14 - Corrected the code to ensure it can be compiled in VS2012.   
  • 2014 Aug 7 - Released the 2nd version : use web service to lookup virtual address in PDB as backup. Changed from hash to symbol ID from PE file to identify the binary.

License

This article, along with any associated source code and files, is licensed under The GNU Lesser General Public License (LGPLv3)


Written By
Team Leader
China China
Jerry is from China. He was captivated by computer programming since 13 years old when first time played with Q-Basic.



  • Windows / Linux & C++
  • iOS & Obj-C
  • .Net & C#
  • Flex/Flash & ActionScript
  • HTML / CSS / Javascript
  • Gaming Server programming / video, audio processing / image & graphics


Contact: vcer(at)qq.com
Chinese Blog: http://blog.csdn.net/wangjia184

Comments and Discussions

 
QuestionGreat Job Pin
BUGLIU23-May-21 23:07
professionalBUGLIU23-May-21 23:07 
QuestionHi Does it work with Unity3d Engine? Pin
Member 83035924-Mar-19 20:52
Member 83035924-Mar-19 20:52 
AnswerRe: Hi Does it work with Unity3d Engine? Pin
clemens.pecinovsky@gmx.at29-May-21 21:14
clemens.pecinovsky@gmx.at29-May-21 21:14 
GeneralSounds useful but limited Pin
UlyssesWu1-Nov-17 21:52
UlyssesWu1-Nov-17 21:52 
QuestionAssemblies from Current Domain Pin
Member 122867659-Oct-16 19:57
Member 122867659-Oct-16 19:57 
NewsProject for 2015VS Pin
MArtmath9-Jun-16 13:06
MArtmath9-Jun-16 13:06 
GeneralWork on > 4.6.1038 net and 2015VS Pin
MArtmath18-Apr-16 23:01
MArtmath18-Apr-16 23:01 
QuestionThis demo is not work Pin
Member 1106044328-Feb-16 19:39
Member 1106044328-Feb-16 19:39 
QuestionUpdating Exception Handler offsets Pin
d j30-May-15 0:59
d j30-May-15 0:59 
QuestionWould it be possible to also release the server source code? Pin
c63726973746910-Apr-15 5:46
c63726973746910-Apr-15 5:46 
AnswerRe: Would it be possible to also release the server source code? Pin
Evit1529-Dec-15 19:01
Evit1529-Dec-15 19:01 
QuestionWhen i try to compile i always get a error Pin
tiktaktoe28-Nov-14 21:53
tiktaktoe28-Nov-14 21:53 
When i try to compile i always get a error with easyhook (also tried latest version) i use VS2013 on x64

LIBCMTD.lib(gs_report.obj) : error LNK2005: ___report_gsfailure is already defined in GS_x64.LIB(gs_report.obj)

Injection64.dll : fatal error LNK1169: one or more multiply defined symbols found

Please tell how to fix thank you!
AnswerRe: When i try to compile i always get a error Pin
tiktaktoe29-Nov-14 11:33
tiktaktoe29-Nov-14 11:33 
Questionhelp me Pin
mohsen.keshavarzi9-Nov-14 7:10
mohsen.keshavarzi9-Nov-14 7:10 
QuestionIl Call instruction Pin
Dmitry Post20-Oct-14 9:34
Dmitry Post20-Oct-14 9:34 
QuestionAppears not to work in remote process Pin
c6372697374693-Oct-14 12:20
c6372697374693-Oct-14 12:20 
AnswerRe: Appears not to work in remote process Pin
Jerry.Wang3-Oct-14 16:20
Jerry.Wang3-Oct-14 16:20 
GeneralRe: Appears not to work in remote process Pin
c6372697374693-Oct-14 20:56
c6372697374693-Oct-14 20:56 
GeneralRe: Appears not to work in remote process Pin
c6372697374695-Oct-14 6:30
c6372697374695-Oct-14 6:30 
GeneralRe: Appears not to work in remote process Pin
f3re14-Oct-14 14:28
f3re14-Oct-14 14:28 
QuestionRe: Appears not to work in remote process Pin
UlyssesWu9-Oct-15 2:14
UlyssesWu9-Oct-15 2:14 
AnswerRe: Appears not to work in remote process Pin
UlyssesWu10-Oct-15 3:04
UlyssesWu10-Oct-15 3:04 
QuestionCan't run in Win2003 Server SP2 Pin
iolir14-Aug-14 3:26
iolir14-Aug-14 3:26 
AnswerRe: Can't run in Win2003 Server SP2 Pin
iolir15-Aug-14 6:05
iolir15-Aug-14 6:05 
GeneralRe: Can't run in Win2003 Server SP2 Pin
Jerry.Wang16-Aug-14 2:10
Jerry.Wang16-Aug-14 2:10 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.