父進程文件句柄被子進程佔用
場景描述:
1.父進程A使用函數fopen打開(建立)一個磁盤文件file.exe.tmp
2.父進程進行長時間的邊下載邊寫入
3.下載寫入完成後,使用fclose關閉文件句柄
4.重命名file.exe.tmp爲file.exe
以上爲理想狀況下的代碼執行流程。
問題:
在第四步,重命名文件有可能失敗,使用 GetLastError 函數發現失敗緣由爲 此文件被另一個進程佔用。
解決:
1.使用微軟官方開發的handle.exe來查找是哪一個進程佔用了這個文件
(handle.exe的下載地址 https://technet.microsoft.com/en-us/sysinternals/bb896655.aspx )
使用方法:在文件下載完成,執行更名函數以前,執行 Handle.exe file.exe.tmp /accepteula 命令(使用管道獲取直接結果並打印)
2.在打印結果中發現,文件是被父進程A的子進程 B 佔用了。
在file.exe.tmp的下載過程當中,父進程A使用 createprocess 函數建立了子進程B,看一下函數原型:
BOOL WINAPI CreateProcess(
_In_opt_ LPCTSTR lpApplicationName,
_Inout_opt_ LPTSTR lpCommandLine,
_In_opt_ LPSECURITY_ATTRIBUTES lpProcessAttributes,
_In_opt_ LPSECURITY_ATTRIBUTES lpThreadAttributes,
_In_ BOOL bInheritHandles,
_In_ DWORD dwCreationFlags,
_In_opt_ LPVOID lpEnvironment,
_In_opt_ LPCTSTR lpCurrentDirectory,
_In_ LPSTARTUPINFO lpStartupInfo,
_Out_ LPPROCESS_INFORMATION lpProcessInformation
);
其中 bInheritHandles 參數的描述爲:
If this parameter TRUE, each inheritable handle in the calling process is inherited by the new process. If the parameter is FALSE, the handles are not inherited. Note that inherited handles have the same value and access rights as the original handles.
簡單的說就是容許子進程繼承父進程的句柄。
3.一種簡單的解決方法:bInheritHandles參數設置爲flase,那麼子進程B就不會佔用這個文件了。
可是我這裏沒法採用該方法,由於這個子進程B是第三方程序,他必須繼承某些句柄才能玩,那麼我換一種方法解決。
4.看fopen函數,實現代碼在CRT中,CRT代碼是開源的,路徑在:Microsoft Visual Studio 10.0\VC\crt\src
一層層往下跟,發現fopen 實際是 __topenfile函數,__topenfile函數中有詳細的 文件打開mode處理流程。
mode處理流程中有一段代碼爲:
case _T('N'):
modeflag |= _O_NOINHERIT;
break;
_O_NOINHERIT的定義爲:
/* Open handle inherit bit */
#define _O_NOINHERIT 0x0080 /* child process doesn't inherit file */
這裏就是控制該句柄是否容許被子進程佔用了。
那麼只須要在fopen的Mode設置爲 "ab+N" 就能夠了。
沿着源碼繼續往下看,發如今CRT這一層最終的調用函數爲 _tsopen_nolock (文件\Microsoft Visual Studio 10.0\VC\crt\src\open.c)
裏面對flag進行了大量的處理,最終調用 CreateFile 函數建立、打開文件,而且有很多的CreateFile函數異常處理代碼。
CreateFile函數是系統API,不開源,要繼續跟蹤就得逆向,就不繼續往下了。
5.因此得出第3種解決方案:直接使用系統API CreateFile 建立、打開文件,
6.第四種解決方案:參考unlock的方法:先openprocess,而後經過 DuplicateHandle 複製句柄,再結束句柄,從而解決子進程B的佔用。這個方法網上能夠很容易搜到源碼,我就不廢話了。
7.總結
以上一共提出了4種方法解決 父進程句柄被子進程佔用的問題,如下對比4種方案:
1.CreateProcess設置參數:很方便,可是若是子進程有特殊的繼承句柄需求,則不適用
2.fopen設置參數:很方便,可跨平臺兼容,修改代碼只需增長一個字節便可解決問題
3.使用 CreateFile 建立、打開文件,替代fopen:CreateFile功能強大,可是要在商業軟件中玩好這個函數不容易,須要處理大量的異常(可參考_tsopen_nolock函數的實現源碼)
4.DuplicateHandle後closehandle:最強大的關閉其餘進程中句柄的方式,主要運用於ring3中解除簡單木馬病毒的文件佔用技術(沒法適用於文件佔坑技術),可是使用風險是有的,真正應用也須要寫一大堆異常處理代碼。
函數