在 win32 控制台应用程序中使用 ntdll.dll 好吗?

Is it good to use ntdll.dll in a win32 console application?

简短: 在我的 C++ 项目中,我需要 read/write 扩展文件属性。我使用备用数据流 (ADS) 来管理它。我的问题是,要打开 ADS,我需要使用 CreateFile API。但它不能满足我的需求。 NtCreateFile 将满足我所有的需求。 (或者 NtSetEaFileNtQueryEaFile)但是 NtCreateFile 不能直接从 win32 控制台应用程序访问。

我知道我可以通过 GetProcAdress 轻松使用此功能。但我想知道你们所有人的意见,如果我确实遗漏了什么?其他一些库已经在使用这种模式,例如 Chromium(https://github.com/chromium-googlesource-mirror/chromium/blob/1c1996b75d3611f56d14e2b30e7ae4eabc101486/src/sandbox/src/win_utils.cc 函数:ResolveNTFunctionPtr) 但我不确定,因为c++项目不是一个业余项目,我问自己是否危险。

我想 NtCreateFile 可能是最安全的方法,因为 winternl.h header 有详细的文档和支持。特别是因为此方法自 windows 2000 年以来未发生变化。但是 NtSetEaFileNtQueryEaFile 的内容完全符合我的需求。他们只记录了一半。 ZwSetEaFileZwQueryEaFile 的文档存在(自 windows 2000 以来未更改)。

我想这样做的原因:

我想通过 ADS 写入和读取文件的扩展属性。但是如果第一次写入给定文件的扩展 属性,我需要用 OPEN_ALWAYS 打开文件。如果文件不存在,它将创建一个新文件,即使我只访问而不是文件的内容流。为避免这种情况,我首先获取原始文件的句柄并检查此句柄是否文件仍然存在。 但我不想在博客上发布任何访问权限降低的文件,因为从我的角度来看,这是一种非常糟糕的模式。用户需要随时拥有对任何文件的完全访问权限。因此,我们打开所有带有标志 FILE_SHARE_DELETE | 的 HANDLES。 FILE_SHARE_READ | FILE_SHARE_WRITE。现在我有比赛了。

auto hFile = CreateFileW(originalPath, …, FILE_SHARE_DELETE | FILE_SHARE_READ | FILE_SHARE_WRITE, …).
// this is the little race: if somebody at least rename originalPath the
// second CreateFileW call will cause the creation of a empty file with the
// path originalPath (the old path).
auto hADS = CreateFileW(originalPath + adsName, …, FILE_SHARE_DELETE | FILE_SHARE_READ | FILE_SHARE_WRITE, OPEN_ALWAYS, …).

这是一个主要问题,尤其是因为这种情况在我们的测试中时有发生。 NtCreateFile 会修复它,因为我可以在第一个 HANDLE 的帮助下创建第二个 HANDLE。因为没有种族。或者 NtSetEaFileNtQueryEaFile 会有所帮助,因为我只需要一个 HANDLE。

问题是,应用程序不需要为将来保存,因为 ADS 无论如何只能在 NTFS 上运行。谁知道什么时候会交换NTFS。但我不想要一个不稳定的行为。我想相信这个方法。如果API以后会发生变化,软件需要适应它,我很好。但我想确定,所有高于或等于 7 的 Windows 都可以处理它。有人可以分享一些经验吗?我很想听听他们的声音。

这道题错了。您针对问题提出的解决方案不是使用 NtCreateFile,而是使用 CreateFile 并将 dwCreationDisposition 设置为 OPEN_EXISTING.

来自documentation

OPEN_EXISTING

Opens a file or device, only if it exists. If the specified file or device does not exist, the function fails and the last-error code is set to ERROR_FILE_NOT_FOUND.

只需打开文件(如果存在)并设置您想要的任何内容。如果文件被重命名,CreateFile returns ERROR_FILE_NOT_FOUND.

问题

现在,对于您提出的解决方案,什么是更好的方法或者为什么不能在 win32 控制台应用程序中使用 ntdll.dll (???)。 同样,您的“更好”方法 - GetProcAddress 与使用针对 ntdll.dll 的链接一样“错误”。在Windows11,或Windows12Windows3030 该功能可能会被删除,两种解决方案(静态导入与动态导入)都将失败。

如果是文档,使用这种 API 并不是真的不安全。在 NtSetEaFileNtQueryEaFileNtCreateFile 的情况下,您可以在 Microsoft 的文档中找到描述。 (记住 NtXxx == ZwXxx)

但是这个API将来可能会改变,微软不保证在下一个Windows版本中会提供相同的方法。如果可以,请使用 public API,因为那样您就安全了。如果不是,则视具体情况而定。在这种情况下,API 中的三种方法自 Windows2000 以来未发生变化。另外,例如 NtSetEaFileNtQueryEaFile 被 Microsoft 用于 WSL(Windows 子系统用于 Linux)。特别是 NtCreateFile 被广泛的开源项目使用。所以这个 API 改变的可能性很小。

在我的用例中,另一个方面很重要。因为我想用ADS,但是ADS只有NTFS支持。所以使用 ADS 也不能保证未来的兼容性。所以我很清楚使用 NtSetEaFileNtQueryEaFile.

但是如何使用这种 API?动态或静态 linking 是可能的。这取决于您的需求,哪个更好。如果是静态 linking,您需要针对 ntdll.lib 下载最后一个 WDK(Windows 驱动程序包)和 link。在动态 linking 的情况下,您可以通过 GetModuleHandle 直接访问 dll 并使用 GetProcAddress 找出方法的地址。在 Windows ntdll.dll 下可以从任何应用程序访问。在这两种情况下,您都没有直接的头文件。需要自己定义头文件或者使用WDK获取。

在我的项目中,动态 linking 是最佳选择。原因是,在每个 windows 上都会选择正确的实现,如果该方法不可用,我有机会停用我的软件中的功能而不是崩溃。由于最后一个原因,Microsoft 建议使用动态方式。

简单伪代码(动态案例):

typedef struct _FILE_FULL_EA_INFORMATION {
    ULONG  NextEntryOffset;
    UCHAR  Flags;
    UCHAR  EaNameLength;
    USHORT EaValueLength;
    CHAR   EaName[1];
} FILE_FULL_EA_INFORMATION, *PFILE_FULL_EA_INFORMATION;

typedef struct _IO_STATUS_BLOCK {
    union {
        NTSTATUS Status;
        PVOID    Pointer;
    };
    ULONG_PTR Information;
} IO_STATUS_BLOCK, *PIO_STATUS_BLOCK;

typedef NTSTATUS(WINAPI *NtSetEaFileFunction)(IN HANDLE FileHandle,
                                              OUT       PIO_STATUS_BLOCK
                                                        IoStatusBlock,
                                              IN PVOID Buffer,
                                              IN ULONG Length);

HMODULE  ntdll                   = GetModuleHandle(L"ntdll.dll");
NtSetEaFileFunction function     = nullptr;
FARPROC *function_ptr            = reinterpret_cast<FARPROC *>(&function);

*function_ptr = GetProcAddress(ntdll, "NtQueryEaFile");

// function could be used normally.

另一个答案不正确。原因是我的问题的原因是,我需要使用 OPEN_ALWAYS。当然,如果你不需要这个flag,就万事大吉了。但就我而言,有时我需要创建 ADS。如果没有 OPEN_ALWAYS 标志,它将不会被创建。