Win7、win8、win10下实现精准截获Explorer拷贝行为
来源:互联网 发布:mysql备份数据库 编辑:程序博客网 时间:2024/06/06 17:00
已经发表于freebuf ( http://www.freebuf.com/column/134192.html)
在企业数据安全中我通常需要监测用户的拷贝行为,特别像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; CErrorStatusError = STATUS_SUCCESS; Irp.UseCurrentStackLocation(); CIoStackLocationStack = 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);staticHRESULT __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
如果以上接口都不存在的话,就调用
SHGetIDListFromObject、SHCreateShellItemArrayFromIDLists来获取接口,
在实际调用中发现以上几个不是所有操作系统都支持,而最后两个函数却支持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去获取目的文件的信息:
BOOLGetDestFolder( 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拷贝行为
- win7/win8/win10
- Win7/Win8/Win10下安装Ubuntu14.04双系统 以及常见问题
- win7/WIN8.1(x64) 下使用MSDE WIN10不行
- win7,win8,win10下配置 Java 环境变量(系统变量)
- win7,win8,win10环境下如何使用dnw!
- 禁止win7/win8自动更新到win10
- Win7,Win8,Win10 UAC绕行方法
- win8实现win7开始菜单
- win7或win8系统下安装win10 9926 双系统图文教程
- MSI格式的安装程序,在Win7、Win8、Win10下无法安装的解决办法
- win7、win8、win10下利用计算机命令对电脑进行定时关机
- Win8下HOOK explorer的CreateProcessW函数
- Win7环境下,用VHD安装Win8,实现双系统!
- win7或win8、win8.1系统下安装ubuntu实现双系统图文教程
- xp下安装win7/win8
- win10、win8和win7下解决php5.3和5.4、5.5等不能加载php_curl.dll的终极解决办法
- win10、win8和win7下解决php5.3和5.4、5.5等不能加载php_curl.dll的终极解决
- boost库之tcp实例(同步方式)
- 有关线程中断和线程阻塞
- C# Linq
- SQL Server 批量转换指定字段类型为另一种类型
- JQuery 学习笔记-2017.05.22
- Win7、win8、win10下实现精准截获Explorer拷贝行为
- 校园二手物品交易系统
- 杭电1042
- python 处理xml文件
- 单点登录原理与简单实现
- Swift--05可选类型
- 2017CCPC湘潭A题Determinant
- Spring对注解(Annotation)处理源码分析2——解析和注入注解配置的资源
- 用这个,3Glasses就能玩Oculus 平台游戏了