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

비밀번호

댓글 작성자
 




... 31  32  33  34  35  36  37  38  39  40  41  [42]  43  44  45  ...
NoWriterDateCnt.TitleFile(s)
12587정성태4/6/202111862개발 환경 구성: 564. Windows 10 - ClickOnce 배포처럼 사용할 수 있는 MSIX 설치 파일 [1]
12586정성태4/5/20219544오류 유형: 710. Windows - Restart-Computer / shutdown 명령어 수행 시 Access is denied(E_ACCESSDENIED)
12585정성태4/5/20219307개발 환경 구성: 563. 기본 생성된 kubeconfig 파일의 내용을 새롭게 생성한 인증서로 구성하는 방법
12584정성태4/1/202110001개발 환경 구성: 562. kubeconfig 파일 없이 kubectl 옵션만으로 실행하는 방법
12583정성태3/29/202111483개발 환경 구성: 561. kubectl 수행 시 다른 k8s 클러스터로 접속하는 방법
12582정성태3/29/202110195오류 유형: 709. Visual C++ - 컴파일 에러 error C2059: syntax error: '__stdcall'
12581정성태3/28/202110105.NET Framework: 1031. WinForm/WPF에서 Console 창을 띄워 출력하는 방법 (2) - Output 디버깅 출력을 AllocConsole로 우회 [2]
12580정성태3/28/20218837오류 유형: 708. SQL Server Management Studio - Execution Timeout Expired.
12579정성태3/28/20218942오류 유형: 707. 중첩 가상화(Nested Virtualization) - The virtual machine could not be started because this platform does not support nested virtualization.
12578정성태3/27/20219205개발 환경 구성: 560. Docker Desktop for Windows 기반의 Kubernetes 구성 (2) - WSL 2 인스턴스에 kind가 구성한 k8s 서비스 위치
12577정성태3/26/202111257개발 환경 구성: 559. Docker Desktop for Windows 기반의 Kubernetes 구성 - WSL 2 인스턴스에 kind 도구로 k8s 클러스터 구성
12576정성태3/25/20219046개발 환경 구성: 558. Docker Desktop for Windows에서 DockerDesktopVM 기반의 Kubernetes 구성 (2) - k8s 서비스 위치
12575정성태3/24/20218126개발 환경 구성: 557. Docker Desktop for Windows에서 DockerDesktopVM 기반의 Kubernetes 구성
12574정성태3/23/202112159.NET Framework: 1030. C# Socket의 Close/Shutdown 동작 (동기 모드)
12573정성태3/22/20219978개발 환경 구성: 556. WSL 인스턴스 초기 설정 명령어 [1]
12572정성태3/22/20219515.NET Framework: 1029. C# - GC 호출로 인한 메모리 압축(Compaction)을 확인하는 방법파일 다운로드1
12571정성태3/21/20218659오류 유형: 706. WSL 2 기반으로 "Enable Kubernetes" 활성화 시 초기화 실패 [1]
12570정성태3/19/202112952개발 환경 구성: 555. openssl - CA로부터 인증받은 새로운 인증서를 생성하는 방법
12569정성태3/18/202111851개발 환경 구성: 554. WSL 인스턴스 export/import 방법 및 단축 아이콘 설정 방법
12568정성태3/18/20217428오류 유형: 705. C# 빌드 - Couldn't process file ... due to its being in the Internet or Restricted zone or having the mark of the web on the file.
12567정성태3/17/20218853개발 환경 구성: 553. Docker Desktop for Windows를 위한 k8s 대시보드 활성화 [1]
12566정성태3/17/20219164개발 환경 구성: 552. Kubernetes - kube-apiserver와 REST API 통신하는 방법 (Docker Desktop for Windows 환경)
12565정성태3/17/20216628오류 유형: 704. curl.exe 실행 시 dll not found 오류
12564정성태3/16/20217127VS.NET IDE: 160. 새 프로젝트 창에 C++/CLI 프로젝트 템플릿이 없는 경우
12563정성태3/16/20219101개발 환경 구성: 551. C# - JIRA REST API 사용 정리 (3) jira-oauth-cli 도구를 이용한 키 관리
12562정성태3/15/202110256개발 환경 구성: 550. C# - JIRA REST API 사용 정리 (2) JIRA OAuth 토큰으로 API 사용하는 방법파일 다운로드1
... 31  32  33  34  35  36  37  38  39  40  41  [42]  43  44  45  ...