성태의 닷넷 이야기
홈 주인
모아 놓은 자료
프로그래밍
질문/답변
사용자 관리
사용자
메뉴
아티클
외부 아티클
유용한 코드
온라인 기능
MathJax 입력기
최근 덧글
[정성태] Detecting blocking calls using asyn...
[정성태] 아쉽게도, 커뮤니티는 아니고 개인 블로그입니다. ^^
[정성태] 질문이 잘 이해가 안 됩니다. 우선, 해당 소스코드에서 ILis...
[양승조
] var대신 dinamic으로 선언해서 해결은 했습니다. 맞는 해...
[양승조
] 또 막혔습니다. ㅠㅠ var list = props[i].Ge...
[양승조
] 아. 감사합니다. 어제는 안됐던것 같은데....정신을 차려야겠네...
[정성태] "props[i].GetValue(props[i])" 코드에서 ...
[정성태] 저렇게 조각 코드 말고, 실제로 재현이 되는 예제 프로젝트를 압...
[정성태] Modules 창(Ctrl+Shift+U)을 띄워서, 해당 Op...
[정성태] 만드실 수 있습니다. 단지, Unity 엔진 내의 스크립트와 W...
글쓰기
제목
이름
암호
전자우편
HTML
홈페이지
유형
제니퍼 .NET
닷넷
COM 개체 관련
스크립트
VC++
VS.NET IDE
Windows
Team Foundation Server
디버깅 기술
오류 유형
개발 환경 구성
웹
기타
Linux
Java
DDK
Math
Phone
Graphics
사물인터넷
부모글 보이기/감추기
내용
<div style='display: inline'> <div style='font-family: 맑은 고딕, Consolas; font-size: 20pt; color: #006699; text-align: center; font-weight: bold'>실행 시에 메서드 가로채기 - CLR Injection: Runtime Method Replacer 개선</div> <br /> 회사 업무랑 관계가 있다 보니, "가로채기" 기술에 대해서는 늘 관심이 있습니다. 비록, 몇 가지 제약으로 인해 채택할 수는 없었지만 기술 검토 과정 중에 알게 된 재미있는 글을 하나 소개해 드립니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; width: 800px; background-color: #fbedbb; overflow-x: scroll; 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 /> 그나저나, "가로 채기" 기술은 개발자라면 누구나 다 관심있는 것 같습니다. 커널 드라이버도 그렇고, 일반 사용자 모드 응용 프로그램도 마찬가지죠. 심지어 "버퍼 오버런"도 일종의 가로채기 기술이니. ^^;<br /> <br /> 이번 글의 주제인 "<a target='_tab' href='http://www.codeproject.com/KB/dotnet/CLRMethodInjection.aspx'>CLR Injection: Runtime Method Replacer</a>"를 실행해 보신 분은 아시겠지만, 크게 2가지 오류가 있습니다.<br /> <br /> <ul> <li>현재의 .NET 3.5 x64에서 DynamicMethod 치환 오류</li> <li>.NET 4.0에서 DynamicMethod 치환 오류</li> </ul> <br /> 저는 위의 2가지 문제를 개선한 코드를 알아볼 텐데요. 사실, 이전에 써놓은 아래의 2가지 글은 이 문제를 해결하기 위해 windbg를 사용하면서 정리가 된 것들입니다. 시간이 허락된다면, 아래의 2가지 글을 가볍게 읽고 시작하는 것도 좋겠습니다. ^^<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; width: 800px; background-color: #fbedbb; overflow-x: scroll; font-family: Consolas, Verdana;' > windbg로 확인하는 .NET CLR 메서드 ; <a target='_tab' href='http://www.sysnet.pe.kr/2/0/940'>http://www.sysnet.pe.kr/2/0/940</a> windbg로 확인하는 .NET CLR LCG 메서드(DynamicMethod) ; <a target='_tab' href='http://www.sysnet.pe.kr/2/0/941'>http://www.sysnet.pe.kr/2/0/941</a> </pre> <br /> <hr style='width: 50%' /><br /> <br /> "<a target='_tab' href='http://www.codeproject.com/KB/dotnet/CLRMethodInjection.aspx'>CLR Injection: Runtime Method Replacer</a>" 글이 쓰인 것은 2009년 6월인데 그 당시까지만 해도 정상작동했을지 모르지만, .NET 3.5 x64 및 .NET 4.0에서는 더 이상 동작하지 않게 되어 버렸습니다.<br /> <br /> 그러하니, 문제가 되는 소스 코드들을 하나씩 살펴보겠습니다. ^^<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; width: 800px; background-color: #fbedbb; overflow-x: scroll; font-family: Consolas, Verdana;' > private static IntPtr GetDynamicMethodAddress(MethodBase method) { unsafe { RuntimeMethodHandle handle = <b style='COLOR: blue'>GetDynamicMethodRuntimeHandle</b>(method); byte* ptr = (byte*)handle.Value.ToPointer(); if (...[.NET 2.0 SP2 이상]...) { RuntimeHelpers.PrepareMethod(handle); if (IntPtr.Size == 8) { // x64 오류 <b style='COLOR: blue'>ulong* address = (ulong*)ptr; address = (ulong*)*(address + 5); return new IntPtr(address + 12);</b> } else { // x86 정상동작 <b style='COLOR: blue'>uint* address = (uint*)ptr; address = (uint*)*(address + 5); return new IntPtr(address + 12);</b> } } else // .NET 2.0 SP2 { ...[생략]... } } } </pre> <br /> 이런 ~~~ ^^ 불안정한 offset 값 조정이군요. 여기를 정리하기에 앞서 다시 내부에서 호출되는 GetDynamicMethodRuntimeHandle 메서드 먼저 살펴보겠습니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; width: 800px; background-color: #fbedbb; overflow-x: scroll; font-family: Consolas, Verdana;' > private static RuntimeMethodHandle GetDynamicMethodRuntimeHandle(MethodBase method) { if (method is DynamicMethod) { FieldInfo fieldInfo = typeof(DynamicMethod).GetField("<b style='COLOR: blue'>m_method</b>",BindingFlags.NonPublic|BindingFlags.Instance); RuntimeMethodHandle handle = ((RuntimeMethodHandle)fieldInfo.GetValue(method)); return handle; } return method.MethodHandle; } </pre> <br /> 아하~~~ m_method에서 구해내는 군요. <a target='_tab' href='http://www.sysnet.pe.kr/2/0/941'>이전에 제가 썼던 글</a>에서처럼 위의 코드는 .NET 4.0에서 더 이상 동작하지 않습니다. 그래서 다음과 같이 수정해 주어야 합니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; width: 800px; background-color: #fbedbb; overflow-x: scroll; font-family: Consolas, Verdana;' > private static RuntimeMethodHandle GetDynamicMethodRuntimeHandle(MethodBase method) { RuntimeMethodHandle handle; <b style='COLOR: blue'> if (Environment.Version.Major == 4) { MethodInfo getMethodDescriptorInfo = typeof(DynamicMethod).GetMethod("GetMethodDescriptor", BindingFlags.NonPublic | BindingFlags.Instance); handle = (RuntimeMethodHandle)getMethodDescriptorInfo.Invoke(method, null); }</b> else { FieldInfo fieldInfo = typeof(DynamicMethod).GetField("m_method", BindingFlags.NonPublic | BindingFlags.Instance); handle = ((RuntimeMethodHandle)fieldInfo.GetValue(method)); } return handle; } </pre> <br /> 그럼 이제 다시 GetDynamicMethodAddress의 옵셋 조정으로 돌아가서 살펴보겠습니다. <br /> <br /> +5/+12의 옵셋 조정으로 구하는 값이 도대체 무엇인지 알아야겠습니다. 사실 지금까지 알아본 바에 의하면 이 값이 RuntimeMethodHandle.GetFunctionPointer()가 반환하는 값일 거라는 것이 제 생각인데요. 맞는지 볼까요? .NET 3.5 x86에서 정상동작을 했으니 환경설정을 그렇게 바꾸고 ReplaceMethod의 최종 *d 값과 RuntimeMethodHandle.GetFunctionPointer() 반환값을 비교해 보면 될 것입니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; width: 800px; background-color: #fbedbb; overflow-x: scroll; font-family: Consolas, Verdana;' > public static void ReplaceMethod(IntPtr srcAdr, MethodBase dest) { IntPtr destAdr = GetMethodAddress(dest); unsafe { if (IntPtr.Size == 8) { // x64... } else { uint* d = (uint*)destAdr.ToPointer(); *d = *((uint*)srcAdr.ToPointer()); <b style='COLOR: blue'>Console.WriteLine("Pointer == " + (*d).ToString("x"));</b> } } } private static IntPtr GetDynamicMethodAddress(MethodBase method) { unsafe { RuntimeMethodHandle handle = GetDynamicMethodRuntimeHandle(method); byte* ptr = (byte*)handle.Value.ToPointer(); if (IsNet20Sp2OrGreater()) { RuntimeHelpers.PrepareMethod(handle); IntPtr pFunction = handle.GetFunctionPointer(); <b style='COLOR: blue'>Console.WriteLine("pFunction == " + pFunction.ToString("x"));</b> ...[생략]... } } } </pre> <br /> 출력 결과는 역시 동일합니다. ^^<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; width: 800px; background-color: #fbedbb; overflow-x: scroll; font-family: Consolas, Verdana;' > pFunction == 4900a8 Pointer == 4900a8 </pre> <br /> 그 외에도, windbg로 연결해서 확인해 보면 결국 "!dumpmd [RuntimeMethodHandle 값]"의 "CodeAddr" 값과 일치한다는 것을 알 수 있습니다. 오호~~~ 여지없이 RuntimeMethodHandle.GetFunctionPointer 값으로도 대체된다는 것을 의미합니다.<br /> <br /> 따라서, 어렵게 옵셋 조정할 필요 없이 다음과 같이 매우 안정적으로 메서드 호출 한 번으로 끝낼 수 있습니다. (아마도, 이런 방식은 앞으로의 패치에 대해서도 좀 더 안정적으로 동작할 수 있을 것입니다.)<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; width: 800px; background-color: #fbedbb; overflow-x: scroll; font-family: Consolas, Verdana;' > private static IntPtr GetDynamicMethodAddress(MethodBase method) { unsafe { RuntimeMethodHandle handle = GetDynamicMethodRuntimeHandle(method); byte* ptr = (byte*)handle.Value.ToPointer(); if (IsNet20Sp2OrGreater()) { RuntimeHelpers.PrepareMethod(handle); <b style='COLOR: blue'>return handle.GetFunctionPointer();</b> /* 이후 주석 처리된 코드는 더 이상 필요하지 않음. if (IntPtr.Size == 8) { ulong* address = (ulong*)ptr; address = (ulong*)*(address + 5); return new IntPtr(address + 12); } else { uint* address = (uint*)ptr; address = (uint*)*(address + 5); return new IntPtr(address + 12); } */ } ... [생략]... </pre> <br /> 다음으로 변경해야 할 곳은, ReplaceMethod에서 갈아치우려는 메서드가 DynamicMethod인 경우 GetFunctionPointer()로 구해진 값을 static 형 메서드와는 구분해서 다음과 같이 대입을 해줘야 합니다. (주의!!! 여기에서 일컫는 static 은 "DynamicMethod"의 반대 의미로 사용된 것일 뿐 instance 메서드와 대비되는 static 메서드를 지칭하는 것이 아닙니다.)<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; width: 800px; background-color: #fbedbb; overflow-x: scroll; font-family: Consolas, Verdana;' > public static void ReplaceMethod(IntPtr srcAdr, MethodBase dest, bool isDynamicSource) { IntPtr destAdr = GetMethodAddress(dest); unsafe { if (IntPtr.Size == 8) { ulong* d = (ulong*)destAdr.ToPointer(); <b style='COLOR: blue'> if (isDynamicSource == true) { *d = (ulong)srcAdr.ToInt64(); }</b> else { *d = *((ulong*)srcAdr.ToPointer()); } } else { uint* d = (uint*)destAdr.ToPointer(); <b style='COLOR: blue'>if (isDynamicSource == true) { *d = (uint)srcAdr.ToInt32(); }</b> else { *d = *((uint*)srcAdr.ToPointer()); } } } } </pre> <br /> 마지막으로, 그다지 중요도는 없는 .NET 버전 체크 메서드인 IsNet20Sp2OrGreater에 다음과 같이 추가를 해줍니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; width: 800px; background-color: #fbedbb; overflow-x: scroll; font-family: Consolas, Verdana;' > private static bool IsNet20Sp2OrGreater() { <b style='COLOR: blue'> if (Environment.Version.Major == 4) { return true; } </b> return Environment.Version.Major == FrameworkVersions.Net20SP2.Major && Environment.Version.MinorRevision >= FrameworkVersions.Net20SP2.MinorRevision; } </pre> <br /> 자, 여기까지 변경하고 .NET 3.5 x64 / .NET 4.0에서 실행을 하면 정상적으로 정적 메서드의 주소를 동적으로 생성된 메서드의 주소로 치환해서 실행이 되는 것을 확인할 수 있습니다.<br /> <br /> 오~~~ 멋지죠? ^^<br /> <br /> [그림 1: .NET 4.0 / .NET 3.5 x64에서 실행]<br /> <img alt='replace_clr_method_1.png' src='/SysWebRes/bbs/replace_clr_method_1.png' /><br /> <br /> <a target='_tab' href='http://www.sysnet.pe.kr/bbs/DownloadAttachment.aspx?fid=554&boardid=331301885'>첨부한 소스 코드</a>는 위의 변경 사항이 반영되었습니다.<br /> <br /> ** 참고로, 원본 소스를 가능한 바꾸지 않으려고 해서 위와 같이 설명을 했는데, 원래는 <a target='_tab' href='http://www.sysnet.pe.kr/bbs/DownloadAttachment.aspx?fid=555&boardid=331301885'>2번째 첨부된 소스 코드</a>로 변경되어야 하는 것이 맞습니다.<br /> <br /><br /><hr /><span style='color: Maroon'>[이 토픽에 대해서 여러분들과 의견을 공유하고 싶습니다. 틀리거나 미흡한 부분 또는 의문 사항이 있으시면 언제든 댓글 남겨주십시오.]</span> </div>
첨부파일
스팸 방지용 인증 번호
8621
(왼쪽의 숫자를 입력해야 합니다.)