Intercepting .NET / CLR Functions

Sometimes, when developing software, it is necessary to embed additional functionality into existing applications without modifying the source code of the applications. Moreover, often the applications themselves exist only in compiled binary form without source code. A widely known method of solving this problem is the so-called. “Splicing” is a method of intercepting functions by changing the code of the target function. Usually, when splicing, the first bytes of the target function are moved to other addresses, and the unconditional jump command (jmp) to the replacement function is written to their original place. Since splicing requires low-level operations with memory, it is carried out using assembly language and C / C ++,

The splicing method for intercepting API functions in Windows is widely described on the Internet and in various literary sources. The simplicity of this interception is determined by the following factors:
  1. the target function is static - it is immediately present in the memory of the loaded module;
  2. the address of the target function is easy to determine (via the module export table or the GetProcAddress function ).

The implementation of substitute functions in C / C ++ when intercepting API functions is the best option, since the Windows API is implemented, as you know, in the C language, and substitute functions can use the same concepts as replaced ones.

With the advent of .NET technology, the situation has radically changed. Dynamically linked libraries created for .NET no longer contain static functions (functions are generated dynamically based on IL intermediate language commands). As a result of this, it is difficult to predict the address in memory at which functions will be placed after dynamic compilation (JIT compilation), as well as to track the moment of JIT compilation itself. In addition, without additional efforts, it is impossible to use the .NET function as a replacement function, since it is not static and is not implemented in C / C ++.

In this article, an algorithm will be described whose application allows you to replace .NET functions with functions also developed in the .NET environment. To understand the given algorithm, we will have to delve into the implementation of the CLR (common language runtime) .NET. In describing the implementation of the CLR, we will simplify some details in order to avoid complicating the understanding of the general essence.

1. Method call methods in the CLR


In the CLR, each function (method) is a set of IL-commands and all information about it is stored in the metadata of the module. When loading a module for each of its classes, the CLR system creates a MethodTable table containing information about the methods of the class. Each class method is described by the MethodDesc structure , one of the fields of which contains the address of the compiled method in memory (when the method is JIT-compiled), and the other contains the index in the MethodTable table , which indicates the address of the adapter (thunk), the contents of which change during execution in depending on whether the method is compiled or not.



Initially (before performing the JIT compilation), one of the four so-called ones acts as an adapter. precode CLR adapters:StubPrecode , FixupPrecode , RemotingPrecode or NDirectImportPrecode . Since the last adapter is used only to call Windows API functions, which can be intercepted directly, we will not consider it.

The main task of each of the precode adapters is to pass the address of the MethodDesc structure that
defines the method used to the ThePreStub internal function ( ThePreStubAMD64 for the x64 platform, marked as Stub in the figure), which performs the following tasks:
  1. JIT compilation of the method identified by the MethodDesc structure;
  2. setting a pointer in the MethodDesc structure to the generated native code;
  3. rewrite the adapter so that it makes an unconditional jump (jmp) to the generated native code;
  4. execution of the generated native code.

Thus, as a result of the initial call to the target method, the method code will not only be generated and executed, but the contents of the adapter will change, which will lead to a direct call to the generated native code during subsequent method calls.

Any .NET method called from the common language runtime passes through the address in the MethodTable table of class methods. However, the CLR provides the ability to invoke a method from an unmanaged C / C ++ environment. To do this, use the following functions: GetFunctionPointer class RuntimeMethodHandle and GetFunctionPointerForDelegate Class Marshal . Addresses returned by the specified functions are also addresses of adapters, among which may be already mentionedStubPrecode , FixupPrecode and RemotingPrecode . As a result of the initial call to the method, it is compiled and executed, and upon the next call, a direct transition to the generated code is performed. At the same time, it is important for us that for an uncompiled method, when it is called both through the method table and through the pointers returned by the functions mentioned, the internal function ThePreStub is called .

2. Precode CLR Adapters


