성태의 닷넷 이야기
홈 주인
모아 놓은 자료
프로그래밍
질문/답변
사용자 관리
사용자
메뉴
아티클
외부 아티클
유용한 코드
온라인 기능
MathJax 입력기
최근 덧글
[정성태] 질문이 잘 이해가 안 됩니다. 우선, 해당 소스코드에서 ILis...
[양승조
] var대신 dinamic으로 선언해서 해결은 했습니다. 맞는 해...
[양승조
] 또 막혔습니다. ㅠㅠ var list = props[i].Ge...
[양승조
] 아. 감사합니다. 어제는 안됐던것 같은데....정신을 차려야겠네...
[정성태] "props[i].GetValue(props[i])" 코드에서 ...
[정성태] 저렇게 조각 코드 말고, 실제로 재현이 되는 예제 프로젝트를 압...
[정성태] Modules 창(Ctrl+Shift+U)을 띄워서, 해당 Op...
[정성태] 만드실 수 있습니다. 단지, Unity 엔진 내의 스크립트와 W...
[공진영] 안녕하세요 좋은글 감사합니다. 현재 제가 wpf로 관제 모...
[정성태] The Windows Registry Adventure #1: ...
글쓰기
제목
이름
암호
전자우편
HTML
홈페이지
유형
제니퍼 .NET
닷넷
COM 개체 관련
스크립트
VC++
VS.NET IDE
Windows
Team Foundation Server
디버깅 기술
오류 유형
개발 환경 구성
웹
기타
Linux
Java
DDK
Math
Phone
Graphics
사물인터넷
부모글 보이기/감추기
내용
<div style='display: inline'> <h1 style='font-family: Malgun Gothic, Consolas; font-size: 20pt; color: #006699; text-align: center; font-weight: bold'>실행 시에 메서드 가로채기 - CLR Injection: Runtime Method Replacer 개선 - 네 번째 이야기(Monitor.Enter 후킹)</h1> <p> .NET Profiler 기술을 활용하면,<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > CLR Profiler - 별도 정의한 .NET 코드를 호출하도록 IL 코드 변경 ; <a target='tab' href='https://www.sysnet.pe.kr/2/0/10959'>https://www.sysnet.pe.kr/2/0/10959</a> </pre> <br /> 런타임에 .NET 응용 프로그램의 IL 코드를 변경하는 것이 가능합니다. 그런데, 만능은 아닙니다. ^^ 가령 Monitor.Enter와 같은 메서드는 IL 코드를 변경할 수 없는데, 왜냐하면 애당초 IL 코드가 없기 때문입니다. <br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > [SecuritySafeCritical] [__DynamicallyInvokable] [MethodImpl(MethodImplOptions.InternalCall)] public static extern void Enter(object obj); </pre> <br /> 보는 바와 같이 IL 코드 없이 곧바로 "InternalCall"로 내부의 Native 함수에 연결되어 있습니다. 따라서, 이런 메서드를 가로채기 하려면 trampoline과 같은 별도의 후킹 방식을 사용해야 합니다. (혹은, Monitor.Enter에 대한 모든 IL 호출을 다른 메서드로 변경하거나.)<br /> <br /> <hr style='width: 50%' /><br /> <br /> 우선, 해당 메서드의 정보를 DetourFunc 라이브러리의 MethodDesc을 이용해 살펴볼까요? ^^<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > // Install-Package DetourFunc -Version 1.1.0 { MethodDesc md = MethodDesc.ReadFromAddress(action.Method.MethodHandle.Value); md.Dump(Console.Out); } /* 출력 결과 [MethodDesc <span style='color: blue; font-weight: bold'>0x7ffc31f3c300</span> - FCall] wTokenRemainder = bb40 (Token = 6003b40) chunkIndex = 0 bFlags2 = 000000B1 (Flags2 == 177) wSlotNumber = 4 wFlags = 20a9 (IsFullSlotNumber == False) MethodTablePtr = 7ffc31f55b10 FunctionPtr = 7ffc34c626b0 */ </pre> <br /> 위의 결과와 함께 windbg로 분석을 해보면,<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > 0:000> <span style='color: blue; font-weight: bold'>!dumpmd 7ffc31f3c300</span> Method Name: System.Threading.Monitor.Enter(System.Object) Class: 00007ffc320c5528 MethodTable: 00007ffc31f55b10 mdToken: 0000000006003b40 Module: 00007ffc31f31000 IsJitted: yes CodeAddr: <span style='color: blue; font-weight: bold'>00007ffc34c626b0</span> Transparency: Safe critical </pre> <br /> 00007ffc34c626b0 주소의 코드들이 마치 패치를 위한 배려라도 한 것처럼,<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > clr!JIT_MonEnter: <span style='color: blue; font-weight: bold'>00007ffc`34c626b0 33d2 xor edx,edx 00007ffc`34c626b2 6666666666660f1f840000000000 nop word ptr [rax+rax]</span> 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: ...[생략]... </pre> <br /> <a target='tab' href='https://www.sysnet.pe.kr/2/0/12148'>웬만한 길이의 trampoline 코드</a>를 넘어서는 <a target='tab' href='https://www.sysnet.pe.kr/2/0/12147#max_len_nop'>nop 코드</a>를 마이크로소프트가 끼워두었습니다. (왜 그랬는지 궁금하군요. ^^;)<br /> <br /> 덕분에 아무런 걱정 없이 trampoline 패치를 할 수 있지만,<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > 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>()) { <span style='color: blue; font-weight: bold'>if (item.JumpPatch(ptrBodyTestMethod, ptrBodyReplaceMethod) == true) { s_originalMethod = item.GetOriginalFunc(); }</span> <span style='color: blue; font-weight: bold'>Console.WriteLine("[After trampoline]"); Monitor.Enter(obj); Console.WriteLine("[before Exit]"); Monitor.Exit(obj);</span> } Console.ReadLine(); } public static void Replaced_TestMethod(object obj) { Console.WriteLine("Replaced_TestMethod called!: " + obj?.ToString()); Console.WriteLine("[before orgmethod]"); <span style='color: blue; font-weight: bold'>s_originalMethod?.Invoke(obj);</span> } } public class Person { public string Name { get; set; } public override string ToString() { return Name; } } } </pre> <br /> 매끄러웠던 진행과 달리, 실행해 보면 예상치 않은 결과에 당황하게 됩니다. ^^;<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > [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 </pre> <br /> 그러니까, 분명히 가로채기는 성공했고 s_originalMethod 메서드 호출까지도 예외 없이 성공했는데, Monitor.Exit 호출 시 해당 <a target='tab' href='https://www.sysnet.pe.kr/2/0/1176#syncblock_ex'>object 인자가 동기화에 사용된 적이 없다</a>고 하는 것입니다.<br /> <br /> <hr style='width: 50%' /><br /> <br /> 정리해 보면, 결국 다음의 3가지 메서드에 전달된 object 인자 중 어떤 하나(또는 셋 모두)는 다른 주소를 가리킨다는 것을 의미합니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > Monitor.Enter(object obj) Replaced_TestMethod(object obj) s_originalMethod?.Invoke(obj) </pre> <br /> 원인을 밝히기 위해 windbg로 해당 메서드들의 object 인자를 추적하다가 s_originalMethod?.Invoke 호출 내에서 다음과 같이 마샬러가 수행되는 것을 발견했습니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > [Marshal.GetDelegateForFunctionPointer가 반환한 s_originalMethod 델리게이트] ...[생략]... 00007ffb`d577a5f0 488d55a8 lea rdx,[rbp-58h] 00007ffb`d577a5f4 488b4d18 mov rcx,qword ptr [rbp+18h] 00007ffb`d577a5f8 e8c3755d5f <span style='color: blue; font-weight: bold'>call clr!StubHelpers::ObjectMarshaler__ConvertToNative</span> (00007ffc`34d51bc0) 00007ffb`d577a5fd c745c401000000 mov dword ptr [rbp-3Ch],1 ss:00000000`00dce824=00000000 ...[생략]... </pre> <br /> 즉, ObjectMarshaler__ConvertToNative 함수에 의해 마샬링된 값을 Marshal.GetDelegateForFunctionPointer가 입력으로 받았던 함수에 전달하는 것입니다. 실제로 그 원본 함수에 전달하는 시점에 다시 rcx 값을 덤프해 보면,<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > 0:000> <span style='color: blue; font-weight: bold'>dd @rcx L6</span> 00000000`004fe738 <span style='color: blue; font-weight: bold'>0000000d</span> 00000000 <span style='color: blue; font-weight: bold'>007b0018 00000000</span> 00000000 00000000 </pre> <br /> 어디서 많이 봤던 패턴이 나옵니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > C# - Marshal.GetNativeVariantForObject 사용 시 메모리 누수(Memory Leak) 발생 및 해결 방법 ; <a target='tab' href='https://www.sysnet.pe.kr/2/0/12157#object_marshal'>https://www.sysnet.pe.kr/2/0/12157#object_marshal</a> </pre> <br /> 그러니까, s_originalMethod?.Invoke(obj) 호출은 내부적으로 다음과 같은 식의 마샬링 작업을 수행했던 것입니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > // 가상 코드 void s_originalMethod(object obj) { IntPtr pAlloc = Marshal.AllocHGlobal(24); // x86 == 16, x64 == 24 Marshal.GetNativeVariantForObject(obj, pAlloc); VARIANT vt = new VARIANT(pAlloc); <span style='color: blue; font-weight: bold'>call [_trampline_jmp_for_monitor_enter](vt);</span> } </pre> <br /> 당연히, 원본 Monitor.Enter는 (object가 아닌) VARIANT 타입의 값을 받게 되고 엉뚱한 객체의 sync block에 대해 잠금 표시를 수행한 것입니다. 그리고, 순서가 바뀌긴 했지만 바로 이 문제를 파악하다가 알게 된 내용들을 정리한 것이 아래의 글들이었습니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > C# - Marshal.GetNativeVariantForObject 사용 시 메모리 누수(Memory Leak) 발생 및 해결 방법 ; <a target='tab' href='https://www.sysnet.pe.kr/2/0/12157'>https://www.sysnet.pe.kr/2/0/12157</a> C# - Marshal.GetIUnknownForObject/GetIDispatchForObject 사용 시 메모리 누수(Memory Leak) 발생 ; <a target='tab' href='https://www.sysnet.pe.kr/2/0/12158'>https://www.sysnet.pe.kr/2/0/12158</a> C#에서 만든 COM 객체를 C/C++로 P/Invoke Interop 시 메모리 누수(Memory Leak) 발생 ; <a target='tab' href='https://www.sysnet.pe.kr/2/0/12162'>https://www.sysnet.pe.kr/2/0/12162</a> </pre> <br /> 결국, Marshal.GetDelegateForFunctionPointer로 동적 생성한 delegate 함수는 내부적으로 Object 등의 객체를 마샬링하는 과정에 Marshal.GetNativeVariantForObject를 수행하고, 여기에서도 VariantClear를 수행하지 않으므로 메모리 누수 현상이 동일하게 발생합니다. 확인을 위해 원문의 코드를 다음과 같이 변경시키면,<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > while (true) { Person obj = new Person { Name = "TEST" }; Monitor.Enter(obj); // 가로채기되었으므로 s_originalMethod 호출 내부의 메모리 누수 발생 } </pre> <br /> 메모리가 지속적으로 증가하는 것을 볼 수 있습니다.<br /> <br /> <hr style='width: 50%' /><br /> <br /> 자, 그럼 어떻게 해야 Monitor.Enter를 (메모리 누수도 없이) 정상적으로 가로채기 할 수 있을까요? 이 문제를 해결하려면 최종적으로 s_originalMethod?.Invoke의 호출에 원본 객체를 자동 마샬링 과정 없이 전달해야 합니다.<br /> <br /> 그러고 보니 닷넷 객체를 가리키는 변수가 담은 값의 실체가 실은 GC 힙의 주소라는 것을 알고 있으니, 따라서 object가 아닌 IntPtr로 s_originalMethod?.Invoke에 전달한다면 내부적으로 마샬링이 발생하지 않을 수 있습니다. 마침 <a target='tab' href='https://www.sysnet.pe.kr/2/0/12142#obj_address'>객체에 대한 GC Heap 상의 주소를 얻는 메서드</a>를 MethodDesc 타입에 추가해 두었으니, 이를 이용해 다음과 같은 식으로 처리할 수 있습니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > public delegate void <span style='color: blue; font-weight: bold'>MonitorEnterMethodRefDelegate(IntPtr objAddress);</span> class Program { static <span style='color: blue; font-weight: bold'>MonitorEnterMethodRefDelegate s_originalMethodRef</span>; static void Main(string[] _) { ...[생략]... using (var item = new TrampolinePatch<<span style='color: blue; font-weight: bold'>MonitorEnterMethodRefDelegate</span>>()) { 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) { <span style='color: blue; font-weight: bold'>IntPtr objAddr = MethodDesc.GetObjectAddress(obj); s_originalMethodRef?.Invoke(objAddr);</span> } } </pre> <br /> 바뀐 코드로 실행해 보면, 정상적으로 Monitor.Exit가 실행이 되며 동기화가 풀리는 것을 확인할 수 있습니다. 정리해 보면, C#에서 Trampoline 기법을 이용해 우회한 메서드가 표면상 IL 코드이긴 해도 결국 내부적으로는 [Managed-To-Native] 층을 거쳐 인자 값들이 마샬링된다는 사실을 잊어서는 안 됩니다.<br /> <br /> (<a target='tab' href='https://www.sysnet.pe.kr/bbs/DownloadAttachment.aspx?fid=1568&boardid=331301885'>첨부 파일은 이 글의 예제 코드를 포함</a>합니다.)<br /> <br /> <hr style='width: 50%' /><br /> <br /> <a target='tab' href='https://www.sysnet.pe.kr/2/0/12142#mcil'>예전의 출력 결과</a> 유형이 IL인 반면, 이번 Monitor.Enter의 경우 "FCall"이라고 나옵니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > /* 출력 결과 [MethodDesc 0x7ffc31f3c300 - <span style='color: blue; font-weight: bold'>FCall</span>] wTokenRemainder = bb40 (Token = 6003b40) chunkIndex = 0 bFlags2 = 000000B1 (Flags2 == 177) wSlotNumber = 4 wFlags = 20a9 (IsFullSlotNumber == False) MethodTablePtr = 7ffc31f55b10 FunctionPtr = 7ffc34c626b0 */ </pre> <br /> 이참에 ^^ 기존에 만들어 둔 MethodDesc에,<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > C# 코드로 접근하는 MethodDesc, MethodTable ; <a target='tab' href='https://www.sysnet.pe.kr/2/0/12142'>https://www.sysnet.pe.kr/2/0/12142</a> </pre> <br /> FCall 유형의 메서드에 대한 필드를,<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > class <span style='color: blue; font-weight: bold'>FCallMethodDesc</span> : public MethodDesc { <span style='color: blue; font-weight: bold'>DWORD m_dwECallID;</span> #ifdef HOST_64BIT <span style='color: blue; font-weight: bold'>DWORD m_padding;</span> #endif }; </pre> <br /> 추가해 다음과 같은 식의 구조체를 마련해 둘 수 있습니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > [StructLayout(LayoutKind.Sequential)] internal struct <span style='color: blue; font-weight: bold'>FCallMethodDescInternalx86</span> { readonly MethodDescInternal _methodDesc; readonly uint _dwECallID; public FCallMethodDescInternalx86(MethodDescInternal methodDesc, uint dwECallID) { _methodDesc = methodDesc; _dwECallID = dwECallID; } } [StructLayout(LayoutKind.Sequential)] internal struct <span style='color: blue; font-weight: bold'>FCallMethodDescInternalx64</span> { readonly MethodDescInternal _methodDesc; readonly uint _dwECallID; <span style='color: blue; font-weight: bold'>readonly uint _padding</span>; public FCallMethodDescInternalx64(MethodDescInternal methodDesc, uint dwECallID, uint padding) { _methodDesc = methodDesc; _dwECallID = dwECallID; _padding = padding; } } </pre> <br /> 실제로 x64 환경에서 Monitor.Enter의 MethodDesc에 대해 덤프를 해 보면,<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > 0:000> <span style='color: blue; font-weight: bold'>dd 7ffc31f3c300 L6</span> 00007ffc`31f3c300 b100bb40 20a95404 004f0001 00000000 00007ffc`31f3c310 34c626b0 00007ffc </pre> <br /> dwECallID와 padding을 확인할 수 있습니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > m_dwECallID == 004f0001 m_padding == 00000000 </pre> <br /> 또한, IL 메서드와 마찬가지로 FCall 메서드의 경우에도 FCallMethodDesc 구조체의 바로 다음 (x64의 경우) 8바이트가 실제 메서드의 주소입니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > 0:000> <span style='color: blue; font-weight: bold'>dq 00007ffc`31f3c310 L1</span> 00007ffc`31f3c310 00007ffc`34c626b0 // 00007ffc`34c626b0 == clr!JIT_MonEnter </pre> <br /> <hr style='width: 50%' /><br /> <br /> 마지막으로, trampoline 과정을 실어 보겠습니다. 우선 Monitor.Enter의 trampoline 가로채기를 위해 아래의 원본 함수에서,<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > clr!JIT_MonEnter: <span style='color: blue; font-weight: bold'>00007ffc`34c626b0 33d2 xor edx,edx 00007ffc`34c626b2 6666666666660f1f840000000000 nop word ptr [rax+rax]</span> 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 </pre> <br /> (다행히 우리가 교체할 메서드 - 이 글에서는 Replaced_TestMethod - 의 위치가 clr!JIT_MonEnter 위치에서 +/- 2GB 이내의 위치여서) 처음 5바이트를 Replaced_TestMethod의 주소로 패치하고,<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > clr!JIT_MonEnter: 00007ffc`34c626b0 <span style='color: blue; font-weight: bold'>e92b5cb2a0</span> 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 ...[생략]... </pre> <br /> 패치 위치의 원본 코드를 VirualAlloc으로 할당받은 주소에 (패치 이후의 원본 함수로 JMP하는 코드까지 포함해) 보관해 두는 것으로 trampoline 처리는 완료됩니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > 00000000`00660000 33d2 <span style='color: blue; font-weight: bold'>xor edx,edx</span> 00000000`00660002 6666666666660f1f840000000000 <span style='color: blue; font-weight: bold'>nop word ptr [rax+rax]</span> 00000000`00660010 48b8c026c634fc7f0000 mov rax,offset clr!JIT_MonEnterWorker_InlineGetThread (00007ffc`34c626c0) 00000000`0066001a ffe0 <span style='color: blue; font-weight: bold'>jmp rax</span> 00000000`0066001c 0000 add byte ptr [rax],al </pre> <br /> 가로채기가 완료된 코드를 실행하면 이후 원본 함수를 실행하기 전 Marshal.GetDelegateForFunctionPointer로 동적 생성한 s_originalMethod 메서드의 body를 지나게 되는데,<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > 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] <span style='color: blue; font-weight: bold'>00007ffb`d577a5f8 e8c3755d5f call clr!StubHelpers::ObjectMarshaler__ConvertToNative (00007ffc`34d51bc0)</span> 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] <span style='color: blue; font-weight: bold'>00007ffb`d577a679 ffd0 call rax</span> 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 ..[생략]... </pre> <br /> 00007ffb`d577a679 주소의 call rax는 00000000`00660000로 실행이 전달돼 원본 Monitor.Enter의 처리를 진행하게 됩니다. 또한 이전에 언급한 ObjectMarshaler__ConvertToNative로 인해 s_originalMethod.Invoke에 전달된 닷넷 객체가 마샬링 되는데,<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > 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] <span style='color: blue; font-weight: bold'>00007ffc`34d51c7c e83ffbf9ff call clr!OleVariant::MarshalOleRefVariantForObject (00007ffc`34cf17c0)</span> 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] <span style='color: blue; font-weight: bold'>00007ffc`34d51c92 e879000000 call clr!OleVariant::MarshalOleVariantForObject (00007ffc`34d51d10)</span> 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 </pre> <br /> 아마도 MarshalOleRefVariantForObject 호출이 내부적으로는 Marshal.GetNativeVariantForObject 코드와 유사한 역할을 수행하는 듯합니다.<br /> </p><br /> <br /><hr /><span style='color: Maroon'>[이 글에 대해서 여러분들과 의견을 공유하고 싶습니다. 틀리거나 미흡한 부분 또는 의문 사항이 있으시면 언제든 댓글 남겨주십시오.]</span> </div>
첨부파일
스팸 방지용 인증 번호
1769
(왼쪽의 숫자를 입력해야 합니다.)