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

비밀번호

댓글 작성자
 




... 31  32  33  34  35  36  37  38  [39]  40  41  42  43  44  45  ...
NoWriterDateCnt.TitleFile(s)
12661정성태6/4/202118957.NET Framework: 1063. C# - MQTT를 이용한 클라이언트/서버(Broker) 통신 예제 [4]파일 다운로드1
12660정성태6/3/202110099.NET Framework: 1062. Windows Forms - 폼 내에서 발생하는 마우스 이벤트를 자식 컨트롤 영역에 상관없이 수신하는 방법 [1]파일 다운로드1
12659정성태6/2/202111327Linux: 40. 우분투 설치 후 MBR 디스크 드라이브 여유 공간이 인식되지 않은 경우 - Logical Volume Management
12658정성태6/2/20218736Windows: 194. Microsoft Store에 있는 구글의 공식 Youtube App
12657정성태6/2/202110038Windows: 193. 윈도우 패키지 관리자 - winget 설치
12656정성태6/1/20218287.NET Framework: 1061. 서버 유형의 COM+에 적용할 수 없는 Server GC
12655정성태6/1/20217768오류 유형: 722. windbg/sos - savemodule - Fail to read memory
12654정성태5/31/20217792오류 유형: 721. Hyper-V - Saved 상태의 VM을 시작 시 오류 발생
12653정성태5/31/202110477.NET Framework: 1060. 닷넷 GC에 새롭게 구현되는 DPAD(Dynamic Promotion And Demotion for GC)
12652정성태5/31/20218583VS.NET IDE: 164. Visual Studio - Web Deploy로 Publish 시 암호창이 매번 뜨는 문제
12651정성태5/31/20218816오류 유형: 720. PostgreSQL - ERROR: 22P02: malformed array literal: "..."
12650정성태5/17/20218151기타: 82. OpenTabletDriver의 버튼에 더블 클릭을 매핑 및 게임에서의 지원 방법
12649정성태5/16/20219464.NET Framework: 1059. 세대 별 GC(Garbage Collection) 방식에서 Card table의 사용 의미 [1]
12648정성태5/16/20218120사물인터넷: 66. PC -> FTDI -> NodeMCU v1 ESP8266 기기를 UART 핀을 연결해 직렬 통신하는 방법파일 다운로드1
12647정성태5/15/20219372.NET Framework: 1058. C# - C++과의 연동을 위한 구조체의 fixed 배열 필드 사용파일 다운로드1
12646정성태5/15/20218490사물인터넷: 65. C# - Arduino IDE의 Serial Monitor 기능 구현파일 다운로드1
12645정성태5/14/20218185사물인터넷: 64. NodeMCU v1 ESP8266 - LittleFS를 이용한 와이파이 접속 정보 업데이트파일 다운로드1
12644정성태5/14/20219327오류 유형: 719. 윈도우 - 제어판의 "프로그램 및 기능" / "Windows 기능 켜기/끄기" 오류 0x800736B3
12643정성태5/14/20218528오류 유형: 718. 서버 유형의 COM+ 사용 시 0x80080005(Server execution failed) 오류 발생
12642정성태5/14/20219466오류 유형: 717. The 'Microsoft.ACE.OLEDB.12.0' provider is not registered on the local machine.
12641정성태5/13/20219174디버깅 기술: 179. 윈도우용 .NET Core 3 이상에서 Windbg의 sos 사용법
12640정성태5/13/202112107오류 유형: 716. RDP 연결 - Because of a protocol error (code: 0x112f), the remote session will be disconnected. [1]
12639정성태5/12/20218990오류 유형: 715. Arduino: Open Serial Monitor - The module '...\detection.node' was compiled against a different Node.js version using NODE_MODULE_VERSION
12638정성태5/12/20219906사물인터넷: 63. NodeMCU v1 ESP8266 - 펌웨어 내 파일 시스템(SPIFFS, LittleFS) 및 EEPROM 활용
12637정성태5/10/20219499사물인터넷: 62. NodeMCU v1 ESP8266 보드의 A0 핀에 다중 아날로그 센서 연결 [1]
12636정성태5/10/20219788사물인터넷: 61. NodeMCU v1 ESP8266 보드의 A0 핀 사용법 - FSR-402 아날로그 압력 센서 연동파일 다운로드1
... 31  32  33  34  35  36  37  38  [39]  40  41  42  43  44  45  ...