Microsoft MVP성태의 닷넷 이야기
글쓴 사람
정성태 (techsharer at outlook.com)
홈페이지
첨부 파일

(시리즈 글이 17개 있습니다.)
VC++: 36. Detours 라이브러리를 이용한 Win32 API - Sleep 호출 가로채기
; https://www.sysnet.pe.kr/2/0/631

.NET Framework: 187. 실행 시에 메서드 가로채기 - CLR Injection: Runtime Method Replacer 개선
; https://www.sysnet.pe.kr/2/0/942

디버깅 기술: 40. 상황별 GetFunctionPointer 반환값 정리 - x86
; https://www.sysnet.pe.kr/2/0/1027

VC++: 56. Win32 API 후킹 - Trampoline API Hooking
; https://www.sysnet.pe.kr/2/0/1231

VC++: 57. 웹 브라우저에서 Flash만 빼고 다른 ActiveX를 차단할 수 있을까?
; https://www.sysnet.pe.kr/2/0/1232

VC++: 58. API Hooking - 64비트를 고려해야 한다면? EasyHook!
; https://www.sysnet.pe.kr/2/0/1242

개발 환경 구성: 419. MIT 라이선스로 무료 공개된 Detours API 후킹 라이브러리
; https://www.sysnet.pe.kr/2/0/11764

.NET Framework: 883. C#으로 구현하는 Win32 API 후킹(예: Sleep 호출 가로채기)
; https://www.sysnet.pe.kr/2/0/12132

.NET Framework: 890. 상황별 GetFunctionPointer 반환값 정리 - x64
; https://www.sysnet.pe.kr/2/0/12143

.NET Framework: 891. 실행 시에 메서드 가로채기 - CLR Injection: Runtime Method Replacer 개선 - 두 번째 이야기
; https://www.sysnet.pe.kr/2/0/12144

디버깅 기술: 163. x64 환경에서 구현하는 다양한 Trampoline 기법
; https://www.sysnet.pe.kr/2/0/12148

.NET Framework: 895. C# - Win32 API를 Trampoline 기법을 이용해 C# 메서드로 가로채는 방법
; https://www.sysnet.pe.kr/2/0/12150

.NET Framework: 896. C# - Win32 API를 Trampoline 기법을 이용해 C# 메서드로 가로채는 방법 - 두 번째 이야기 (원본 함수 호출)
; https://www.sysnet.pe.kr/2/0/12151

.NET Framework: 897. 실행 시에 메서드 가로채기 - CLR Injection: Runtime Method Replacer 개선 - 세 번째 이야기(Trampoline 후킹)
; https://www.sysnet.pe.kr/2/0/12152

.NET Framework: 898. Trampoline을 이용한 후킹의 한계
; https://www.sysnet.pe.kr/2/0/12153

.NET Framework: 900. 실행 시에 메서드 가로채기 - CLR Injection: Runtime Method Replacer 개선 - 네 번째 이야기(Monitor.Enter 후킹)
; https://www.sysnet.pe.kr/2/0/12165

.NET Framework: 968. C# 9.0의 Function pointer를 이용한 함수 주소 구하는 방법
; https://www.sysnet.pe.kr/2/0/12409




실행 시에 메서드 가로채기 - CLR Injection: Runtime Method Replacer 개선 - 두 번째 이야기

지난 글에,

실행 시에 메서드 가로채기 - CLR Injection: Runtime Method Replacer 개선
; https://www.sysnet.pe.kr/2/0/942

codeproject의 글 하나를 소개했는데요.

CLR Injection: Runtime Method Replacer
; http://www.codeproject.com/KB/dotnet/CLRMethodInjection.aspx

우선, "실행 시에 메서드 가로채기 - CLR Injection: Runtime Method Replacer 개선" 글에서의 코드를 정리해 DetourFunc 프로젝트에 반영했으니,

C#으로 구현하는 Win32 API 후킹(예: Sleep 호출 가로채기)
; https://www.sysnet.pe.kr/2/0/12132

이를 사용하면 다음과 같이 일반 닷넷 메서드의 호출을 가로챌 수 있습니다.

