当我们启动一个子进程,
打开读写管道,同时设置子进程继承这些管道的句柄,
我们就可以操作子进程的标准输入和标准输出了。
这样有个弊端,子进程会继承父进程打开的所有文件句柄。
如果子进程不退出,一直持续任务,那么,被打开的文件就无法重命名和删除了。
有两个解决的办法:
1. CreateFile的时候属性参数直接指定不允许继承句柄。但是这个方法有个弊端:
c/c++的文件操作并没有提供属性设置,默认的属性是被继承的。
如果写的是跨平台程序必须使用标准库时,就没有办法了。
2. 使用系统API枚举所有打开的句柄内核对象,然后关闭这些内核对象。
代码如下:
头文件定义如下:
1 #pragma warning(disable: 4996) // _tcsnicmp deprecated 2 #include3 4 // This makro assures that INVALID_HANDLE_VALUE (0xFFFFFFFF) returns FALSE 5 #define IsConsoleHandle(h) (((((ULONG_PTR)h) & 0x10000003) == 0x3) ? TRUE : FALSE) 6 #define SystemHandleInformation (SystemProcessInformation + 11) 7 #define ObjectNameInformation (ObjectTypeInformation - 1) 8 9 struct OBJECT_NAME_INFORMATION 10 { 11 UNICODE_STRING Name; // defined in winternl.h 12 TCHAR NameBuffer; 13 }; 14 15 typedef NTSTATUS(NTAPI* PNtQueryObject)(HANDLE Handle, OBJECT_INFORMATION_CLASS Info, PVOID Buffer, ULONG BufferSize, PULONG ReturnLength); 16 17 typedef LONG NTSTATUS; 18 19 typedef struct _SYSTEM_HANDLE 20 { 21 DWORD dwProcessId; 22 BYTE bObjectType; 23 BYTE bFlags; 24 WORD wValue; 25 PVOID pAddress; 26 DWORD GrantedAccess; 27 }SYSTEM_HANDLE; 28 29 typedef struct _SYSTEM_HANDLE_INFORMATION 30 { 31 DWORD dwCount; 32 SYSTEM_HANDLE Handles[1]; 33 } SYSTEM_HANDLE_INFORMATION, *PSYSTEM_HANDLE_INFORMATION, **PPSYSTEM_HANDLE_INFORMATION; 34 35 36 typedef NTSTATUS(WINAPI *PNtQuerySystemInformation) 37 (IN SYSTEM_INFORMATION_CLASS SystemInformationClass, 38 OUT PVOID SystemInformation, 39 IN ULONG SystemInformationLength, 40 OUT PULONG ReturnLength OPTIONAL);
实现代码:
// returns // "\Device\HarddiskVolume3" (Harddisk Drive) // "\Device\HarddiskVolume3\Temp" (Harddisk Directory) // "\Device\HarddiskVolume3\Temp\transparent.jpeg" (Harddisk File) // "\Device\Harddisk1\DP(1)0-0+6\foto.jpg" (USB stick) // "\Device\TrueCryptVolumeP\Data\Passwords.txt" (Truecrypt Volume) // "\Device\Floppy0\Autoexec.bat" (Floppy disk) // "\Device\CdRom1\VIDEO_TS\VTS_01_0.VOB" (DVD drive) // "\Device\Serial1" (real COM port) // "\Device\USBSER000" (virtual COM port) // "\Device\Mup\ComputerName\C$\Boot.ini" (network drive share, Windows 7) // "\Device\LanmanRedirector\ComputerName\C$\Boot.ini" (network drive share, Windwos XP) // "\Device\LanmanRedirector\ComputerName\Shares\Dance.m3u" (network folder share, Windwos XP) // "\Device\Afd" (internet socket) // "\Device\Console000F" (unique name for any Console handle) // "\Device\NamedPipe\Pipename" (named pipe) // "\BaseNamedObjects\Objectname" (named mutex, named event, named semaphore) // "\REGISTRY\MACHINE\SOFTWARE\Classes\.txt" (HKEY_CLASSES_ROOT\.txt) DWORD GetNtPathFromHandle(HANDLE h_File, CString* ps_NTPath) { if (h_File == 0 || h_File == INVALID_HANDLE_VALUE) return ERROR_INVALID_HANDLE; // NtQueryObject() returns STATUS_INVALID_HANDLE for Console handles if (IsConsoleHandle(h_File)) { ps_NTPath->Format(_T("\\Device\\Console%04X"), (DWORD)(DWORD_PTR)h_File); return ERROR_SUCCESS; } BYTE u8_Buffer[2000]; DWORD u32_ReqLength = 0; UNICODE_STRING* pk_Info = &((OBJECT_NAME_INFORMATION*)u8_Buffer)->Name; pk_Info->Buffer = 0; pk_Info->Length = 0; HMODULE h_NtDll = GetModuleHandle(_T("Ntdll.dll")); // Ntdll is loaded into EVERY process! PNtQueryObject NtQueryObject = (PNtQueryObject)GetProcAddress(h_NtDll, "NtQueryObject"); if (NtQueryObject == NULL) { return ERROR_FUNCTION_FAILED; } // IMPORTANT: The return value from NtQueryObject is bullshit! (driver bug?) // - The function may return STATUS_NOT_SUPPORTED although it has successfully written to the buffer. // - The function returns STATUS_SUCCESS although h_File == 0xFFFFFFFF NtQueryObject(h_File, OBJECT_INFORMATION_CLASS(ObjectNameInformation), u8_Buffer, sizeof(u8_Buffer), &u32_ReqLength); // On error pk_Info->Buffer is NULL if (!pk_Info->Buffer || !pk_Info->Length) return ERROR_FILE_NOT_FOUND; pk_Info->Buffer[pk_Info->Length / 2] = 0; // Length in Bytes! *ps_NTPath = pk_Info->Buffer; return ERROR_SUCCESS; } // converts // "\Device\HarddiskVolume3" -> "E:" // "\Device\HarddiskVolume3\Temp" -> "E:\Temp" // "\Device\HarddiskVolume3\Temp\transparent.jpeg" -> "E:\Temp\transparent.jpeg" // "\Device\Harddisk1\DP(1)0-0+6\foto.jpg" -> "I:\foto.jpg" // "\Device\TrueCryptVolumeP\Data\Passwords.txt" -> "P:\Data\Passwords.txt" // "\Device\Floppy0\Autoexec.bat" -> "A:\Autoexec.bat" // "\Device\CdRom1\VIDEO_TS\VTS_01_0.VOB" -> "H:\VIDEO_TS\VTS_01_0.VOB" // "\Device\Serial1" -> "COM1" // "\Device\USBSER000" -> "COM4" // "\Device\Mup\ComputerName\C$\Boot.ini" -> "\\ComputerName\C$\Boot.ini" // "\Device\LanmanRedirector\ComputerName\C$\Boot.ini" -> "\\ComputerName\C$\Boot.ini" // "\Device\LanmanRedirector\ComputerName\Shares\Dance.m3u" -> "\\ComputerName\Shares\Dance.m3u" // returns an error for any other device type DWORD GetDosPathFromNtPath(const TCHAR* u16_NTPath, CString* ps_DosPath) { DWORD u32_Error; if (_tcsnicmp(u16_NTPath, _T("\\Device\\Serial"), 14) == 0 || // e.g. "Serial1" _tcsnicmp(u16_NTPath, _T("\\Device\\UsbSer"), 14) == 0) // e.g. "USBSER000" { HKEY h_Key; if (u32_Error = RegOpenKeyEx(HKEY_LOCAL_MACHINE, _T("Hardware\\DeviceMap\\SerialComm"), 0, KEY_QUERY_VALUE, &h_Key)) return u32_Error; TCHAR u16_ComPort[50]; DWORD u32_Type; DWORD u32_Size = sizeof(u16_ComPort); if (u32_Error = RegQueryValueEx(h_Key, u16_NTPath, 0, &u32_Type, (BYTE*)u16_ComPort, &u32_Size)) { RegCloseKey(h_Key); return ERROR_UNKNOWN_PORT; } *ps_DosPath = u16_ComPort; RegCloseKey(h_Key); return ERROR_SUCCESS; } if (_tcsnicmp(u16_NTPath, _T("\\Device\\LanmanRedirector\\"), 25) == 0) // Win XP { *ps_DosPath = _T("\\\\"); *ps_DosPath += (u16_NTPath + 25); return ERROR_SUCCESS; } if (_tcsnicmp(u16_NTPath, _T("\\Device\\Mup\\"), 12) == 0) // Win 7 { *ps_DosPath = _T("\\\\"); *ps_DosPath += (u16_NTPath + 12); return ERROR_SUCCESS; } TCHAR u16_Drives[300]; if (!GetLogicalDriveStrings(300, u16_Drives)) return GetLastError(); TCHAR* u16_Drv = u16_Drives; while (u16_Drv[0]) { TCHAR* u16_Next = u16_Drv + _tcslen(u16_Drv) + 1; u16_Drv[2] = 0; // the backslash is not allowed for QueryDosDevice() TCHAR u16_NtVolume[1000]; u16_NtVolume[0] = 0; // may return multiple strings! // returns very weird strings for network shares if (!QueryDosDevice(u16_Drv, u16_NtVolume, sizeof(u16_NtVolume) / sizeof(TCHAR))) return GetLastError(); int s32_Len = (int)_tcslen(u16_NtVolume); if (s32_Len > 0 && _tcsnicmp(u16_NTPath, u16_NtVolume, s32_Len) == 0) { *ps_DosPath = u16_Drv; *ps_DosPath += (u16_NTPath + s32_Len); return ERROR_SUCCESS; } u16_Drv = u16_Next; } return ERROR_BAD_PATHNAME; } //EnableTokenPrivilege( SE_DEBUG_NAME ); BOOL EnableTokenPrivilege(LPCTSTR pszPrivilege) { // do it only once static bool bEnabled = false; if (bEnabled) { return TRUE; } bEnabled = true; HANDLE hToken = 0; TOKEN_PRIVILEGES tkp = { 0 }; // Get a token for this process. if (!OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, &hToken)) { return FALSE; } // Get the LUID for the privilege. if (LookupPrivilegeValue(NULL, pszPrivilege, &tkp.Privileges[0].Luid)) { tkp.PrivilegeCount = 1; // one privilege to set tkp.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED; // Set the privilege for this process. AdjustTokenPrivileges(hToken, FALSE, &tkp, 0, (PTOKEN_PRIVILEGES)NULL, 0); if (GetLastError() != ERROR_SUCCESS) return FALSE; return TRUE; } return FALSE; } /* * Basically, with the above code, we can close the handles created by another process. * After closing the handle, we can rename or delete that file or directory. * But there are cases where after closing the handle, we can rename the folder but deleting is not possible. * using this option on a DLL used by another process is just ignored. */ BOOL CloseHandleWithProcess(SYSTEM_HANDLE& sh) { HANDLE hFile = (HANDLE)sh.wValue; HANDLE hProcess = OpenProcess(PROCESS_DUP_HANDLE, FALSE, sh.dwProcessId); if (hProcess) { HANDLE hDup = 0; BOOL b = DuplicateHandle(hProcess, hFile, GetCurrentProcess(), &hDup, DUPLICATE_SAME_ACCESS, FALSE, DUPLICATE_CLOSE_SOURCE); if (hDup) { CloseHandle(hDup); } CloseHandle(hProcess); } return FALSE; } BOOL RenameWithClose(const CString& srcpath, const CString& dstpath) { if (MoveFileEx(srcpath, dstpath, MOVEFILE_REPLACE_EXISTING)) { return TRUE; } else { // The process cannot access the file because it is being used by another process // Try to close file handle used by another process if (GetLastError() == ERROR_SHARING_VIOLATION) { HMODULE hModule = GetModuleHandle(_T("ntdll.dll")); PNtQuerySystemInformation NtQuerySystemInformation = (PNtQuerySystemInformation)GetProcAddress(hModule, "NtQuerySystemInformation"); if (NtQuerySystemInformation == NULL) { SetLastError(ERROR_SHARING_VIOLATION); return FALSE; } else { // Get the list of all handles in the system PSYSTEM_HANDLE_INFORMATION pSysHandleInformation = new SYSTEM_HANDLE_INFORMATION; DWORD size = sizeof(SYSTEM_HANDLE_INFORMATION); DWORD needed = 0; NTSTATUS status = NtQuerySystemInformation(SYSTEM_INFORMATION_CLASS(SystemHandleInformation), pSysHandleInformation, size, &needed); if (!NT_SUCCESS(status)) { if (0 == needed) { delete pSysHandleInformation; SetLastError(ERROR_SHARING_VIOLATION); return FALSE;// some other error } // The previously supplied buffer wasn't enough. delete pSysHandleInformation; size = needed + 1024; pSysHandleInformation = (PSYSTEM_HANDLE_INFORMATION)new BYTE[size]; status = NtQuerySystemInformation(SYSTEM_INFORMATION_CLASS(SystemHandleInformation), pSysHandleInformation, size, &needed); if (!NT_SUCCESS(status)) { // some other error so quit. delete pSysHandleInformation; SetLastError(ERROR_SHARING_VIOLATION); return FALSE; } } // iterate over every handle for (DWORD i = 0; i < pSysHandleInformation->dwCount; i++) { HANDLE hDup = (HANDLE)pSysHandleInformation->Handles[i].wValue; if (pSysHandleInformation->Handles[i].dwProcessId == GetCurrentProcessId()) { CString strNtPath; CString strDosPath; GetNtPathFromHandle(hDup, &strNtPath); GetDosPathFromNtPath(strNtPath, &strDosPath); if (strDosPath == srcpath) { _tprintf(_T("%s: (process id: %d) (filetype: %d) with handle 0x%x\n"), (LPCTSTR)strDosPath, pSysHandleInformation->Handles[i].dwProcessId, pSysHandleInformation->Handles[i].bObjectType, pSysHandleInformation->Handles[i].wValue); //now we can close file open by another process //do rename or delete file again //EnableTokenPrivilege(SE_DEBUG_NAME); //CloseHandleWithProcess(pSysHandleInformation->Handles[i]); } } } delete pSysHandleInformation; } } else { return FALSE; } } return TRUE; }