在尝试实现我发现的一些示例代码时,我遇到了一个错误,我无法理解它的原因。所以这就是它。
在标题中,我看到它声明了类似的东西。但是,当我尝试编译它时,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
);
我不完全确定是什么导致它。如果有人发布一些在线参考资料以及我了解更多,那将会很棒。
您需要了解编译器和链接器的工作原理。编译器创建Common Object File Format (COFF)文件。这里存在COFF Symbol Table为源文件中声明的每个符号。它以二进制形式实现为IMAGE_SYMBOL
(查看winnt.h
或ntimage.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.lib
或ntdllp.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
将没有任何不同 - 代码将是绝对相同的