// 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();

        TestMethod(true);
        NetMethodReplacer.ReplaceMethod(oldAction.Method, newAction.Method);
        TestMethod(true);
    }

    [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

TestMethod
NewMethod
*/

보는 바와 같이 TestMethod의 동작이 NewMethod로 치환되었습니다.




그런데 새롭게 바뀐 JIT 컴파일 방식으로 인해,

Windbg 환경에서 확인해 본 .NET 메서드 JIT 컴파일 전과 후 - 두 번째 이야기
; https://www.sysnet.pe.kr/2/0/12133

Runtime Method Replacer의 동작에 결함이 생기게 되었습니다. 이것을 간단하게 다음과 같이 재현할 수 있습니다.

for (int i = 0; i < 10000; i++)
{
    TestMethod(false);
}

NetMethodReplacer.ReplaceMethod(oldAction.Method, newAction.Method);
TestMethod(true);

/* 출력 결과
TestMethod
*/

그러니까, NetMethodReplacer.ReplaceMethod 호출 이전에 Fixup Precode가 call에서 jmp로 바뀌도록 TestMethod를 충분히 불러주면 이후 ReplaceMethod를 해도 효력이 없는 것입니다. 그 이유를 간단하게 정리해 보면, NetMethodReplacer.ReplaceMethod 메서드는 대상 코드를 치환하기 위해 MethodDesc의 8바이트 위치에 값을 써 PreStubWorker 단계에서 MethodDesc::GetMethodEntryPoint 함수가 그 값을 이용할 수 있게 만드는데, Fixup Precode가 Method의 Body로 향하는 jmp 문으로 일단 바뀌게 되면 이후부터는 MethodDesc의 8바이트 위치에 값을 써도 아무런 영향을 주지 못하기 때문입니다.

따라서, NetMethodReplacer.ReplaceMethod 메서드를 이용한다면 대상 메서드의 어셈블리가 로드되는 - 즉, 메서드들이 호출되지 않았을 - 초기 시점에 안전하게 치환 작업을 마무리해야만 합니다.




그나저나... 혹시 몇 번의 호출만에 call이 jmp로 바뀌게 되는 걸까요? 예전에 테스트했을 때는,

.NET Core 2.1 - Tiered Compilation 도입
; https://www.sysnet.pe.kr/2/0/11539

30번 정도였는데 이번에도 비슷할지... 다음과 같은 식으로 코드를 만들어 검증할 수 있습니다.

using System;
using System.Runtime.InteropServices;

class Program
{
    static void Main(string[] args)
    {
        Action action = TestMethod;

        IntPtr oldPtr = action.Method.MethodHandle.GetFunctionPointer();
        byte oldOPCode = Marshal.ReadByte(oldPtr); // call로 시작하므로 0xe8

        int count = 0;
        while (true)
        {
            TestMethod();
            count++;

            if (oldOPCode != Marshal.ReadByte(oldPtr)) // jmp는 0xe9
            {
                break;
            }
        }

        Console.WriteLine(count);
    }

    static void TestMethod()
    {
        Console.WriteLine("TEST");
    }
}

/* 출력 결과
TEST
TEST
2
*/

그렇습니다. 단 두 번째의 호출에서 call에서 jmp 문으로 바뀝니다. 그러니까, 마이크로소프트는 단 한 번만 호출되는 메서드의 수가 적지 않은 비율을 차지한다는... 통계를 가지고 있는 듯하군요. ^^

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




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







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

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

비밀번호

댓글 작성자
 




... 136  137  [138]  139  140  141  142  143  144  145  146  147  148  149  150  ...
NoWriterDateCnt.TitleFile(s)
1605정성태1/23/201421146개발 환경 구성: 212. Visual Studio Online과 "Monaco" 서비스 연동
1604정성태1/23/201421468오류 유형: 216. 윈도우 서버 백업 - Hyper-V 가상 머신이 백업되지 않는 경우 (2)
1603정성태1/23/201433592개발 환경 구성: 211. Hyper-V - Generation 2 유형의 VM 생성 시 ISO 부팅이 안된다면? [1]
1602정성태1/22/201423783디버깅 기술: 62. windbg - 사용자 모드 원격 디버깅
1601정성태1/22/201427412오류 유형: 215. windbg - Symbol file could not be found. Defaulted to export symbols
1600정성태1/19/201424029.NET Framework: 410. C# - 재귀호출을 스택 자료구조와 반복문을 이용해 대체하는 방법을 Paralle.For와 함께? [1]파일 다운로드1
1599정성태1/18/201432171.NET Framework: 409. C# - 재귀호출을 스택 자료구조와 반복문을 이용해 대체하는 방법 [1]파일 다운로드1
1598정성태1/17/201425504디버깅 기술: 61. NT 서비스 시작 단계에서 닷넷 메서드에 BP를 걸어 디버깅하는 방법
1597정성태1/17/201424099Phone: 9. Xamarin Android에 구글 AdMob 사용하는 방법 [1]
1596정성태1/17/201423041오류 유형: 214. Local SYSTEM 계정으로 실행된 IE에서 다운로드가 안 되는 문제
1595정성태1/16/201420032오류 유형: 213. attrib - Not resetting system file
1594정성태1/15/201422185오류 유형: 212. 마이크로소프트 라이브 계정 로그인 실패하는 경우
1593정성태1/14/201420805오류 유형: 211. ASP.NET 응용 프로그램을 IIS Express에서 디버깅할 때 "Requested registry access is not allowed" 오류 발생
1592정성태1/14/201421108오류 유형: 210. 2대의 AD가 있는 경우 도메인에 컴퓨터 추가를 실패한다면? [1]
1591정성태1/14/201423274오류 유형: 209. DebugDiag: Unable to find mscordacwks_x86_x86_[...version...].dll
1590정성태1/14/201423928오류 유형: 208. VSS Writer - NTDS 오류
1589정성태1/14/201432860Windows: 85. 컴퓨터를 껐는데도 어느 순간 자동으로 켜진다면? [2]
1588정성태1/14/201429638Windows: 84. 윈도우 7/8 - 메뉴 항목이 잔상으로 남는 문제
1587정성태1/14/201425570디버깅 기술: 60. NT 서비스가 시작하자마자 디버거를 연결시키는 방법 (2)
1586정성태1/14/201427274디버깅 기술: 59. NT 서비스가 시작하자마자 디버거를 연결시키는 방법 (1) [1]
1585정성태1/14/201430182VS.NET IDE: 84. Visual Studio를 이용한 파일 비교(diff)
1584정성태1/13/201432480Windows: 83. 윈도우 8 - UI가 있는 프로그램을 Local SYSTEM 권한의 세션 0 데스크톱에서 실행하는 방법
1583정성태1/13/201430467Windows: 82. 윈도우 8 - "Interactive Services Detection" 서비스 시작하는 방법 [1]
1582정성태1/12/201428952개발 환경 구성: 210. 원격 데스크톱(RDP) 접속 프로그램 - Royal TS [1]
1581정성태1/12/201430285.NET Framework: 408. 자바와 닷넷의 제네릭 차이점 - 중간 언어 및 공변/반공변 처리 [8]
1580정성태1/12/201440320.NET Framework: 407. 닷넷 사용자 정의 예외 클래스의 최소 구현 코드 [1]
... 136  137  [138]  139  140  141  142  143  144  145  146  147  148  149  150  ...