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) = ; virtual HRESULT STDMETHODCALLTYPE Unadvise(
DWORD dwCookie) = ; virtual HRESULT STDMETHODCALLTYPE SetOperationFlags(
DWORD dwOperationFlags) = ; virtual HRESULT STDMETHODCALLTYPE SetProgressMessage(
__RPC__in_string LPCWSTR pszMessage) = ; virtual HRESULT STDMETHODCALLTYPE SetProgressDialog(
__RPC__in_opt IOperationsProgressDialog *popd) = ; virtual HRESULT STDMETHODCALLTYPE SetProperties(
__RPC__in_opt IPropertyChangeArray *pproparray) = ; virtual HRESULT STDMETHODCALLTYPE SetOwnerWindow(
__RPC__in HWND hwndOwner) = ; virtual HRESULT STDMETHODCALLTYPE ApplyPropertiesToItem(
__RPC__in_opt IShellItem *psiItem) = ; virtual HRESULT STDMETHODCALLTYPE ApplyPropertiesToItems(
__RPC__in_opt IUnknown *punkItems) = ; virtual HRESULT STDMETHODCALLTYPE RenameItem(
__RPC__in_opt IShellItem *psiItem,
__RPC__in_string LPCWSTR pszNewName,
__RPC__in_opt IFileOperationProgressSink *pfopsItem) = ; virtual HRESULT STDMETHODCALLTYPE RenameItems(
__RPC__in_opt IUnknown *pUnkItems,
__RPC__in_string LPCWSTR pszNewName) = ; 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) = ; virtual HRESULT STDMETHODCALLTYPE MoveItems(
__RPC__in_opt IUnknown *punkItems,
__RPC__in_opt IShellItem *psiDestinationFolder) = ; 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) = ; virtual HRESULT STDMETHODCALLTYPE CopyItems(
__RPC__in_opt IUnknown *punkItems,
__RPC__in_opt IShellItem *psiDestinationFolder) = ; virtual HRESULT STDMETHODCALLTYPE DeleteItem(
__RPC__in_opt IShellItem *psiItem,
__RPC__in_opt IFileOperationProgressSink *pfopsItem) = ; virtual HRESULT STDMETHODCALLTYPE DeleteItems(
__RPC__in_opt IUnknown *punkItems) = ; 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) = ; virtual HRESULT STDMETHODCALLTYPE PerformOperations( void) = ; virtual HRESULT STDMETHODCALLTYPE GetAnyOperationsAborted(
__RPC__out BOOL *pfAnyOperationsAborted) = ; };
我们回到之前那个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 = ;
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) = ; virtual HRESULT STDMETHODCALLTYPE GetPropertyStore(
/* [in] */ GETPROPERTYSTOREFLAGS flags,
/* [in] */ __RPC__in REFIID riid,
/* [iid_is][out] */ __RPC__deref_out_opt void **ppv) = ; virtual HRESULT STDMETHODCALLTYPE GetPropertyDescriptionList(
/* [in] */ __RPC__in REFPROPERTYKEY keyType,
/* [in] */ __RPC__in REFIID riid,
/* [iid_is][out] */ __RPC__deref_out_opt void **ppv) = ; virtual HRESULT STDMETHODCALLTYPE GetAttributes(
/* [in] */ SIATTRIBFLAGS AttribFlags,
/* [in] */ SFGAOF sfgaoMask,
/* [out] */ __RPC__out SFGAOF *psfgaoAttribs) = ; virtual HRESULT STDMETHODCALLTYPE GetCount(
/* [out] */ __RPC__out DWORD *pdwNumItems) = ; virtual HRESULT STDMETHODCALLTYPE GetItemAt(
/* [in] */ DWORD dwIndex,
/* [out] */ __RPC__deref_out_opt IShellItem **ppsi) = ; virtual HRESULT STDMETHODCALLTYPE EnumItems(
/* [out] */ __RPC__deref_out_opt IEnumShellItems **ppenumShellItems) = ;
};
大致我们可以写出获取信息的文件的函数
UINT
GetFilesFromDataObjectWin(
IUnknown *iUnknown,
LPWSTR **ppPath)
{
HRESULT hr = E_FAIL;
LPITEMIDLIST Pv = NULL;
int nCount = ;
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,,sizeof(LPWSTR)*nCount); for ( int Index = ; 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 + ];
memset(*ppPath[Index],,sizeof(WCHAR)*(Length + ));
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) = ; virtual HRESULT STDMETHODCALLTYPE GetParent(
/* [out] */ __RPC__deref_out_opt IShellItem **ppsi) = ; virtual HRESULT STDMETHODCALLTYPE GetDisplayName(
/* [in] */ SIGDN sigdnName,
/* [string][out] */ __RPC__deref_out_opt_string LPWSTR *ppszName) = ; virtual HRESULT STDMETHODCALLTYPE GetAttributes(
/* [in] */ SFGAOF sfgaoMask,
/* [out] */ __RPC__out SFGAOF *psfgaoAttribs) = ; virtual HRESULT STDMETHODCALLTYPE Compare(
/* [in] */ __RPC__in_opt IShellItem *psi,
/* [in] */ SICHINTF hint,
/* [out] */ __RPC__out int *piOrder) = ; };
根据定义我们就可以通过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)
Win7、win8、win10下实现精准截获Explorer拷贝行为的更多相关文章
- Win7/Win8/Win10下安装Ubuntu14.04双系统 以及常见问题
整理自网络. 1. 制作镜像 将ubantu镜像刻录到优盘(我使用UltraISO刻录,镜像下载地址:链接: http://pan.baidu.com/s/1bndbcGv 密码: qsmb) 2. ...
- 通过修改manifest文件来解决Vista/Win7/Win8/win10下应用程序兼容性问题
https://www.cnblogs.com/snowbook/p/5363990.html
- Tomcat 在win7/win8 系统下tomcat-users.xml.new(拒绝访问)解决方法
tomcat启动报错No UserDatabase component found under key UserDatabase 也可以这样处理 Tomcat 在win7/win8 系统下tomcat ...
- win7/win8/win10 系统
WIN7/WIN8/WIN10 系统安装 http://www.windows7en.com/Win7/18572.html
- SQL Server 2000 绿色精简版gsql适用于xp/win7/win8/win10
老的程序员肯定都用过sql2000数据库,我在2006-2010年之间,做的不少网站也都是sql2000数据库的,但是后来随着mysql的兴起,就逐渐不再使用sql数据库了.但是最近有个客户的网站要修 ...
- Win8/Win10下程序经常无响应的解决办法
如果你使用Win8/Win10系统时经常出现程序无响应的问题不仿试下如下解决办法. 表现症状: 任何程序都有可能出现无响应(记事本.Visual Studio.QQ.视频播放器等) 一旦一个程序出现未 ...
- UEFI+GPT安装WIN7,WIN8/WIN10下安装WIN7双系统
一.BIOS更改 首先来bios更改:我们知道,uefi+gpt引导虽然出来的时间比较长,但是win7还不能完全的支持,所以在使用uefi+gpt安装win7的时候就会出现各种错误!所以我们在安装Wi ...
- 最新32位和64位xp,win7,win8,win10系统大全(电脑装机版)
一.系统主要特点 1.安装维护方便快速 - 全自动无人值守安装,采用万能GHOST技术,安装系统过程只需3-5分钟,适 合新旧各种机型. - 集成常见硬件驱动,智能识别+预解压技术,绝大多数硬件可以快 ...
- 2015年度精品 最新力作32位和64位xp,win7,win8,win10系统下载(电脑城专用版)
一.系统主要特点 1.安装维护方便快速 - 全自动无人值守安装,采用万能GHOST技术,安装系统过程只需3-5分钟,适 合新旧各种机型. - 集成常见硬件驱动,智能识别+预解压技术,绝大多数硬件可以快 ...
随机推荐
- IDEA + SpringBoot + maven 项目文件说明
Springboot + maven + IDEA + git 项目文件介绍 1..gitignore 分布式版本控制系统git的配置文件,意思为忽略提交 在 .gitingore 文件中,遵循相应 ...
- 人脸识别之Python DLib库进行人脸关键点识别
一.首先安装DLib模块 这里只介绍linux安装的过程,windows安装过程请自行百度 1.首先,安装dlib.skimage前:先安装libboost sudo apt-get install ...
- Linux分布式消息队列RocketMQ部署与监控--双Master
环境准备:CentOS_6.5_x64 IP: 192.168.0.249 dbTest249 Master1 IP: 192.168.0.251 webTest251 Master2 下载 ali ...
- Nginx实用整理
1. nginx 简述 1.1Nginx是轻量级高并发HTTP服务器和反向代理服务器:同时也是一个IMAP.POP3.SMTP代理服务器:Nginx可以作为一个HTTP服务器进行网站的发布处理,另外N ...
- PAT Basic 1019 数字黑洞 (20 分)
给定任一个各位数字不完全相同的 4 位正整数,如果我们先把 4 个数字按非递增排序,再按非递减排序,然后用第 1 个数字减第 2 个数字,将得到一个新的数字.一直重复这样做,我们很快会停在有“数字黑洞 ...
- python文件操作-1.将PDF转成Excel
# https://www.jianshu.com/p/f33233e4c712 import pdfplumber # 为了操作PDF from openpyxl import Workbook w ...
- Java 基础知识点滴(1)
1. ==与equals的区别 ==是用来判断两个变量的值是否相等.要比较两个基本变量或引用变量是否相等,只能用==: equal用来判断两个独立对象里面的内容是否一样. 例子: String s1 ...
- 欧拉函数 || Calculation 2 || HDU 3501
题面: 题解:欧拉函数的基础应用,再套个很 easy 的等差数列前 n 项和就成了. 啊,最近在补作业+准备月考+学数论,题就没怎么写,感觉菜得一匹>_< CSL加油加油~! 代码: #i ...
- 配置Nexus为maven的私服
1.配置Nexus为maven的私服 第一种方式:在项目的POM中如下配置 <repositories> <repository> <id>nexus_public ...
- Mongodb的mapreduce
简单的看了一下mapreduce,我尝试不看详细的api去做一个group效果,结果遇到了很多问题,罗列在这里,如果别人也遇到了类似的bug,可以检索到结果. //先看person表的数据 > ...