Пиши безопасно Крис Касперски ака мыщъх Спецвыпуск: Хакер, номер #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, что намного удобнее. Как этому помешать? Оказывается, чтобы ослепить ИДУ, достаточно изменить всего несколько байтов в начале каждой библиотечной функции. |