zoukankan      html  css  js  c++  java
  • Win7、win8、win10下实现精准截获Explorer拷贝行为

    介绍了windows下对Explorer的拷贝动作的精确截获,这个在企业数据安全dlp产品系列中减少审计的噪音很有效,方便运营人员做针对性的审计。

    在企业数据安全中我通常需要监测用户的拷贝行为,特别像explorer这样的进程,方法很多比如文件过滤驱动监测文件的打开与读写,但是这样会有很多噪音产生,实现的不好的话也可能会造成用户在桌面操作感受不良好,比如卡,所以我们需要的是一种更精准地方法,下面我们就来分析下如何去更加精准的定位拷贝。

    一、            分析

    我们都知道windows软件读写的方法很多比如c库一些读写函数,fopen 、fread、fwrite,c++流函数std::stream,但是都会进入windows的底层读写,还有一类文件操作函数就是Shell函数SHFileOperation,不管什么函数最后都会调用windows自己常用的文件操作函数就是CreateFileA、CreateFileW、ReadFile、WriteFile,我们可以使用调试器去在CreateFileW函数设置断点,但是这个一个问题我们必须在explorer进程空间里设断点,但是会阻碍正常的桌面UI操作,为了方便我们操作桌面,写一个文件过滤驱动,去过滤我们需要的信息,比如我们把一个.txt文件从目录A拷贝到目录B,我们可以在过滤驱动的IRP_MJ_CREATE的例程函数里这样写

    CErrorStatus  PreCreate( CIrp& Irp, zipmbool& IsIrpCompleted)
    {
        CFileObject FileObject;
        CErrorStatus Error = STATUS_SUCCESS;
        Irp.UseCurrentStackLocation();
        CIoStackLocation Stack = Irp.StackLocation();
    
        do 
        {
            if( wcsstr(FileObject.FileName()->Buffer,L".txt"))
            {
                if (FileObject.RelatedFileObject())
                {
                    
                }           
            }       
        } while (FALSE);
        
        return Error;
    }

    这样过滤会减少很多干扰,利用windbg的虚拟机的双机器调试。

    加载驱动后,我们可以设置断点在if ( FileObject.RelatedFileObject()),这行代码上。

    然后拷贝一个txt的文件从A到B,如下图

     这里windbg调试器就会停下,进入刚才设置的断点位置。

    输入命令kb

    发现堆栈是从user层到内核层在到本驱动的PreCreate函数,这时user层显示的只是地址,是因为符号没加载,继续输入.reload  /user,慢慢就显示了

    引起注意的是红色框显示的堆栈:

    Nt!NtCreateFile

    KernelBase!CreateFile

    Kernel32!CreateFileWImplementation

    Shell32!CFSTransfer:_OpenSrcFileWithRetry

    Shell32! CFSTransfer::OpenItem

    Shell32!CDelegatingTansfer:: OpenItem

    Shell32! CCopyOperation::Do

    Shell32! CCopyWorkItem::_DoOperation

    Shell32! CCopyWorkItem::_SetupAndPerformOp

    Shell32! CCopyWorkItem::ProcessWorkItem

    Shell32!CRecursiveFolderoperation::Do

    Shell32!CFileOperation::_EnumRootDo

    Shell32!CFileOperation::PrepareAndDoOperation

    Shell32!CFileOperation::PreformOperation

    Shell32!SHFileOperationEx

    由上可以看出最主要的是调用了SHFileOperationEx函数,这好说了,使用IDA打开shell32.dll分析这个函数

    IDA显示这个函数调用了SHCreateFileOperation

    继续跟进SHCreateFileOperation

    它创建的是GUID_947aab5f_0a5c_4c13_b4d6_4bf7836fc9f8这个类的实例,隶属于FileOperaton, 写过com的人都知道947aab5f_0a5c_4c13_b4d6_4bf7836fc9f8这个实例id就是com中的 IFileOperation的com库接口,查看windows的sdk定义如下

    MIDL_INTERFACE("947aab5f-0a5c-4c13-b4d6-4bf7836fc9f8")
        IFileOperation : public IUnknown
        {
        public:
            virtual HRESULT STDMETHODCALLTYPE Advise( 
                 __RPC__in_opt IFileOperationProgressSink *pfops,
                __RPC__out DWORD *pdwCookie) = 0;
            
            virtual HRESULT STDMETHODCALLTYPE Unadvise( 
                DWORD dwCookie) = 0;
            
            virtual HRESULT STDMETHODCALLTYPE SetOperationFlags( 
                 DWORD dwOperationFlags) = 0;
            
            virtual HRESULT STDMETHODCALLTYPE SetProgressMessage( 
                 __RPC__in_string LPCWSTR pszMessage) = 0;
            
            virtual HRESULT STDMETHODCALLTYPE SetProgressDialog( 
                 __RPC__in_opt IOperationsProgressDialog *popd) = 0;
            
            virtual HRESULT STDMETHODCALLTYPE SetProperties( 
                 __RPC__in_opt IPropertyChangeArray *pproparray) = 0;
            
            virtual HRESULT STDMETHODCALLTYPE SetOwnerWindow( 
                __RPC__in HWND hwndOwner) = 0;
            
            virtual HRESULT STDMETHODCALLTYPE ApplyPropertiesToItem( 
                __RPC__in_opt IShellItem *psiItem) = 0;
            
            virtual HRESULT STDMETHODCALLTYPE ApplyPropertiesToItems( 
                 __RPC__in_opt IUnknown *punkItems) = 0;
            
            virtual HRESULT STDMETHODCALLTYPE RenameItem( 
                 __RPC__in_opt IShellItem *psiItem,
                 __RPC__in_string LPCWSTR pszNewName,
                __RPC__in_opt IFileOperationProgressSink *pfopsItem) = 0;
            
            virtual HRESULT STDMETHODCALLTYPE RenameItems( 
                 __RPC__in_opt IUnknown *pUnkItems,
                 __RPC__in_string LPCWSTR pszNewName) = 0;
            
            virtual HRESULT STDMETHODCALLTYPE MoveItem( 
                __RPC__in_opt IShellItem *psiItem,
                 __RPC__in_opt IShellItem *psiDestinationFolder,
                 __RPC__in_opt_string LPCWSTR pszNewName,
                __RPC__in_opt IFileOperationProgressSink *pfopsItem) = 0;
            
            virtual HRESULT STDMETHODCALLTYPE MoveItems( 
                __RPC__in_opt IUnknown *punkItems,
                __RPC__in_opt IShellItem *psiDestinationFolder) = 0;
            
            virtual HRESULT STDMETHODCALLTYPE CopyItem( 
                 __RPC__in_opt IShellItem *psiItem,
                 __RPC__in_opt IShellItem *psiDestinationFolder,
                 __RPC__in_opt_string LPCWSTR pszCopyName,
                 __RPC__in_opt IFileOperationProgressSink *pfopsItem) = 0;
            
            virtual HRESULT STDMETHODCALLTYPE CopyItems( 
                 __RPC__in_opt IUnknown *punkItems,
                 __RPC__in_opt IShellItem *psiDestinationFolder) = 0;
            
            virtual HRESULT STDMETHODCALLTYPE DeleteItem( 
                __RPC__in_opt IShellItem *psiItem,
                __RPC__in_opt IFileOperationProgressSink *pfopsItem) = 0;
            
            virtual HRESULT STDMETHODCALLTYPE DeleteItems( 
                 __RPC__in_opt IUnknown *punkItems) = 0;
            
            virtual HRESULT STDMETHODCALLTYPE NewItem( 
                 __RPC__in_opt IShellItem *psiDestinationFolder,
                 DWORD dwFileAttributes,
                 __RPC__in_opt_string LPCWSTR pszName,
                 __RPC__in_opt_string LPCWSTR pszTemplateName,
                 __RPC__in_opt IFileOperationProgressSink *pfopsItem) = 0;
            
            virtual HRESULT STDMETHODCALLTYPE PerformOperations( void) = 0;
            
            virtual HRESULT STDMETHODCALLTYPE GetAnyOperationsAborted( 
                 __RPC__out BOOL *pfAnyOperationsAborted) = 0;
            
        };

    我们回到之前那个Ex函数,创建完IFileOperation的实例后,就开始调用里面的函数

    分别调用了

    Call dword ptr[esi+0x20h]

    Call dword pre[esi+0x2Ch] 或者Call  dword pre[esi+0x4Ch]

    Call dword pre[esi+0x44h] 或者 Call dword pre[esi+0x3Ch]

    其他函数都是设置Explorer的拷贝Item的属性的函数,+0×44这个函数对应的是IFileOperation::Copy Items,而+0x3C对应的函数是IFileOperation::Move tems,而我们这次的拷贝动作调用的就是IFileOperation::CopyItems,下面我们有方案了我们可以hook这个IFileOpertion的接口库实现精准截获桌面的拷贝动作。

    一、            实现

    CopyItems为例子

     定义一个CFileOperation

    #define QueryInterface_Index 0
    #define AddRef_Index (QueryInterface_Index + 1)
    #define Release_Index (AddRef_Index + 1)
    #define Advice_Index (Release_Index + 1)
    #define Unadvise_Index (Advice_Index + 1)
    #define SetOperationFlags_Index (Unadvise_Index + 1)
    #define SetProgressMessage_Index (SetOperationFlags_Index + 1)
    #define SetProgressDialog_Index (SetProgressMessage_Index + 1)
    #define SetProperties_Index (SetProgressDialog_Index + 1)
    #define SetOwnerWindow_Index (SetProperties_Index + 1)
    #define ApplyPropertiesToItem_Index (SetOwnerWindow_Index + 1)
    #define ApplyPropertiesToItems_Index (ApplyPropertiesToItem_Index + 1)
    #define RenameItem_Index (ApplyPropertiesToItems_Index + 1)
    #define RenameItems_Index (RenameItem_Index + 1)
    #define MoveItem_Index (RenameItems_Index + 1)
    #define MoveItems_Index (MoveItem_Index + 1)
    #define CopyItem_Index (MoveItems_Index + 1)
    #define CopyItems_Index (CopyItem_Index + 1)
    #define DeleteItem_Index (CopyItems_Index + 1)
    #define DeleteItems_Index (DeleteItem_Index + 1)
    #define NewItem_Index (DeleteItems_Index + 1)
    #define PerformOperations_Index (NewItem_Index + 1)
    #define GetAnyOperationAborted_Index (PerformOperations_Index + 1)
    #define HOOK(a, b) b##_old = (P##b)HookVtbl(a, 0, b##_Index,
    (PBYTE)b##_new)
    
    
    class CFileOperation
    {
    
    public:
        CFileOperation(void);
        ~CFileOperation(void);
    
        int HookVtbl(void* pObject, unsigned int classIdx, unsigned int     methodIdx, int newMethod);
    
        static HRESULT __stdcall CopyItems_new(IFileOperation *pThis,
    
        IUnknown *punkItems,
    
        IShellItem *psiDestinationFolder);
    
        BOOL Init();
    };

    在初始化的时候我们需要获取接口并却修改截获CopyItems和MoveItems的指针接口

    BOOL CFileOperation::Init()
    {
        IFileOperation* Pf = NULL;
        CoInitialize( NULL );
    
        do 
        {
            HRESULT hr = CoCreateInstance(
    
                CLSID_FileOperation,
                NULL,
                CLSCTX_ALL,
                IID_IFileOperation,
                (PVOID*)&Pf);
                HOOK(Pf, CopyItems);
    
        } while (FALSE);
    }
    
    
    int CFileOperation::HookVtbl(void* pObject, unsigned int classIdx, unsigned int methodIdx, int newMethod)
    {
    
        int** vtbl = (int**)pObject;
        DWORD oldProtect = 0;
        int oldMethod = vtbl[classIdx][methodIdx];
    
        VirtualProtect(vtbl[classIdx] + sizeof(int*) * methodIdx, sizeof(int*), PAGE_READWRITE, &oldProtect);
    
        vtbl[classIdx][methodIdx] = newMethod;
    
        VirtualProtect(vtbl[classIdx] + sizeof(int*) * methodIdx, sizeof(int*), oldProtect, &oldProtect);
    
        return oldMethod;
    }
    
    HRESULT CFileOperation::CopyItems_new(IFileOperation *pThis,
        IUnknown *punkItems,
        IShellItem *psiDestinationFolder)
    {
        HRESULT hr = CopyItems_old(pThis, punkItems, psiDestinationFolder);
        return hr;
    }

    这里生成了dll后我们需要注入到桌面进程中,这样就可以实现截获CopyItems接口,注意这里只是完成了第一步,这个接口传进来的参数只是一个Item内存结构,我们需要获取具体的数据,下面继续。

     从上面分析我们得知winvista以后的桌面拷贝操作最终使用的是IFileOperation接口,在shell32.dll里对应的就是CFileOperation类

     CopyItems对应的就是

    继续进入

    CFileOperation::_AddOperationMulti

     可以看到首先判断DestFile这个参数是否是Folder文件夹,如果是文件夹就是开始枚举ShellItem这个参数,我们继续进入EnumShellItemsFromUnknown函数

    从上面我们看出该函数会QueryInterface各个接口:

    _GUID_70629033_e363_4a28_a567_0db78006e6d7

    _GUID_b63ea76d_1f85_456f_a19c_48159efa858b

    _GUID_d0191542_7954_4908_bc06_b2360bbe45ba

    _GUID_0000010e_0000_0000_c000_000000000046

    如果以上接口都不存在的话,就调用

    SHGetIDListFromObjectSHCreateShellItemArrayFromIDLists来获取接口,

    在实际调用中发现以上几个不是所有操作系统都支持,而最后两个函数却支持winvista以后的所有系统,所以我们就用最后两个函数来获取源信息,而这两个函数在shell32.dll都是导出的接口定义如下:

    HRESULT SHCreateShellItemArrayFromIDLists(

                                    _In_  UINT                   cidl,

                                  _In_  PCIDLIST_ABSOLUTE_ARRAY rgpidl,

                                 _Out_ IShellItemArray        **ppsiItemArray

    );

    最后获取的是IShellItemArray接口,接口定义为:

    MIDL_INTERFACE("b63ea76d-1f85-456f-a19c-48159efa858b")
    
        IShellItemArray : public IUnknown
        {
    
        public:
    
            virtual HRESULT STDMETHODCALLTYPE BindToHandler( 
                /* [unique][in] */ __RPC__in_opt IBindCtx *pbc,
                /* [in] */ __RPC__in REFGUID bhid,
                /* [in] */ __RPC__in REFIID riid,
                /* [iid_is][out] */ __RPC__deref_out_opt void **ppvOut) = 0;
    
            virtual HRESULT STDMETHODCALLTYPE GetPropertyStore( 
                /* [in] */ GETPROPERTYSTOREFLAGS flags,
                /* [in] */ __RPC__in REFIID riid,
                /* [iid_is][out] */ __RPC__deref_out_opt void **ppv) = 0;
          
            virtual HRESULT STDMETHODCALLTYPE GetPropertyDescriptionList( 
                /* [in] */ __RPC__in REFPROPERTYKEY keyType,
                /* [in] */ __RPC__in REFIID riid,
                /* [iid_is][out] */ __RPC__deref_out_opt void **ppv) = 0;
    
            virtual HRESULT STDMETHODCALLTYPE GetAttributes( 
                /* [in] */ SIATTRIBFLAGS AttribFlags,
                /* [in] */ SFGAOF sfgaoMask,
                /* [out] */ __RPC__out SFGAOF *psfgaoAttribs) = 0;
    
            virtual HRESULT STDMETHODCALLTYPE GetCount( 
                /* [out] */ __RPC__out DWORD *pdwNumItems) = 0;
    
            virtual HRESULT STDMETHODCALLTYPE GetItemAt( 
                /* [in] */ DWORD dwIndex,
                /* [out] */ __RPC__deref_out_opt IShellItem **ppsi) = 0;
     
            virtual HRESULT STDMETHODCALLTYPE EnumItems( 
                /* [out] */ __RPC__deref_out_opt IEnumShellItems **ppenumShellItems) = 0;
    };

    大致我们可以写出获取信息的文件的函数

    UINT 
    GetFilesFromDataObjectWin(
        IUnknown *iUnknown,
        LPWSTR **ppPath)
    {
        HRESULT hr = E_FAIL;
        LPITEMIDLIST Pv = NULL;
        int nCount = 0;
        IShellItemArray* ShellItemArray = NULL;
        do 
        {
    
            __try
            {
                hr = SHGetIDListFromObject(iUnknown,&Pv);
                if ( FAILED(hr) )
                {
                    break;
                }
    
                hr = SHCreateShellItemArrayFromIDLists(
                    TRUE,
                    (LPCITEMIDLIST *)&Pv,
                    &ShellItemArray);
    
                if ( FAILED(hr) )
                {
                    break;
                }
                
                hr = ShellItemArray->GetCount((ULONG*)&nCount);
    
                if ( !nCount )
                {
                    break;
                }
    
                *ppPath = new LPWSTR[nCount];
                memset(*ppPath,0,sizeof(LPWSTR)*nCount);
            
                for ( int Index = 0 ; Index < nCount ; Index++)
                {
                    IShellItem* ShellItem = NULL;
    
                    if( SUCCEEDED(ShellItemArray->GetItemAt( 
                                                        Index,
                                                        &ShellItem)))
                    {
                        if ( ShellItem /*&& ShellItem->GetAttributes(
                                                            0x20000000,
                                                            &GAof ) == 0 */)
                        {
                            LPWSTR Temp = NULL;
                            if(SUCCEEDED(ShellItem->GetDisplayName(
                                                SIGDN_FILESYSPATH,
                                                &Temp)))
                            {
                                __try
                                {
                                    if ( Temp )
                                    {
                                        int Length = wcslen(Temp);
                                        *ppPath[Index] = new WCHAR[Length + 1];
                                        memset(*ppPath[Index],0,sizeof(WCHAR)*(Length + 1));
                                        wcsncpy( 
                                            *ppPath[Index],
                                            Temp,
                                            Length);
                                        CoTaskMemFree(Temp);
                                        Temp = NULL;
    
                                    }
                                }
                                __except(EXCEPTION_EXECUTE_HANDLER)
                                {
                                    if ( Temp )
                                    {
                                        CoTaskMemFree(Temp);
                                        Temp = NULL;
                                    }
                                }
                                
                                
                            }
                        }
    
    
                        if ( ShellItem )
                        {
                            ShellItem->Release();
                        }
                        
                    }
                }
            }
            __finally
            {
    
                if ( ShellItemArray )
                {
                    ShellItemArray->Release();
                }
    
                if ( Pv)
                {
                    CoTaskMemFree(Pv);
                }
            }
        
        } while (FALSE);
    
        return nCount;
    }

    以上是获取源文件的信息,而拷贝目的的信息获取就很简单了,CopyItem的定义的目的中的参数

    IShellItem *psiDestinationFolder,中的IShellItem 定义如下

    MIDL_INTERFACE("43826d1e-e718-42ee-bc55-a1e261c37bfe")
    
        IShellItem : public IUnknown
        {
    
        public:
    
            virtual HRESULT STDMETHODCALLTYPE BindToHandler( 
                /* [unique][in] */ __RPC__in_opt IBindCtx *pbc,
                /* [in] */ __RPC__in REFGUID bhid,
                /* [in] */ __RPC__in REFIID riid,
                /* [iid_is][out] */ __RPC__deref_out_opt void **ppv) = 0;
            
            virtual HRESULT STDMETHODCALLTYPE GetParent( 
                /* [out] */ __RPC__deref_out_opt IShellItem **ppsi) = 0;
            
            virtual HRESULT STDMETHODCALLTYPE GetDisplayName( 
                /* [in] */ SIGDN sigdnName,
                /* [string][out] */ __RPC__deref_out_opt_string LPWSTR *ppszName) = 0;
            
            virtual HRESULT STDMETHODCALLTYPE GetAttributes( 
                /* [in] */ SFGAOF sfgaoMask,
                /* [out] */ __RPC__out SFGAOF *psfgaoAttribs) = 0;
            
            virtual HRESULT STDMETHODCALLTYPE Compare( 
                /* [in] */ __RPC__in_opt IShellItem *psi,
                /* [in] */ SICHINTF hint,
                /* [out] */ __RPC__out int *piOrder) = 0;        
    
    };

    根据定义我们就可以通过GetDisplayName去获取目的文件的信息:

    BOOL
    GetDestFolder(IShellItem* psiDestinationFolder, LPWSTR* lpDst )
    {
        do 
        {
            HRESULT hr = psiDestinationFolder->GetDisplayName(
                SIGDN_FILESYSPATH,
                lpDst);
    
            if(FAILED(hr))
            {
                break;
            }
    
            return TRUE;
    
        } while (FALSE);
    
    
        return FALSE;    
    }

    综合起来,我们可以实现如下:

    HRESULT CFileOperation::CopyItems_new(IFileOperation *pThis, IUnknown *punkItems, IShellItem *psiDestinationFolder)
    {
        HRESULT hr = CopyItems_old(pThis, punkItems, psiDestinationFolder);
    
        LPWSTR lpDst = NULL;
        PWSTR* lpSrc = NULL;
    
        //获取目的信息
        GetDestFolder(psiDestinationFolder, &lpDst) );
        //获取源文件信息        
        GetFilesFromDataObjectWin(punkItems, &lpSrc);
        return hr;
    }

    至此我们就很就精准地截获了windows桌面程序的拷贝、剪切的具体动作,也获取详细的文件名称,我们甚至还能准确地得知该动作是否成功,更加详细的信息就留给读者去研究,总之shell32.dll是个很值得开发人员去研究的,里面有很多意想不到的东西,甚至能写出很简单的程序实现很复杂有效的功能,可以充分利用windows系统给我们提供的便捷的库。

    引用

    Win7、win8、win10下实现精准截获Explorer拷贝行为 (freebuf)

  • 相关阅读:
    信号量整理
    Linux下串口配置初步探寻
    Vmware启动ubuntu 出现错误。
    软件工程结课作业
    第四次博客作业-结对项目
    20194731 软件工程第三次作业——博文软件质量保障初探
    20194731 第一次作业-博客初体验
    20194731 自动生成四则运算题第一版报告
    误删除 mySQL 用户解决办法
    mySQL的安装过程---二进制和源码安装
  • 原文地址:https://www.cnblogs.com/code1992/p/11642370.html
Copyright © 2011-2022 走看看