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




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

비밀번호

댓글 작성자
 




1  2  3  4  5  6  7  8  9  [10]  11  12  13  14  15  ...
NoWriterDateCnt.TitleFile(s)
13372정성태6/15/20233109개발 환경 구성: 682. SQL Server TLS 통신을 위해 사용되는 키 길이 확인 방법
13371정성태6/15/20233130개발 환경 구성: 681. openssl - 인증서 버전(V1 / V3)
13370정성태6/14/20233295개발 환경 구성: 680. C# - Ubuntu + Microsoft.Data.SqlClient + SQL Server 2008 R2 연결 방법 - TLS 1.2 지원
13369정성태6/13/20233092개발 환경 구성: 679. PyCharm(을 비롯해 JetBrains에 속한 여타) IDE에서 내부 Window들의 탭이 없어진 경우
13368정성태6/13/20233224개발 환경 구성: 678. openssl로 생성한 인증서를 SQL Server의 암호화 인증서로 설정하는 방법
13367정성태6/10/20233327오류 유형: 864. openssl로 만든 pfx 인증서를 Windows Server 2016 이하에서 등록 시 "The password you entered is incorrect" 오류 발생
13366정성태6/10/20233130.NET Framework: 2128. C# - 윈도우 시스템에서 지원하는 암호화 목록(Cipher Suites) 나열파일 다운로드1
13365정성태6/8/20232895오류 유형: 863. MODIFY FILE encountered operating system error 112(failed to retrieve text for this error. Reason: 15105)
13364정성태6/8/20233677.NET Framework: 2127. C# - Ubuntu + Microsoft.Data.SqlClient + SQL Server 2008 R2 연결 방법 [1]
13363정성태6/7/20233240스크립트: 49. 파이썬 - "Transformers (신경망 언어모델 라이브러리) 강좌" - 1장 2절 코드 실행 결과
13362정성태6/1/20233163.NET Framework: 2126. C# - 서버 측의 요청 제어 (Microsoft.AspNetCore.RateLimiting)파일 다운로드1
13361정성태5/31/20233638오류 유형: 862. Facebook - ASP.NET/WebClient 사용 시 graph.facebook.com/me 호출에 대해 403 Forbidden 오류
13360정성태5/31/20233030오류 유형: 861. WSL/docker - failed to start shim: start failed: io.containerd.runc.v2: create new shim socket
13359정성태5/19/20233350오류 유형: 860. Docker Desktop - k8s 초기화 무한 반복한다면?
13358정성태5/17/20233659.NET Framework: 2125. C# - Semantic Kernel의 Semantic Memory 사용 예제 [1]파일 다운로드1
13357정성태5/16/20233463.NET Framework: 2124. C# - Semantic Kernel의 Planner 사용 예제파일 다운로드1
13356정성태5/15/20233758DDK: 10. Device Driver 테스트 설치 관련 오류 (Code 37, Code 31) 및 인증서 관련 정리
13355정성태5/12/20233682.NET Framework: 2123. C# - Semantic Kernel의 ChatGPT 대화 구현 [1]파일 다운로드1
13354정성태5/12/20233954.NET Framework: 2122. C# - "Use Unicode UTF-8 for worldwide language support" 설정을 한 경우, 한글 입력이 '\0' 문자로 처리
13352정성태5/12/20233566.NET Framework: 2121. C# - Semantic Kernel의 대화 문맥 유지파일 다운로드1
13351정성태5/11/20234068VS.NET IDE: 185. Visual Studio - 원격 Docker container 내에 실행 중인 응용 프로그램에 대한 디버깅 [1]
13350정성태5/11/20233318오류 유형: 859. Windows Date and Time - Unable to continue. You do not have permission to perform this task
13349정성태5/11/20233645.NET Framework: 2120. C# - Semantic Kernel의 Skill과 Function 사용 예제파일 다운로드1
13348정성태5/10/20233568.NET Framework: 2119. C# - Semantic Kernel의 "Basic Loading of the Kernel" 예제
13347정성태5/10/20233929.NET Framework: 2118. C# - Semantic Kernel의 Prompt chaining 예제파일 다운로드1
13346정성태5/10/20233775오류 유형: 858. RDP 원격 환경과 로컬 PC 간의 Ctrl+C, Ctrl+V 복사가 안 되는 문제
1  2  3  4  5  6  7  8  9  [10]  11  12  13  14  15  ...