.NET程序hook的另一种优雅实现-C++/CLI

概述

在正式开始之前,我们需要先了解以下几个概念:

  • CLI(Common Language Infrastructure,通用语言框架):提供了一套可执行代码和它所运行需要的虚拟执行环境的规范.

  • CLR(Common Language Runtime,公共语言运行时):和Java虚拟机一样也是一个运行时环境,负责资源管理(内存分配和垃圾收集),并保证应用和底层操作系统之间必要的分离.可以说微软的.NET基础CLR是CLI的一个实例.

  • C++ /CLI:实现了C和.NET的无缝连接,可以使用C和C#混合的方式来完成应用程序代码的编写.

  • .NET程序编译运行流程:将源代码编译为微软中间语言MSIL,运行的时候即时编译为本地机器语言,同时.NET代码运行时有一个CLR环境来管理程序.

对于Hook来说,首先关键的一步就是确定目标函数的地址,而.NET程序的Native函数地址是运行的时候即时编译的,函数地址不确定.但幸运的是我们可以通过C#的RuntimeMethodHandle.GetFunctionPointer()函数来获取编译后的Native函数地址,另外需要注意一点的就是在.NET中,假设函数没有被直接或间接使用,那函数就不会被编译.因此在使用上述接口获取编译后的Native函数地址之前,我们还需要使用C#中的System::Runtime::CompilerServices::RuntimeHelpers::PrepareMethod()函数来进行函数的编译.

通过上述描述,我们可以很轻易就想到用C++来完成Hook代码的编写,用C#来完成.NET程序函数编译地址的确定.

目标程序

假设我们要Hook的目标C#窗体应用程序如下:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
using System;
using System.Windows.Forms;

namespace CLuoHun
{
    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
        }

        private void btnStatic_Click(object sender, EventArgs e)
        {
            TstStaticFunc("TstStaticFunc");
        }

        private void btnInstance_Click(object sender, EventArgs e)
        {
            TstInstanceFunc("TstInstanceFunc");
        }

        private void btnGeneric_Click(object sender, EventArgs e)
        {
            TstGenericFunc("TstGenericFunc");
        }

        //静态方法
        public static void TstStaticFunc(String strMsg)
        {
            MessageBox.Show(strMsg);
        }

        //实例方法
        private void TstInstanceFunc(String strMsg)
        {
            MessageBox.Show(strMsg);
        }

        //泛型方法
        private void TstGenericFunc<T>(T strMsg)
        {
            MessageBox.Show(strMsg.ToString());
        }
    }
}

后面我们以静态方法TstStaticFunc、实例方法TstInstanceFunc以及泛型方法TstGenericFunc为例来展开讲解C++ /CLI Hook代码的编写.

Hook代码

  1. 创建一个C++ Dll项目,启用CLR支持

启用CLR支持

  1. 修改编译选项

修改编译选项

在配置属性下C/C++的命令行中添加/Zc:twoPhase-

  1. Dll的Hook代码如下:
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
#using <mscorlib.dll>
#include <msclr\marshal_cppstd.h>
using namespace System;
using namespace std;
using namespace Reflection;
using namespace System::Runtime::InteropServices;
using namespace msclr::interop;

#include <memory>
#include <string>  

#include "Detours/include/LuoDetours.h"
#include "OutputDebugString/OutDebuf.h"

void __clrcall MyStaticFunc(String^ strMsg) 
{
	//cli的string转c++ char*
	char* szMsg = (char*)(void*)Marshal::StringToHGlobalAnsi(strMsg).ToPointer();
	MessageBoxA(NULL, "MyStaticFunc", szMsg, MB_OK);
}

void __clrcall MyInstanceFunc(Object% obj, String^ strMsg)
{
	//cli的string转c++ char*
	char* szMsg = (char*)(void*)Marshal::StringToHGlobalAnsi(strMsg).ToPointer();
	MessageBoxA(NULL, "MyInstanceFunc", szMsg, MB_OK);
}

