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

时间:2019-05-09 09:41:22

标签: c++ winapi visual-c++ file-io

简短: 在我的c ++项目中,我需要读取/写入扩展文件属性。我使用备用数据流(ADS)对其进行了管理。我的问题是,要打开ADS,我需要使用CreateFile API。但这不能满足我的需求。 NtCreateFile将满足我的所有需求。 (或选择NtSetEaFileNtQueryEaFile)但是NtCreateFile不能从Win32控制台应用程序直接访问。

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

我猜想NtCreateFile可能是最安全的方法,因为winternl.h标头已对其进行了很好的说明和支持。特别是因为自Windows 2000以来此方法一直未更改。但是NtSetEaFileNtQueryEaFile完全适合我的需求。它们只有一半记录在案。存在ZwSetEaFileZwQueryEaFile的文档(自Windows 2000以来未更改)。

为什么要这么做:

我想通过ADS从文件写入和读取扩展属性。但是,如果是第一次编写给定文件的扩展属性,我需要使用OPEN_ALWAYS打开文件。如果文件不存在,即使我不访问文件的内容流,它也会创建一个新文件。为了避免这种情况,我首先获取原始文件的句柄,并使用此HANDLE检查该文件是否仍然存在。 但是我不想写任何访问权限降低的文件,因为从我的角度来看,这是一个非常糟糕的模式。用户需要随时拥有对任何文件的完全访问权限。因此,我们打开所有带有标志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会有所帮助,因为我只需要一个手柄。

问题是,将来不必保存该应用程序,因为ADS只能在NTFS上运行。谁知道NTFS何时会被交换。但是我不想要片状的行为。我想相信这个方法。如果API将来会更改并且软件需要适应它,我很好。但我想确定的是,所有高于或等于7的Windows都可以处理。有人分享一些经验吗?我很想听听他们的话。

2 个答案:

答案 0 :(得分:3)

这个问题是错误的。为您的问题建议的解决方案不是使用NtCreateFile,而是使用dwCreationDisposition设置为OPEN_EXISTING的CreateFile。

来自documentation

  

OPEN_EXISTING

     

仅打开文件或设备(如果存在)。如果指定文件或   设备不存在,功能失败,最后一个错误代码是   设置为ERROR_FILE_NOT_FOUND。

仅打开文件(如果存在)并设置所需的内容。如果文件已重命名,则CreateFile返回ERROR_FILE_NOT_FOUND。

问题

现在,对于您提出的解决方案,什么是更好的方法,或者为什么在Win32控制台应用程序(???)中不能使用ntdll.dll。 同样,您的“更好”的方法-GetProcAddress与使用针对ntdll.dll的链接“相同”。在 Windows 11 Windows 12 Windows 3030 中,该功能可能已删除,并且两种解决方案(静态导入与动态导入)都将失败。

答案 1 :(得分:0)

如果此类API是文档,使用它们并不是真的不安全。对于NtSetEaFileNtQueryEaFileNtCreateFile,您可以在Microsoft Doc中找到说明。 (请记住NtXxx == ZwXxx)

但是此API将来可能会更改,并且Microsoft不保证它将在下一Windows版本中提供相同的方法。如果可以,请使用公共API,因为这样做很安全。如果不是,则逐案决定。在这种情况下,自Windows 2000以来,API中的三种方法均未更改。另外,例如,NtSetEaFileNtQueryEaFile被Microsoft用于WSL(Linux的Windows子系统)。特别是NtCreateFile被广泛的OpenSource Projects使用。因此,该API更改的可能性很小。

在我的用例中,另一个方面很重要。因为我想使用ADS,但是NTFS仅支持ADS。因此,使用ADS并不能确保将来的兼容性。因此,使用NtSetEaFileNtQueryEaFile对我来说很清楚。

但是如何使用这种API?可以进行动态或静态链接。这取决于您的需求,哪个更好。如果是静态链接,则需要下载最新的WDK(Windows驱动程序工具包)并链接到ntdll.lib。如果是动态链接,则可以直接通过GetModuleHandle访问dll,并使用GetProcAddress查找方法的地址。在Windows下,ntdll.dll可以从任何应用程序访问。在这两种情况下,您都没有直接的头文件。您必须自己定义头文件,或使用WDK来获取它们。

在我的项目中,动态链接是最佳选择。原因是,在每个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。当然,如果不需要此标志,一切都很好。但就我而言,我需要创建ADS。没有OPEN_ALWAYS标志就不会创建它。