Издательский дом ООО "Гейм Лэнд"СПЕЦВЫПУСК ЖУРНАЛА ХАКЕР #57, АВГУСТ 2005 г.

Пиши безопасно

Крис Касперски ака мыщъх

Спецвыпуск: Хакер, номер #057, стр. 057-070-4


Активное использование виртуальных функций существенно затрудняет дизассемблирование. Виртуальные функции вызываются по косвенным ссылкам, и над вычислением эффективных адресов приходится основательно потрудиться.

Вот пример:

Виртуальные и невиртуальные функции

class Base{

public: virtual void demo_1(void){printf("BASE DEMO 2\n");};

virtual void demo_2(void) = 0;};

class Derived:public Base{

public: virtual void demo_1(void){printf("DERIVED\n");};

virtual void demo_2(void){printf("DERIVED DEMO 2\n");};

};

main(){Base *p = new Derived; p->demo(); p->demo_2();printf("non-virtual\n");}

А вот его дизассемблерный фрагмент:

Вызов виртуальных и невиртуальных функций

.text:0040101B mov eax, [esi]

.text:0040101D mov ecx, esi

.text:0040101F call dword ptr [eax] ; вызов виртуальной функции 1

.text:00401021 mov edx, [esi]

.text:00401023 mov ecx, esi

.text:00401025 call dword ptr [edx+4] ; вызов виртуальной функции 2

.text:00401028 push offset aNonvirtual;

.text:0040102D call sub_4010B0 ; вызов невиртуальной функции

Что мы видим? Вызов невиртуальной функции осуществляется по непосредственному значению (константе), равной в данном случае 4010B0h. А с виртуальными функциями все намного сложнее. Команда call dword prt [eax] передает управление по адресу, хранящемуся в двойном слове, на которое указывает регистр EAX, загружающийся, в свою очередь, из двойного слова, на которое указывает регистр ESI, а сам ESI… И такие цепочки могут быть длинными, очень длинными, причем исходный указатель инициализируется совсем в другом месте (как правило, в пра-пра-праматеринской функции). В общем, с виртуальными функциями современные дизассемблеры еще не справляются (к Delphi и Builder это не относится - для них существует множество отличных декомпиляторов).

Только необходимо помнить, что оптимизирующие компиляторы при первой же возможности заменят виртуальную функцию на статическую. Просто объявить функцию как виртуальную недостаточно - нужно ИСПОЛЬЗОВАТЬ ее как виртуальную.

Совет №4: ослепляй FLIRT

Типичная программа наполовину состоит из библиотечных функций, анализ которых занимает море времени и усилий (особенно если это какой-нибудь интерфейсный компонент). IDA PRO поддерживает шикарную технологию FLIRT (Fast Library Identification and Recognition Technology), автоматически распознающую функции большинства популярных библиотек, в результате чего задача хакера существенно упрощается.

Вот фрагмент защитного механизма, считывающий имя пользователя и серийный номер из окна редактирования функцией TControl::GetText:

Имена библиотечных функций, автоматические распознанные ИДОЙ

CODE:0048D2F7 mov eax, [ebx+328h]

CODE:0048D2FD call @TControl@GetText$qqrv ; TControl::GetText(void)

...

CODE:0048D309 mov eax, [ebx+320h]

CODE:0048D30F call @TControl@GetText$qqrv ; TControl::GetText(void)

Размер защитного кода несопоставим с размером библиотечных функций, которые он использует (библиотечные функции на порядок "жирнее"). Если бы не FLIRT, взлом затянулся бы надолго. А так пришел, увидел, отломил. Тем более что большинство хакеров предпочитает ставить точки останова не на GetWindowTextA, а на @TControl@GetText$qqrv, что намного удобнее. Как этому помешать? Оказывается, чтобы ослепить ИДУ, достаточно изменить всего несколько байтов в начале каждой библиотечной функции.

Назад на стр. 057-070-3  Содержание  Вперед на стр. 057-070-5