ntdll函数转发声明

问题描述 投票:-2回答:1

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

在标题中,我看到它声明了类似的东西。但是,当我尝试编译它时,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
); 

我不完全确定是什么导致它。如果有人发布一些在线参考资料以及我了解更多,那将会很棒。

c++ windows winapi
1个回答
3
投票

您需要了解编译器和链接器的工作原理。编译器创建Common Object File Format (COFF)文件。这里存在COFF Symbol Table为源文件中声明的每个符号。它以二进制形式实现为IMAGE_SYMBOL(查看winnt.hntimage.h)。对我们来说最有趣的是截面数值(SHORT SectionNumber;)。一般来说 - 符号可以定义 - 已创建的符号,并在文件中分配存储地址和空间。 (部分表中基于一的索引)或未定义 - 已在文件中引用但尚未分配存储地址的符号。

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

当你使用

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

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

在第二种情况下

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

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

还阅读Symbol Processing以更好地理解这个过程。

我还建议 - 在没有/GL选项的情况下编译你的c / c ++文件(只有/HEADERS DUMPBIN选项可用于使用/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 *))

在第二种情况下。

UNDEF vs SECTx


从二进制视图中你可以声明指针大小(4或8字节)变量(__imp_NtOpenSection?NtOpenSection@@...),它将保持地址到函数。在这两种情况下,二进制级别的间接调用都是相同的:

call [__imp_NtOpenSection]call [?NtOpenSection@@...]。不同 - 在__imp_的情况下 - 这个变量的地址(__imp_NtOpenSection)将写在PE结构Import Lookup Table,它将被放置在Import Address Table。结果 - loader(ntdll中的代码)将自动解析地址或NtOpenSection并将此地址存储在__imp_NtOpenSection变量中。如果地址未解决,则加载您的PE失败。所以当你的代码开始执行时 - 在__imp_NtOpenSection里面已经是NtOpenSection函数的有效地址,你可以使用它 - 从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将没有任何不同 - 代码将是绝对相同的

© www.soinside.com 2019 - 2024. All rights reserved.