|
|||||||||||||||||||||||||
|
|||||||||||||||||||||||||
|
Announcements
Want a new Job?
Chapters
Services
Feature Zones
|
Table of Contents
IntroductionThis article is the obvious culmination of the previous effort of writing the Rebel.NET application and the first of a two series of articles about the .NET Framework internals and the protections available for .NET assemblies. The next article will be about .NET native compiling. As the JIT inner workings haven't been analyzed yet, .NET protections are quite naïve nowadays. This situation will rapidly change as soon as the reverse engineering community will focus its attention on this technology. These two articles are aimed to raise the consciousness about the current state of .NET protections and what is possible to achieve but hasn't been done yet. In particular, the current article about .NET code injection represents, let's say, the present, whereas the next one about .NET native compiling represents the future. What I'm presenting in these two articles is new at the time I'm writing it, but I expect it to become obsolete in less than a year. Of course, this is obvious as I'm moving the first steps out from current .NET protections in the direction of better ones. But this article isn't really about protections: exploring the .NET Framework internals can be useful for many purposes. So, talking about protections is just a means to an end. What is .NET Code Injection?.NET code injection is the "strong" brother of .NET packers (which unpack the entire assembly in memory). What .NET code injectors do is to hook the JIT and when the MSIL code of a method is requested, they filter the request and provide the real MSIL instead of the MSIL contained in the assembly, which, most of the times, is just a ret. By injecting one (or quasi) method at a time, the MSIL code will remain concealed. Even if one manages to dump the code, it isn't to be expected that the protection left the necessary space for the real MSIL code in the .NET assembly, although many commercial protections do so. Rebuilding the assembly from scratch is the universally valid way to proceed. This, of course, is not a problem with Rebel.NET. It should be obvious to the reader that .NET code injectors aren't reliable protections. It's just playing hide and seek with the reverser. But, as many software producers are putting their intellectual property in the hands of such protections, it is necessary to analyze them throughout. How Does .NET Code Injection Work?One thing should be clear from the beginning: there isn't only one method to inject MSIL. Thus, to remove this kind of protection you have to evaluate the specific case. A very clean approach, though unused yet, would be to inject the MSIL through the .NET profiling API. There's a very in depth article about the .NET profiling API by Aleksandr Mikunov on MSDN. Anyway, as I already said, this approach isn't used by .NET protections. I referred to this approach as clean, simply because it uses the API provided by the framework itself. Thus, it'll work on every .NET Framework no matter what. Whereas .NET protections usually hook the JIT and this, although it might work just as well, it is not guaranteed to do so. The .NET Framework's JIT is contained in the mscorjit.dll module. To identify the part of the JIT being hooked by the .NET protection, there's a very simple an effect way: dumping the mscorjit.dll module from the protection's process and comparing it to the original module on disk. I wrote a little CFF Explorer Script to make a comparison of a PE section which excludes the IAT in the comparison. This is especially useful when comparing the It was a ten minute job and it is extremely useful to identify the type of hook applied to the JIT. Let's look at a possible output of this script: Comparison between section 0 of ... C:\...\mscorjit.dll
... and C:\...\mscorjit_dumped.dll
Differences found at: RVA1 RVA2
000460A0 000460A0
000460A1 000460A1
000460A2 000460A2
000460A3 000460A3
Number of differences found: 4. By looking at the patched .text:790A60A0 ??_7CILJit@@6B@ dd offset
?compileMethod@CILJit@@EAG?AW4CorJitResult
@@PAVICorJitInfo@@PAUCORINFO_METHOD_INFO@@IPAPAEPAK@Z
.text:790A60A0 ; DATA XREF: getJit()+Eo.text:790A60A0 ;
CILJit::compileMethod(ICorJitInfo *,CORINFO_METHOD_INFO *,uint,uchar * *,ulong *)
.text:790A60A4 dd offset ?clearCache@CILJit@@EAGXXZ ; CILJit::clearCache(void)
.text:790A60A8 dd offset ?isCacheCleanupRequired@CILJit@@EAGHXZ ;
CILJit::isCacheCleanupRequired(void)
The patched .NET Internals (Part 1: JIT)In this paragraph, I'll present to you the As the Let's take a look at the struct CORINFO_METHOD_INFO
{
CORINFO_METHOD_HANDLE ftn;
CORINFO_MODULE_HANDLE scope;
BYTE * ILCode;
unsigned ILCodeSize;
unsigned short maxStack;
unsigned short EHcount;
CorInfoOptions options;
CORINFO_SIG_INFO args;
CORINFO_SIG_INFO locals;
};
The only thing code injectors have to do is to provide a valid MSIL pointer and size given the two members of this structure: The pointer to the extern "C"
ICorJitCompiler* __stdcall getJit()
{
static char FJitBuff[sizeof(FJitCompiler)];
if (ILJitter == 0)
{
// no need to check for out of memory, since caller checks
// for return value of NULL
ILJitter = new(FJitBuff) FJitCompiler();
_ASSERTE(ILJitter != NULL);
}
return(ILJitter);
}
And this is about all that code injectors ought to know to do their job. But we go further. The class FJitCompiler : public ICorJitCompiler
{
public:
/* the jitting function */
CorJitResult __stdcall compileMethod (
ICorJitInfo* comp, /* IN */
CORINFO_METHOD_INFO* info, /* IN */
unsigned flags, /* IN */
BYTE ** nativeEntry, /* OUT */
ULONG * nativeSizeOfCode /* OUT */
);
/* notification from VM to clear caches */
void __stdcall clearCache();
BOOL __stdcall isCacheCleanupRequired();
static BOOL Init();
static void Terminate();
private:
/* grab and remember the jitInterface helper addresses that we need at runtime */
BOOL GetJitHelpers(ICorJitInfo* jitInfo);
};
Basically, the /*********************************************************************************
* a ICorJitInfo is the main interface that the JIT uses to call back to the EE and
* get information
*********************************************************************************/
class ICorJitInfo : public virtual ICorDynamicInfo
{
public:
// return memory manager that the JIT can use to allocate a regular memory
virtual IEEMemoryManager* __stdcall getMemoryManager() = 0;
// get a block of memory for the code, readonly data, and read-write data
virtual void __stdcall allocMem (
ULONG hotCodeSize, /* IN */
ULONG coldCodeSize, /* IN */
ULONG roDataSize, /* IN */
ULONG rwDataSize, /* IN */
ULONG xcptnsCount, /* IN */
CorJitAllocMemFlag flag, /* IN */
void ** hotCodeBlock, /* OUT */
void ** coldCodeBlock, /* OUT */
void ** roDataBlock, /* OUT */
void ** rwDataBlock /* OUT */
) = 0;
// Get a block of memory needed for the code manager information,
// (the info for enumerating the GC pointers while crawling the
// stack frame).
// Note that allocMem must be called first
virtual void * __stdcall allocGCInfo (
ULONG size /* IN */
) = 0;
virtual void * __stdcall getEHInfo(
) = 0;
virtual void __stdcall yieldExecution() = 0;
// indicate how many exception handlers blocks are to be returned
// this is guaranteed to be called before any 'setEHinfo' call.
// Note that allocMem must be called before this method can be called
virtual void __stdcall setEHcount (
unsigned cEH /* IN */
) = 0;
// set the values for one particular exception handler block
//
// Handler regions should be lexically contiguous.
// This is because FinallyIsUnwinding() uses lexicality to
// determine if a "finally" clause is executing
virtual void __stdcall setEHinfo (
unsigned EHnumber, /* IN */
const CORINFO_EH_CLAUSE *clause /* IN */
) = 0;
// Level -> fatalError, Level 2 -> Error, Level 3 -> Warning
// Level 4 means happens 10 times in a run, level 5 means 100,
// level 6 means 1000 ...
// returns non-zero if the logging succeeded
virtual BOOL __cdecl logMsg(unsigned level, const char* fmt, va_list args) = 0;
// do an assert. will return true if the code should retry (DebugBreak)
// returns false, if the assert should be ignored.
virtual int __stdcall doAssert
(const char* szFile, int iLine, const char* szExpr) = 0;
struct ProfileBuffer
{
ULONG bbOffset;
ULONG bbCount;
};
// allocate a basic block profile buffer where execution counts will be stored
// for jitted basic blocks.
virtual HRESULT __stdcall allocBBProfileBuffer (
ULONG size,
ProfileBuffer ** profileBuffer
) = 0;
// get profile information to be used for optimizing the current method. The format
// of the buffer is the same as the format the JIT passes to allocBBProfileBuffer.
virtual HRESULT __stdcall getBBProfileData(
CORINFO_METHOD_HANDLE ftnHnd,
ULONG * size,
ProfileBuffer ** profileBuffer,
ULONG * numRuns
) = 0;
};
This doesn't seem much, but the /*****************************************************************************
* ICorDynamicInfo contains EE interface methods which return values that may
* change from invocation to invocation. They cannot be embedded in persisted
* data; they must be required each time the EE is run.
*****************************************************************************/
class ICorDynamicInfo : public virtual ICorStaticInfo
{
public:
//
// These methods return values to the JIT which are not constant
// from session to session.
//
// These methods take an extra parameter : void **ppIndirection.
// If a JIT supports generation of prejit code (install-o-jit), it
// must pass a non-null value for this parameter, and check the
// resulting value. If *ppIndirection is NULL, code should be
// generated normally. If non-null, then the value of
// *ppIndirection is an address in the cookie table, and the code
// generator needs to generate an indirection through the table to
// get the resulting value. In this case, the return result of the
// function must NOT be directly embedded in the generated code.
//
// Note that if a JIT does not support prejit code generation, it
// may ignore the extra parameter & pass the default of NULL - the
// prejit ICorDynamicInfo implementation will see this & generate
// an error if the jitter is used in a prejit scenario.
//
// Return details about EE internal data structures
virtual DWORD __stdcall getThreadTLSIndex(
void **ppIndirection = NULL
) = 0;
virtual const void * __stdcall getInlinedCallFrameVptr(
void **ppIndirection = NULL
) = 0;
virtual LONG * __stdcall getAddrOfCaptureThreadGlobal(
void **ppIndirection = NULL
) = 0;
virtual SIZE_T* __stdcall
getAddrModuleDomainID(CORINFO_MODULE_HANDLE module) = 0;
// return the native entry point to an EE helper (see CorInfoHelpFunc)
virtual void* __stdcall getHelperFtn (
CorInfoHelpFunc ftnNum,
void **ppIndirection = NULL,
InfoAccessModule *pAccessModule = NULL
) = 0;
// return a callable address of the function (native code). This function
// may return a different value (depending on whether the method has
// been JITed or not. pAccessType is an in-out parameter. The JIT
// specifies what level of indirection it desires, and the EE sets it
// to what it can provide (which may not be the same).
virtual void __stdcall getFunctionEntryPoint(
CORINFO_METHOD_HANDLE ftn, /* IN */
InfoAccessType requestedAccessType, /* IN */
CORINFO_CONST_LOOKUP * pResult, /* OUT */
CORINFO_ACCESS_FLAGS accessFlags = CORINFO_ACCESS_ANY) = 0;
// return a directly callable address. This can be used similarly to the
// value returned by getFunctionEntryPoint() except that it is
// guaranteed to be the same for a given function.
// pAccessType is an in-out parameter. The JIT
// specifies what level of indirection it desires, and the EE sets it
// to what it can provide (which may not be the same).
virtual void __stdcall getFunctionFixedEntryPointInfo(
CORINFO_MODULE_HANDLE scopeHnd,
unsigned metaTOK,
CORINFO_CONTEXT_HANDLE context,
CORINFO_LOOKUP * pResult) = 0;
// get the synchronization handle that is passed to monXstatic function
virtual void* __stdcall getMethodSync(
CORINFO_METHOD_HANDLE ftn,
void **ppIndirection = NULL
) = 0;
// These entry points must be called if a handle is being embedded in
// the code to be passed to a JIT helper function. (as opposed to just
// being passed back into the ICorInfo interface.)
// a module handle may not always be available.
// A call to embedModuleHandle should always
// be preceded by a call to canEmbedModuleHandleForHelper.
// A dynamicMethod does not have a module
virtual bool __stdcall canEmbedModuleHandleForHelper(
CORINFO_MODULE_HANDLE handle
) = 0;
virtual CORINFO_MODULE_HANDLE __stdcall embedModuleHandle(
CORINFO_MODULE_HANDLE handle,
void **ppIndirection = NULL
) = 0;
virtual CORINFO_CLASS_HANDLE __stdcall embedClassHandle(
CORINFO_CLASS_HANDLE handle,
void **ppIndirection = NULL
) = 0;
virtual CORINFO_METHOD_HANDLE __stdcall embedMethodHandle(
CORINFO_METHOD_HANDLE handle,
void **ppIndirection = NULL
) = 0;
virtual CORINFO_FIELD_HANDLE __stdcall embedFieldHandle(
CORINFO_FIELD_HANDLE handle,
void **ppIndirection = NULL
) = 0;
// Given a module scope (module), a method handle (context) and
// a metadata token (metaTOK), fetch the handle
// (type, field or method) associated with the token.
// If this is not possible at compile-time (because the current method's
// code is shared and the token contains generic parameters)
// then indicate how the handle should be looked up at run-time.
//
// Type tokens can be combined with CORINFO_ANNOT_MASK flags
// to obtain array type handles. These are typically required by the 'newarr'
// instruction which takes a token for the *element* type of the array.
//
// Similarly method tokens can be combined with CORINFO_ANNOT_MASK flags
// method entry points. These are typically required by the 'call' and 'ldftn'
// instructions.
//
// Byrefs or System.Void should only occur in method and local signatures, which
// are accessed using ICorClassInfo and ICorClassInfo.getChildType. ldtoken is one
// exception from this rule. allowAllTypes should be set to true only for
// ldtoken only!
//
virtual void __stdcall embedGenericHandle(
CORINFO_MODULE_HANDLE module,
unsigned metaTOK,
CORINFO_CONTEXT_HANDLE context,
CorInfoTokenKind tokenKind,
CORINFO_GENERICHANDLE_RESULT *pResult) = 0;
// Return information used to locate the exact enclosing type of the current method.
// Used only to invoke .cctor method from code shared across generic instantiations
// !needsRuntimeLookup statically known (enclosing type of method itself)
// needsRuntimeLookup:
// CORINFO_LOOKUP_THISOBJ use vtable pointer of 'this' param
// CORINFO_LOOKUP_CLASSPARAM use vtable hidden param
// CORINFO_LOOKUP_METHODPARAM use enclosing type of method-desc hidden param
virtual CORINFO_LOOKUP_KIND __stdcall getLocationOfThisType(
CORINFO_METHOD_HANDLE context
) = 0;
// return the unmanaged target *if method has already been prelinked.*
virtual void* __stdcall getPInvokeUnmanagedTarget(
CORINFO_METHOD_HANDLE method,
void **ppIndirection = NULL
) = 0;
// return address of fixup area for late-bound PInvoke calls.
virtual void* __stdcall getAddressOfPInvokeFixup(
CORINFO_METHOD_HANDLE method,
void **ppIndirection = NULL
) = 0;
// Generate a cookie based on the signature that would needs to be passed
// to CORINFO_HELP_PINVOKE_CALLI
virtual LPVOID GetCookieForPInvokeCalliSig(
CORINFO_SIG_INFO* szMetaSig,
void ** ppIndirection = NULL
) = 0;
// Gets a handle that is checked to see if the current method is
// included in "JustMyCode"
virtual CORINFO_JUST_MY_CODE_HANDLE __stdcall getJustMyCodeHandle(
CORINFO_METHOD_HANDLE method,
CORINFO_JUST_MY_CODE_HANDLE**ppIndirection = NULL
) = 0;
// Gets a method handle that can be used to correlate profiling data.
// This is the IP of a native method, or the address of the descriptor struct
// for IL. Always guaranteed to be unique per process, and not to move. */
virtual void __stdcall GetProfilingHandle(
CORINFO_METHOD_HANDLE method,
BOOL *pbHookFunction,
void **pEEHandle,
void **pProfilerHandle,
BOOL *pbIndirectedHandles
) = 0;
// returns the offset into the interface table
virtual unsigned __stdcall getInterfaceTableOffset (
CORINFO_CLASS_HANDLE cls,
void **ppIndirection = NULL
) = 0;
//return the address of a pointer to a callable stub that will do the virtual
//or interface call
//
// When inlining methodBeingCompiledHnd should be the originating caller
// in a sequence of nested
// inlines, e.g. it is used to determine if the code being generated is domain
// neutral or not.
virtual void __stdcall getCallInfo(
CORINFO_METHOD_HANDLE methodBeingCompiledHnd,
CORINFO_MODULE_HANDLE tokenScope,
unsigned methodToken,
// the type token from a preceding constraint.
unsigned constraintToken,
// prefix instruction (if any)
CORINFO_CONTEXT_HANDLE tokenContext,
CORINFO_CALLINFO_FLAGS flags,
CORINFO_CALL_INFO *pResult) = 0;
// Returns TRUE if the Class Domain ID is the RID of the class
// (currently true for every class
// except reflection emitted classes and generics)
virtual BOOL __stdcall isRIDClassDomainID(CORINFO_CLASS_HANDLE cls) = 0;
// returns the class's domain ID for accessing shared statics
virtual unsigned __stdcall getClassDomainID (
CORINFO_CLASS_HANDLE cls,
void **ppIndirection = NULL
) = 0;
virtual size_t __stdcall getModuleDomainID (
CORINFO_MODULE_HANDLE module,
void **ppIndirection = NULL
) = 0;
// return the data's address (for static fields only)
virtual void* __stdcall getFieldAddress(
CORINFO_FIELD_HANDLE field,
void **ppIndirection = NULL
) = 0;
// registers a vararg sig & returns a VM cookie for it
//(which can contain other stuff)
virtual CORINFO_VARARGS_HANDLE __stdcall getVarArgsHandle(
CORINFO_SIG_INFO *pSig,
void **ppIndirection = NULL
) = 0;
// Allocate a string literal on the heap and return a handle to it
virtual InfoAccessType __stdcall constructStringLiteral(
CORINFO_MODULE_HANDLE module,
mdToken metaTok,
void **ppInfo
) = 0;
// (static fields only) given that 'field' refers to thread local store,
// return the ID (TLS index), which is used to find the beginning of the
// TLS data area for the particular DLL 'field' is associated with.
virtual DWORD __stdcall getFieldThreadLocalStoreID (
CORINFO_FIELD_HANDLE field,
void **ppIndirection = NULL
) = 0;
// returns the class typedesc given a methodTok (needed for arrays since
// they share a common method table, so we can't use getMethodClass)
virtual CORINFO_CLASS_HANDLE __stdcall findMethodClass(
CORINFO_MODULE_HANDLE module,
mdToken methodTok,
CORINFO_METHOD_HANDLE context
) = 0;
// Sets another object to intercept calls to "self"
virtual void __stdcall setOverride(
ICorDynamicInfo *pOverride
) = 0;
// Adds an active dependency from the context method's module to the given module
virtual void __stdcall addActiveDependency(
CORINFO_MODULE_HANDLE moduleFrom,
CORINFO_MODULE_HANDLE moduleTo
) = 0;
virtual CORINFO_METHOD_HANDLE __stdcall GetDelegateCtor(
CORINFO_METHOD_HANDLE methHnd,
CORINFO_CLASS_HANDLE clsHnd,
CORINFO_METHOD_HANDLE targetMethodHnd,
DelegateCtorArgs * pCtorData
) = 0;
virtual void __stdcall MethodCompileComplete(
CORINFO_METHOD_HANDLE methHnd
) = 0;
};
Now this seems already much more interesting indeed. But we aren't done yet as the class ICorStaticInfo : public virtual ICorMethodInfo, public virtual ICorModuleInfo,
public virtual ICorClassInfo, public virtual ICorFieldInfo,
public virtual ICorDebugInfo, public virtual ICorArgInfo,
public virtual ICorLinkInfo, public virtual ICorErrorInfo
{
public:
// Return details about EE internal data structures
virtual void __stdcall getEEInfo(
CORINFO_EE_INFO *pEEInfoOut
) = 0;
};
Let's look at just one of them ( class ICorMethodInfo
{
public:
// this function is for debugging only. It returns the method name
// and if 'moduleName' is non-null, it sets it to something that will
// says which method (a class name, or a module name)
virtual const char* __stdcall getMethodName (
CORINFO_METHOD_HANDLE ftn, /* IN */
const char **moduleName /* OUT */
) = 0;
// this function is for debugging only. It returns a value that
// is will always be the same for a given method. It is used
// to implement the 'jitRange' functionality
virtual unsigned __stdcall getMethodHash (
CORINFO_METHOD_HANDLE ftn /* IN */
) = 0;
// return flags (defined above, CORINFO_FLG_PUBLIC ...)
// The callerHnd can be either the methodBeingCompiled or the immediate
// caller of an inlined function.
virtual DWORD __stdcall getMethodAttribs (
CORINFO_METHOD_HANDLE calleeHnd, /* IN */
CORINFO_METHOD_HANDLE callerHnd /* IN */
) = 0;
// sets private JIT flags, which can be, retrieved using getAttrib.
virtual void __stdcall setMethodAttribs (
CORINFO_METHOD_HANDLE ftn, /* IN */
CorInfoMethodRuntimeFlags attribs /* IN */
) = 0;
// Given a method descriptor ftnHnd, extract signature information into sigInfo
//
// 'memberParent' is typically only set when verifying. It should be the
// result of calling getMemberParent.
virtual void __stdcall getMethodSig (
CORINFO_METHOD_HANDLE ftn, /* IN */
CORINFO_SIG_INFO *sig, /* OUT */
CORINFO_CLASS_HANDLE memberParent = NULL /* IN */
) = 0;
/*********************************************************************
* Note the following methods can only be used on functions known
* to be IL. This includes the method being compiled and any method
* that 'getMethodInfo' returns true for
*********************************************************************/
// return information about a method private to the implementation
// returns false if method is not IL, or is otherwise unavailable.
// This method is used to fetch data needed to inline functions
virtual bool __stdcall getMethodInfo (
CORINFO_METHOD_HANDLE ftn, /* IN */
CORINFO_METHOD_INFO* info /* OUT */
) = 0;
// Decides if you have any limitations for inlining. If everything's OK,
// it will return INLINE_PASS and will fill out pRestrictions
// with a mask of restrictions the caller of this function must respect.
// If caller passes pRestrictions = NULL, if there are any restrictions
// INLINE_FAIL will be returned
//
//
// The inlined method need not be verified
virtual CorInfoInline __stdcall canInline (
CORINFO_METHOD_HANDLE callerHnd, /* IN */
CORINFO_METHOD_HANDLE calleeHnd, /* IN */
DWORD* pRestrictions /* OUT */
) = 0;
// Returns false if the call is across assemblies thus we cannot tailcall
virtual bool __stdcall canTailCall (
CORINFO_METHOD_HANDLE callerHnd, /* IN */
CORINFO_METHOD_HANDLE calleeHnd, /* IN */
bool fIsTailPrefix /* IN */
) = 0;
// Returns false if precompiled code must ensure that
// the EE's DoPrestub function gets run before the
// code for the method is used, i.e. if it returns false
// then an indirect call must be made.
//
// Returning true does not guarantee that a direct call can be made:
// there can be other reasons why the entry point cannot be embedded.
//
virtual bool __stdcall canSkipMethodPreparation (
CORINFO_METHOD_HANDLE callerHnd, /* IN */
CORINFO_METHOD_HANDLE calleeHnd, /* IN */
bool fCheckCode, /* IN */
CorInfoIndirectCallReason *pReason = NULL,
CORINFO_ACCESS_FLAGS accessFlags = CORINFO_ACCESS_ANY) = 0;
// Returns true if a direct call can be made via the method entry point
//
virtual bool __stdcall canCallDirectViaEntryPointThunk (
CORINFO_METHOD_HANDLE calleeHnd, /* IN */
void ** pEntryPoint /* OUT */
) = 0;
// get individual exception handler
virtual void __stdcall getEHinfo(
CORINFO_METHOD_HANDLE ftn, /* IN */
unsigned EHnumber, /* IN */
CORINFO_EH_CLAUSE* clause /* OUT */
) = 0;
// return class it belongs to
virtual CORINFO_CLASS_HANDLE __stdcall getMethodClass (
CORINFO_METHOD_HANDLE method
) = 0;
// return module it belongs to
virtual CORINFO_MODULE_HANDLE __stdcall getMethodModule (
CORINFO_METHOD_HANDLE method
) = 0;
// This function returns the offset of the specified method in the
// vtable of it's owning class or interface.
virtual unsigned __stdcall getMethodVTableOffset (
CORINFO_METHOD_HANDLE method
) = 0;
// If a method's attributes have (getMethodAttribs) CORINFO_FLG_INTRINSIC set,
// getIntrinsicID() returns the intrinsic ID.
virtual CorInfoIntrinsics __stdcall getIntrinsicID(
CORINFO_METHOD_HANDLE method
) = 0;
// return the unmanaged calling convention for a PInvoke
virtual CorInfoUnmanagedCallConv __stdcall getUnmanagedCallConv(
CORINFO_METHOD_HANDLE method
) = 0;
// return if any marshaling is required for PInvoke methods. Note that
// method == 0 => calli. The call site sig is only needed for the
// varargs or calli case
virtual BOOL __stdcall pInvokeMarshalingRequired(
CORINFO_METHOD_HANDLE method,
CORINFO_SIG_INFO* callSiteSig
) = 0;
// Check Visibility rules.
// For Protected (family access) members, type of the instance is also
// considered when checking visibility rules.
virtual BOOL __stdcall canAccessMethod(
CORINFO_METHOD_HANDLE context,
CORINFO_CLASS_HANDLE parent,
CORINFO_METHOD_HANDLE target,
CORINFO_CLASS_HANDLE instance
) = 0;
// Check constraints on method type arguments (only).
// The parent class should be checked separately using
// satisfiesClassConstraints(parent).
virtual BOOL __stdcall satisfiesMethodConstraints(
CORINFO_CLASS_HANDLE parent, // the exact parent of the method
CORINFO_METHOD_HANDLE method
) = 0;
// Given a delegate target class, a target method parent class, a target method,
// a delegate class, a scope, the target method ref,
// and the delegate constructor member ref
// check if the method signature is compatible with the Invoke method of the delegate
// (under the typical instantiation of any free type variables
// in the memberref signatures).
// NB: arguments 2-4 could be inferred from 5-7, but are assumed to be available,
// and thus passed in for efficiency.
virtual BOOL __stdcall isCompatibleDelegate(
CORINFO_CLASS_HANDLE objCls, /* type of the delegate target,
if any */
CORINFO_CLASS_HANDLE methodParentCls, /* exact parent of
the target method, if any */
CORINFO_METHOD_HANDLE method, /* (representative) target
method, if any */
CORINFO_CLASS_HANDLE delegateCls, /* exact type of the delegate */
CORINFO_MODULE_HANDLE moduleHnd, /* scope of the
following refs */
unsigned methodMemberRef, /* memberref of the
target method */
unsigned delegateConstructorMemberRef /* memberref of the
delegate constructor */
) = 0;
// Indicates if the method is an instance of the generic
// method that passes (or has passed) verification
virtual CorInfoInstantiationVerification __stdcall isInstantiationOfVerifiedGeneric (
CORINFO_METHOD_HANDLE method /* IN */
) = 0;
// Loads the constraints on a typical method definition, detecting cycles;
// for use in verification.
virtual void __stdcall initConstraintsForVerification(
CORINFO_METHOD_HANDLE method, /* IN */
BOOL *pfHasCircularClassConstraints, /* OUT */
BOOL *pfHasCircularMethodConstraint /* OUT */
) = 0;
// Returns enum whether the method does not require verification
// Also see ICorModuleInfo::canSkipVerification
virtual CorInfoCanSkipVerificationResult __stdcall canSkipMethodVerification (
CORINFO_METHOD_HANDLE ftnHandle, /* IN */
BOOL fQuickCheckOnly
) = 0;
// Determines whether a callout is allowed.
virtual CorInfoIsCallAllowedResult __stdcall isCallAllowed (
CORINFO_METHOD_HANDLE callerHnd, // IN
CORINFO_METHOD_HANDLE calleeHnd, // IN
CORINFO_CALL_ALLOWED_INFO * CallAllowedInfo // OUT
) = 0;
// load and restore the method
virtual void __stdcall methodMustBeLoadedBeforeCodeIsRun(
CORINFO_METHOD_HANDLE method
) = 0;
virtual CORINFO_METHOD_HANDLE __stdcall mapMethodDeclToMethodImpl(
CORINFO_METHOD_HANDLE method
) = 0;
// Returns the global cookie for the /GS unsafe buffer checks
// The cookie might be a constant value (JIT), or a handle to memory location (Ngen)
virtual void __stdcall getGSCookie(
GSCookie * pCookieVal, // OUT
GSCookie ** ppCookieVal // OUT
) = 0;
};
As you can see, the // Cookie types consumed by the code generator (these are opaque values
// not inspected by the code generator):
typedef struct CORINFO_ASSEMBLY_STRUCT_* CORINFO_ASSEMBLY_HANDLE;
typedef struct CORINFO_MODULE_STRUCT_* CORINFO_MODULE_HANDLE;
typedef struct CORINFO_DEPENDENCY_STRUCT_* CORINFO_DEPENDENCY_HANDLE;
typedef struct CORINFO_CLASS_STRUCT_* CORINFO_CLASS_HANDLE;
typedef struct CORINFO_METHOD_STRUCT_* CORINFO_METHOD_HANDLE;
typedef struct CORINFO_FIELD_STRUCT_* CORINFO_FIELD_HANDLE;
// represents a list of argument types
typedef struct CORINFO_ARG_LIST_STRUCT_* CORINFO_ARG_LIST_HANDLE;
// represents the whole list
typedef struct CORINFO_SIG_STRUCT_* CORINFO_SIG_HANDLE;
typedef struct CORINFO_JUST_MY_CODE_HANDLE_*CORINFO_JUST_MY_CODE_HANDLE;
// a handle guaranteed to be unique per process
typedef struct CORINFO_PROFILING_STRUCT_* CORINFO_PROFILING_HANDLE;
typedef DWORD* CORINFO_SHAREDMODULEID_HANDLE;
// a generic handle (could be any of the above)
typedef struct CORINFO_GENERIC_STRUCT_* CORINFO_GENERIC_HANDLE;
The structures are not defined in the code: as the comment states, they're "opaque". Actually, these handles are just pointers. But we'll see that later. What should be understood now is that they are used by the methods of the JIT to identify things. I pasted all the declarations above to give the reader an idea of the kind of power over the JIT given by the two includes I mentioned earlier. Let's have look, for instance, at the first method of virtual const char* __stdcall getMethodName (
CORINFO_METHOD_HANDLE ftn, /* IN */
const char **moduleName /* OUT */
) = 0;
This function retrieves the method's name and class. And I'll use it to give a basic example of how to hook the JIT and retrieve basic information from it. But first, I have to introduce another thing: the .NET assembly loader. The .NET Assembly LoaderAs we need to hook the JIT before the victim assembly is jitted, the best way to do this is to load the victim assembly from another assembly which in the meantime has already hooked the JIT. I call this the loader and this method works only when the protection isn't wrapping the assembly into a native executable. In that case, you might consider adding the hook DLL to the import table of the native EXE or creating the victim process in a suspended state, injecting the DLL and then resuming the execution. There are several ways to load an assembly into the current address space. Unfortunately, the most used ones often do not work in all cases. For example a common way to do this is use the This is the very simple approach I'm using in this article: namespace rbloader
{
static class Program
{
///
Keep in mind that with certain protections this code loading method won't work. So, you have to evaluate each case (since it depends on how the injector is implemented) and find a way to hook the JIT before the protection does. Also, the first executed assembly decides the .NET Framework platform. Thus, the loader should be compiled consequently. What this means is that you can choose the platform on which a certain assembly has to run from the Visual Studio options. If you choose a 64-bit platform, then the PE of the assembly will be a 64-bit PE which can run only on the platform it was meant for, whereas 32-bit PEs can run on every platform. To force a 32-bit PE to use the x86 framework, a flag has to be set in the .NET Directory's Flags field:
The code I wrote in this article is 64-bit compatible, but I only tested it on x86. Thus, the loader has the 32-bit code set. To use the loader in a 64-bit context, you have to unset this flag. JIT Hooking ExampleWhat I'm going to present here is a little example of how to hook the JIT and retrieve information from it. What I'm going to do is to show the class and method name of each jitted method and of each method this method is calling. This is a good time to introduce the fact that the #include "stdafx.h"
#include <tchar.h>
#include <CorHdr.h>
#include "corinfo.h"
#include "corjit.h"
#include "DisasMSIL.h"
HINSTANCE hInstance;
extern "C" __declspec(dllexport) void HookJIT();
VOID DisplayMethodAndCalls(ICorJitInfo *comp, CORINFO_METHOD_INFO *info);
BOOL APIENTRY DllMain( HMODULE hModule,
DWORD dwReason,
LPVOID lpReserved
)
{
hInstance = (HINSTANCE) hModule;
HookJIT();
return TRUE;
}
//
// Hook JIT's compileMethod
//
BOOL bHooked = FALSE;
ULONG_PTR *(__stdcall *p_getJit)();
typedef int (__stdcall *compileMethod_def)(ULONG_PTR classthis, ICorJitInfo *comp,
CORINFO_METHOD_INFO *info, unsigned flags,
BYTE **nativeEntry, ULONG *nativeSizeOfCode);
struct JIT
{
compileMethod_def compileMethod;
};
compileMethod_def compileMethod;
int __stdcall my_compileMethod(ULONG_PTR classthis, ICorJitInfo *comp,
CORINFO_METHOD_INFO *info, unsigned flags,
BYTE **nativeEntry, ULONG *nativeSizeOfCode);
extern "C" __declspec(dllexport) void HookJIT()
{
if (bHooked) return;
LoadLibrary(_T("mscoree.dll"));
HMODULE hJitMod = LoadLibrary(_T("mscorjit.dll"));
if (!hJitMod)
return;
p_getJit = (ULONG_PTR *(__stdcall *)()) GetProcAddress(hJitMod, "getJit");
if (p_getJit)
{
JIT *pJit = (JIT *) *((ULONG_PTR *) p_getJit());
if (pJit)
{
DWORD OldProtect;
VirtualProtect(pJit, sizeof (ULONG_PTR), PAGE_READWRITE, &OldProtect);
compileMethod = pJit->compileMethod;
pJit->compileMethod = &my_compileMethod;
VirtualProtect(pJit, sizeof (ULONG_PTR), OldProtect, &OldProtect);
bHooked = TRUE;
}
}
}
//
// hooked compileMethod
//
/*__declspec (naked) */
int __stdcall my_compileMethod(ULONG_PTR classthis, ICorJitInfo *comp,
CORINFO_METHOD_INFO *info, unsigned flags,
BYTE **nativeEntry, ULONG *nativeSizeOfCode)
{
// in case somebody hooks us (x86 only)
#ifdef _M_IX86
__asm
{
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
}
#endif
// call original method
// I'm not using the naked + jmp approach to avoid x64 incompatibilities
int nRet =
compileMethod(classthis, comp, info, flags, nativeEntry, nativeSizeOfCode);
//
// Displays the current method and its calls
//
DisplayMethodAndCalls(comp, info);
return nRet;
}
VOID DisplayMethodAndCalls(ICorJitInfo *comp,
CORINFO_METHOD_INFO *info)
{
const char *szMethodName = NULL;
const char *szClassName = NULL;
szMethodName = comp->getMethodName(info->ftn, &szClassName);
char CurMethod[200];
sprintf_s(CurMethod, 200, "%s::%s", szClassName, szMethodName);
char Calls[0x1000];
strcpy_s(Calls, 0x1000, "Methods called:\r\n\r\n");
//
// retrieve calls
//
#define MAX_INSTR 100
ILOPCODE_STRUCT ilopar[MAX_INSTR];
DISASMSIL_OFFSET CodeBase = 0;
BYTE *pCur = info->ILCode;
UINT nSize = info->ILCodeSize;
UINT nDisasmedInstr;
while (DisasMSIL(pCur, nSize, CodeBase, ilopar, MAX_INSTR,
&nDisasmedInstr))
{
//
// check the instructions for calls
//
for (UINT x = 0; x < nDisasmedInstr; x++)
{
if (info->ILCode[ilopar[x].Offset] == ILOPCODE_CALL)
{
DWORD dwToken = *((DWORD *) &info->ILCode[ilopar[x].Offset + 1]);
CORINFO_METHOD_HANDLE hCallHandle =
comp->findMethod(info->scope, dwToken, info->ftn);
szMethodName = comp->getMethodName(hCallHandle, &szClassName);
strcat_s(Calls, 0x1000, szClassName);
strcat_s(Calls, 0x1000, "::");
strcat_s(Calls, 0x1000, szMethodName);
strcat_s(Calls, 0x1000, "\r\n");
}
}
//
// end loop?
//
if (nDisasmedInstr < MAX_INSTR) break;
//
// next instructions
//
DISASMSIL_OFFSET next = ilopar[nDisasmedInstr - 1].Offset - CodeBase;
next += ilopar[nDisasmedInstr - 1].Size;
pCur += next;
nSize -= next;
CodeBase += next;
}
//
// Show MessageBox
//
MessageBoxA(0, Calls, CurMethod, MB_ICONINFORMATION);
}
The
As you can see, I didn't include the The .NET Code EjectorWhen writing a dumper, or better a code ejector, for a code injecting protection, one has to choose how to proceed in order to collect the original MSIL code. There are two ways to proceed: either "stealth" or "brute". By stealth I mean dumping the MSIL only of those methods which have been jitted during execution. If you proceed that way, you'll need to retrieve along with the MSIL code, the token of the method, in order to rebuild the assembly then with Rebel.NET. There's a very useful function to retrieve the token from a comp->getMethodDefFromMethod(info->ftn)
The stealth method will always work 100%, but it has the disadvantage that one can't be sure that all the methods in an assembly will be jitted. However, it is worth mentioning, since in some cases the goal to achieve is to dump just a few methods in order to decompile and analyze them. The other way to eject the MSIL code I called "brute". By brute I mean forcing the protection to decrypt every method in a .NET assembly at once. What is necessary to do is to collect the The code ejector I wrote also features a little dumper of the jitted assemblies. This is quite useful, since most times you have to dump the protected assembly. This can also be achieved by a generic .NET unpacker.
As you can see from the image, the dialog contains a "Generate Rebel File" button. When this button is pressed a rebel report file is requested as input. Basically, one needs to dump (if necessary) the assembly to rebuild first. Then, use the Rebel.NET to create a report file from that assembly. Only the methods should be included in the report file:
This report file will be used during the code ejection process to calculate the number of methods and to retrieve the MSIL code address and size. Actually, the JIT can be used to retrieve this information, but there's a problem. If you noticed, the 03B8E1F0 01 00 00 3B 74 01 00 00 ...;t...
03B8E200 02 00 01 08 0D 00 00 00 03 00 02 08 75 01 00 00 ............u...
03B8E210 04 00 03 39 76 01 20 00 05 00 04 08 77 01 00 00 ...9v. .....w...
The first word seems to represent the method's number and after 8 bytes comes the next method. So, in theory, it might even be possible to calculate the right But, as said, this is not the way I proceeded. I used a Rebel.NET report file to retrieve the necessary data. This approach could obviously be changed. What follows is the code of the .NET code ejector. #include "stdafx.h"
#include <CommCtrl.h>
#include <CommDlg.h>
#include <tlhelp32.h>
#include <tchar.h>
#include <CorHdr.h>
#include "corinfo.h"
#include "corjit.h"
#include "RebelDotNET.h"
#include "resource.h"
#ifndef PAGE_SIZE
#define PAGE_SIZE 0x1000
#endif
#define IS_FLAG(Value, Flag) ((Value & Flag) == Flag)
HINSTANCE hInstance;
extern " | ||||||||||||||||||||||||