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

已经发表于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;


    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的虚拟机的双机器调试。

QQ图片20170420180431.png

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

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

QQ图片20170421150212.png

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

QQ图片20170420112954.png

输入命令kb

QQ图片20170420113051.png

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

QQ图片20170420124353.png

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

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

QQ图片20170421153422.png

继续跟进SHCreateFileOperation

QQ图片20170509143839.png

它创建的是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类

QQ图片20170509133901.png

CopyItems对应的就是

QQ图片20170509144331.png

继续进入

CFileOperation::_AddOperationMulti

QQ图片20170509144530.png

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

QQ图片20170509135140.png

QQ图片20170509135212.png

从上面我们看出该函数会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系统给我们提供的便捷的库。


评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值