.NET程序hook的另一种优雅实现——C++/CLI
2023-5-8 18:2:49 Author: M01N Team(查看原文) 阅读量:27 收藏

01 概述

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

  • 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程序函数编译地址的确定。

02 目标程序

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

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代码的编写。

03 Hook代码

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

2. 修改编译选项

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

3. Dll的Hook代码如下:

#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函数为其提供具体的类型参数

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

04 总结

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

附录 参考链接

[1]https://zh.wikipedia.org/wiki/C%2B%2B/CLI

[2]https://cloud.tencent.com/developer/article/1120293

[3]https://learn.microsoft.com/zh-cn/cpp/preprocessor/managed-unmanaged?view=msvc-170

[4]https://blog.csdn.net/xfgryujk/article/details/79053312

[5]https://mp.weixin.qq.com/s/zon4XQ_c240ujtjLPcLZng

绿盟科技天元实验室专注于新型实战化攻防对抗技术研究。

研究目标包括:漏洞利用技术、防御绕过技术、攻击隐匿技术、攻击持久化技术等蓝军技术,以及攻击技战术、攻击框架的研究。涵盖Web安全、终端安全、AD安全、云安全等多个技术领域的攻击技术研究,以及工业互联网、车联网等业务场景的攻击技术研究。通过研究攻击对抗技术,从攻击视角提供识别风险的方法和手段,为威胁对抗提供决策支撑。

M01N Team公众号

聚焦高级攻防对抗热点技术

绿盟科技蓝军技术研究战队

官方攻防交流群

网络安全一手资讯

攻防技术答疑解惑

扫码加好友即可拉群


文章来源: http://mp.weixin.qq.com/s?__biz=MzkyMTI0NjA3OA==&mid=2247491354&idx=1&sn=bf4ff588f34f772f17524b8a1b055852&chksm=c187df0bf6f0561ddb94314b367f9938439a0aeb978e5bb74d678335099e5022b15369a8d88d#rd
如有侵权请联系:admin#unsafe.sh