Let us now take a separate look at the CLR precode adapters and indicate how, knowing only the binary code of the adapter itself, we can determine the address of the MethodDesc structure associated with this adapter and the address of the ThePreStub internal function (in the future, this will come in handy). In addition, we indicate how to determine the address of the generated code in the specified adapter after performing JIT compilation.
  1. StubPrecode . At the moment of its creation, the value of the address of the MethodDesc structure is embedded into thespecified adapterdirectly by the CLR system (as a direct value in the assembler command). The adapter code depends only on the hardware platform and does not depend on the CLR version. For various hardware platforms, it has the following form:
    x86:    
            mov eax, pMethodDesc 
            mov ebp, ebp         
            jmp ThePreStub       
    x64: 
            mov r10, pMethodDesc 
            jmp ThePreStub       
            

    Thus, the address of the MethodDesc structure is passed to the ThePreStub function in the eax register (for x86) or r10 (for x64). During the analysis of memory, the specified address can be read explicitly at offset 1 (for x86) or 2 (for x64) of the adapter, taking into account the processor capacity. The address of ThePreStub function can be calculated by adding the relative offset built into the last jmp command with the completion address of the specified command.

    After the JIT compilation is completed, the transition address is replaced from the address of ThePreStub function with the address of the generated code and the contents of the adapter become the following:
    x86: 
            mov eax, pMethodDesc 
            mov ebp, ebp         
            jmp NativeCode       
    x64: 
            mov r10, pMethodDesc 
            jmp NativeCode       
            

    The method for determining the address of the generated code after the JIT compilation is the same as the method for determining the address of the ThePreStub function before the JIT compilation.

  2. FixupPrecode . The specified adapter was designed to optimize memory usage. It takes 8 bytes on all hardware platforms, which is less than the size of the StubPrecode adapter(12 bytes for x86 and 16 bytes for x64). The adapter code for all hardware platforms and CLR versions is as follows:

            call PrecodeFixupThunk  
            db 0x5E                
            db MethodDescChunkIndex 
            db PrecodeChunkIndex    
    или
            call PrecodeFixupThunk  
            db 0xСС                 
            db MethodDescChunkIndex 
            db PrecodeChunkIndex    
            

    When using FixupPrecode adapters, the CLR complies with the following two requirements:

    1. adapter- specific MethodDesc structures are combined in continuous blocks of MethodDescChunk memory :

    2. FixupPrecode-переходники также объединяются в непрерывный блок памяти, причем в указанном блоке после окончания переходников системой CLR встраивается базовый адрес pMethodDescChunkBase структур MethodDesc в блоке памяти MethodDescChunk:

      call PrecodeFixupThunk       
      db ?                         
      db MethodDescChunkIndex      
      db PrecodeChunkIndex         
      ...                          
      call PrecodeFixupThunk       
      db ?                         
      db MethodDescChunkIndex      
      db 2                         
      call PrecodeFixupThunk       
      db ?                         
      db MethodDescChunkIndex      
      db 1                         
      call PrecodeFixupThunk       
      db ?                         
      db MethodDescChunkIndex      
      db 0                         
      dd pMethodDescChunkBase (x86)
      dq pMethodDescChunkBase (x64)
                 


    При такой организации памяти адрес структуры MethodDesc для определенного переходника FixupPrecode задается по следующей формуле:

    aдрес MethodDesc = pMethodDescChunkBase + MethodDescChunkIndex * sizeof(void*),

    где базовое смещение (pMethodDescChunkBase) извлекается по следующему адресу:

    адрес pMethodDescChunkBase = адрес FixupPrecode + 8 + PrecodeChunkIndex * 8,

    а MethodDescChunkIndex и PrecodeChunkIndex — байтовые значения, встроенные в PrecodeFixupThunk.

    The value of the address of the MethodDesc structure by the CLR is calculated inside the optional adapter PrecodeFixupThunk , which exists in the singular and is intended only for calculating and passing the specified address to ThePreStub in the eax (for x86) or r10 (for x64) register. Here is the code for the PrecodeFixupThunk adapter for various hardware platforms.
    x86: 
            pop    eax	                                            
            push   esi	                                            
            push   edi	                                            
            movzx  esi, byte ptr  [eax + 0x2]	                    
            movzx  edi, byte ptr  [eax + 0x1]	                    
            mov    eax, dword ptr [eax + esi * 8 + 0x3]             
            lea    eax, [eax + edi * 4] 			                
            pop    edi	                                            
            pop    esi	                                            
            jmp    dword ptr [g_dwPreStubAddr] (для CLR 2.0)        
            jmp    ThePreStub                  (для CLR 4.0 и выше)                  
    x64: 
            pop    rax	                                            
            movzx  r10, byte ptr  [rax + 0x2]	                    
            movzx  r11, byte ptr  [rax + 0x1]	                    
            mov    rax, qword ptr [rax + r10 * 8 + 0x3]	            
            lea    r10, [rax + r11 * 8] 			                
            jmp    ThePreStub                                       
            

    The address of the ThePreStub internal function using the FixupPrecode adapter can be calculated in two stages:

    1. calculate the address of the PrecodeFixupThunk adapter by adding the relative offset built into the first command of the call FixupPrecode- adapter to the completion address of the specified command;
    2. for all platforms except CLR 2.0 x86, calculate the ThePreStub address by adding the relative offset built into the last jmp command of the PrecodeFixupThunk adapter with the completion address of the specified command;
    3. for the CLR 2.0 x86 platform, extract the ThePreStub address to the address that is built into the last jmp command (indirect addressing through the internal variable g_dwPreStubAddr ).


    After completing the JIT compilation in the FixupPrecode adapter , the first call command is replaced with the jmp command, replacing the transition address from the address of the PrecodeFixupThunk adapter to the address of the generated code. In addition, if the first command is followed by byte 0x5E, then it is replaced by byte 0x5F (these bytes are an indicator of the presence or absence of JIT compilation, byte 0xCC means no information). Thus, after replacement, the contents of the adapter are as follows:

            jmp NativeCode         
            db 0x5E                
            db MethodDescChunkIndex
            db PrecodeChunkIndex   
    или
            jmp NativeCode          
            db 0xСС                 
            db MethodDescChunkIndex 
            db PrecodeChunkIndex    
            

    After JIT compilation, the address of the generated code is calculated by adding the relative offset built into the first jmp command to the completion address of the specified command.

  3. RemotingPrecode . The specified adapter is used when calling methods of objects that may exist in another application domain. The adapter code is as follows:
    x86: 
            mov eax, pMethodDesc        
            nop                         
            call PrecodeRemotingThunk   
            jmp ThePreStub              
    x64: 
            test rcx,rcx
            je Local
            mov rax, qword ptr [rcx]
            mov r10, ProxyAddress
            cmp rax, r10
            je Remote
    Local:  mov rax, ThePreStub
            jmp rax
    Remote: mov r10, pMethodDesc
            mov rax, RemotingCheck
            jmp rax
            

    As with the StubPrecode adapter , at the time of its creation in RemotingPrecode , the value of the address of the MethodDesc structure is built in by the CLR system directly (as a direct value in the assembler command). The specified value can be extracted at offset 1 (for x86) and 37 (for x64). The address of ThePreStub function is the result of adding the relative offset built into the last jmp command with the address of the completion of the specified command (for x86) or the direct value at offset 25 (for x64).

    For objects that do not belong to other domains, after the JIT compilation, the transition address is replaced with the address of the ThePreStub functionto the address of the generated code, therefore, the method of determining the address of the generated code after performing the JIT compilation is the same as the method of determining the address of the ThePreStub function before performing the JIT compilation. For objects belonging to other domains, after the JIT compilation, the body of the RemotingPrecode adapter does not change. For simplicity, we do not consider the option of using RemotingPrecode for objects that do not belong to the application domain.

