Microsoft MVP성태의 닷넷 이야기
글쓴 사람
홈페이지
첨부 파일

실행 시에 메서드 가로채기 - CLR Injection: Runtime Method Replacer 개선 - 세 번째 이야기(Monitor.Enter 후킹)

.NET Profiler 기술을 활용하면,

CLR Profiler - 별도 정의한 .NET 코드를 호출하도록 IL 코드 변경
; https://www.sysnet.pe.kr/2/0/10959

런타임에 .NET 응용 프로그램의 IL 코드를 변경하는 것이 가능합니다. 그런데, 만능은 아닙니다. ^^ 가령 Monitor.Enter와 같은 메서드는 IL 코드를 변경할 수 없는데, 왜냐하면 애당초 IL 코드가 없기 때문입니다.

[SecuritySafeCritical]
[__DynamicallyInvokable]
[MethodImpl(MethodImplOptions.InternalCall)]
public static extern void Enter(object obj);

보는 바와 같이 IL 코드 없이 곧바로 "InternalCall"로 내부의 Native 함수에 연결되어 있습니다. 따라서, 이런 메서드를 가로채기 하려면 trampoline과 같은 별도의 후킹 방식을 사용해야 합니다. (혹은, Monitor.Enter에 대한 모든 IL 호출을 다른 메서드로 변경하거나.)




우선, 해당 메서드의 정보를 DetourFunc 라이브러리의 MethodDesc을 이용해 살펴볼까요? ^^

// Install-Package DetourFunc -Version 1.1.0

{
    MethodDesc md = MethodDesc.ReadFromAddress(action.Method.MethodHandle.Value);
    md.Dump(Console.Out);
}

/* 출력 결과
[MethodDesc 0x7ffc31f3c300 - FCall]
        wTokenRemainder = bb40 (Token = 6003b40)
        chunkIndex = 0
        bFlags2 = 000000B1 (Flags2 == 177)
        wSlotNumber = 4
        wFlags = 20a9 (IsFullSlotNumber == False)
        MethodTablePtr = 7ffc31f55b10
        FunctionPtr = 7ffc34c626b0
*/

위의 결과와 함께 windbg로 분석을 해보면,

0:000> !dumpmd 7ffc31f3c300
Method Name:  System.Threading.Monitor.Enter(System.Object)
Class:        00007ffc320c5528
MethodTable:  00007ffc31f55b10
mdToken:      0000000006003b40
Module:       00007ffc31f31000
IsJitted:     yes
CodeAddr:     00007ffc34c626b0
Transparency: Safe critical

00007ffc34c626b0 주소의 코드들이 마치 패치를 위한 배려라도 한 것처럼,

