성태의 닷넷 이야기
홈 주인
모아 놓은 자료
프로그래밍
질문/답변
사용자 관리
사용자
메뉴
아티클
외부 아티클
유용한 코드
온라인 기능
MathJax 입력기
최근 덧글
[정성태] Java - How to use the Foreign Funct...
[정성태] 제가 큰 실수를 했군요. ^^; Delegate를 통한 Bein...
[정성태] Working with Rust Libraries from C#...
[정성태] Detecting blocking calls using asyn...
[정성태] 아쉽게도, 커뮤니티는 아니고 개인 블로그입니다. ^^
[정성태] 질문이 잘 이해가 안 됩니다. 우선, 해당 소스코드에서 ILis...
[양승조
] var대신 dinamic으로 선언해서 해결은 했습니다. 맞는 해...
[양승조
] 또 막혔습니다. ㅠㅠ var list = props[i].Ge...
[양승조
] 아. 감사합니다. 어제는 안됐던것 같은데....정신을 차려야겠네...
[정성태] "props[i].GetValue(props[i])" 코드에서 ...
글쓰기
제목
이름
암호
전자우편
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 개선 - 두 번째 이야기</h1> <p> 지난 글에,<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > 실행 시에 메서드 가로채기 - CLR Injection: Runtime Method Replacer 개선 ; <a target='tab' href='https://www.sysnet.pe.kr/2/0/942'>https://www.sysnet.pe.kr/2/0/942</a> </pre> <br /> codeproject의 글 하나를 소개했는데요.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > CLR Injection: Runtime Method Replacer ; <a target='tab' href='http://www.codeproject.com/KB/dotnet/CLRMethodInjection.aspx'>http://www.codeproject.com/KB/dotnet/CLRMethodInjection.aspx</a> </pre> <br /> 우선, "<a target='tab' href='https://www.sysnet.pe.kr/2/0/942'>실행 시에 메서드 가로채기 - CLR Injection: Runtime Method Replacer 개선</a>" 글에서의 코드를 정리해 DetourFunc 프로젝트에 반영했으니,<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > C#으로 구현하는 Win32 API 후킹(예: Sleep 호출 가로채기) ; <a target='tab' href='https://www.sysnet.pe.kr/2/0/12132'>https://www.sysnet.pe.kr/2/0/12132</a> </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;' > // Install-Package DetourFunc -Version 1.0.7 using DetourFunc; using System; class Program { static void Main(string[] _) { Action<bool> oldAction = TestMethod; Action<bool> newAction = NewMethod; Console.WriteLine($"oldFunc == {oldAction.Method.MethodHandle.GetFunctionPointer().ToInt64():x}"); Console.WriteLine($"newFunc == {newAction.Method.MethodHandle.GetFunctionPointer().ToInt64():x}"); Console.WriteLine(); <span style='color: blue; font-weight: bold'>TestMethod(true); NetMethodReplacer.ReplaceMethod(oldAction.Method, newAction.Method); TestMethod(true);</span> } [MethodImpl(MethodImplOptions.NoInlining)] static void TestMethod(bool showMessage) { if (showMessage == true) { Console.WriteLine("TestMethod"); } } [MethodImpl(MethodImplOptions.NoInlining)] static void NewMethod(bool showMessage) { if (showMessage == true) { Console.WriteLine("NewMethod"); } } } /* 출력 결과 oldFunc == 7ffe48320488 newFunc == 7ffe48320490 <span style='color: blue; font-weight: bold'>TestMethod NewMethod</span> */ </pre> <br /> 보는 바와 같이 TestMethod의 동작이 NewMethod로 치환되었습니다.<br /> <br /> <hr style='width: 50%' /><br /> <br /> 그런데 새롭게 바뀐 JIT 컴파일 방식으로 인해,<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > Windbg 환경에서 확인해 본 .NET 메서드 JIT 컴파일 전과 후 - 두 번째 이야기 ; <a target='tab' href='https://www.sysnet.pe.kr/2/0/12133'>https://www.sysnet.pe.kr/2/0/12133</a> </pre> <br /> Runtime Method Replacer의 동작에 결함이 생기게 되었습니다. 이것을 간단하게 다음과 같이 재현할 수 있습니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > for (int i = 0; i < 10000; i++) { <span style='color: blue; font-weight: bold'>TestMethod</span>(false); } <span style='color: blue; font-weight: bold'>NetMethodReplacer.ReplaceMethod</span>(oldAction.Method, newAction.Method); <span style='color: blue; font-weight: bold'>TestMethod</span>(true); /* 출력 결과 TestMethod */ </pre> <br /> 그러니까, NetMethodReplacer.ReplaceMethod 호출 이전에 Fixup Precode가 call에서 jmp로 바뀌도록 TestMethod를 충분히 불러주면 이후 ReplaceMethod를 해도 효력이 없는 것입니다. 그 이유를 간단하게 정리해 보면, NetMethodReplacer.ReplaceMethod 메서드는 대상 코드를 치환하기 위해 MethodDesc의 8바이트 위치에 값을 써 PreStubWorker 단계에서 MethodDesc::GetMethodEntryPoint 함수가 그 값을 이용할 수 있게 만드는데, Fixup Precode가 Method의 Body로 향하는 jmp 문으로 일단 바뀌게 되면 이후부터는 MethodDesc의 8바이트 위치에 값을 써도 아무런 영향을 주지 못하기 때문입니다.<br /> <br /> 따라서, NetMethodReplacer.ReplaceMethod 메서드를 이용한다면 대상 메서드의 어셈블리가 로드되는 - 즉, 메서드들이 호출되지 않았을 - 초기 시점에 안전하게 치환 작업을 마무리해야만 합니다.<br /> <br /> <hr style='width: 50%' /><br /> <br /> <a name='tier_n'></a> 그나저나... 혹시 몇 번의 호출만에 call이 jmp로 바뀌게 되는 걸까요? 예전에 테스트했을 때는,<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > .NET Core 2.1 - Tiered Compilation 도입 ; <a target='tab' href='https://www.sysnet.pe.kr/2/0/11539'>https://www.sysnet.pe.kr/2/0/11539</a> </pre> <br /> 30번 정도였는데 이번에도 비슷할지... 다음과 같은 식으로 코드를 만들어 검증할 수 있습니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > using System; using System.Runtime.InteropServices; class Program { static void Main(string[] args) { Action action = TestMethod; IntPtr oldPtr = action.Method.MethodHandle.GetFunctionPointer(); <span style='color: blue; font-weight: bold'>byte oldOPCode = Marshal.ReadByte(oldPtr);</span> // call로 시작하므로 0xe8 int count = 0; while (true) { TestMethod(); count++; <span style='color: blue; font-weight: bold'>if (oldOPCode != Marshal.ReadByte(oldPtr))</span> // jmp는 0xe9 { break; } } Console.WriteLine(count); } static void TestMethod() { Console.WriteLine("TEST"); } } /* 출력 결과 TEST TEST 2 */ </pre> <br /> 그렇습니다. 단 두 번째의 호출에서 call에서 jmp 문으로 바뀝니다. 그러니까, 마이크로소프트는 단 한 번만 호출되는 메서드의 수가 적지 않은 비율을 차지한다는... 통계를 가지고 있는 듯하군요. ^^<br /> <br /> (<a target='tab' href='https://www.sysnet.pe.kr/bbs/DownloadAttachment.aspx?fid=1553&boardid=331301885'>첨부 파일은 이 글의 예제 코드를 포함</a>합니다.)<br /> </p><br /> <br /><hr /><span style='color: Maroon'>[이 글에 대해서 여러분들과 의견을 공유하고 싶습니다. 틀리거나 미흡한 부분 또는 의문 사항이 있으시면 언제든 댓글 남겨주십시오.]</span> </div>
첨부파일
스팸 방지용 인증 번호
1743
(왼쪽의 숫자를 입력해야 합니다.)