[转]Windows系统中监控文件复制操作的几种方式

1. ICopyHook

作用: 监视文件夹和打印机移动,删除, 重命名, 复制操作. 可以得到源和目标文件名. 可以控制拒绝操作.

缺点: 不能对文件进行控制. 只对Shell文件操作有效, 对原生Api MoveFile, CopyFile之类的操作无效.

用法: 从ICopyHook派生一个COM对象, 重载CopyCallbackA和CopyCallbackW, 然后把COM注册到HKRC\Directory\ShellEx\CopyHookHandlers\中去

2. 文件改变通知

作用: 监视一个文件夹下的文件修改(写入, 删除, 重命名), 并可以注册到一个窗口来处理通知.

缺点: 只是通知, 不可以拒绝操作. 不能区分是否文件复制操作还是移动操作, 不能拿到源文件名. 只对Shell文件操作有效, 对原生Api MoveFile, CopyFile之类的操作无效.

用法: SHChangeNotifyRegister 注册一个窗口接收文件改变同; 或者FindFirstChangeNotification 结合FindNextChangeNotification 的方式处理.

3.IShellExtInit

作用: 每一个Shell扩展对象创建都会触发IShellExInit::Initialize调用, 在Shell中, 用户的对文件的复制粘贴操作, 最终会被解析成文件对象的拖拽操作, 然后触发拖拽目标对象的Shell扩展对象的调用. 所以在文件夹和盘符对象上注册一个IShellExtInit可以监视到拖拽到文件夹对象的事件. 也就是可以监视到文件复制或移动到文件夹的操作. 并且同时可以从IShellExitInit::Initialize中可以获取到源文件名.

缺点: 同通知一样, 不能拒绝文件操作. 只对Shell文件操作有效, 对原生Api MoveFile, CopyFile之类的操作无效.

用法: 从IShellExtInit中派生一个COM对象, 重载Initialize, 在Initialize传来的第一个参数是目标目录名, 第二个参数中可以获取所有源文件名, 第三个参数是一个注册表对象句柄.

下面给一段处理样例:

HRESULT STDMETHODCALLTYPE CKCopyHook::Initialize(
 
 __in_opt  PCIDLIST_ABSOLUTE
pidlFolder,
 
 __in_opt  IDataObject
*pdtobj,
 
 __in_opt  HKEY
hkeyProgID)
{
 HRESULT ret = E_INVALIDARG;
 if (NtQueryObject
== 0)
 {
  NtQueryObject =
(PFNtQueryObject)GetProcAddress(LoadLibraryA("ntdll.dll"),
"NtQueryObject");
 }

if (NtQueryObject == 0)
  return E_FAIL;

// 获取 hkeyProgID 的名称
 std::auto_ptr<WCHAR> buffer(new
WCHAR[4096]);
 DWORD retlen = 0;
 if (NtQueryObject(hkeyProgID,
ObjectNameInformation, buffer.get(), 4096, &retlen) >
0)
  return E_INVALIDARG;

POBJECT_NAME_INFORMATION poni =
(POBJECT_NAME_INFORMATION)buffer.get();
 poni->Name.Buffer[poni->Name.Length]
= 0;

DbgOutPutMessageW(L"[%s] hkeyProgID=0x%x=[%s]", __FUNCTIONW__,
hkeyProgID, poni->Name.Buffer);

// 当 hkeyProgID 是 Folder 项时才进行文件处理
 if
(wcsnicmp(PathFindFileNameW(poni->Name.Buffer), L"Folder", 6) !=
0)
  return S_OK;

if (!SHGetPathFromIDListW(pidlFolder,
buffer.get()))
  return E_INVALIDARG;

std::wstring DestStr = buffer.get();

ret = E_INVALIDARG;

COleDataObject oledo;
 oledo.Attach(pdtobj,
FALSE);
 HGLOBAL  GlobalData;
 GlobalData =
oledo.GetGlobalData(CF_HDROP);
 if
(GlobalData)
 {
  HDROP hDrop =
(HDROP)GlobalLock(GlobalData);
  if
(hDrop)
  {
   //
枚举拖拽的源文件
   int nFiles = DragQueryFileW(hDrop, 0xFFFFFFFF,
NULL, 0);
   std::vector<std::wstring>
SrcStrs;
   for (int i=0; i<nFiles;
++i)
   {
    if (DragQueryFile(hDrop,
i, buffer.get(),
4096))
    {
     SrcStrs.push_back(buffer.get());
    }
   }
   
   if
(OnCopyFile(SrcStrs, DestStr, 0))
    ret = S_OK;

GlobalUnlock(hDrop);
  }
  GlobalFree(GlobalData);
 }

return ret;
}

4. 文件过滤驱动

作用: 控制所有文件原子操作

缺点: 不能(或者说很难)追踪文件复制, 移动操作.

用法: 反正没用, 不写了.

5. API Hooking

作用: 拦截CopyFile, MoveFile等Api, 可以任意控制文件复制操作, 可以拒绝文件操作, 也可以在复制前后插入自定义的操作,
相当灵活.

缺点: 麻烦, 相当麻烦, 兼容性差.

用法: Api Hooking的技术这里就不再陈述了.

需要拦截的API相当多, 从kernel32.dll中导出的 MoveFile* CopyFile* 系列函数, Vista之前的系统中,
Shell都是使用ShFileOperation进行文件操作的, ShFileOperation 内部也是调用kernel32中的这些函数,
所以可以不处理ShFileOperation.

但是Vista之后的系统, Shell改为调用ShFileOperationEx, ShFilerOperationEx内部并不使用CopyFile,
MoveCopy等的函数, 而是使用CreateFile, ReadFile, WriteFile 重叠IO进行文件操作,
并且ShFileOperationEx没有在任何dll中导出. 这样就对拦截ShFilerOperationEx带来很大的麻烦.

不过可以利用搜索特征代码的方式从内存中, 搜索到ShFilerOperationEx的地址.

下面这个是32位系统中ShFilerOperationEx的开头的特征代码, 在Shell32.dll内存空间中, 32位的Vista,
Win7适用

const BYTE SHFileOperationExCodeMark[] = {
0x8B, 0xFF, 0x55, 0x8B,
0xEC, 0x83, 0xEC, 0x18, 0xA1, 0xFF, 0xFF, 0xFF, 0xFF, 0x33, 0xC5, 0x89,
0x45,
0xFC, 0x8B, 0x45, 0x0C, 0x8B, 0x4D, 0x1C, 0x53, 0x8B, 0x5D, 0x10, 0x56, 0x8B,
0x75, 0x20,
0x57, 0xFF, 0x75, 0x08, 0x89, 0x45, 0xF0, 0x8B, 0x45, 0x14, 0x89,
0x45, 0xEC, 0xBF, 0xFF, 0xFF,
0xFF, 0xFF, 0xE8, 0xFF, 0xFF, 0xFF, 0xFF, 0x85,
0xC0, 0x0F, 0x84, 0xFF, 0xFF, 0xFF, 0xFF, 0x8D,
};

//其中的0xFF的位置跳过比对

上一篇:linux中删除文件内空白行的几种方法。


下一篇:android studio This client is too old to work with the working copy at