3. ThePreStub function


As already mentioned, the internal ThePreStub function does the following:
  1. JIT compilation of the method identified by the MethodDesc structure;
  2. setting a pointer in the MethodDesc structure to the generated native code;
  3. rewrite the adapter so that it makes an unconditional jump (jmp) to the generated native code;
  4. execution of the generated native code.

In all versions of the CLR and hardware platforms, ThePreStub function is implemented in the CLR at the hardware level by calling the internal PreStubWorker function and then transferring control (via the jmp command) to the address returned by the specified function. For completeness, we present the code of the ThePreStub function for various platforms.

ThePreStub Function Code (x64)
CLR 4.6 и выше:
        push        r15  
        push        r14  
        push        r13  
        push        r12  
        push        rbp  
        push        rbx  
        push        rsi  
        push        rdi  
        sub         rsp,68h  
        mov         qword ptr   [rsp+0B0h],rcx  
        mov         qword ptr   [rsp+0B8h],rdx  
        mov         qword ptr   [rsp+0C0h],r8  
        mov         qword ptr   [rsp+0C8h],r9  
        movdqa      xmmword ptr [rsp+ 20h],xmm0  
        movdqa      xmmword ptr [rsp+ 30h],xmm1  
        movdqa      xmmword ptr [rsp+ 40h],xmm2  
        movdqa      xmmword ptr [rsp+ 50h],xmm3  
        lea         rcx,[rsp+68h]  
        mov         rdx,r10  
        call        PreStubWorker  
        movdqa      xmm0,xmmword ptr [rsp+20h]  
        movdqa      xmm1,xmmword ptr [rsp+ 30h]  
        movdqa      xmm2,xmmword ptr [rsp+ 40h]  
        movdqa      xmm3,xmmword ptr [rsp+ 50h]  
        mov         rcx,qword ptr    [rsp+0B0h]  
        mov         rdx,qword ptr    [rsp+0B8h]  
        mov         r8,qword ptr     [rsp+0C0h]  
        mov         r9,qword ptr     [rsp+0C8h]  
        add         rsp,68h  
        pop         rdi  
        pop         rsi  
        pop         rbx  
        pop         rbp  
        pop         r12  
        pop         r13  
        pop         r14  
        pop         r15  
        jmp         rax  
