首页 > 技术文章 > 关闭其它进程占用的文件句柄

jojodru 2016-04-01 19:07 原文

当我们启动一个子进程,

打开读写管道,同时设置子进程继承这些管道的句柄,

我们就可以操作子进程的标准输入和标准输出了。

这样有个弊端,子进程会继承父进程打开的所有文件句柄。

如果子进程不退出,一直持续任务,那么,被打开的文件就无法重命名和删除了。

有两个解决的办法:

1. CreateFile的时候属性参数直接指定不允许继承句柄。但是这个方法有个弊端:

    c/c++的文件操作并没有提供属性设置,默认的属性是被继承的。

    如果写的是跨平台程序必须使用标准库时,就没有办法了。

 

2. 使用系统API枚举所有打开的句柄内核对象,然后关闭这些内核对象。

    代码如下:

    头文件定义如下:

 1 #pragma warning(disable: 4996) // _tcsnicmp deprecated
 2 #include <winternl.h>
 3 
 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;
}

 

推荐阅读