基于EasyHook实现监控explorer资源管理器文件复制、删除、剪切等操作
一、前言
最近自己在研究一个项目,需要实现对explorer资源管理器文件操作的监控功能,网上找到一些通过C++实现Hook explorer文件操作的方法,由于本人习惯用.NET开发程序,加之C/C++基础较差,所以一直在研究如何用.NET实现,花了一周多的时间,终于基本实现了通过C# Hook资源管理器文件操作的功能,这里给出一些核心的内容,供大家参考。
二、EasyHook
1.简介
EasyHook是一款功能强大的挂钩引擎,开源免费,支持64位,可通过.NET语言调用(如C#、VB.NET),相关使用方法和源码下载可参考官方网站:http://easyhook.github.io/
2.远程注入
我们这里需要远程注入explorer程序实现Hook资源管理器的功能,所以首先要了解一下EasyHook的远程注入(Remote Hooking)功能,官网有一篇远程文件监控的教程,可以参考:http://easyhook.github.io/tutorials/remotefilemonitor.html
简单总结一下:远程注入功能实现包括两个步骤,需要建立两个工程,一是创建一个injection payload,也就是创建需要注入到其他进程的类库(.dll)文件;二是创建一个主程序实现将第一步生成的dll注入到其他进程及监听功能。
步骤一主要用到两个类,ServerInterface和InjectionEntryPoint(类名可自定义),其中ServerInterface实现注入目标进程后的dll与主程序之间的通信功能,需要继承MarshalByRefObject类;InjectionEntryPoint实现本地Hook安装和Hook成功后调用用户自定义逻辑的功能,需要继承EasyHook.IEntryPoint接口,包括构造函数和Run方法,构造函数的Run方法的参数必须一致,这些参数可以在主程序远程注入时传入,参数个数不限,可根据具体需求自定义。比如我这里将主程序的进程ID和句柄作为参数:
//构造函数
public InjectionEntryPoint(EasyHook.RemoteHooking.IContext context, string channelName, int passPID, int passHandle)
{
_server = EasyHook.RemoteHooking.IpcConnectClient<ServerInterface>(channelName);
_server.Ping();
}
//Run函数
public void Run( EasyHook.RemoteHooking.IContext context,string channelName, int passPID, int passHandle)
{
//这里实现本地hook安装和hook成功后调用用户自定义函数的功能
}
步骤二是创建主程序,主要实现远程注入和监听显示的功能,注入时可根据步骤一的定义传入相关参数,核心代码如下:
// Create the IPC server using the FileMonitorIPC.ServiceInterface class as a singleton
EasyHook.RemoteHooking.IpcCreateServer<FileMonitorHook.ServerInterface>(ref channelName, System.Runtime.Remoting.WellKnownObjectMode.Singleton);
string injectionLibrary = Path.Combine(Path.GetDirectoryName(System.Reflection.Assembly.GetExecutingAssembly().Location), "FileMonitorHook.dll"); //FileMonitorHook.dll为步骤一生成的类库文件,该文件需要在主程序工程中引用 EasyHook.RemoteHooking.Inject( targetPID, // ID of process to inject into
injectionLibrary, // 32-bit library to inject (if target is 32-bit)
injectionLibrary, // 64-bit library to inject (if target is 64-bit)
channelName , // the parameters to pass into injected library
passPID ,
passHandle // 这两个步骤一中为自定义参数,根据需求可以再增加...
);
三、IFileOperation接口
WIN7及以上的系统的explorer实现文件操作直接跳过了API层,而是使用了COM来代替,这个COM接口就是 IFileOperation。
关于IFileOperation接口的详细说明可参考微软官方文档:https://docs.microsoft.com/zh-cn/windows/desktop/api/shobjidl_core/nn-shobjidl_core-ifileoperation ;
网上也有大神使用C++实现了Hook IFileOperation接口,可参考:https://blog.csdn.net/wzsy/article/details/17665311
实现文件操作的监控主要需要Hook IFileOperation接口的以下函数(C#表示):
复制: void CopyItems(
IntPtr punkItems,
IntPtr psiDestinationFolder);
删除: void DeleteItems(
IntPtr punkItems);
剪切: void MoveItems(
IntPtr punkItems,
IntPtr psiDestinationFolder);
经测试CopyItem、DeleteItem、MoveItem单数形式的无法实现hook,可能是explorer根本没有调用这些函数。
四、EasyHook实现COM接口的Hook
网上关于EasyHook的介绍以及官方教程都是以Hook API函数为例,对于COM接口Hook的介绍几乎没有,这也困扰了比较长的一段时间,后面在EasyHook github官方主页的提问区找到了一丝线索,加上自己的一些研究,算是基本掌握了实现COM接口hook的方法。
实现方法主要包括两个步骤:
步骤一为根据guid定义COM接口所属的类和接口本身,类ID和接口ID可通过具体名称在c++头文件中查看,名称格式如CLSID_IFileOperation、IID_IFileOperation。在C#中定义如下:
#region IFileOperation
[ComVisible(true)]
[Guid("3ad05575-8857-4850-9277-11b85bdb8e09")]
public class CLSID_IFileOperation
{
//接口所属类的内容可为空,仅方便后续通过EasyHook API确定接口位置
}
[ComImport]
[Guid("947aab5f-0a5c-4c13-b4d6-4bf7836fc9f8")]
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
public interface IID_IFileOperation
{
uint Advise(IntPtr pfops, IntPtr pdwCookie);
void Unadvise(uint dwCookie);
void SetOperationFlags(IntPtr dwOperationFlags);
void SetProgressMessage(
[MarshalAs(UnmanagedType.LPWStr)] string pszMessage);
void SetProgressDialog(
[MarshalAs(UnmanagedType.Interface)] object popd);
void SetProperties(
[MarshalAs(UnmanagedType.Interface)] object pproparray);
void SetOwnerWindow(uint hwndParent);
void ApplyPropertiesToItem(IntPtr psiItem);
void ApplyPropertiesToItems(
[MarshalAs(UnmanagedType.Interface)] object punkItems);
void RenameItem(IntPtr psiItem,
[MarshalAs(UnmanagedType.LPWStr)] string pszNewName,
IntPtr pfopsItem);
void RenameItems(
IntPtr pUnkItems,
[MarshalAs(UnmanagedType.LPWStr)] string pszNewName);
void MoveItem(
IntPtr psiItem,
IntPtr psiDestinationFolder,
[MarshalAs(UnmanagedType.LPWStr)] string pszNewName,
IntPtr pfopsItem);
void MoveItems(
IntPtr punkItems,
IntPtr psiDestinationFolder);
void CopyItem(
IntPtr psiItem,
IntPtr psiDestinationFolder,
[MarshalAs(UnmanagedType.LPWStr)] string pszCopyName,
IntPtr pfopsItem);
void CopyItems(
IntPtr punkItems,
IntPtr psiDestinationFolder);
void DeleteItem(
IntPtr psiItem,
IntPtr pfopsItem);
void DeleteItems(
IntPtr punkItems);
uint NewItem(
IntPtr psiDestinationFolder,
IntPtr dwFileAttributes,
[MarshalAs(UnmanagedType.LPWStr)] string pszName,
[MarshalAs(UnmanagedType.LPWStr)] string pszTemplateName,
IntPtr pfopsItem);
long PerformOperations();
[return: MarshalAs(UnmanagedType.Bool)]
bool GetAnyOperationsAborted();
}
#endregion
步骤二为通过EasyHook API 确定接口位置并安装钩子,以安装复制文件监控钩子为例,主要代码如下:
/**复制文件钩子**/
COMClassInfo copyItemscom = new COMClassInfo(typeof(CLSID_IFileOperation), typeof(IID_IFileOperation), "CopyItems"); //
copyItemscom.Query();
var CopyItemsHook = EasyHook.LocalHook.Create(copyItemscom.MethodPointers[0], new CopyItems_Delegate(CopyItemsHooked), this);
// 激活钩子
CopyItemsHook.ThreadACL.SetExclusiveACL(new Int32[] { 0 });
其中CopyItems_Delegate为文件复制的委托函数,可定义如下:
[UnmanagedFunctionPointer(CallingConvention.StdCall, CharSet = CharSet.Unicode, SetLastError = false)]
public delegate void CopyItems_Delegate(IID_IFileOperation self, IntPtr punkItems,
IntPtr psiDestinationFolder); //这里第一个参数需传入接口实例,为的是可以在替换的复制hook函数中调用原始的复制函数,达到不影响原始操作的目的。
CopyItemsHooked为成功Hook的替换执行的复制函数,其参数与文件复制委托函数一致,这个函数是实现文件操作监控的关键,下一部分将重点介绍。
五、文件操作Hook成功后替换函数的实现和解析
文件操作Hook成功后会执行我们替换的新函数,如前面介绍的CopyItemsHooked,在函数中可通过接口实例来调用原始函数来执行原始操作,其函数结构如下:
public void CopyItemsHooked(IID_IFileOperation self, IntPtr punkItems,IntPtr psiDestinationFolder)
{
self.CopyItems(punkItems, psiDestinationFolder); //执行原始操作,也可不调用这句来实现拦截文件操作的目的
}
我们要实现文件操作监控,就必须要对这个函数传入的参数进行解析,查看微软官方文档可以发现CopyItems的原型为:HRESULT CopyItems( IUnknown *punkItems, IShellItem *psiDestinationFolder );
其中punkItems为IUnknown接口类型,可能是IShellItemArray, IDataObject, IEnumShellItems或者IPersistIDList中的某一种,psiDestinationFolder为IShellItem接口类型。
那么我们能通过C#直接获取到其中的文件信息呢?恐怕不行,至少我不会。这里也困扰了一些时间,后面终于想到解决方案了:.NET直接实现不了解析,C++总可以吧,先用C++编写相关解析方法,然后生成类库dll,再用C#调用C++生成的类库不就可以了。
这里需要掌握使用C++编写C#可以调用的类库的知识,不了解的同学可以参考这篇文章:https://www.cnblogs.com/94cool/p/5772376.html
需要注意的是C++生成的dll区分32位和64位,可以创建两个不同平台(win32和X64)的dll文件,达到兼容的目的,如图所示,我代码里用到了Unicode编码,所以字符集配置为使用 Unicode 字符集。
话不多说,上代码。
获取IShellItem接口的文件信息函数:
LPWSTR __stdcall GetDestFloderName( IShellItem* psiDestinationIntptr)
{
IShellItem* psiDestinationFolder = (IShellItem*)(psiDestinationIntptr);
LPWSTR lpDst = NULL;
psiDestinationFolder->GetDisplayName(SIGDN_FILESYSPATH, &lpDst);
return lpDst;
}
获取复制、剪切文件IUnknown接口文件信息函数(经测试复制、剪切文件的IUnknown接口为IPersistIDList):
LPWSTR __stdcall GetSrcFloderName(IUnknown *iUnknown)
{
IPersistIDList *iPersistIDList = NULL;
HRESULT hr = iUnknown->QueryInterface(IID_IPersistIDList, (void **)&iPersistIDList); //通过iUnknown接口查找IPersistIDList接口
PIDLIST_ABSOLUTE abSourcePidl;
hr = iPersistIDList->GetIDList(&abSourcePidl);
TCHAR tchPath[255];
SHGetPathFromIDList(abSourcePidl, tchPath);
return tchPath;
}
DeleteItems的函数原型为:
HRESULT DeleteItems( IUnknown *punkItems );
另外删除、重命名文件的IUnknown接口也有所不同,经测试删除文件IUnknown接口为IDataObject、重命名文件IUnknown接口为IShellItemArray,实现方法都大同小异(IDataObject文件信息解析相对复杂一点,前文第三节链接教程中提供了相应解析函数),在此不一一列出。
附上解析类库的头文件:
#include "stdafx.h"
#include <Shlobj.h>
#include <string>
#include <shellapi.h>
using std::string;
using std::wstring;
typedef WCHAR WPATH[MAX_PATH];
extern "C" _declspec(dllexport) LPWSTR __stdcall GetDestFloderName( IShellItem* psiDestinationIntptr);
extern "C" _declspec(dllexport) LPWSTR __stdcall GetSrcFloderName( IUnknown* punkItems);
extern "C" _declspec(dllexport) LPWSTR __stdcall GetDeleteFileName( IUnknown * punkItems);
extern "C" _declspec(dllexport) LPWSTR __stdcall GetRenameSrcFloderName( IUnknown * punkItems)
C++类库生成成功后,如何在C#程序中调用呢?这里用到c# DllImport调用外部dll的功能(C++中的LPWSTR可直接对应C#中的sting),部分代码如下:
//调用64位dll
[DllImport("MyConvert64.dll", EntryPoint = "GetDestFloderName", CharSet = CharSet.Unicode)]
extern static string GetDestFloderName64(IntPtr psiIntptr);
[DllImport("MyConvert64.dll", EntryPoint = "GetSrcFloderName", CharSet = CharSet.Unicode)]
extern static IntPtr GetSrcFloderName64(IntPtr psiIntptr);
//调用32位dll
[DllImport("MyConvert32.dll", EntryPoint = "GetDestFloderName", CharSet = CharSet.Unicode)]
extern static string GetDestFloderName32(IntPtr psiIntptr);
[DllImport("MyConvert32.dll", EntryPoint = "GetSrcFloderName", CharSet = CharSet.Unicode)]
extern static IntPtr GetSrcFloderName32(IntPtr psiIntptr);
这里的函数名称可自定义,只需保证EntryPoint项的函数名称与dll中函数一致即可。那么如何让程序根据系统位数自动选择不同的函数呢?可以这样实现:
public static string GetDestFloderName(IntPtr psiIntptr)
{
return Environment.Is64BitOperatingSystem ? GetDestFloderName64(psiIntptr) : GetDestFloderName32(psiIntptr);
}
public static IntPtr GetSrcFloderName(IntPtr psiIntptr)
{
return Environment.Is64BitOperatingSystem ? GetSrcFloderName64(psiIntptr) : GetSrcFloderName32(psiIntptr);
}
这样我们就可以在hook成功后的替换函数中使用C++编写的解析函数了,以复制文件hook函数为例:
public void CopyItemsHooked(IID_IFileOperation self, IntPtr punkItems,
IntPtr psiDestinationFolder)
{
try
{
string filename = GetDestFloderName(psiDestinationFolder); //调用解析函数获取目标文件夹信息
IntPtr srcintptr = GetSrcFloderName(punkItems); //这里获取源文件返回的是一个指针
string ss = Marshal.PtrToStringUni(srcintptr); //可将指针转换为string信息,注意是Unicode编码
operationtag = "复制文件-messagesplit-" + ss + "-messagesplit-" + filename;
self.CopyItems(punkItems, psiDestinationFolder); //执行原始操作
_server.ReportMessage(selfhandle, "开始-messagesplit-" + operationtag); //向主程序传递复制文件操作信息
}
catch (Exception ee)
{
_server.ReportMessage(selfhandle, ee.ToString()); //异常处理
}
}
六、文件操作Hook效果
完成Injection payload(也就是远程注入的dll)的创建后,根据上文介绍创建一个主程序来实现远程注入和监听的功能,explorer的进程ID可通过以下代码获取: Process.GetProcessesByName("explorer")[0].Id;远程注入操作见第二节或参考EasyHook官方文档。运行主程序完成远程注入后即可在实现对explorer文件操作的监控,效果如下图所示。
七、结语
EasyHook确实让Hook变得Easy了,给了我很大的帮助,在此感谢开发团队的无私奉献。以上代码为我实际项目中部分内容,表意为先,不一定能直接使用,写这篇文章的目的是为了让读者加深对EasyHook的理解,希望能有所帮助。
原文链接:https://blog.csdn.net/weixin_42011520/article/details/84193237
基于EasyHook实现监控explorer资源管理器文件复制、删除、剪切等操作的更多相关文章
- Vs 控件错位 右侧资源管理器文件夹点击也不管用,显示异常
问题:显卡驱动异常. 缘由:驱动精灵万能显卡安装系统 解决方案:根据笔记本型号去官网下载适配显卡驱动.
- Clover 3 --- Windows Explorer 资源管理器的一个扩展,为其增加类似谷歌 Chrome 浏览器的多标签页功能。
http://cn.ejie.me/ http://cn.ejie.me/uploads/setup_clover@3.4.6.exe 软件下载 默认图标实在比较难看,更换图标 更改图标---选择图 ...
- Java文件复制删除操作合集
import java.io.*; public class FileOperate { public FileOperate() { } /** * 新建目录 * @param folderPath ...
- VC++ MFC文件的移动复制删除更名遍历操作
1.判断文件是否存在 利用CFile类和CFileStatus类判断 CFileStatus filestatus; if (CFile::GetStatus(_T("d://softist ...
- windows资源管理器多标签打开 windows文件夹多标签浏览 浏览器tab页面一样浏览文件夹 clover win8 win10 报错 无响应问题怎么解决 clover卡死 clover怎么换皮肤
大家都知道,我们打开一堆文件夹的时候,是什么样子 “厚厚的一叠”图标堆叠在一起的,非常的不方便 那么,是不是可以像浏览器一样的tab页面展示呢? 答案是可以的 安装好就是这样子的 是不是方便漂亮了很多 ...
- Clover(资源管理器增强)
Clover(资源管理器增强) 下载地址:http://www.orsoon.com/Soft/13157.html 功能: Windows Explorer 资源管理器的一个扩展,为其增加类似谷歌 ...
- [转]C#中调用资源管理器(Explorer.exe)打开指定文件夹 + 并选中指定文件 + 调用(系统默认的播放类)软件(如WMP)打开(播放歌曲等)文件
原文:http://www.crifan.com/csharp_call_explorer_to_open_destinate_folder_and_select_specific_file/ C#中 ...
- The Quad - Directory Explorer(一款四窗口的文件资源管理器)
官网:http://www.q-dir.com/ 参考这位兄弟的介绍:https://www.cnblogs.com/clso/p/4694486.html 一款四窗口的文件资源管理器.
- 20160223 - Windows 10 的文件资源管理器下出现重复文件夹的解决办法
现象: 安装 OneDrive 从 Windows 7.8.8.1 升级来的 Windows 10 的电脑,可能会出现文件资源管理器左侧面板中出现重复的文件夹. 通常有:视频.图片.文档.下载.音频. ...
随机推荐
- eclipse经常弹出Subversion Native Library Not Available解决方案
- layui ri laydate的常规使用,并且日期最大不能超过当前日期
laydate的常规使用,分为两种方式实现日期组件 一.在 layui 模块中使用 下载layui 地址 :https://www.layui.com/ 引入资源路径 js 和 css 通过下面 ...
- mysql 关于log_bin_trust_function_creators变量
在mysql创建自定义函数的时候,有时候会报以下错误: Error Code: 1418. This function has none of DETERMINISTIC, NO SQL, or RE ...
- 从零开始学MySQL(三)
经过上两节的洗礼,我们能够连接上服务器,并成功地进入与mysql交互的会话中了.那么现在就可以发起SQL语句,让服务器来执行它了!这听起来很酷吧?接下来,我们开始学习MySQL的相关知识. 本文概览: ...
- JAVA中如何定义自定义注解
了解注解 注解是Java1.5,JDK5.0引用的技术,与类,接口,枚举处于同一层次 .它可以声明在包.类.字段.方法.局部变量.方法参数等的前面,用来对这些元素进行说明,注释 . 在Java中,自带 ...
- java中将jsonObject字符串转化为Map对象
java中将jsonObject字符串转化为Map对象 1.我们这里使用json-lib包进行转换,可在http://json-lib.sourceforge.net/下载依赖于下面的jar包: ja ...
- httprunner如何提取数据串联上下游接口
httprunner进行接口测试时,从上一个接口提取参数传递给下游接口,如何获取数据里最后一个值? 突然被学员问道一个httprunner的问题,惭愧的是大猫之前没有是通过httprunner,又不好 ...
- 牛客假日团队赛5 L Catch That Cow HDU 2717 (BFS)
链接:https://ac.nowcoder.com/acm/contest/984/L 来源:牛客网 Catch That Cow 时间限制:C/C++ 1秒,其他语言2秒 空间限制:C/C++ 3 ...
- php判断变量是否为数字is_numeric()
is_numeric — 检测变量是否为数字或数字字符 <?php $tests = array( "31", 1380, "1e4", "no ...
- Vue基础第二章
1.数据绑定与数据声明 Vue中的数据绑定就是让与Vue实例绑定的DOM节点或script标签内的变量之间数据更新互相影响,即数据绑定后Vue实例的数据修改会使DOM节点的数据或者script标签内的 ...