Microsoft MVP성태의 닷넷 이야기
글쓴 사람
정성태 (techsharer at outlook.com)
홈페이지
첨부 파일
(연관된 글이 1개 있습니다.)
(시리즈 글이 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




C# 9.0의 Function pointer를 이용한 함수 주소 구하는 방법

그러고 보니,

C# 9.0 - (6) Function pointers
; https://www.sysnet.pe.kr/2/0/12374

신규 문법은 unmanaged, managed 메서드에 대해 모두 함수 포인터를 지원하는데, 그것 자체가 포인터이므로 사용하기도 간단합니다. 살짝 테스트를 해볼까요? ^^

using System;
using System.Reflection;

class Program
{
    static void Main(string[] args)
    {
        // JIT 완료를 위해!
        Program.CallMyMethod();

        unsafe
        {
            delegate*<void> func = &Program.CallMyMethod;
            Console.WriteLine("function pointer: " + new IntPtr(func).ToInt64().ToString("x"));
        }
    }

    public static void CallMyMethod()
    {
        Console.WriteLine("CallMyMethod");
    }
}

위의 프로그램을 비주얼 스튜디오에서 F5 디버깅으로 확인하면, func 주소에 해당하는 코드가 다음과 같이 jmp를 가리키고 있는 것을 볼 수 있습니다.

01210448 E9 23 06 00 00       jmp         Program.CallMyMethod() (01210A70h)  
0121044D 5F                   pop         edi  

재미있는 것은, 위의 프로그램을 F5 디버깅이 아닌 (Ctrl + F5로) 그냥 실행했을 경우엔 _PrecodeFixupThunk의 호출 코드를 가리킨다는 점입니다.

02A80460 E8 AB EC 4B 6F       call        _PrecodeFixupThunk@0 (71F3F110h)  
02A80465 5E                   pop         esi  
02A80466 00 00                add         byte ptr [eax],al  
02A80468 6C                   ins         byte ptr es:[edi],dx  
02A80469 4D                   dec         ebp  
02A8046A 09 01                or          dword ptr [ecx],eax  
02A8046C 00 00                add         byte ptr [eax],al  
02A8046E 00 00                add         byte ptr [eax],al  

예전에 위의 thunk 호출에 대해 확인하는 방법을 살펴본 적이 있었는데요, 그것에 따라 계산해,

rax == 02A80465
r10 == 0, r11 == 0
MethodDesc == [rax + r10 * 4 + 3] == [02A80468] == 01094d6c

얻은 값(01094d6c)을 windbg의 dumpmd로 보면 Program.CallMyMethod를 가리킵니다.

0:000> !DumpMD  01094d6c
Method Name:  Program.CallMyMethod()
Class:        010912f8
MethodTable:  01094d80
mdToken:      06000003
Module:       01094044
IsJitted:     yes
CodeAddr:     02a80a50
Transparency: Critical

당연히 (이전에 Program.CallMyMethod 호출을 했으므로) CodeAddr의 위치는 기계어로 번역된 함수의 처음 부분에 해당합니다.

    59:     public static void CallMyMethod()
    60:     {
02A80A50 55                   push        ebp  
02A80A51 8B EC                mov         ebp,esp  
02A80A53 83 3D F0 42 09 01 00 cmp         dword ptr ds:[10942F0h],0  
02A80A5A 74 05                je          Program.CallMyMethod()+011h (02A80A61h)  
02A80A5C E8 1F F3 85 6F       call        JIT_DbgIsJustMyCode (722DFD80h)  
02A80A61 90                   nop  
    61:         Console.WriteLine("CallMyMethod");
02A80A62 8B 0D 44 23 BA 03    mov         ecx,dword ptr ds:[3BA2344h]  
02A80A68 E8 F7 44 D2 6D       call        System.Console.WriteLine(System.String) (707A4F64h)  
02A80A6D 90                   nop  
    62:     }
02A80A6E 90                   nop  
02A80A6F 5D                   pop         ebp  
02A80A70 C3                   ret  

그러니까, 함수 포인터의 호출은 일반 메서드의 접근과는 별도로 또 다른 call site의 역할을 하도록 처리한 것 같습니다. 따라서 precode fixup을 호출한다고 해서 함수 포인터의 속도에 영향이 있는 것은 아닙니다. 왜냐하면 마찬가지로 함수 포인터의 호출 역시 처음 한 번만 precode fixup 단계를 거칠 뿐 이후에는 jmp로 바뀌기 때문입니다.




정리해 보면, 함수 포인터를 통한 닷넷 메서드의 주소를 (F5, Ctrl+F5 실행 방식에 상관없이) 가져오고 싶다면 아쉽게도 함수 포인터가 한 번은 실행되었어야 가능합니다. 그런 경우라면, 다음과 같은 식의 코드로 간단하게 처리할 수 있습니다.

using System;
using System.Reflection;
using System.Runtime.CompilerServices;

class Program
{
    static void Main(string[] args)
    {
        // MethodHandle.GetFunctionPointer() 값과 비교를 위해!
        MethodInfo mi = typeof(Program).GetMethod("CallMyMethod",
           System.Reflection.BindingFlags.Static | System.Reflection.BindingFlags.Public);
        RuntimeHelpers.PrepareMethod(mi.MethodHandle);

        unsafe
        {
            delegate*<void> func = &Program.CallMyMethod;
            func();

            IntPtr ptrFunc = ReadJmpPointer32(func, 0xe9);

            Console.WriteLine("function pointer: " + new IntPtr(func).ToInt64().ToString("x"));
            Console.WriteLine("function pointer target: " + ptrFunc.ToInt64().ToString("x"));
            Console.WriteLine("MethodInfo: " + mi.MethodHandle.GetFunctionPointer().ToInt64().ToString("x"));

            Console.ReadLine();
        }
    }

    private static unsafe IntPtr ReadJmpPointer32(delegate*<void> func, byte jmpCode)
    {
        IntPtr ptr = new IntPtr(func);

        byte* pBuf = (byte*)ptr;
        if (*pBuf != jmpCode)
        {
            return IntPtr.Zero;
        }

        if (IntPtr.Size == 4)
        {
            int pos = pBuf[1] | (pBuf[2] << 8) | (pBuf[3] << 16) | pBuf[4] << 24;
            return IntPtr.Add(ptr, pos + /* jmp 5bytes */ 5);
        }

        throw new ApplicationException("x64 - Not supported yet");
    }

    public static void CallMyMethod()
    {
        Console.WriteLine("CallMyMethod");
    }
}

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

시간 되시면 다음의 글도 한 번 보시고. ^^

상황별 GetFunctionPointer 반환값 정리
; https://www.sysnet.pe.kr/2/0/1027

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

C++의 inline asm 사용을 .NET으로 포팅하는 방법 - 두 번째 이야기
; https://www.sysnet.pe.kr/2/0/10889




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

[연관 글]






[최초 등록일: ]
[최종 수정일: 11/12/2020]

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

비밀번호

댓글 작성자
 




... 76  77  78  79  80  [81]  82  83  84  85  86  87  88  89  90  ...
NoWriterDateCnt.TitleFile(s)
11908정성태5/22/201919804.NET Framework: 836. C# - Python range 함수 구현파일 다운로드1
11907정성태5/22/201916570오류 유형: 541. msbuild - MSB4024 The imported project file "...targets" could not be loaded
11906정성태5/21/201916779.NET Framework: 835. .NET Core/C# - 리눅스 syslog에 로그 남기는 방법
11905정성태5/21/201917361.NET Framework: 834. C# - 폴더 경로 문자열에서 "..", "." 표기를 고려한 최종 문자열을 얻는 방법 - 두 번째 이야기
11904정성태5/21/201925726.NET Framework: 833. C# - Open Hardware Monitor를 이용한 CPU 온도 정보 [1]파일 다운로드1
11903정성태5/21/201919548오류 유형: 540. .NET Core - System.PlatformNotSupportedException: The named version of this synchronization primitive is not supported on this platform.
11902정성태5/21/201917740오류 유형: 539. mstest 실행 시 "The directory name is invalid." 오류 발생
11901정성태5/21/201919617오류 유형: 538. msbuild 오류 - Could not find a part of the path '%LOCALAPPDATA%\Temp\2\.NETFramework,Version=v4.0.AssemblyAttributes.cs'
11900정성태5/18/201918552오류 유형: 537. "sfc /scannow" 실행 중 시스템이 부팅되는 현상
11899정성태5/17/201919409Linux: 9. Linux에서 윈도우의 OutputDebugString 대신 사용할 수 있는 syslog [1]
11898정성태5/16/201921205VC++: 130. C++ string의 c_str과 data 함수의 차이점 [3]
11897정성태5/16/201928191오류 유형: 536. Visual Studio - "Developer Pack"을 설치했는데도 "대상 프레임워크" 목록에 나오지 않는 경우 [2]
11896정성태5/15/201923082개발 환경 구성: 440. C#, C++ - double의 Infinity, NaN 표현 방식파일 다운로드1
11895정성태5/12/201920953.NET Framework: 832. ML.NET Model Builder - 회귀(Regression), 다중 분류(Multi-class classification) 예제파일 다운로드1
11894정성태5/10/201922667VS.NET IDE: 135. Visual Studio - ML.NET Model Builder 소개 [5]
11893정성태5/10/201919599오류 유형: 535. C# 6.0 이상의 문법을 컴파일 시 오류가 발생한다면?
11892정성태5/10/201919418웹: 38. HTTP Cookie의 expires 시간 형식(RFC7231)
11891정성태5/9/201922512.NET Framework: 831. (번역글) .NET Internals Cookbook Part 12 - Memory structure, attributes, handles
11890정성태5/8/201917585개발 환경 구성: 439. "Visual Studio Enterprise is required to execute the test." 메시지와 관련된 코드 기록
11889정성태5/8/201918319개발 환경 구성: 438. mstest, QTAgent의 로그 파일 설정 방법
11888정성태5/8/201935669.NET Framework: 830. C# - 비동기 호출을 취소하는 CancellationToken의 간단한 예제 코드 [1]파일 다운로드1
11887정성태5/8/201921254.NET Framework: 829. C# - yield 문을 사용할 수 있는 메서드의 조건
11886정성태5/7/201919116오류 유형: 534. mstest.exe 실행 시 "Visual Studio Enterprise is required to execute the test." 오류 [2]
11885정성태5/7/201915987오류 유형: 533. mstest.exe 실행 시 "File extension specified '.loadtest' is not a valid test extension." 오류 발생
11884정성태5/5/201920823.NET Framework: 828. C# DLL에서 Win32 C/C++처럼 dllexport 함수를 제공하는 방법 - 두 번째 이야기
11883정성태5/3/201926007.NET Framework: 827. C# - 인터넷 시간 서버로부터 받은 시간을 윈도우에 적용하는 방법파일 다운로드1
... 76  77  78  79  80  [81]  82  83  84  85  86  87  88  89  90  ...