Вы должны понимать, как работают компилятор и компоновщик.Компилятор создает Общий формат файла объекта (COFF) файлов.здесь существует Таблица символов COFF для каждого символа, объявленного в исходном файле.в двоичном виде он реализован как IMAGE_SYMBOL
(глядя в winnt.h
или ntimage.h
).здесь наиболее интересным для нас является Значения номера раздела (SHORT SectionNumber;
).в общих словах - символ может быть Определен - Символы, которые были созданы и которым назначены адреса хранения и пространство внутри файла.(индекс на основе единицы в таблицу разделов) или Undefined - символы, на которые есть ссылки в файле, но которым не присвоен адрес хранения.
IMAGE_SYM_UNDEFINED
- символьной записи еще не присвоен раздел.Нулевое значение указывает, что ссылка на внешний символ определена в другом месте.Значение, отличное от нуля, является общим символом, размер которого определяется значением.
при использовании
NTSYSAPI
NTSTATUS
NTAPI
NtOpenSection(
OUT PHANDLE SectionHandle,
IN ACCESS_MASK DesiredAccess,
IN POBJECT_ATTRIBUTES ObjectAttributes
);
compiler create __imp_NtOpenSection
(x64, arm, arm64) или __imp__NtOpenSection@12
(x86) символ со значением IMAGE_SYM_UNDEFINED
- на самом деле вы здесь объявляете функцию, но вы не реализуете ее.эта функция (NtOpenSection
) должна быть определена (реализована) в другом месте.когда ссылка на линкер - это поиск его реализации (__imp_NtOpenSection
символ) во всех файлах obj и lib, который передается ему в качестве входных данных.если он не может найти его реализацию - IMAGE_SYMBOL
запись с однозначным индексом в таблицу разделов, - говорит он, - неразрешенный внешний символ.так что вы должны либо реализовать функцию (символ) самостоятельно, либо передать компоновщику lib или файл obj, где эта функция реализована.в случае пользовательского режима он реализован в ntdll.lib
или ntdllp.lib
.поэтому вам и нужно передать один из этих файлов lib на вход компоновщика - это ошибка разрешения.
во втором случае
NTSTATUS(NTAPI *NtOpenSection)(
OUT PHANDLE SectionHandle,
IN ACCESS_MASK DesiredAccess,
IN POBJECT_ATTRIBUTES ObjectAttributes
);
, который вы объявляете, и внедряете переменную.он уже был создан и ему присвоены адреса хранения и пространство внутри файла.в результате здесь нет никаких неразрешенных внешних факторов.
также прочитайте Обработка символов для лучшего понимания этого процесса.
также я предлагаю - скомпилировать ваш файл c / c ++ без опции /GL
( Только опция /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
против SECTx
из двоичного представления в обоих случаях вы объявляете переменную размера указателя (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_NtOpenSection
уже будет действительный адрес функции NtOpenSection
, и вы можете просто использовать его - вызвать NtOpenSection
из кода c / c ++ или call [__imp_NtOpenSection]
из asm.
во втором случае (?NtOpenSection@@...
или [_]NtOpenSection
, если вы объявите его с помощью extern "C"
или из кода c, _
только для x86) - это будет просто переменная.и когда вы начинаете кодировать execute, здесь будет 0 (если вы объявите его как глобальный / статический) или неопределенное значение (локальная переменная в стеке).перед вызовом NtOpenSection
через такую переменную вам нужно сначала ее инициализировать - назначить реальный адрес NtOpenSection
.скажем как *(void**)&NtOpenSection = GetProcAddress(GetModuleHandle(L"ntdll"), "NtOpenSection");
.после этого вы можете использовать его.и как я говорю - не будет никаких различий в вызове NtOpenSection
через объявление # 1 или # 2 - код будет абсолютно одинаковым