clr!JIT_MonEnter:
00007ffc`34c626b0 33d2            xor     edx,edx
00007ffc`34c626b2 6666666666660f1f840000000000 nop word ptr [rax+rax]
clr!JIT_MonEnterWorker_InlineGetThread:
00007ffc`34c626c0 56              push    rsi
00007ffc`34c626c1 488bf2          mov     rsi,rdx
00007ffc`34c626c4 4885c9          test    rcx,rcx
00007ffc`34c626c7 0f84a0010000    je      clr!JIT_MonEnterWorker_InlineGetThread_GetThread_PatchLabel+0x1a0 (00007ffc`34c6286d)
clr!JIT_MonEnterWorker_InlineGetThread_GetThread_PatchLabel:
...[생략]...

웬만한 길이의 trampoline 코드를 넘어서는 nop 코드를 마이크로소프트가 끼워두었습니다. (왜 그랬는지 궁금하군요. ^^;)

덕분에 아무런 걱정 없이 trampoline 패치를 할 수 있지만,

using DetourFunc;
using DetourFunc.Clr;
using System;
using System.Threading;

namespace ConsoleApp1
{
    public delegate void MonitorEnterMethodDelegate(object obj);

    class Program
    {
        static MonitorEnterMethodDelegate s_originalMethod;
        static MonitorEnterMethodRefDelegate s_originalMethodRef;

        static void Main(string[] _)
        {
            Person obj = new Person { Name = "TEST" };

            IntPtr ptrBodyTestMethod;
            {
                MonitorEnterMethodDelegate enterMethod = Monitor.Enter;
                MethodDesc md = MethodDesc.ReadFromMethodInfo(enterMethod.Method);
                ptrBodyTestMethod = md.GetNativeFunctionPointer();
            }

            IntPtr ptrBodyReplaceMethod;

            {
                MonitorEnterMethodDelegate action = Replaced_TestMethod;
                MethodDesc md = MethodDesc.ReadFromMethodInfo(action.Method);
                ptrBodyReplaceMethod = md.GetNativeFunctionPointer();
            }

            using (var item = new TrampolinePatch<MonitorEnterMethodDelegate>())
            {
                if (item.JumpPatch(ptrBodyTestMethod, ptrBodyReplaceMethod) == true)
                {
                    s_originalMethod = item.GetOriginalFunc();
                }

                Console.WriteLine("[After trampoline]");
                Monitor.Enter(obj);

                Console.WriteLine("[before Exit]");
                Monitor.Exit(obj);
            }

            Console.ReadLine();            
        }

        public static void Replaced_TestMethod(object obj)
        {
            Console.WriteLine("Replaced_TestMethod called!: " + obj?.ToString());
            Console.WriteLine("[before orgmethod]");

            s_originalMethod?.Invoke(obj);
        }
    }

    public class Person
    {
        public string Name { get; set; }

        public override string ToString()
        {
            return Name;
        }
    }
}

매끄러웠던 진행과 달리, 실행해 보면 예상치 않은 결과에 당황하게 됩니다. ^^;

[After trampoline]
Replaced_TestMethod called!: TEST
[before orgmethod]

[before Exit]

Unhandled Exception: System.Threading.SynchronizationLockException: Object synchronization method was called from an unsynchronized block of code.
   at ConsoleApp1.Program.Main(String[] _) in C:\ConsoleApp1\ConsoleApp1\Program.cs:line 84

그러니까, 분명히 가로채기는 성공했고 s_originalMethod 메서드 호출까지도 예외 없이 성공했는데, Monitor.Exit 호출 시 해당 object 인자가 동기화에 사용된 적이 없다고 하는 것입니다.




정리해 보면, 결국 다음의 3가지 메서드에 전달된 object 인자 중 어떤 하나(또는 셋 모두)는 다른 주소를 가리킨다는 것을 의미합니다.

Monitor.Enter(object obj)
Replaced_TestMethod(object obj)
s_originalMethod?.Invoke(obj)

원인을 밝히기 위해 windbg로 해당 메서드들의 object 인자를 추적하다가 s_originalMethod?.Invoke 호출 내에서 다음과 같이 마샬러가 수행되는 것을 발견했습니다.

[Marshal.GetDelegateForFunctionPointer가 반환한 s_originalMethod 델리게이트]

...[생략]...
00007ffb`d577a5f0 488d55a8        lea     rdx,[rbp-58h]
00007ffb`d577a5f4 488b4d18        mov     rcx,qword ptr [rbp+18h]
00007ffb`d577a5f8 e8c3755d5f      call    clr!StubHelpers::ObjectMarshaler__ConvertToNative (00007ffc`34d51bc0)
00007ffb`d577a5fd c745c401000000  mov     dword ptr [rbp-3Ch],1 ss:00000000`00dce824=00000000
...[생략]...

즉, ObjectMarshaler__ConvertToNative 함수에 의해 마샬링된 값을 Marshal.GetDelegateForFunctionPointer가 입력으로 받았던 함수에 전달하는 것입니다. 실제로 그 원본 함수에 전달하는 시점에 다시 rcx 값을 덤프해 보면,

0:000> dd @rcx L6
00000000`004fe738 0000000d 00000000 007b0018 00000000 00000000 00000000 

어디서 많이 봤던 패턴이 나옵니다.

C# - Marshal.GetNativeVariantForObject 사용 시 메모리 누수(Memory Leak) 발생 및 해결 방법
; https://www.sysnet.pe.kr/2/0/12157#object_marshal

그러니까, s_originalMethod?.Invoke(obj) 호출은 내부적으로 다음과 같은 식의 마샬링 작업을 수행했던 것입니다.

// 가상 코드
void s_originalMethod(object obj)
{
    IntPtr pAlloc = Marshal.AllocHGlobal(24); // x86 == 16, x64 == 24
    Marshal.GetNativeVariantForObject(obj, pAlloc);

    VARIANT vt = new VARIANT(pAlloc);

    call [_trampline_jmp_for_monitor_enter](vt);
}

당연히, 원본 Monitor.Enter는 (object가 아닌) VARIANT 타입의 값을 받게 되고 엉뚱한 객체의 sync block에 대해 잠금 표시를 수행한 것입니다. 그리고, 순서가 바뀌긴 했지만 바로 이 문제를 파악하다가 알게 된 내용들을 정리한 것이 아래의 글들이었습니다.

C# - Marshal.GetNativeVariantForObject 사용 시 메모리 누수(Memory Leak) 발생 및 해결 방법
; https://www.sysnet.pe.kr/2/0/12157

C# - Marshal.GetIUnknownForObject/GetIDispatchForObject 사용 시 메모리 누수(Memory Leak) 발생
; https://www.sysnet.pe.kr/2/0/12158

C#에서 만든 COM 객체를 C/C++로 P/Invoke Interop 시 메모리 누수(Memory Leak) 발생
; https://www.sysnet.pe.kr/2/0/12162

결국, Marshal.GetDelegateForFunctionPointer로 동적 생성한 delegate 함수는 내부적으로 Object 등의 객체를 마샬링하는 과정에 Marshal.GetNativeVariantForObject를 수행하고, 여기에서도 VariantClear를 수행하지 않으므로 메모리 누수 현상이 동일하게 발생합니다. 확인을 위해 원문의 코드를 다음과 같이 변경시키면,

while (true)
{
    Person obj = new Person { Name = "TEST" };
    Monitor.Enter(obj); // 가로채기되었으므로 s_originalMethod 호출 내부의 메모리 누수 발생
}

메모리가 지속적으로 증가하는 것을 볼 수 있습니다.




자, 그럼 어떻게 해야 Monitor.Enter를 (메모리 누수도 없이) 정상적으로 가로채기 할 수 있을까요? 이 문제를 해결하려면 최종적으로 s_originalMethod?.Invoke의 호출에 원본 객체를 자동 마샬링 과정 없이 전달해야 합니다.

그러고 보니 닷넷 객체를 가리키는 변수가 담은 값의 실체가 실은 GC 힙의 주소라는 것을 알고 있으니, 따라서 object가 아닌 IntPtr로 s_originalMethod?.Invoke에 전달한다면 내부적으로 마샬링이 발생하지 않을 수 있습니다. 마침 객체에 대한 GC Heap 상의 주소를 얻는 메서드를 MethodDesc 타입에 추가해 두었으니, 이를 이용해 다음과 같은 식으로 처리할 수 있습니다.

public delegate void MonitorEnterMethodRefDelegate(IntPtr objAddress);

class Program
{
    static MonitorEnterMethodRefDelegate s_originalMethodRef;

    static void Main(string[] _)
    {
        ...[생략]...

        using (var item = new TrampolinePatch<MonitorEnterMethodRefDelegate>())
        {
            if (item.JumpPatch(ptrBodyTestMethod, ptrBodyReplaceMethod) == true)
            {
                s_originalMethodRef = item.GetOriginalFunc();
            }

            Monitor.Enter(obj);
            Monitor.Exit(obj);
        }

        Console.ReadLine();            
    }

    public static void Replaced_TestMethod(object obj)
    {
        IntPtr objAddr = MethodDesc.GetObjectAddress(obj);
        s_originalMethodRef?.Invoke(objAddr);
    }
}

바뀐 코드로 실행해 보면, 정상적으로 Monitor.Exit가 실행이 되며 동기화가 풀리는 것을 확인할 수 있습니다. 정리해 보면, C#에서 Trampoline 기법을 이용해 우회한 메서드가 표면상 IL 코드이긴 해도 결국 내부적으로는 [Managed-To-Native] 층을 거쳐 인자 값들이 마샬링된다는 사실을 잊어서는 안 됩니다.

(첨부 파일은 이 글의 예제 코드를 포함합니다.)




예전의 출력 결과 유형이 IL인 반면, 이번 Monitor.Enter의 경우 "FCall"이라고 나옵니다.

/* 출력 결과
[MethodDesc 0x7ffc31f3c300 - FCall]
        wTokenRemainder = bb40 (Token = 6003b40)
        chunkIndex = 0
        bFlags2 = 000000B1 (Flags2 == 177)
        wSlotNumber = 4
        wFlags = 20a9 (IsFullSlotNumber == False)
        MethodTablePtr = 7ffc31f55b10
        FunctionPtr = 7ffc34c626b0
*/

이참에 ^^ 기존에 만들어 둔 MethodDesc에,

C# 코드로 접근하는 MethodDesc, MethodTable
; https://www.sysnet.pe.kr/2/0/12142

FCall 유형의 메서드에 대한 필드를,

class FCallMethodDesc : public MethodDesc
{
    DWORD   m_dwECallID;
#ifdef HOST_64BIT
    DWORD   m_padding;
#endif
};

추가해 다음과 같은 식의 구조체를 마련해 둘 수 있습니다.

[StructLayout(LayoutKind.Sequential)]
internal struct FCallMethodDescInternalx86
{
    readonly MethodDescInternal _methodDesc;
    readonly uint _dwECallID;

    public FCallMethodDescInternalx86(MethodDescInternal methodDesc, uint dwECallID)
    {
        _methodDesc = methodDesc;
        _dwECallID = dwECallID;
    }
}

[StructLayout(LayoutKind.Sequential)]
internal struct FCallMethodDescInternalx64
{
    readonly MethodDescInternal _methodDesc;
    readonly uint _dwECallID;
    readonly uint _padding;

    public FCallMethodDescInternalx64(MethodDescInternal methodDesc, uint dwECallID, uint padding)
    {
        _methodDesc = methodDesc;
        _dwECallID = dwECallID;
        _padding = padding;
    }
}

실제로 x64 환경에서 Monitor.Enter의 MethodDesc에 대해 덤프를 해 보면,

0:000> dd 7ffc31f3c300 L6
00007ffc`31f3c300  b100bb40 20a95404 004f0001 00000000
00007ffc`31f3c310  34c626b0 00007ffc

dwECallID와 padding을 확인할 수 있습니다.

m_dwECallID == 004f0001
m_padding == 00000000

또한, IL 메서드와 마찬가지로 FCall 메서드의 경우에도 FCallMethodDesc 구조체의 바로 다음 (x64의 경우) 8바이트가 실제 메서드의 주소입니다.

0:000> dq 00007ffc`31f3c310 L1
00007ffc`31f3c310  00007ffc`34c626b0 // 00007ffc`34c626b0 == clr!JIT_MonEnter




마지막으로, trampoline 과정을 실어 보겠습니다. 우선 Monitor.Enter의 trampoline 가로채기를 위해 아래의 원본 함수에서,

clr!JIT_MonEnter:
00007ffc`34c626b0 33d2            xor     edx,edx
00007ffc`34c626b2 6666666666660f1f840000000000 nop word ptr [rax+rax]
clr!JIT_MonEnterWorker_InlineGetThread:
00007ffc`34c626c0 56              push    rsi
00007ffc`34c626c1 488bf2          mov     rsi,rdx
00007ffc`34c626c4 4885c9          test    rcx,rcx
00007ffc`34c626c7 0f84a0010000    je      clr!JIT_MonEnterWorker_InlineGetThread_GetThread_PatchLabel+0x1a0 (00007ffc`34c6286d)
clr!JIT_MonEnterWorker_InlineGetThread_GetThread_PatchLabel:
00007ffc`34c626cd 654c8b1c2598140000 mov   r11,qword ptr gs:[1498h]
00007ffc`34c626d6 448b152b09a100  mov     r10d,dword ptr [clr!g_SpinConstants (00007ffc`35673008)]
00007ffc`34c626dd 418b4308        mov     eax,dword ptr [r11+8]
00007ffc`34c626e1 83e05f          and     eax,5Fh
00007ffc`34c626e4 0f8583010000    jne     clr!JIT_MonEnterWorker_InlineGetThread_GetThread_PatchLabel+0x1a0 (00007ffc`34c6286d)
00007ffc`34c626ea 4c8d41fc        lea     r8,[rcx-4]
00007ffc`34c626ee 418b00          mov     eax,dword ptr [r8]
00007ffc`34c626f1 a9ffff0018      test    eax,1800FFFFh
00007ffc`34c626f6 7528            jne     clr!JIT_MonEnterWorker_InlineGetThread_GetThread_PatchLabel+0x53 (00007ffc`34c62720)
00007ffc`34c626f8 418b532c        mov     edx,dword ptr [r11+2Ch]
00007ffc`34c626fc 81faff030000    cmp     edx,3FFh
00007ffc`34c62702 0f8765010000    ja      clr!JIT_MonEnterWorker_InlineGetThread_GetThread_PatchLabel+0x1a0 (00007ffc`34c6286d)
00007ffc`34c62708 0bd0            or      edx,eax
00007ffc`34c6270a f0410fb110      lock cmpxchg dword ptr [r8],edx
00007ffc`34c6270f 754f            jne     clr!JIT_MonEnterWorker_InlineGetThread_GetThread_PatchLabel+0x93 (00007ffc`34c62760)
00007ffc`34c62711 4183432801      add     dword ptr [r11+28h],1
00007ffc`34c62716 4885f6          test    rsi,rsi
00007ffc`34c62719 7403            je      clr!JIT_MonEnterWorker_InlineGetThread_GetThread_PatchLabel+0x51 (00007ffc`34c6271e)
00007ffc`34c6271b c60601          mov     byte ptr [rsi],1
00007ffc`34c6271e 5e              pop     rsi
00007ffc`34c6271f c3              ret

(다행히 우리가 교체할 메서드 - 이 글에서는 Replaced_TestMethod - 의 위치가 clr!JIT_MonEnter 위치에서 +/- 2GB 이내의 위치여서) 처음 5바이트를 Replaced_TestMethod의 주소로 패치하고,

clr!JIT_MonEnter:
00007ffc`34c626b0 e92b5cb2a0      jmp     00007ffb`d57882e0
00007ffc`34c626b5 6666660f1f840000000000 nop word ptr [rax+rax]
clr!JIT_MonEnterWorker_InlineGetThread:
00007ffc`34c626c0 56              push    rsi
00007ffc`34c626c1 488bf2          mov     rsi,rdx
00007ffc`34c626c4 4885c9          test    rcx,rcx
...[생략]...

패치 위치의 원본 코드를 VirualAlloc으로 할당받은 주소에 (패치 이후의 원본 함수로 JMP하는 코드까지 포함해) 보관해 두는 것으로 trampoline 처리는 완료됩니다.

00000000`00660000 33d2            xor     edx,edx
00000000`00660002 6666666666660f1f840000000000 nop word ptr [rax+rax]
00000000`00660010 48b8c026c634fc7f0000 mov rax,offset clr!JIT_MonEnterWorker_InlineGetThread (00007ffc`34c626c0)
00000000`0066001a ffe0            jmp     rax
00000000`0066001c 0000            add     byte ptr [rax],al

가로채기가 완료된 코드를 실행하면 이후 원본 함수를 실행하기 전 Marshal.GetDelegateForFunctionPointer로 동적 생성한 s_originalMethod 메서드의 body를 지나게 되는데,

00007ffb`d577a572 57              push    rdi
00007ffb`d577a573 4156            push    r14
00007ffb`d577a575 4155            push    r13
00007ffb`d577a577 4154            push    r12
00007ffb`d577a579 57              push    rdi
00007ffb`d577a57a 56              push    rsi
00007ffb`d577a57b 53              push    rbx
00007ffb`d577a57c 4881ecd8000000  sub     rsp,0D8h
00007ffb`d577a583 c5f877          vzeroupper
00007ffb`d577a586 488dac2410010000 lea     rbp,[rsp+110h]
00007ffb`d577a58e 4c899560ffffff  mov     qword ptr [rbp-0A0h],r10
00007ffb`d577a595 4889a510ffffff  mov     qword ptr [rbp-0F0h],rsp
00007ffb`d577a59c 48894d10        mov     qword ptr [rbp+10h],rcx
00007ffb`d577a5a0 48895518        mov     qword ptr [rbp+18h],rdx ss:00000000`00d5e898=0000000000000000
00007ffb`d577a5a4 488d8d28ffffff  lea     rcx,[rbp-0D8h]
00007ffb`d577a5ab 498bd2          mov     rdx,r10
00007ffb`d577a5ae e88da84e5f      call    clr!JIT_InitPInvokeFrame (00007ffc`34c64e40)
00007ffb`d577a5b3 48894580        mov     qword ptr [rbp-80h],rax
00007ffb`d577a5b7 488bcc          mov     rcx,rsp
00007ffb`d577a5ba 48898d48ffffff  mov     qword ptr [rbp-0B8h],rcx
00007ffb`d577a5c1 488bcd          mov     rcx,rbp
00007ffb`d577a5c4 48898d58ffffff  mov     qword ptr [rbp-0A8h],rcx
00007ffb`d577a5cb 488b4d80        mov     rcx,qword ptr [rbp-80h]
00007ffb`d577a5cf 488d8528ffffff  lea     rax,[rbp-0D8h]
00007ffb`d577a5d6 48894110        mov     qword ptr [rcx+10h],rax
00007ffb`d577a5da 33c9            xor     ecx,ecx
00007ffb`d577a5dc 4863c9          movsxd  rcx,ecx
00007ffb`d577a5df e8ac6a625f      call    clr!StubHelpers::DemandPermission (00007ffc`34da1090)
00007ffb`d577a5e4 33d2            xor     edx,edx
00007ffb`d577a5e6 8955c4          mov     dword ptr [rbp-3Ch],edx
00007ffb`d577a5e9 90              nop
00007ffb`d577a5ea 66c745a80000    mov     word ptr [rbp-58h],0
00007ffb`d577a5f0 488d55a8        lea     rdx,[rbp-58h]
00007ffb`d577a5f4 488b4d18        mov     rcx,qword ptr [rbp+18h]
00007ffb`d577a5f8 e8c3755d5f      call    clr!StubHelpers::ObjectMarshaler__ConvertToNative (00007ffc`34d51bc0)
00007ffb`d577a5fd c745c401000000  mov     dword ptr [rbp-3Ch],1
00007ffb`d577a604 90              nop
00007ffb`d577a605 c4e17a6f4da8    vmovdqu xmm1,xmmword ptr [rbp-58h]
00007ffb`d577a60b c4e17a7f4d90    vmovdqu xmmword ptr [rbp-70h],xmm1
00007ffb`d577a611 488b55b8        mov     rdx,qword ptr [rbp-48h]
00007ffb`d577a615 488955a0        mov     qword ptr [rbp-60h],rdx
00007ffb`d577a619 488d9560ffffff  lea     rdx,[rbp-0A0h]
00007ffb`d577a620 488b4d10        mov     rcx,qword ptr [rbp+10h]
00007ffb`d577a624 e847a46d5f      call    clr!StubHelpers::GetDelegateTarget (00007ffc`34e54a70)
00007ffb`d577a629 48894588        mov     qword ptr [rbp-78h],rax
00007ffb`d577a62d c4e17a6f4d90    vmovdqu xmm1,xmmword ptr [rbp-70h]
00007ffb`d577a633 c4e17a7f8d68ffffff vmovdqu xmmword ptr [rbp-98h],xmm1
00007ffb`d577a63c 488b4da0        mov     rcx,qword ptr [rbp-60h]
00007ffb`d577a640 48898d78ffffff  mov     qword ptr [rbp-88h],rcx

00007ffb`d577a647 488d8d68ffffff  lea     rcx,[rbp-98h]
00007ffb`d577a64e 4533db          xor     r11d,r11d
00007ffb`d577a651 488b8560ffffff  mov     rax,qword ptr [rbp-0A0h]
00007ffb`d577a658 48898538ffffff  mov     qword ptr [rbp-0C8h],rax
00007ffb`d577a65f 488d0515000000  lea     rax,[00007ffb`d577a67b]
00007ffb`d577a666 48898550ffffff  mov     qword ptr [rbp-0B0h],rax
00007ffb`d577a66d 488b4580        mov     rax,qword ptr [rbp-80h]
00007ffb`d577a671 c6400c00        mov     byte ptr [rax+0Ch],0
00007ffb`d577a675 488b4588        mov     rax,qword ptr [rbp-78h]
00007ffb`d577a679 ffd0            call    rax

00007ffb`d577a67b 488b5580        mov     rdx,qword ptr [rbp-80h]
00007ffb`d577a67f c6420c01        mov     byte ptr [rdx+0Ch],1
00007ffb`d577a683 833dbe89ef5f00  cmp     dword ptr [clr!g_TrapReturningThreads (00007ffc`35673048)],0
00007ffb`d577a68a 7406            je      00007ffb`d577a692
00007ffb`d577a68c ff157695ef5f    call    qword ptr [clr!hlpDynamicFuncTable+0x68 (00007ffc`35673c08)]
00007ffb`d577a692 e809c64e5f      call    clr!StubHelpers::SetLastError (00007ffc`34c66ca0)
00007ffb`d577a697 90              nop
00007ffb`d577a698 90              nop
..[생략]...

00007ffb`d577a679 주소의 call rax는 00000000`00660000로 실행이 전달돼 원본 Monitor.Enter의 처리를 진행하게 됩니다. 또한 이전에 언급한 ObjectMarshaler__ConvertToNative로 인해 s_originalMethod.Invoke에 전달된 닷넷 객체가 마샬링 되는데,

clr!StubHelpers::ObjectMarshaler__ConvertToNative:
00007ffc`34d51bc0 488bc4          mov     rax,rsp
00007ffc`34d51bc3 48895010        mov     qword ptr [rax+10h],rdx
00007ffc`34d51bc7 4156            push    r14
00007ffc`34d51bc9 4881ec60010000  sub     rsp,160h
00007ffc`34d51bd0 48c7442458feffffff mov   qword ptr [rsp+58h],0FFFFFFFFFFFFFFFEh
00007ffc`34d51bd9 48895808        mov     qword ptr [rax+8],rbx
00007ffc`34d51bdd 48897018        mov     qword ptr [rax+18h],rsi
00007ffc`34d51be1 48897820        mov     qword ptr [rax+20h],rdi
00007ffc`34d51be5 488bda          mov     rbx,rdx
00007ffc`34d51be8 488d3dd1ffffff  lea     rdi,[clr!StubHelpers::ObjectMarshaler__ConvertToNative (00007ffc`34d51bc0)]
00007ffc`34d51bef 48897c2438      mov     qword ptr [rsp+38h],rdi
00007ffc`34d51bf4 48894c2428      mov     qword ptr [rsp+28h],rcx
00007ffc`34d51bf9 83a4249000000000 and     dword ptr [rsp+90h],0
00007ffc`34d51c01 4889bc24a0000000 mov     qword ptr [rsp+0A0h],rdi
00007ffc`34d51c09 488d05e8036e00  lea     rax,[clr!HelperMethodFrame_1OBJ::`vftable' (00007ffc`35431ff8)]
00007ffc`34d51c10 4889442478      mov     qword ptr [rsp+78h],rax
00007ffc`34d51c15 488d442428      lea     rax,[rsp+28h]
00007ffc`34d51c1a 4889842450010000 mov     qword ptr [rsp+150h],rax
00007ffc`34d51c22 488d8c24a8000000 lea     rcx,[rsp+0A8h]
00007ffc`34d51c2a e8c132f1ff      call    clr!LazyMachStateCaptureState (00007ffc`34c64ef0)
00007ffc`34d51c2f 488d4c2478      lea     rcx,[rsp+78h]
00007ffc`34d51c34 e8f732f1ff      call    clr!HelperMethodFrame::Push (00007ffc`34c64f30)
00007ffc`34d51c39 488b8c2498000000 mov     rcx,qword ptr [rsp+98h]
00007ffc`34d51c41 33f6            xor     esi,esi
00007ffc`34d51c43 4532f6          xor     r14b,r14b
00007ffc`34d51c46 8a05ec139200    mov     al,byte ptr [clr!g_StackProbingEnabled (00007ffc`35673038)]
00007ffc`34d51c4c 84c0            test    al,al
00007ffc`34d51c4e 741a            je      clr!StubHelpers::ObjectMarshaler__ConvertToNative+0xaa (00007ffc`34d51c6a)
00007ffc`34d51c50 e877052400      call    clr!DefaultRetailStackProbeWorker (00007ffc`34f921cc)
00007ffc`34d51c55 8a05dd139200    mov     al,byte ptr [clr!g_StackProbingEnabled (00007ffc`35673038)]
00007ffc`34d51c5b 84c0            test    al,al
00007ffc`34d51c5d 740b            je      clr!StubHelpers::ObjectMarshaler__ConvertToNative+0xaa (00007ffc`34d51c6a)
00007ffc`34d51c5f 488d4c2448      lea     rcx,[rsp+48h]
00007ffc`34d51c64 e82b162400      call    clr!SOIntolerantTransitionHandler::CtorImpl (00007ffc`34f93294)
00007ffc`34d51c69 90              nop
00007ffc`34d51c6a b800400000      mov     eax,4000h
00007ffc`34d51c6f 488bd3          mov     rdx,rbx
00007ffc`34d51c72 668503          test    word ptr [rbx],ax
00007ffc`34d51c75 740c            je      clr!StubHelpers::ObjectMarshaler__ConvertToNative+0xc3 (00007ffc`34d51c83)
00007ffc`34d51c77 488d4c2428      lea     rcx,[rsp+28h]
00007ffc`34d51c7c e83ffbf9ff      call    clr!OleVariant::MarshalOleRefVariantForObject (00007ffc`34cf17c0)
00007ffc`34d51c81 eb14            jmp     clr!StubHelpers::ObjectMarshaler__ConvertToNative+0xd7 (00007ffc`34d51c97)
00007ffc`34d51c83 488d442428      lea     rax,[rsp+28h]
00007ffc`34d51c88 4889442430      mov     qword ptr [rsp+30h],rax
00007ffc`34d51c8d 488d4c2430      lea     rcx,[rsp+30h]
00007ffc`34d51c92 e879000000      call    clr!OleVariant::MarshalOleVariantForObject (00007ffc`34d51d10)
00007ffc`34d51c97 c644244800      mov     byte ptr [rsp+48h],0 ss:00000000`0078e548=00
00007ffc`34d51c9c 803d9513920000  cmp     byte ptr [clr!g_StackProbingEnabled (00007ffc`35673038)],0
00007ffc`34d51ca3 740b            je      clr!StubHelpers::ObjectMarshaler__ConvertToNative+0xf0 (00007ffc`34d51cb0)
00007ffc`34d51ca5 488d4c2448      lea     rcx,[rsp+48h]
00007ffc`34d51caa e81d162400      call    clr!SOIntolerantTransitionHandler::DtorImpl (00007ffc`34f932cc)
00007ffc`34d51caf 90              nop
00007ffc`34d51cb0 eb17            jmp     clr!StubHelpers::ObjectMarshaler__ConvertToNative+0x109 (00007ffc`34d51cc9)
00007ffc`34d51cb2 488b9c2478010000 mov     rbx,qword ptr [rsp+178h]
00007ffc`34d51cba 488b7c2438      mov     rdi,qword ptr [rsp+38h]
00007ffc`34d51cbf 488b742440      mov     rsi,qword ptr [rsp+40h]
00007ffc`34d51cc4 448a742420      mov     r14b,byte ptr [rsp+20h]
00007ffc`34d51cc9 4584f6          test    r14b,r14b
00007ffc`34d51ccc 7409            je      clr!StubHelpers::ObjectMarshaler__ConvertToNative+0x117 (00007ffc`34d51cd7)
00007ffc`34d51cce 488bd6          mov     rdx,rsi
00007ffc`34d51cd1 e862c10f00      call    clr!UnwindAndContinueRethrowHelperAfterCatch (00007ffc`34e4de38)
00007ffc`34d51cd6 cc              int     3
00007ffc`34d51cd7 488d4c2478      lea     rcx,[rsp+78h]
00007ffc`34d51cdc e88f32f1ff      call    clr!HelperMethodFrame::Pop (00007ffc`34c64f70)
00007ffc`34d51ce1 488d8c24a8000000 lea     rcx,[rsp+0A8h]
00007ffc`34d51ce9 e8e226f1ff      call    clr!HelperMethodFrameRestoreState (00007ffc`34c643d0)
00007ffc`34d51cee 85c0            test    eax,eax
00007ffc`34d51cf0 0f8503ffffff    jne     clr!StubHelpers::ObjectMarshaler__ConvertToNative+0x39 (00007ffc`34d51bf9)
00007ffc`34d51cf6 4c8d9c2460010000 lea     r11,[rsp+160h]
00007ffc`34d51cfe 498b5b10        mov     rbx,qword ptr [r11+10h]
00007ffc`34d51d02 498b7320        mov     rsi,qword ptr [r11+20h]
00007ffc`34d51d06 498b7b28        mov     rdi,qword ptr [r11+28h]
00007ffc`34d51d0a 498be3          mov     rsp,r11
00007ffc`34d51d0d 415e            pop     r14
00007ffc`34d51d0f c3              ret

아마도 MarshalOleRefVariantForObject 호출이 내부적으로는 Marshal.GetNativeVariantForObject 코드와 유사한 역할을 수행하는 듯합니다.




[이 글에 대해서 여러분들과 의견을 공유하고 싶습니다. 틀리거나 미흡한 부분 또는 의문 사항이 있으시면 언제든 댓글 남겨주십시오.]

[연관 글]





[최초 등록일: ]
[최종 수정일: 3/2/2020 ]

Creative Commons License
이 저작물은 크리에이티브 커먼즈 코리아 저작자표시-비영리-변경금지 2.0 대한민국 라이센스에 따라 이용하실 수 있습니다.
by SeongTae Jeong, mailto:techsharer@outlook.com

비밀번호

댓글 쓴 사람
 




1  [2]  3  4  5  6  7  8  9  10  11  12  13  14  15  ...
NoWriterDateCnt.TitleFile(s)
12239정성태6/21/202076Linux: 30. (윈도우라면 DLL에 속하는) .so 파일이 텍스트로 구성된 사례
12238정성태6/21/2020106.NET Framework: 913. C# - SharpDX + DXGI를 이용한 윈도우 화면 캡처 라이브러리
12237정성태7/8/2020122.NET Framework: 912. 리눅스 환경의 .NET Core에서 "test".IndexOf("\0")가 0을 반환
12236정성태6/19/202075오류 유형: 621. .NET Standard 대상으로 빌드 시 dynamic 예약어에서 컴파일 오류 - error CS0656: Missing compiler required member 'Microsoft.CSharp.RuntimeBinder.CSharpArgumentInfo.Create'
12235정성태6/19/202050오류 유형: 620. Windows 10 - Inaccessible boot device 블루 스크린
12234정성태6/19/202050개발 환경 구성: 494. NuGet - nuspec의 패키지 스키마 버전(네임스페이스) 업데이트 방법
12233정성태6/19/202042오류 유형: 619. SQL 서버 - The transaction log for database '...' is full due to 'LOG_BACKUP'. - 두 번째 이야기
12232정성태6/19/202038오류 유형: 618. SharePoint - StoreBusyRetryLater 오류
12231정성태6/15/2020124.NET Framework: 911. Console/Service Application을 위한 SynchronizationContext - AsyncContext
12230정성태6/15/202051오류 유형: 617. IMetaDataImport::GetMethodProps가 반환하는 IL 코드 주소(RVA) 문제
12229정성태6/13/2020245.NET Framework: 910. USB/IP PROJECT를 이용해 C#으로 USB Keyboard + Mouse 가상 장치 만들기 [1]
12228정성태6/12/2020141.NET Framework: 909. C# - Source Generator를 적용한 XmlCodeGenerator파일 다운로드1
12227정성태6/12/20202114오류 유형: 616. Visual Studio의 느린 업데이트 속도에 대한 원인 분석 [5]
12226정성태6/19/2020134개발 환경 구성: 493. OpenVPN의 네트워크 구성파일 다운로드1
12225정성태6/11/2020109개발 환경 구성: 492. 윈도우에 OpenVPN 설치 - 클라이언트 측 구성
12224정성태6/11/2020216개발 환경 구성: 491. 윈도우에 OpenVPN 설치 - 서버 측 구성
12223정성태6/9/2020244.NET Framework: 908. C# - Source Generator 소개 [2]파일 다운로드1
12222정성태6/3/202097VS.NET IDE: 146. error information: "CryptQueryObject" (-2147024893/0x80070003)
12221정성태6/3/2020104Windows: 168. 비어 있지 않은 디렉터리로 symbolic link(junction) 연결하는 방법
12220정성태6/3/2020108.NET Framework: 907. C# DLL로부터 TLB 및 C/C++ 헤더 파일(TLH)을 생성하는 방법
12219정성태6/1/2020269.NET Framework: 906. C# - lock (this), lock (typeof(...))를 사용하면 안 되는 이유파일 다운로드1
12218정성태5/31/2020216.NET Framework: 905. C# - DirectX 게임 클라이언트 실행 중 키보드 입력을 감지하는 방법 [1]
12217정성태5/24/2020108오류 유형: 615. Transaction count after EXECUTE indicates a mismatching number of BEGIN and COMMIT statements. Previous count = 0, current count = 1.
12216정성태5/15/2020233.NET Framework: 904. USB/IP PROJECT를 이용해 C#으로 USB Keyboard 가상 장치 만들기
12215정성태5/12/2020346개발 환경 구성: 490. C# - (Wireshark의) USBPcap을 이용한 USB 패킷 모니터링파일 다운로드1
12214정성태5/5/2020184개발 환경 구성: 489. 정식 인증서가 있는 경우 Device Driver 서명하는 방법 (2) - UEFI/SecureBoot
1  [2]  3  4  5  6  7  8  9  10  11  12  13  14  15  ...