当使用ReadDirectoryChanges时,如何规避UAC?

3
我有一个应用程序需要通过ReadDirectoryChangesW监视主要驱动器的文件变化。然而,当UAC启用时,它无法工作。
所有的Windows API调用都成功了,但我没有收到任何更改通知。
我可以通过分别监视根目录下的每个目录来解决这个问题,但这是一个问题,因为如果有太多的目录,它可能会导致蓝屏。
有没有一种可接受的方法绕过UAC并在整个主要驱动器上接收文件更改通知?
相关的CreateFileReadDirectoryChangesW如下。在不起作用的情况下,directory是C:\。如果我监视任何辅助驱动器(例如E:\、F:\、G:\),它会按预期工作。没有任何调用返回错误。
HANDLE fileHandle = CreateFileW(directory.c_str(), FILE_LIST_DIRECTORY, 
    FILE_SHARE_READ | FILE_SHARE_DELETE, NULL, OPEN_EXISTING, 
    FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OVERLAPPED, NULL);

BOOL success = ReadDirectoryChangesW(fileHandle, watched.buffer.data(),
    watched.buffer.size(), TRUE, 
    FILE_NOTIFY_CHANGE_FILE_NAME | FILE_NOTIFY_CHANGE_LAST_WRITE, 
    NULL, &watched.overlapped, NULL);

有趣的是,.NET中的System.IO.FileSystemWatcher可以正确工作,并且它使用了和我使用的完全相同的函数和参数,但它表现正常。

1
如果应用程序被提升权限,它还能正常工作吗? - Harry Johnston
1
你如何在非提升用户模式进程中使计算机蓝屏?尽管如此,如果你消耗足够的资源,可能会让用户的脸变成蓝色... - ixe013
ReadDirectoryChangesW 在系统内存中分配给定缓冲区的副本。 - Collin Dauphinee
@dauphic:在蓝屏中,您得到了哪个错误代码检查? - 0xC0000022L
你尝试过使用同步而不是异步IO吗?这对我很有效,这是我所做的唯一明显不同之处。(如果您想要完整的测试代码,请给我发电子邮件。) - Harry Johnston
显示剩余9条评论
3个回答

1

这是一些可用的测试代码,供将来参考。

#include <Windows.h>

#include <stdio.h>

int main(int argc, char ** argv)
{
    HANDLE filehandle;
    BYTE buffer[65536];
    DWORD dw;
    FILE_NOTIFY_INFORMATION * fni;
    OVERLAPPED overlapped = {0};

    overlapped.hEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
    if (overlapped.hEvent == NULL)
    {
        printf("CreateEvent: %u\n", GetLastError());
        return 1;
    }

    filehandle = CreateFile(L"C:\\", 
        FILE_LIST_DIRECTORY, 
        FILE_SHARE_READ | FILE_SHARE_DELETE, 
        NULL, 
        OPEN_EXISTING, 
        FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OVERLAPPED, 
        NULL);

    if (filehandle == INVALID_HANDLE_VALUE)
    {
        printf("CreateFile: %u\n", GetLastError());
        return 1;
    }

    for (;;)
    {
        if (!ReadDirectoryChangesW(filehandle, buffer, sizeof(buffer),
            TRUE, 
            FILE_NOTIFY_CHANGE_FILE_NAME | FILE_NOTIFY_CHANGE_LAST_WRITE, 
            NULL, &overlapped, NULL))
        {
            printf("ReadDirectoryChangesW: %u\n", GetLastError());
            return 1;
        }

        printf("Queued OK.\n");

        if (!GetOverlappedResult(filehandle, &overlapped, &dw, TRUE))
        {
            printf("GetOverlappedResult: %u\n", GetLastError());
            return 1;
        }

        printf("%u bytes read.\n", dw);

        fni = (FILE_NOTIFY_INFORMATION *)buffer;

        for (;;)
        {
            printf("Next entry offset = %u\n", fni->NextEntryOffset);
            printf("Action = %u\n", fni->Action);
            printf("File name = %.*ws\n", 
                                  fni->FileNameLength / 2, 
                                  fni->FileName);

            if (fni->NextEntryOffset == 0) break;

            fni = (FILE_NOTIFY_INFORMATION *)
                               (((BYTE *)fni) + fni->NextEntryOffset);
        }
    }

    printf("All done\n");
    return 0;
}

1

首先,对于使用ReadDirectoryChangesW API的应用程序最好以提升的权限运行,并为您的应用程序创建一个清单文件,并将requireAdministrator设置为requestedExecutionLevel级别。请参考这里进行检查。

如果您正在使用它,请尝试从CreateFile调用中删除FILE_SHARE_WRITE

另一种选择是将您的程序作为服务运行。我不确定这对您的需求是否适用。您可以发布一些代码,说明如何获取文件句柄以及向ReadDirectoryChangesW传递什么内容。


manifest选项可以确保您的应用程序始终以管理员权限运行,而无需用户以管理员身份运行它。您还可以尝试从CreateFile调用中删除FILE_SHARE_DELETE。 - Zaid Amir
而且它在你的C驱动器上不起作用,因为它是系统驱动器... 如果所有其他尝试都失败了,最好将您的应用程序作为服务运行。 - Zaid Amir
我已更改清单以要求管理员权限并删除了FILE_SHARE_DELETE。行为没有变化,它仍然无法接收系统驱动器上的更改。目前,将其作为服务不可能。 - Collin Dauphinee
实际上,由于你想要监视整个系统驱动器,因此强烈建议将其制作为一个服务。 - Zaid Amir
它将被合并成一个,但现在不行。需要另一种方法来完成这个任务,因为System.IO.FileSystemWatcher正在使用完全相同的参数。 - Collin Dauphinee
显示剩余5条评论

0
您可以像这样自己调整进程的权限:
// enable the required privileges for this process

LPCTSTR arPrivelegeNames[] = {  SE_BACKUP_NAME,
                                SE_RESTORE_NAME,
                                SE_CHANGE_NOTIFY_NAME
                             };

for (int i=0; i<(sizeof(arPrivelegeNames)/sizeof(LPCTSTR)); ++i)
{
    CAutoGeneralHandle hToken;
    if (OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES, hToken.GetPointer()))
    {
        TOKEN_PRIVILEGES tp = { 1 };

        if (LookupPrivilegeValue(NULL, arPrivelegeNames[i],  &tp.Privileges[0].Luid))
        {
            tp.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;

            AdjustTokenPrivileges(hToken, FALSE, &tp, sizeof(tp), NULL, NULL);
        }
    }
}

这也适用于非特权进程(即普通用户进程)。


您不需要具备SE_BACKUP_NAME特权即可使用FILE_FLAG_BACKUP_SEMANTICS打开目录句柄。 - Harry Johnston

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接