CLR 4.0:
        lea     rax, [rsp + 0x08]
        push    r10
        push    r15
        push    r14
        push    r13
        push    r12
        push    rbp
        push    rbx
        push    rsi
        push    rdi
        push    rax
        sub     rsp, 0x78
        mov     qword ptr   [rsp + 0xD0], rcx
        mov     qword ptr   [rsp + 0xD8], rdx
        mov     qword ptr   [rsp + 0xE0], r8
        mov     qword ptr   [rsp + 0xE8], r9
        movdqa  xmmword ptr [rsp + 0x20], xmm0
        movdqa  xmmword ptr [rsp + 0x30], xmm1
        movdqa  xmmword ptr [rsp + 0x40], xmm2
        movdqa  xmmword ptr [rsp + 0x50], xmm3
        lea     rcx, qword ptr [rsp + 0x68]
        call    PreStubWorker
        movdqa  xmm0, xmmword ptr [rsp + 0x20]
        movdqa  xmm1, xmmword ptr [rsp + 0x30]
        movdqa  xmm2, xmmword ptr [rsp + 0x40]
        movdqa  xmm3, xmmword ptr [rsp + 0x50]
        mov     rcx, qword ptr    [rsp + 0xD0]
        mov     rdx, qword ptr    [rsp + 0xD8]
        mov     r8 , qword ptr    [rsp + 0xE0]
        mov     r9 , qword ptr    [rsp + 0xE8]
        nop     
        add     rsp, 0x80
        pop     rdi
        pop     rsi
        pop     rbx
        pop     rbp
        pop     r12
        pop     r13
        pop     r14
        pop     r15
        pop     r10
        jmp     rax
