суббота, 27 февраля 2010 г.

LoadLibraryEx(DONT_RESOLVE_DLL_REFERENCES) имеет фундаментальный изъян

Это перевод LoadLibraryEx(DONT_RESOLVE_DLL_REFERENCES) is fundamentally flawed. Автор: Реймонд Чен.

У функции LoadLibraryEx есть флаг, называемый DONT_RESOLVE_DLL_REFERENCES. В документации сказано:
Если это значение задано, а выполняемый модуль является DLL, то система не будет вызывать её DllMain для инициализации и завершения процесса и потоков. Также система не будет загружать (дополнительные) библиотеки, на которые ссылается данный модуль.

Если вы планируете только получать доступ к данным или ресурсам этой DLL, то лучше использовать флаг LOAD_LIBRARY_AS_DATAFILE.
По-моему, текст выше составлен так, что он "предполагает", что флаг LOAD_LIBRARY_AS_DATAFILE недостаточно сильный.

DONT_RESOLVE_DLL_REFERENCES - это бомба замедленного действия.

Посмотрите внимательно на то, что делает этот флаг и что он не делает. Модуль загружается в память, но его функция инициализации не вызывается, а DLL по зависимостям не загружаются. В результате вы не можете выполнять код из этой DLL (более точно - вы можете, но вы сразу же вылетите, потому что DLL не была инициализирована и ни один из её импортов не был установлен). Однако, в отличие от флага LOAD_LIBRARY_AS_DATAFILE, загружаемая DLL может быть найдена функцией GetModuleHandle и может быть использована для GetProcAddress.

Очевидно, что вызов GetProcAddress для чего-то, что было загружено с помощью DONT_RESOLVE_DLL_REFERENCES, является плохой идеей, потому что, как мы уже заметили, вы не можете выполнять какой-либо код из этой DLL. В чём смысл получать адрес процедуры, которую вы никогда не сможете вызвать?

А вот часть с GetModuleHandle и включает таймер на бомбе.

Довольно частой вещью является вызов GetModuleHandle, чтобы узнать, не загружена ли DLL, а если да - то использование GetProcAddress для получения процедуры и её вызова. Если DLL была загружена с DONT_RESOLVE_DLL_REFERENCES, то обе процедуры успешно выполнятся, но целевая функция вылетит при вызове. Код который это делает, не имеет ни малейшего представления, что эта DLL была загружена с флагом DONT_RESOLVE_DLL_REFERENCES; у него нет способа защититься от этого.

(Заметьте, что код выше в любом случае не безопасен, потому что код, который изначально запустил DLL, может решить вызвать для ней FreeLibrary (в другом потоке), что приведёт к "выдёргиванию" кода прямо из под носа у потока, вызвавшего GetProcAddress. Эта вторая проблема может быть "исправлена" использованием GetModuleHandleEx, которой можно указать на увеличение счётчика ссылок, но это не решит первую проблему).

Даже если вы используете LoadLibrary для загрузки DLL и передадите этот описатель в GetProcAddress, вы всё ещё вылетите, потому что LoadLibrary увидит, что DLL уже загружена и просто увеличит счётчик ссылок.
program Project1;

uses
Windows;

var
h: HINST;
f: function(hWnd: HWND; Operation, FileName, Parameters,
Directory: PAnsiChar; ShowCmd: Integer): HINST; stdcall;
begin
if ParamCount > 0 then // запускаем бомбу
LoadLibraryEx('shell32.dll', 0, DONT_RESOLVE_DLL_REFERENCES);

// А вот код-жертва
h := LoadLibrary('shell32.dll');
if h <> 0 then
begin
f := GetProcAddress(h, 'ShellExecuteA');
if Assigned(f) then
f(0, nil, 'notepad.exe', nil, nil, SW_SHOWNORMAL);
end;
FreeLibrary(h);
end.
Если вы запустите код выше без параметров командной строки, то всё будет работать: блокнот запустится без всяких проблем. Однако, если вы зададите хотя бы один параметр, это запустит таймер на бомбе, который сработает, когда будет выполняться невинный код ниже.

(прим.пер.: я использовал Delphi 7 и Windows XP под виртуалкой, так как на моём ноуте при комбинации Delphi 2010 + Windows 7 библиотека Shell32 оказывается загруженной до момента вызова LoadLibraryEx; может быть, это работает какая-то дополнительная программа; я не копал)

Другими словами, DONT_RESOLVE_DLL_REFERENCES имеет фундаментальный изъян и не должен использоваться. Этот флаг существует исключительно ради обратной совместимости.

1 комментарий:

  1. Игорь Шевченко7 марта 2010 г. в 21:18

    Не знаю, в тему или нет, но для значения, возвращаемого LoadLibraryEx с флагом LOAD_LIBRARY_AS_DATAFILE невозможно получить заголовки образа по RtlImageNtHeaders, в отличие от LoadLibraryEx с DONT_RESOLVE_DLL_REFERENCES.
    В свое время стояла задача проверить, можно ли выполнить загрузку DLL не загружая ее фактически, просмотрев рекурсивно список импортов. Так что изъян изъяном, но бывают случаи, где может пригодиться именно такой флаг.

    Пользуясь случаем, хочу поблагодарить за переводы.

    ОтветитьУдалить

Можно использовать некоторые HTML-теги, например:

<b>Жирный</b>
<i>Курсив</i>
<a href="http://www.example.com/">Ссылка</a>

Вам необязательно регистрироваться для комментирования - для этого просто выберите из списка "Анонимный" (для анонимного комментария) или "Имя/URL" (для указания вашего имени и ссылки на сайт). Все прочие варианты потребуют от вас входа в вашу учётку.

Пожалуйста, по возможности используйте "Имя/URL" вместо "Анонимный". URL можно просто не указывать.

Ваше сообщение может быть помечено как спам спам-фильтром - не волнуйтесь, оно появится после проверки администратором.

Примечание. Отправлять комментарии могут только участники этого блога.