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的位置跳過比對