NTDLL 函数前向声明

ntdll function forward declaration

本文关键字:声明 函数 NTDLL      更新时间:2023-10-16

在尝试实现我发现的一些示例代码时,我遇到了一个错误,我无法完全理解其原因。所以它来了。

在标题中,我看到它声明了类似的东西。但是,当我尝试编译它时,VS 给了我一个错误,其中包含此"未解析的外部符号..."。

NTSYSAPI
NTSTATUS
NTAPI
NtOpenSection(
OUT PHANDLE             SectionHandle,
IN ACCESS_MASK          DesiredAccess,
IN POBJECT_ATTRIBUTES   ObjectAttributes
);

但是,当我将其更改为类似的东西时,我不再看到错误。

NTSTATUS(NTAPI *NtOpenSection)(
OUT PHANDLE              SectionHandle,
IN ACCESS_MASK           DesiredAccess,
IN POBJECT_ATTRIBUTES    ObjectAttributes
); 

我不完全确定是什么原因造成的。如果有人也发布一些在线参考资料,让我了解更多,那就太好了。

您需要了解编译器和链接器的工作原理。 编译器创建通用对象文件格式 (COFF(文件。 这里存在源文件中声明的每个符号的COFF 符号表。 以二进制形式,它实现为IMAGE_SYMBOL(查找winnt.hntimage.h(。 这里对我们来说最有趣的是节号值(SHORT SectionNumber;(。 一般而言 - 符号可以定义– 已创建并在文件中分配存储地址和空间的符号。(从 1 开始索引到节表中(或未定义– 已在文件中引用但尚未分配存储地址的符号。

IMAGE_SYM_UNDEFINED-尚未为符号记录分配 部分。值为零表示对外部的引用 符号在别处定义。非零值是常用符号 具有由值指定的大小。

当您使用

NTSYSAPI
NTSTATUS
NTAPI
NtOpenSection(
OUT PHANDLE             SectionHandle,
IN ACCESS_MASK          DesiredAccess,
IN POBJECT_ATTRIBUTES   ObjectAttributes
);

编译器使用IMAGE_SYM_UNDEFINED节值创建__imp_NtOpenSection(x64,arm,arm64(或__imp__NtOpenSection@12(x86(符号 - 实际上你在这里声明函数,但你没有实现它。 这个函数(NtOpenSection(必须在其他地方定义(实现(。 当链接器链接 - 它在所有 OBJ 和 lib 文件中搜索它的实现(__imp_NtOpenSection符号(,并将其作为输入传递给他。 如果找不到它的实现- 将具有 1 的索引的记录IMAGE_SYMBOL到节表 - 他说 - 未解析的外部符号。所以你必须自己实现函数(符号(,或者给链接器库或 obj 文件,其中实现了这个函数。在用户模式下,它以ntdll.libntdllp.lib实现。所以你需要把这个库文件之一传递给链接器输入 - 这个解决错误。

在第二种情况下

NTSTATUS(NTAPI *NtOpenSection)(
OUT PHANDLE              SectionHandle,
IN ACCESS_MASK           DesiredAccess,
IN POBJECT_ATTRIBUTES    ObjectAttributes
); 

声明并实现变量。 它已被创建并在文件中分配了存储地址和空间。 因此,这里没有任何未解析的外部。

另请阅读符号处理以更好地了解此过程。

我还建议 - 编译您的 C/C++ 文件而不/GL选项(只有/HEADERSDUMPBIN 选项可用于使用/GL编译器选项生成的文件。

link.exe /dump /symbols your.obj > some.txt

并在此处查找NtOpenSection

00000000 UNDEF  notype       External     | __imp_NtOpenSection

在第一种情况下和

00000000 SECT4  notype       External     | ?NtOpenSection@@3P6AJPEAPEAXKPEAUOBJECT_ATTRIBUTES@@@ZEA (long (__cdecl* NtOpenSection)(void * *,unsigned long,struct OBJECT_ATTRIBUTES *))

在第二种情况下。

UNDEFSECTx


从二进制视图 在这两种情况下,您都声明指针大小(4或8字节(变量(__imp_NtOpenSection?NtOpenSection@@...(,该变量将保持地址以运行。 在这两种情况下,二进制级别的间接调用将是相同的

call [__imp_NtOpenSection]call [?NtOpenSection@@...]. 不同的 -__imp_在这种情况下 - 这个变量的地址(__imp_NtOpenSection(将被写入PE结构Import Lookup Table,并被放置在Import Address Table中。 结果 - 加载器(NTDLL中的代码(将自动解析地址或NtOpenSection并将此地址存储在__imp_NtOpenSection变量中。 或故障加载您的 PE, 如果地址将无法解析。因此,当您的代码开始执行时 - 内部已经__imp_NtOpenSectionNtOpenSection函数的有效地址,您可以使用它 - 从C/C ++代码调用NtOpenSection或从ASM调用call [__imp_NtOpenSection]

在第二种情况下(?NtOpenSection@@...[_]NtOpenSection,如果您使用extern "C"或 C 代码声明它,则仅适用于 x86,_( - 这将只是可变的。 当你的代码开始执行时 - 这里将是0(如果你将其声明为全局/静态(或未定义的值(堆栈中的局部变量(。 在通过此类变量调用NtOpenSection之前, 你需要首先初始化它 - 分配NtOpenSection的真实地址。说*(void**)&NtOpenSection = GetProcAddress(GetModuleHandle(L"ntdll"), "NtOpenSection");.在此之后,您可以使用它。我怎么说 - 通过 #1 或 #2 声明调用NtOpenSection没有任何不同 - 代码将是绝对相同的