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

비밀번호

댓글 작성자
 




... 166  167  [168]  169  170  171  172  173  174  175  176  177  178  179  180  ...
NoWriterDateCnt.TitleFile(s)
830정성태1/10/201023528개발 환경 구성: 69. Windows Internal Database
829정성태1/7/201022909개발 환경 구성: 68. ODP.NET + OraMTS 사용
828정성태1/7/201030783개발 환경 구성: 67. 환경 변수를 이용한 다중 ODAC 버전 테스트
827정성태1/4/201023019개발 환경 구성: 66. .NET 응용 프로그램에서 64비트 Oracle Data Access Components 사용 (2)
826정성태1/3/201030463기타: 28. 2009년 인기 순위 정리
825정성태1/3/201047237개발 환경 구성: 65. .NET 응용 프로그램에서 64비트 Oracle Data Access Components 사용 [5]
823정성태1/1/201024436개발 환경 구성: 64. ODP.NET 설치 작업 없이 ASP.NET 응용 프로그램 배포파일 다운로드1
822정성태1/1/201086649개발 환경 구성: 63. ODP.NET 설치 없이 .NET 클라이언트 프로그램 배포하는 방법 [6]파일 다운로드2
821정성태1/1/201049523개발 환경 구성: 62. .NET 응용 프로그램에서 Oracle XE 사용 [1]
820정성태12/29/200920944개발 환경 구성: 61. Oxite 소스 코드를 Visual Studio 2010으로 마이그레이션파일 다운로드1
818정성태12/27/200919628개발 환경 구성: 60. Cassini 서버를 localhost 이외의 주소에서 접근하도록 변경
817정성태12/21/200921112개발 환경 구성: 59. WebDev 2.0에서 실행하는 Pet Shop 4.0
816정성태12/19/200922825개발 환경 구성: 58. Pet Shop 4.0을 IIS 없이 실행하는 방법 [1]파일 다운로드1
815정성태12/18/200925661개발 환경 구성: 57. Pet Shop 4.0 - SQL Server Compact Edition Version - 두 번째 이야기파일 다운로드1
814정성태12/16/200920994오류 유형: 91. VS2010 beta2 - The application cannot start
812정성태12/11/200924621개발 환경 구성: 56. Pet Shop 4.0 - SQL Server Compact Edition Version파일 다운로드2
811정성태12/2/200938146.NET Framework: 169. [in, out] 배열을 C#에서 C/C++로 넘기는 방법 - 두 번째 이야기 [8]파일 다운로드2
810정성태12/1/200934596.NET Framework: 168. [in,out] 배열을 C#에서 C/C++로 넘기는 방법 [3]
809정성태11/27/200924551오류 유형: 90. Method not found: 'Void System.Reflection.Emit.DynamicMethod..ctor(System.String, System.Type, System.Type[])'.
808정성태11/26/200933403VC++: 38. X64 빌드 오류: error LNK2001: unresolved external symbol [COMDLL]_ProxyFileInfo
807정성태11/23/200927144웹: 14. 로컬에 있는 HTML 페이지를 보호 모드에서 여는 방법
802정성태11/19/200929189.NET Framework: 167. 다른 스레드의 호출 스택 덤프 구하는 방법파일 다운로드1
801정성태11/18/200936840개발 환경 구성: 55. Hyper-V와 고성능 그래픽 카드의 성능 문제 해결 [1]
800정성태11/13/200929603VS.NET IDE: 66. 수동으로 구성해 본 VC++ 프로젝트 설정: ReleaseMinDependency
798정성태11/10/200928261개발 환경 구성: 54. .NET 개발자가 처음 설치해 본 TOMCAT [2]
797정성태11/9/200924646개발 환경 구성: 53. 물리 PC에 설치된 Windows 7을 Hyper-V로 이전하면?
... 166  167  [168]  169  170  171  172  173  174  175  176  177  178  179  180  ...