CLR 2.0:
        lea    rax, [rsp + 0x08]	          
        push   r10	
        push   r15	
        push   r14	
        push   r13	
        push   r12	
        push   rbp	
        push   rbx	
        push   rsi	
        push   rdi	
        push   rax	
        sub    rsp, 0x78	
        mov    qword ptr   [rsp + 0xD0], rcx		  
        mov    qword ptr   [rsp + 0xD8], rdx	  
        mov    qword ptr   [rsp + 0xE0], r8	  
        mov    qword ptr   [rsp + 0xE8], r9	  
        movdqa xmmword ptr [rsp + 0x20], xmm0
        movdqa xmmword ptr [rsp + 0x30], xmm1
        movdqa xmmword ptr [rsp + 0x40], xmm2
        movdqa xmmword ptr [rsp + 0x50], xmm3
        call   PrestubMethodFrame::GetMethodFrameVPtr
        mov    qword ptr [rsp + 0x68], rax	
        mov    rax, qword ptr [s_gsCookie]
        mov    qword ptr [rsp + 0x60], rax	
        call   GetThread
        mov    r12, rax	
        mov    rdx, qword ptr [r12 + 0x10]	
        mov    qword ptr [rsp + 0x70], rdx	
        lea    rcx, [rsp + 0x68]	
        mov    qword ptr [r12 + 0x10], rcx	
        call   PreStubWorker
        mov    rcx, qword ptr [r12 + 0x10]	
        mov    rdx, qword ptr [rcx + 0x08]	
        mov    qword ptr [r12 + 0x10], rdx
        movdqa xmm0, xmmword ptr [rsp + 0x20]
        movdqa xmm1, xmmword ptr [rsp + 0x30]
        movdqa xmm2, xmmword ptr [rsp + 0x40]
        movdqa xmm3, xmmword ptr [rsp + 0x50]
        mov    rcx, qword ptr    [rsp + 0xD0]	  
        mov    rdx, qword ptr    [rsp + 0xD8]	  
        mov    r8 , qword ptr    [rsp + 0xE0]	  
        mov    r9 , qword ptr    [rsp + 0xE8]	  
        nop	
        add    rsp, 0x80	  
        pop    rdi	
        pop    rsi	
        pop    rbx	
        pop    rbp	
        pop    r12
        pop    r13	
        pop    r14	
        pop    r15	
        pop    r10	
        jmp    rax
    


ThePreStub Function Code (x86)
CLR 4.6 и выше:
        push    ebp  
        mov     ebp,esp  
        push    ebx  
        push    esi  
        push    edi  
        push    ecx  
        push    edx  
        mov     esi,esp  
        push    eax  
        push    esi  
        call   PreStubWorker
        pop     edx  
        pop     ecx  
        pop     edi  
        pop     esi  
        pop     ebx  
        pop     ebp  
        jmp     eax  
CLR 4.0:
        push   ebp	
        mov    ebp, esp	
        push   ebx	
        push   esi	
        push   edi	
        push   ecx	
        push   edx	
        push   eax	
        sub    esp, 0x0C	
        lea    esi, [esp + 0x04]	
        push   esi	
        call   PreStubWorker
        add    esp, 0x10	
        pop    edx	
        pop    ecx	
        pop    edi	
        pop    esi	
        pop    ebx	
        pop    ebp	
        jmp    eax	
CLR 2.0:
        push   eax
        push   edx
        push   PrestubMethodFrame::'vftable'
        push   ebp	
        push   ebx	
        push   esi	
        push   edi	
        lea    esi, [esp + 0x10]
        push   dword ptr [esi + 0x0C]
        push   ebp	
        mov    ebp, esp	
        push   ecx	
        push   edx	
        mov    ebx, dword ptr fs:0x0E34
        mov    edi, dworp ptr [ebx + 0x0C]
        mov    dword ptr [esi + 0x04], edi
        mov    dword ptr [ebx + 0x0C], esi
        push   cookie
        push   esi
        call   PreStubWorker
        mov    dword ptr [ebx + 0x0C], edi
        mov    ecx, dword ptr [esi + 0x08]
        mov    dword ptr [esi + 0x08], eax
        mov    eax, ecx
        add    esp, 0x04	
        pop    edx	
        pop    ecx	
        mov    esp, ebp	
        pop    ebp	
        add    esp, 0x04	
        pop    edi	
        pop    esi	
        pop    ebx	
        pop    ebp	
        add    esp, 0x08	
        ret    
    


Knowing the binary structure of precode adapters, the address of ThePreStub function can be determined as follows:
  1. We define an arbitrary static CLR method (you can even make it empty), prohibiting inline embedding and precompilation:

    public delegate void EmptyDelegate();
    [MethodImplAttribute(
     MethodImplOptions.NoInlining | MethodImplOptions.NoOptimization)]
    public static void Empty() {}
        

  2. Create and lock the delegate of the method in memory and define the address returned by the RuntimeMethodHandle.GetFunctionPointer function :

    EmptyDelegate function = Empty; 
    GCHandle gc = GCHandle.Alloc(function);
    IntPtr methodPtr = function.Method.MethodHandle.GetFunctionPointer();
        

  3. If the commands at methodPtr match the sample adapter StubPrecode , then you should use the method of calculating the address of ThePreStub function from paragraph 1 of section 2. If the commands at the received address match the sample adapter FixupPrecode , then use the method of calculating the address of ThePreStub function from paragraph 2 of section 2 .

  4. To cancel the memory lock of the delegate of the method:

    gc.Free();
        