void __clrcall MyGenericFunc(Object% obj, String^ strMsg)
{
	//cli的string转c++ char*
	char* szMsg = (char*)(void*)Marshal::StringToHGlobalAnsi(strMsg).ToPointer();
	MessageBoxA(NULL, "MyGenericFunc", szMsg, MB_OK);
}

DWORD WINAPI ThreadProc(LPVOID lpThreadParameter)
{
	//①反射找到函数
	Type^ type = Type::GetType("CLuoHun.Form1, CLuoHun");

	//静态方法 public static void TstStaticFunc(String strMsg)
	MethodInfo^ staticMethod = type->GetMethod("TstStaticFunc", BindingFlags::Public | BindingFlags::Static);

	//实例方法 private void TstInstanceFunc(String strMsg)
	MethodInfo^ instanceMethod = type->GetMethod("TstInstanceFunc", BindingFlags::NonPublic | BindingFlags::Instance);

	//泛型方法 private void TstGenericFunc<T>(T strMsg)
	MethodInfo^ genericMethodPre = type->GetMethod("TstGenericFunc", BindingFlags::NonPublic | BindingFlags::Instance);
	MethodInfo^ genericMethod = genericMethodPre->MakeGenericMethod(String::typeid);

	//②JIT 编译函数
	System::Runtime::CompilerServices::RuntimeHelpers::PrepareMethod(staticMethod->MethodHandle);
	System::Runtime::CompilerServices::RuntimeHelpers::PrepareMethod(instanceMethod->MethodHandle);
	System::Runtime::CompilerServices::RuntimeHelpers::PrepareMethod(genericMethod->MethodHandle);

	//③获取函数地址
	void* staticMethodAddr = (void*)staticMethod->MethodHandle.GetFunctionPointer();
	void* instanceMethodAddr = (void*)instanceMethod->MethodHandle.GetFunctionPointer();
	void* genericMethodAddr = (void*)genericMethod->MethodHandle.GetFunctionPointer();

	//DbgPrintf("TstStaticFunc address: 0x%x", staticMethodAddr);
	//DbgPrintf("TstInstanceFunc address: 0x%x", instanceMethodAddr);
	//DbgPrintf("TstGenericFunc address: 0x%x", genericMethodAddr);

	//④Hook
    AddHook(&(PVOID&)staticMethodAddr, MyStaticFunc);
	AddHook(&(PVOID&)instanceMethodAddr, MyInstanceFunc);
	AddHook(&(PVOID&)genericMethodAddr, MyGenericFunc);

	return TRUE;
}

//#pragma unmanaged
#pragma managed(push, off) //编译为native代码
BOOL APIENTRY DllMain( HMODULE hModule,
                       DWORD  dwReason,
                       LPVOID lpReserved
                     )
{
	if (dwReason == DLL_PROCESS_ATTACH)
	{
		DisableThreadLibraryCalls(hModule);

		::CreateThread(NULL, NULL, ThreadProc, NULL, NULL, NULL);
	}
	else if (dwReason == DLL_PROCESS_DETACH)
	{
	}
	return TRUE;
}
#pragma managed(pop)
注意
  • 对于Dll工程来说,需要使用pragma指令将DllMain函数编译为native代码

  • Hook函数的调用约定为__clrcall

  • 对于实例函数的Hook,要多写一个Object参数

  • 对于泛型方法,要调用MethodInfo.MakeGenericMethod函数为其提供具体的类型参数

  1. 将上述代码编写的Dll注入到目标C#程序中即可实现指定函数的Hook

Hook结果

总结

对.NET程序的Hook,当然也可以通过C#这种高级语言来完成,但是异常麻烦.C++ /CLI通过将托管环境和Native环境整合在一起,允许开发者在编写托管代码的同时,仍能直接访问Native代码,借助于C++操作底层代码的强大特性,我们可以很轻易的完成Hook操作.

参考链接

C++/CLI

托管C++、C++/CLI、CLR

managed 和 unmanaged pragma

C++/CLI实现inline hook .NET程序

AMSI 绕过之.Net API Hook


相关内容

0%