При разработке драйверов, я стараюсь не собирать различные исполняемые файлы для разных версий Windows. В большинстве случаев проблемы, связанные с разным набором API, решаются тем, что адреса функций, которые присутствуют только на более старших версиях ядра ОС, можно получить через динамический импорт.
Для получения адреса функции по имени можно использовать функцию MmGetSystemRoutineAddress. К сожалению эта функция имеет ограничение, о чем написано в MSDN:
Поэтому если есть необходимость обрабатывать динамический импорт не из ядра и не из HAL'а, то придется эту функцию писать руками. Второй интересный момент, относящийся к MmGetSystemRoutineAddress, связан с входным параметром. Почему эта функция принимает UNICODE-строку для меня остается загадкой. Особенно расстраивает то, что внутри вызова этой функции строка все же преобразуется к ANSI-строке, что приводит к лишним манипуляциям с пулом. Из этого я делаю вывод, что не только я пользуюсь самописной функцией получения адреса экспортируемой функции по адресу загруженного модуля и ANSI-строке :) Назовем ее GetProcAddress. Реализация такой функции достаточно тривиальная (90% процентов кода это разбор директории экспортов PE-файла), поэтому я не буду приводить ее здесь.
Не для кого не секрет, что включенный verifier заменяет в целевом исполняемом модуле статически импортируемые из модуля ядра функции на свои, которые начинаются с префикса Verifier. Мне стало интересно как же обстоят дела с динамическим импортом, поэтому я предлагаю посмотреть на результат получения адреса функции различными способами. Для этого будем использовать следующий тестовый код:
static UNICODE_STRING usExAcquireResourceSharedLite = RTL_CONSTANT_STRING(L"ExAcquireResourceSharedLite"); PVOID pFuncStatic = &::ExAcquireResourceSharedLite; PVOID pFuncDynMm = MmGetSystemRoutineAddress(&usExAcquireResourceSharedLite); PVOID pFuncDynamic = GetProcAddress(pNtoskrnlBase, "ExAcquireResourceSharedLite"); __debugbreak();
Переменная pNtoskrnlBase в вышеприведенном примере содержит адрес загрузки модуля ядра.
Для начала запустим (естественно, с включенным verifier'ом) на Windows Server 2003 R2 SP2 (3790):
Затем запустим этот же код под verifier'ом на Windows 7 SP1 (7601). Результат отличается:
Как видно из вышеприведенных данных (результат выполнения "ln @@C++(pFuncDynMm)"), на Win7 функция MmGetSystemRoutineAddress обрабатывает ситуацию, когда на вызывающий драйвер включен verifier и возвращает более правильный адрес запрашиваемой функции.
На самом деле разница на старшей версии ОС в том, что появилась функция nt!VerifierMmGetSystemRoutineAddress (она появилась в Windows Vista (6000)). Так как MmGetSystemRoutineAddress была использована статическим вызовом, вместо нее реально была вызвана nt!VerifierMmGetSystemRoutineAddress, которая является достаточно простой proxy-оберткой:
push ebp mov ebp,esp push esi mov esi,[ebp][8] push esi call MmGetSystemRoutineAddress test eax,eax jz .000744511 push eax call VfThunkAdjustExportAddressIfHooked ; .00744511: pop esi pop ebp retn 4
Из вышесказанного можно лишь еще раз подтвердить старую истину, что использование велосипедов вредно для вашего кода.
ΞρεΤΙκ