4. PreStubWorker Function


The PreStubWorker function performs the following actions:
  1. JIT compilation of the method identified by the MethodDesc structure;
  2. setting a pointer in the MethodDesc structure to the generated native code;
  3. rewrite the adapter so that it makes an unconditional jump (jmp) to the generated native code;
  4. return the ThePreStub function of the address of the changed adapter.

The PreStubWorker function has the following C declaration (according to the CLR source):

для CLR 4.6 и выше: void* __stdcall PreStubWorker(TransitionBlock* pTransitionBlock, MethodDesc* pMD);
для CLR ниже 4.6:   void* __stdcall PreStubWorker(PrestubMethodFrame *pPFrame);
    

Using this fact, the code listings of the ThePreStub function , and the fact that the TheDreStub function in the eax (for x86) and r10 (for x64) registers is passed the value of the MethodDesc address , you can determine how the PreStubWorker function accesses the value of the MethodDesc inside :
  1. for CLR 4.6 (and above), the specified value is extracted from the second parameter passed to the function;
  2. for CLR below 4.6 x86 platform the value is located at offset 8 of the structure addressed by the pPFrame parameter ;
  3. for the CLR below 4.6 x64 platform, the value is located at the address, 16 bytes less than the value of the address located at offset 16 of the structure addressed by the pPFrame parameter .

Knowing the address of the ThePreStub internal function and based on the above listings of its code, you can specify an algorithm for calculating the address of the PreStubWorker internal function without using fixed offsets inside the ThePreStub function (which, as you can see, change with each new version of the CLR):
  1. for x86 and x64 platforms (except for CLR 2.0), the specified address will be the result of adding the relative offset built into the call command, which is unique in ThePreStub function , with the completion address of the specified command;
  2. for x64 CLR 2.0, the specified address will be the result of adding the relative offset built into the call command, which is preceded by the lea command, with the address of the call command completion.

You can find the required call commands during execution if you have a built-in disassembler that can determine the codes and sizes of commands in runtime.

5. The interception algorithm


Summarizing all of the above, we can suggest the following way to intercept .NET functions:
  1. Get the address of the replacement method using a call to RuntimeMethodHandle.GetFunctionPointer ;
  2. if the replaced method is already JIT-compiled, then find the address in the memory of the generated native code and intercept the specified address to execute the replacement method;
  3. if the replaced method is not yet JIT-compiled, then
    1. calculate the address of its MethodDesc structure ;
    2. calculate the address and intercept the PreStubWorker function in such a way that the original implementation is called in the substitute PreStubWorker method;
    3. add additional logic to the PreStubWorker replacement function for the case when the function uses the MethodDesc address that matches the required address. In this case, after calling the original implementation, get the address of the generated native method and intercept the received address to execute the replacement method.


After all the foregoing, the above paragraphs of the algorithm do not require detailed explanations, with the exception of paragraphs 2 and 3.1.

Clause 2 talks about determining the address of a real generated native code (without any adapters). The algorithm below is based on knowledge of the binary structure of the adapters generated by the CLR environment and calculates the specified address (or returns NULL if there is no JIT compilation).
  1. Get the address of a .NET method by calling RuntimeMethodHandle.GetFunctionPointer .
  2. If the commands at the received address coincide with the sample adapter StubPrecode or RemotingPrecode , then extract the address of the compiled code as described in section 1 and 3 of Section 2. If the specified address matches the address of the ThePreStub function , then the JIT method was not compiled and should be return null. Otherwise, return the address of the compiled code.
  3. Until the current address matches the address of the ThePreStub function , do the following:
    1. if the current address points to the jmp command, then go to the destination address for the jmp command;
    2. otherwise, if the current address points to the call command, then check the destination address of the call command. If it is equal to the PrecodeFixupThunk adapter (the case of the FixupPrecode adapter before the JIT compilation), then return NULL. Otherwise, return the address where the call command is located (or the destination address for the call command);
    3. otherwise, return the current address.

  4. Return NULL since the address of ThePreStub function has been reached .

