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);//④HookAddHook(&(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公众号
聚焦高级攻防对抗热点技术
绿盟科技蓝军技术研究战队
官方攻防交流群
网络安全一手资讯
攻防技术答疑解惑
扫码加好友即可拉群