zoukankan      html  css  js  c++  java
  • 基于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

  • 相关阅读:
    sqlserver中判断表或临时表是否存在
    Delphi 简单方法搜索定位TreeView项
    hdu 2010 水仙花数
    hdu 1061 Rightmost Digit
    hdu 2041 超级楼梯
    hdu 2012 素数判定
    hdu 1425 sort
    hdu 1071 The area
    hdu 1005 Number Sequence
    hdu 1021 Fibonacci Again
  • 原文地址:https://www.cnblogs.com/code1992/p/11374884.html
Copyright © 2011-2022 走看看