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

(시리즈 글이 16개 있습니다.)
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: 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

비밀번호

댓글 작성자
 




... 61  62  63  64  65  66  67  68  [69]  70  71  72  73  74  75  ...
NoWriterDateCnt.TitleFile(s)
11922정성태5/29/201911570.NET Framework: 840. ML.NET 데이터 정규화파일 다운로드1
11921정성태5/28/201916505Math: 55. C# - 다항식을 위한 최소 자승법(Least Squares Method)파일 다운로드1
11920정성태5/28/201910061.NET Framework: 839. C# - PLplot 색상 제어
11919정성태5/27/201913209Math: 54. C# - 최소 자승법의 1차 함수에 대한 매개변수를 단순 for 문으로 구하는 방법 [1]파일 다운로드1
11918정성태5/25/201914383Math: 53. C# - 행렬식을 이용한 최소 자승법(LSM: Least Square Method)파일 다운로드1
11917정성태5/24/201914530Math: 52. MathNet을 이용한 간단한 통계 정보 처리 - 분산/표준편차파일 다운로드1
11916정성태5/24/201912517Math: 51. MathNET + OxyPlot을 이용한 간단한 통계 정보 처리 - Histogram파일 다운로드1
11915정성태5/24/201914814Linux: 11. 리눅스의 환경 변수 관련 함수 정리 - putenv, setenv, unsetenv
11914정성태5/24/201914508Linux: 10. 윈도우의 GetTickCount와 리눅스의 clock_gettime파일 다운로드1
11913정성태5/23/201912143.NET Framework: 838. C# - 숫자형 타입의 bit(2진) 문자열, 16진수 문자열 구하는 방법파일 다운로드1
11912정성태5/23/201911792VS.NET IDE: 137. Visual Studio 2019 버전 16.1부터 리눅스 C/C++ 프로젝트에 추가된 WSL 지원
11911정성태5/23/201910870VS.NET IDE: 136. Visual Studio 2019 - 리눅스 C/C++ 프로젝트에 인텔리센스가 동작하지 않는 경우
11910정성태5/23/201919540Math: 50. C# - MathNet.Numerics의 Matrix(행렬) 연산 [1]파일 다운로드1
11909정성태5/22/201913956.NET Framework: 837. C# - PLplot 사용 예제 [1]파일 다운로드1
11908정성태5/22/201912341.NET Framework: 836. C# - Python range 함수 구현파일 다운로드1
11907정성태5/22/201910127오류 유형: 541. msbuild - MSB4024 The imported project file "...targets" could not be loaded
11906정성태5/21/201910084.NET Framework: 835. .NET Core/C# - 리눅스 syslog에 로그 남기는 방법
11905정성태5/21/201910760.NET Framework: 834. C# - 폴더 경로 문자열에서 "..", "." 표기를 고려한 최종 문자열을 얻는 방법 - 두 번째 이야기
11904정성태5/21/201917045.NET Framework: 833. C# - Open Hardware Monitor를 이용한 CPU 온도 정보 [1]파일 다운로드1
11903정성태5/21/201912015오류 유형: 540. .NET Core - System.PlatformNotSupportedException: The named version of this synchronization primitive is not supported on this platform.
11902정성태5/21/201911160오류 유형: 539. mstest 실행 시 "The directory name is invalid." 오류 발생
11901정성태5/21/201912305오류 유형: 538. msbuild 오류 - Could not find a part of the path '%LOCALAPPDATA%\Temp\2\.NETFramework,Version=v4.0.AssemblyAttributes.cs'
11900정성태5/18/201911558오류 유형: 537. "sfc /scannow" 실행 중 시스템이 부팅되는 현상
11899정성태5/17/201912586Linux: 9. Linux에서 윈도우의 OutputDebugString 대신 사용할 수 있는 syslog [1]
11898정성태5/16/201913970VC++: 130. C++ string의 c_str과 data 함수의 차이점 [3]
11897정성태5/16/201920641오류 유형: 536. Visual Studio - "Developer Pack"을 설치했는데도 "대상 프레임워크" 목록에 나오지 않는 경우 [2]
... 61  62  63  64  65  66  67  68  [69]  70  71  72  73  74  75  ...