Clause 3.1 talks about defining the address of a MethodDesc structure for an uncompiled method. The algorithm below is based on the knowledge of the binary structure of the adapters generated by the CLR environment and calculates the specified address (or NULL in some cases if there is a JIT compilation).
  1. Get the address of a .NET method by calling RuntimeMethodHandle.GetFunctionPointer .
  2. If the commands at the received address coincide with the sample adapter StubPrecode or RemotingPrecode , then calculate the address of the MethodDesc structure , as described in paragraph 1 and paragraph 3 of section 2.
  3. Until the current address matches the address of the ThePreStub function , do the following:
    1. if the current address points to the jmp command, then check the byte immediately after the jmp command. If it is 0x5F ( FixupPrecode case after JIT compilation), then calculate the address of the MethodDesc structure , as described in section 2, section 2. Otherwise, go to the address for the jmp command;
    2. otherwise, if the current address points to the call command, then check the destination address of the call command. If it is equal to the PrecodeFixupThunk adapter (the case of the FixupPrecode- adapter before JIT compilation), then calculate the address of the MethodDesc structure , as described in Section 2, section 2. Otherwise, return NULL;
    3. otherwise, return NULL.

  4. Return NULL (the specified item must be unreachable).

6. Conclusion


The performance of the above algorithm has been repeatedly tested in practice (including in industrial developments) on various versions of .NET and hardware platforms. Based on it, the .NET library was developed, using which the interception of .NET functions becomes quite simple to use. Here is an example of using interception using the developed library.

Suppose you want to intercept the Open function of the SqlConnection class . Then the interception code when using the developed library may look like in C # in the following way:

public static class HookedConnection
{
    public static RTX.NET.HookHandle OpenHandle;
    [MethodImplAttribute(MethodImplOptions.NoInlining)]
    public static void Open(SqlConnection connection)
    {
        // вывести строку соединения
        Console.WriteLine(connection.ConnectionString); 
        // вызвать базовую функцию
        OpenHandle.Call(connection); 
    } 
} 
    

Here, the OpenHandle variable contains a descriptor with which you can call the implementation of the replaced function and which is initialized as a result of assigning an interception:

using (ConnectionEntry entry = new ConnectionEntry()) 
{ 
    Test(); 
}
    

where the ConnectionEntry class is the so-called “Interception manager”:

public class ConnectionEntry : RTX.NET.HookDispatcher, RTX.NET.IHookLoadHandler
{
    // обрабатываемые типы
    public virtual string[] GetTypes() 
    { 
        // указать класс для перехватываемых методов
        return new string[] { "System.Data.SqlClient.SqlConnection"}; 
    }
    // обработчик загрузки типов
    public virtual void OnLoad(RTX.NET.HookDispatcher dispatcher, Type type)
    {
        // перехватить методы
        HookedConnection.OpenHandle = HookOpen(dispatcher, type); 
    }
    private RTX.NET.HookHandle HookOpen(
        RTX.NET.HookDispatcher dispatcher, Type targetType)
    {
        // указать имя и тип параметров метода
        string name = "Open"; Type[] types = Type.EmptyTypes; 
        // указать атрибуты метода
        BindingFlags flags = BindingFlags.Public | 
        BindingFlags.Instance | BindingFlags.InvokeMethod; 
        // выполнить перехват
        return dispatcher.Install(targetType, name, 
            typeof(HookedConnection), name, flags, types
        ); 
    }
}
    

Then when executing the Test function

public static void Test()
{
    SqlConnection connection = new SqlConnection(); 
    connection.ConnectionString = @"Server=(localdb)\v11.0;" + 
        @"AttachDbFileName=C:\MyFolder\MyData.mdf;Integrated Security=true;"; 
    connection.Open (); 
    connection.Close(); 
}
    

the following message will be displayed in the console:

Server=(localdb)\v11.0;AttachDbFileName=C:\MyFolder\MyData.mdf;Integrated Security=true;
    

Also popular now: