Обфускация и ее преодоление

         

Черный ящик квадрата Малевича


Существуют различные способы анализа алгоритмов работы устройств, "схема" которых недоступна. "Запутанную" программу можно рассматривать как "черный ящик" со входом и выходом, абстрагируясь от машинного кода и выполняя анализ на гораздо более высоком уровне.

Очень большую информацию несут в себе вызовы API-функций (вместе с аргументами и возвращаемыми значениями), а если хакеру еще удастся перехватить и библиотечные функции вместе с RTL, то картина происходящего будет в общих чертах ясна. По крайней мере хакер сможет выяснить к чему "привязывается" защита и каким образом узнает об окончании испытательного периода. А большего для взлома зачастую и не нужно! Вместо того, чтобы анализировать код самой программы, хакер будет исследовать каким образом она взаимодействует с "внешним миром", то есть с ОС! Тогда на "внутренний" мир защиты можно будет забить. Конечно, не для всех программ это срабатывает, но многие ломаются именно так!

Art.exe|0FF6D4E|GetProcAddress(77F80000,01049A04:"NtContinue") returns: 77F92796

Art.exe|0FF6D4E|GetProcAddress(77F80000,01049A3C:"NtRaiseException") returns: 77F860F2

Art.exe|0FF6D4E|GetProcAddress(77F80000,01049A7C:"KiUserExceptionDispatcher")returns;

Art.exe|0FF6D4E|GetProcAddress(77F80000,01049AC4:"NtQuerySystemInformation") returns;

Art.exe|0FF6D4E|GetProcAddress(77F80000,01049B0C:"NtAllocateVirtualMemory") returns;

Art.exe|0FF6D4E|GetProcAddress(77F80000,01049B50:"NtFreeVirtualMemory") returns;

Art.exe|0FF6D4E|GetProcAddress(77F80000,01049B90:"NtMapViewOfSection") returns;

Art.exe|0FEE7C2|VirtualAlloc(00000000,0000027D,00001000,00000040) returns: 01220000

Art.exe|10000AE|GetModuleFileNameA(00400000, 0012FE61, 000000FF) returns: 0000003B

Art.exe|0FFDA16|CreateFileA(0012FE61:"C:\bin\ElcomSoft\AdvancedRegistryTrace...",,,,)

Art.exe|0FFDBC3|CreateFileMappingA(9Ch,00h,02h,00h,00h,00h) returns: 000000A0

Art.exe|0FFDBD3|CloseHandle(0000009C) returns: 00000001

Art.exe|0FFDBF8|MapViewOfFile(A0h, 04h, 00h, 00h, 00h) returns: 01230000

Art.exe|0FE4EDD|GetActiveWindow() returns: 00000000

Art.exe|0FD5D98|MessageBoxA(0,499DC:"Debugger detected.",,"Protection Error") returns;

Art.exe|FFFFFFF|ExitProcess(72542079)



Как это ломается




Чтобы ощутить все прелести обфускации на собственной шкуре, достаточно взять Armadillo, упаковать свою собственную программу типа "hello, world!", а затем ковырнуть ее отладчиком или дизассемблером. Мама родная! Это же с ума сойти можно! Сколько ни трассируй программу, а смысла все равно не видно! Нас окружает кромешная тьма, непроглядный мрак диких джунглей запутанного кода. Неужели кому-то под силу это сломать? Поверьте, парни, это возможно!



Как работает обфускатор


Как говорят медики, СПИД— это еще не приговор. Тоже самое и обфускация. Далеко не каждый обфускатор использует продвинутые методики "запутывания" и высаживаться на измену при этом слове не надо.

В простейшем случае, полиморфный генератор просто "накачивает" программу кучей незначащих команд типа nop, xchg reg,reg, or reg,reg[2]; никогда не выполняющимися переходами типа xor reg,reg/jnz junk, где xor – значимая команда, а junk – "мертвый код", и т. д. Вот, например:

       or     ch, ch               ; "мусор", не воздействующий на регистр ch,

                                  ; но воздействующий на регистр флагов, однако

                                  ; это воздействие перекрываются последующим xor

      

       xor    eax,eax                    ; потенциально значимая команда

                                  ; (почему "потенциально" будет пояснено ниже)

      

       seto   bl                   ; "мусор", устанавливающий bl

в 1, если есть

                                  ; переполнение, а после xor

его всегда нет

      

       repne  jnz short loc_43409A ; "мусор", передающий управление если не ноль,

                                  ; но после xor

флаг нуля всегда установлен,

                                  ; плюс бессмысленный префикс repne

      

       rep    jnp short loc_43408D ; "мусор", передающий управление если нечет

                                  ; но после xor

флаг четности всегда установлен

      

       jo     short loc_434094     ; "мусор", передающий управление если флаг

                                  ; переполнения установлен, а он сброшен xor

      

       xchg   ebx,ebx                    ; "мусор", обмен регистров ebx

местами



код, замусоренный обфускатором


Не слишком сложный скрипт для IDA PRO найдет все явно незначимые команды и пометит их как "мусорные" или же вовсе удалит их к едреням. Ильфак уже давно написал "highlighter" — плагин, как раз и предназначенный для этой цели (см. рис. 4) и распространяющийся в исходных текстах на бесплатной основе: http://www.hexblog.com/ida_pro/files/highlighter.zip. Впрочем, бесплатность эта весьма условна. Чтобы скомпилировать плагин нужен IDA SDK, причем не какой-нибудь, а только последней версии. То есть, у большинства пользователей IDA Pro скомпилировать его не получится, но это не повод расстраиваться — ведь точно такую же штуку можно реализовать и самостоятельно, используя встроенный в IDA Pro язык скриптов, затратив на это буквально полчаса (сам язык подробно описан книге "Образ мышления — IDA PRO", электронную версию которой можно бесплатно скачать с моего мыщъхиного сервера ftp://nezumi.org.ru).



протокол трассера


Намного нагляднее дизассемблерного листинга, правда? Теперь не нужно прыгать по условным переходам, гадая какие из них выполняются, а какие нет, к тому же естественным образом исчезает проблема перекрытия машинных команд. Обратите внимание на адреса 434012h, 00434013h и 00434016h. Ба! Так это же наши "перекрытые" команды! То, что дизассемблеру удавалось показать с таким трудом, трассер отдает нам задаром! Это реальный поток выполнения программы, в котором много мусора, но по крайней мере нет скрытых команд, с которыми приходится сталкиваться в дизассемблере.

Полученный протокол трассировки можно (и нужно!) прогонять через различные программы-фильтры (их так же придется написать самостоятельно), распознающие и удаляющие мусорные инструкции. Впрочем, эту операцию можно выполнить и вручную, загрузив протокол в любой редактор (например, в тот, что встроен в FAR). После нескольких минут работы мы получим следующий реально значимый код:

001B:00434001  E800000000  CALL   00434006

001B:00434006  5D          POP    EBP

001B:00434077  33C9        XOR    ECX,ECX

001B:004340C3  33C0        XOR    EAX,EAX

001B:004340D3  8B0424             MOV    EAX,[ESP]

001B:004340DB  C60090             MOV    BYTE PTR [EAX],90

001B:00434105  83ED06             SUB    EBP,06



шпионаж на API функциями несет в себе очень много информации


Большая ошибка большинства обфускаторов в том, что "запутывая" код, они забывают "запутать" структуру данных (ну разве что только зашифровывают их). Это позволяет использовать классические приемы взлома типа "прямой поиск регистрационных данных в памяти". Хакер вводит произвольный регистрационный номер, находит отладчиком его в памяти, ставит точку останова и всплывает в "запутанной" процедуре, а затем смотрит обстоятельства дел. В половине случаев после серии долгих разбирательств запутанная процедура возвращает TRUE/FALSE (и тогда хакер просто правит условный переход), в другой половине — защита генерирует "эталонный" регистрационный номер, легко обнаруживаемый визуальным осмотром дампа памяти (и в этом случае хакер просто вводит подсмотренный номер в программу). Более сложные защитные механизмы встречаются крайне редко, но и тогда зачастую удается сгенерировать валидный номер "руками" самой защиты, если она построена по схеме if (func_generate_reg_num(user_name) == entered_reg_num) all_ok() else fuck_off();
Как нетрудно догадаться, хакер находит процедуру func_generate_reg_num (а находит от ее по срабатываю точки останова на user_name) и "подсматривает" возвращаемый результат. Данная методика совершенно "прозрачна" и пробивает любые навесные упаковщики, лишний раз подтверждая известный тезис, что грамотно защитить программу — это не грибов надербанить!



виртуальный реестр и слежение за ним


Можно поступить проще — достаточно перехватить базовые API-функции для работы с системным временем, файловой системой, сетью и реестром, не забывая про функции DeviceIoControl и другие подобные ей. Тогда мы сможем организовать "легкую" и весьма быстродействующую виртуальную машину, подсовывающую защите отдельную файловую систему и реестр. Кстати говоря, некоторые протекторы "гадят" в реестре и замуровать их в застенках виртуальной машины сам Джа велел.

Конечно, это не сработает для тех защит, которые, например, работают 30 минут, а затем требуют перезапуска программы, поскольку, существует очень много способов отсчитать эти 30 минут даже без обращения к API. Виртуальная машина бессильна в борьбе с надоедливым NAG-скринами или баннерами, которые крутит бесплатная версия программы, но на универсальность предложенной методики никто и не претендовал. Если программу можно сломать этим путем — хорошо, если нет — значит, используем другие пути, атакуя ее по одному из сценариев, описанных выше.



демонстрация техники "перекрытия" машинных команд, используемой обфускаторами


Обратите внимание на команду "043401Dh:jmp short loc_434013+2" (выделена полужирным), прыгающую по адресу 434013h+2h == 434015h, то есть в середину инструкции 434013h:seto bl (так же выделена полужирным). Именно, что в середину! С точки зрения дизассемблера (даже такого продвинутого как IDA Pro), команда является "атомарной" структурной единицей, то есть неделимой. На самом же деле, всякая машинная инструкция состоит из последовательности байт и может быть выполнена с любого места! Во всяком случае x86 процессоры не требуют выравнивания кода. Другими словами, у нас нет "команд" у нас есть только байты! Если начать выполнение инструкции не с первого байта мы получим совсем другую команду! К сожалению, IDA Pro не позволяет узнать какую. Чтобы выполнить переход "043401Dh:jmp short loc_434013+2" мы должны подвести курсор к метке loc_434013 и нажать <U>
, чтобы "раскрошить" дизассемблерный код на байты, а после перейти по адресу 434015h и нажать <C>
, чтобы превратить байты в дизассемблерный код, в результате чего получится следующее:

.adata:0043400E unk_43400E db 0B8h ; ¬          ; CODE XREF: .adata:loc_434023j

.adata:0043400F            db 0EBh ; ы

.adata:00434010            db    7

.adata:00434011            db 0B9h ; ¦

.adata:00434012 loc_434012:                     ; CODE XREF: .adata:loc_43401Aj

.adata:00434012            jmp    short loc_434023

.adata:00434014

.adata:00434014            nop

.adata:00434015

.adata:00434015 loc_434015:                     ; CODE XREF: .adata:loc_43401Dj

.adata:00434015            jmp    short loc_43401F     ; ß прыжок сюда

.adata:00434017

.adata:00434017            std

.adata:00434018            jmp    short loc_434025

.adata:0043401A

.adata:0043401A loc_43401A:                     ; CODE XREF: .adata:00434009j

.adata:0043401A            repne jmp short loc_434012



вскрытие" наложенной команды


Мы видим, что на месте seto bl возникла пара инструкций jmp loc_43401F/std. Какой из двух листингов правильный? Первый или второй? По отдельности — ни тот, ни другой. Они становятся "правильными" только когда их двое! Но удержать эти подробности в голове — нереально, а быстро переключаться между двумя вариантами IDA Pro не позволяет. Остается загонять "альтернативный" листинг в комментарии, но это возможно только тогда их двое. Если же одна и та же машинная команда имеет три и более "точек входа", то комментарии уже не спасают и возникает путаница, вынуждающая вместо дизассемблера использовать трассер (сравнимте дизассемблерный листинг с протоколом трассера, приведенном в листинге 8).

Изощренные обфускаторы отслеживают зависимости по данным, внедряя осмысленные инструкции с "нулевым эффектом". Поясним это на конкретном примере. Допустим, встретилась обфускатору конструкция:

       PUSH EAX             ; <- последнее обращение к eax

       MOV EAX,EBX          ; <- реинициализация eax



оригинальный код до обфускации


Легко показать, что между последним обращением к eax и его реницилизацией можно как угодно модифицировать регистр eax без ущерба для выполнения программы, поскольку любые операции присвоения все равно будут перекрыты командой mov eax,ebx. "Запутанный" код может выглядеть например, так:

       push eax             ; ß последнее значимое обращение к eax

       xor eax,eax          ; мусор

l1:

       inc eax                     ; мусор

       jz     l2            ; мусор

       cmp    eax, ebx      ; мусор

       jnz    l1            ; мусор

       cmp    eax, ecx      ; мусор

       jge    l1            ; мусор

l2:

       sub eax, 666h        ; мусор

       shl eax, 1           ; мусор

       mov eax, ebx         ; ß значимая реиницилизация eax



код после обфускации


Еще обфускаторы могут временно сохранять регистр на стеке, а затем вволю "поизмывавшись" над ним, восстанавливать прежнее значение.

001B:0043402C  50          PUSH   EAX           ; сохраняем eax

001B:0043402D  51          PUSH   ECX           ; сохраняем ecx

001B:0043402E  EB0F        JMP    0043403F

001B:0043403F  F2EBF5             REPNZ JMP 00434037

001B:00434037  EB0F        JMP    00434048

001B:00434048  EBE9        JMP    00434033

001B:00434033  B8EB07B9EB  MOV    EAX,EBB907EB  ; "гадим" в eax

001B:0043403B  08FD        OR     CH,BH         ; "гадим" в ch

001B:0043403D  EB0B        JMP    0043404A

001B:0043404A  F3EBE4             REPZ   JMP 00434031

001B:00434031  EB0F        JMP    00434042

001B:00434042  EBF6        JMP    0043403A

001B:0043403A  EB08        JMP    00434044

001B:00434044  F2EB08             REPNZ  JMP 0043404F

001B:0043404F  59          POP    ECX           ; восстанавливаем ecx

001B:00434050  58          POP    EAX           ; восстанавливаем eax



временное сохранение


Команда MOV EAX,EBB907EBh на первый взгляд выглядит "значимой", но на самом деле это "мусор", нейтрализуемый командами push eax/pop eax, и по сути весь этот конгломерат имеет нулевой эффект, то есть является совершенно ничего не делающим кодом. Поэтому, делать вывод о "значимости" команд нужно с очень большой осторожностью и до тех пор, пока не будет доказано, что данный кусок кода действительно имеет какой-то эффект, он должен считаться "мусором" по умолчанию.

Некоторые обускаторы любят внедрять подложные расшифровщики, расшифровывающие и тут же зашифровывающие произвольные фрагменты памяти (см. листинг 7).

00434105   83ED 06          SUB EBP,6

00434108   B8 3B010000            MOV EAX,13B

0043410D   03C5            ADD EAX,EBP

0043410F   33DB            XOR EBX,EBX

00434111   81C3 01010101   ADD EBX,1010101

00434117   3118            XOR DWORD PTR DS:[EAX],EBX ; <- расшифровываем

00434119   8138 78540000   CMP DWORD PTR DS:[EAX],5478

0043411F   74 04           JE SHORT app_test.00434125

00434121   3118            XOR DWORD PTR DS:[EAX],EBX ; <- зашифровываем

00434123  ^EB EC           JMP SHORT app_test.00434111



подложный" расшифровщик, внедренный обфускатором


Разумеется, все эти действия вносят побочные эффекты (как минимум воздействуют на флаги) и обфускатору приходится выполнять множество дополнительных проверок, чтобы не убедиться, что эти побочные действия не окажут рокового воздействия на защищаемую программу. Разработка качественного и надежного запутывателя — сложная инженерная задача, но потраченное время стоит того. Бесполезность "инструкций с нулевым эффектом" уже не распознается визуально и обычный трассер тут ничем не поможет. Необходимо трассировать не только поток управления, но и поток данных, то есть отслеживать реальные изменения значений регистров/ячеек памяти. Обычного для этого используется графы, и команда легендарного Володи с не менее легендарного wasm'а вплотную приблизилась к решению этой задачи. Все не так уж и сложно. Как только граф замыкается сам на себя, все "лишние" операции над данными удаляются и остается только суть.

Анализатор "LOCO" (см. рис. 6), созданный тройкой магов по имени Matias Madou, Ludo Van Put и Koen De Bosschere, является практически единственным доступным инструментом, который только есть. К сожалению, для практической работы он все-таки непригоден и больше напоминает игрушку, тем не менее стоящую того, чтобы с ней повозиться. Исходный код (вместе с документацией и кучей интересных статей на тему [де]обфускации) можно бесплатно скачать с официального сайта Diablo (http://www.elis.ugent.be/diablo/?q=obfuscation), однако, работать он будет только под UNIX. Windows-хакеры отдыхают и сосут (лапу).



листинг "вычищенный" в ручную


Основную проблему создают циклы. Трассер разворачивает их в длинный многокилометровый многократно повторяющий код. Запаришься его пролистывать! Так что без фильтра, распознающего и "сворачивающего" повторяющие конструкции нам не обойтись.

Хорошая идея — пропустить протокол трассера через оптимизирующий компилятор, использующий системы графов для устранения лишних операций присвоения (внимание! пропускать именно протокол трассера, а не дизассемблерный листинг, поскольку последний неверен, неполон и вообще никуда неканает!). Математических преобразований в стиле sin(x)2+cos(x)2 он, конечно же, распознать не сможет, но выбросит значительную часть "инструкций с нулевым эффектом", а нам не придется реализовывать систему графов и писать то, что было написано задолго до нас. К тому же, превзойти создателей оптимизирующих компиляторов нам все равно не удастся, правда, здесь есть одно "но". Компиляторы с большой осторожностью оптимизируют обращения к памяти, поэтому "ложные" расшифровщики типа листинга 7 компилятором оптимизированы не будут, не смотря на их очевидную "нулевую эффективность". Эту часть работы мы будем должны выполнить самостоятельно или же… просто смириться с тем, что из листинга вычищен не весь мусор. (Но ведь нам и не нужен "идеальный" листинг, правда? Будем есть, что даеют).

За основу лучше всего взять компилятор gcc, поскольку его исходные тексты открыты. Разумеется, просто взять и "оптимизировать" протокол трассера не получится, ведь он "написан" на языке ассемблера! У нас есть два пути: написать сравнительно простой транслятор, превращающий дизассемблерный протокол трассера в программу на Си (и тогда ее будет можно оптимизировать любым компилятором, а не только gcc), но лучше оттранслировать протокол трассера в промежуточный язык gcc (описанный в документации), пропустив его через "гнутый" оптимизатор. В этом случае мы получаем возможность сообщить оптимизатору некоторую дополнительную информацию о структуре программы, выловленную нашим трассером. Эффективность "чистки" кода от этого только повыситься. Короче говоря, наш трассер (и программы-фильтры) будут работать с оптимизатором в связке.

А там уже и до метадо-декомпилятора недалеко, тем более что работы в этом направлении ведутся не только в хакерских, но и "академических" кругах. Так что анализ "запутанного" кода — не такая уж сложная задача.

Кстати говоря, процедуры, обработанные обфускатором, резко отличаются от всех остальных и могут быть найдены простым статистическим анализом процентного содержания различных машинных команд. У "запутанных" процедур оно будет уж очень специфичным. К тому же, такие процедуры как правило до неприличия длинны. Логично, если код процедуры кем-то запутан, то это не просто так! Здесь явно прячется защитный механизм! Процедура проверки регистрационного номера или что-то типа того. Обфускация в этом случае только на пользу хакеру!



Обфускация и ее преодоление


крис касперски аргентинский болотный бобер nezumi el raton ака жирный нутряк ибн мыщъх

"sorry guys — there was so much fuzz on all message boards about the new Elliptic Curves DSA in latest Armadillo — we just had to do it... Take it as proof of concept, that it is not enough to implement strong crypto, you also should understand it for implementing well... Shouts go out to all, who made this possible especially to clive for donating some CPU power :-)"[1]

TMG group

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



Распутывание кода


Как бы хакер не избегал анализа запутанного кода, рано или поздно он вляпается в ситуацию, когда полная реконструкция алгоритма будет действительно необходима! Сражение с обфускатором неизбежно! А раз так, к нему нужно подготовить себя заранее. Посмотрим, какие тузы у нас есть в рукавах и чем мы можем ему противостоять.

Начнем с того, что напишем трассер. Собственно говоря, написать его все равно придется, хотя бы уже затем, чтобы понять как работает отладчик. Лучше, если это будет "термоядерный" трассер, работающий на нулевом кольце и обходящий антиотладочные приемы, которые так любят использовать обфускаторы.

Протокол трассировки программы, уже знакомой нам по листингу2, будет выглядеть так (если трассер писать лень, можно использовать soft-ice, просто отключив окно кода командной WC, тогда результат трассировки командой T будет "вываливать" в нижнее окно, откуда его можно добыть сохранив историю команд в Symbol Loader'е: File-> Save Soft-Ice History As):

001B:00434001  E800000000         CALL   00434006

001B:00434006  5D                 POP    EBP

001B:00434007  50                 PUSH   EAX

001B:00434008  51                 PUSH   ECX

001B:00434009  EB0F               JMP    0043401A             (JUMP v)

001B:0043401A  F2EBF5                    REPNZ  JMP 00434012         (JUMP ^)

001B:00434012* EB0F               JMP    00434023             (JUMP v)

001B:00434023  EBE9               JMP    0043400E             (JUMP ^)

001B:0043400E  B8EB07B9EB         MOV    EAX,EBB907EB

001B:00434013* 0F90EB                    SETO   BL

001B:00434016* 08FD               OR     CH,BH

001B:00434018  EB0B               JMP    00434025             (JUMP v)

001B:00434025  F3EBE4                    REPZ   JMP  0043400C        (JUMP ^)

001B:0043400C  EB0F               JMP    0043401D             (JUMP v)

001B:0043401D  EBF6               JMP    00434015             (JUMP ^)

001B:00434015  EB08               JMP    0043401F             (JUMP v)


001B:0043401F  F2EB08                    REPNZ  JMP 0043402A         (JUMP v)

001B:0043402A  59                 POP    ECX

001B:0043402B  58                 POP    EAX

001B:0043402C  50                 PUSH   EAX

001B:0043402D  51                 PUSH   ECX

001B:0043402E  EB0F               JMP    0043403F             (JUMP v)

001B:0043403F  F2EBF5                    REPNZ  JMP 00434037         (JUMP ^)

001B:00434037  EB0F               JMP    00434048             (JUMP v)

001B:00434048  EBE9               JMP    00434033             (JUMP ^)

001B:00434033  B8EB07B9EB         MOV    EAX,EBB907EB

001B:00434038  0F90EB                    SETO   BL

001B:0043403B  08FD               OR     CH,BH

001B:0043403D  EB0B               JMP    0043404A             (JUMP v)

001B:0043404A  F3EBE4                    REPZ   JMP  00434031        (JUMP ^)

001B:00434031  EB0F               JMP    00434042             (JUMP v)

001B:00434042  EBF6               JMP    0043403A             (JUMP ^)

001B:0043403A  EB08               JMP    00434044             (JUMP v)

001B:00434044  F2EB08                    REPNZ  JMP 0043404F         (JUMP v)

001B:0043404F  59                 POP    ECX

001B:00434050  58                 POP    EAX


попытка взлома Armadill'ы


Начнем с того, что с работающей программы практически всегда можно снять дамп, как бы этому не сопротивлялся распаковщик. Методики борьбы с распаковщиками довольно разнообразны и заслуживают отдельной статьи, мы же говорим об обускации, вот и будем говорить не отвлекаясь. Отметим лишь механизм динамической расшифровки CopyMem II, используемый Armadillo, при котором память расшифровывается постранично: Armadillo перехватывает обращение к зашифрованной странице через атрибут NO_ACCESS и механизм структурных исключений, расшифровывает ее, а затем зашифровывает вновь. Тем не менее, вполне реально написать драйвер, отслеживающий возникновение исключений и дампящий страницу после завершения ее расшифровки. Анализировать "запутанный" код протектора для этого совсем необязательно, однако, не все и не всегда бывает так радужно…



основной режим работы IDA Pro 5.x


Как сказал Юрий Харон "У меня возникает ощущение, что Ильфака зомбировали :)" и мыщъх с ним полностью согласен. Только Ильфак к этому мнению не прислушивается и больше всего озабочен увеличением объемов продаж, чтобы IDA Pro покупали даже те, кто ни хрена не смыслит ни в хакерстве, ни в дизассемблировании, и вместо реально работающего инструмента

хочет видеть красивое графическое представление потока выполнения программы, которое можно повесить на стену или показать начальству. Тем же самым занимается F-Secure, пугающая пользователей картинками в стиле "ню" (http://www.f-secure.com/weblog/archives/archive-092005.html, см. рис. 11).



nfo от хакерской группы


Методы обфускации активно используются продвинутыми упаковщиками типа Armadillo (ныне переименованного в Software Passport, который можно скачать с фирменного сайта http://siliconrealms.com/armadillo.shtml), eXtreme Protector (адрес сайта разработчиков http://www.oreans.com/xprotector/) и т. д. Большинство протекторов "запутывают" только свой собственный распаковщик, опасаясь вмешиваться в код защищаемой программы, т. к. это чревато неожиданным появлением глюков в самых различных местах. Какому программисту такая защита понравится? Тем не менее, обфускация процедур проверки серийного номера (ключевого файла) встречается достаточно часто. Обычно она реализуется в полуавтоматическом режиме, когда создатель защиты тем или иным образом взаимодействует с офускатором (например, пишет скрипт, который обфускатор транслирует в замусоренный машинный код, изображая из себя "неэффективный" компилятор).



официальный сайт протектора-обфускатора eXtreme Protector


Обфускация конкретно досаждает хакерам, препятствуя реконструкции алгоритмов и быстрому взлому защит, но эти проблемы меркнут перед ситуацией в антивирусной индустрии. Чтобы взломать программу, анализировать ее алгоритм в общем-то и необязательно. А вот обнаружить зловредный код (он же malware) без этого уже не удастся! Но заботы "стражей правопорядка" нас не волнуют, — пускай они сами с ними парятся, в конце концов за это им платят. Мы же сосредоточимся исключительно на методиках взлома "запутанных" программ, с которыми хакерам приходится сталкиваться все чаще и чаще.



попытка взлома программы, защищенной Armadill'oй, приводит к жутким ругательствам защиты


Предлагаемые здесь методики нацелены преимущество на программы, защищенные "испытательным сроком", то есть какое-то время программа должна запускаться в полнофункциональном режиме, не требуя ключа (а большинство программ именно на таких условиях и распространяются). Кому-то это условие может показаться излишне жестоким. А как же программы с заблокированными возможностями или программы, вообще не запускающиеся без ключа? Увы! В общем случае их взломать вообще невозможно! Если программист зашифровал часть программы стойким криптографическим алгоритмом, то без знания ключа до заблокированных возможностей хакеру уже будет не дотянуться! Правда, если у него есть хотя бы один-единственный ключ (несколько хакеров купили программу в складчину), то ситуация заметно упрощается, но в этом случае проще распространять сам ключ, чем ковыряться в недрах запутанного кода. Кстати, какими же все-таки приемами запутывания пользуются обфускаторы?



результат работы плагина


Более сложные обфускаторы "перемешивают" код, закручивая поток управления в запутанную спираль условных/безусловных переходов, использующих технику "перекрытия" команд: некоторые байты принадлежат сразу двум, а в некоторых случаях и трем (!) машинным инструкциям, что "ослепляет" дизассемблеры, заставляя их генерировать неполный и неправильный листинг!



визуализация потока выполнения


Впрочем, в интерактивном режиме (хвала IDA Pro) дизассемблировать код все-таки возможно, но очень уж утомительно. Лучше воспользоваться трассером, генерирующим листинг реально выполняемых машинных команд. Заодно это позволяет избавиться от части мусора и "мертвого" кода, но о трассерах мы еще поговорим, а пока вернемся к дизассемблерам. Рассмотрим фрагмент листинга, сгенерированный IDA Pro:

.adata:0043400E loc_43400E:                     ; CODE XREF: .adata:00434023j

.adata:0043400E                                 ; .adata:loc_43401Aj

.adata:0043400E            mov    eax, 0EBB907EBh

.adata:00434013

.adata:00434013 loc_434013:                     ; CODE XREF: .adata:loc_43401Dj

.adata:00434013            seto   bl            ; ß прыжок в середину команды

.adata:00434016            or     ch, bh

.adata:00434018            jmp    short loc_434025

.adata:00434018

.adata:0043401A loc_43401A:                     ; CODE XREF: .adata:00434009j

.adata:0043401A            repne jmp short near ptr loc_43400E+4

.adata:0043401D

.adata:0043401D loc_43401D:                     ; CODE XREF: .adata:loc_43400Cj

.adata:0043401D            jmp    short near ptr loc_434013+2



внешний вид анализатора LOCO


Самые извращенные (просите, самые совершенные) обфускаторы выполняют математические преобразования программного кода, а это кранты. В частности, команда "a++" может быть замена на эквивалентную ей конструкцию a += (sin(x)2 + cos(x)2), где sin/cos вычисляются "вручную" посредством самого "тупого" и громоздкого алгоритма, распознать в котором исходную формулу не сможет и академик.

Классические трассеры данных с такой задачей уже не справляются, ведь в этом случае граф не замыкается сам на себя и внесенная обфускатором избыточность не удаляется! Однако, в интерактивном режиме кое-что все-таки можно сделать. Смотрите, на входе мы имеем переменную "a", которая после долгих и загадочных манипуляций, увеличивается на единицу. Если код линеен и инвариантен по отношению к другим данным (то есть не зависит от них), хакер может смело заменить всю эту замутку на "a++". Главное — чтобы исследовательский инструмент обеспечивал удобный, наглядный и непротиворечивый способ визуализации данных. Пока таких инструментов нет, и это — задача будущего! (Ну? У кого чешутся руки? Кто жалуется, что ему не во что вгрызться зубами, что нет задачи достойной его интеллекта? Дерзайте!)



взлом программы с помощью точек останова в soft-ice и окна memory


В "тяжелых" случаях помогает слежение за данными, то есть опять-таки за дампом памяти. Хакер включает трассер и "втыкает" в окно "memory", анализируя характер изменения переменных. Переменные — это ключ ко всему. Они позволяют реконструировать алгоритм даже без знания кода, точнее говоря, существуют методики реконструкции кода по характеру изменения переменных. На данный момент они еще не очень хорошо отработаны и практически нигде не описаны, но в хакерских кулуарах уже идут оживленные разговоры. Это перспективное направление, в котором стоит копать.



трехмерное графическое


Спрашивается — а на хрена такая красивая трехмерная "репрезентация" вообще нужна? Что она реально

отображает? С другой стороны, от "низкоуровневого" дизассемблирования на уровне ассемблерных команд тоже не много пользы. Современные программы стали слишком большими, количество уровней абстракций измеряется многими десятками и "плотность" значимого кода неумолимо стремиться нулю. Программа, размером в сто мегабайт реализует простейший алгоритм, в былые времена с легкостью умещающихся в несколько килобайт. Какие там обфускаторы…

Отсюда — многочисленные попытки визуализации потока выполнения программы, поднимающие нас на уровень анализа структуры

кода, спускаясь к машинным командам только там, где это действительно необходимо. К сожалению, эта методика работает намного хуже, чем выглядит и только усложняет анализ. Стандартный режим дизассемблирования, к которому мы все привыкли, все еще присутствует в IDA PRO (во всяком случае пока), но уже не является режимом по умолчанию. Вот такая блин тенденция…



>>> Врезка ссылки по теме


[1]

"извините парни — столько было туманных бредней на всех форумах об этих новых Эллиптических Кривых DSA в последнем упаковщике Armadillo — мы просто должны быть сделать это… возьмите наш взлом как демонстрационный пример, доказывающий, что недостаточно просто реализовать сильное крипто, вам бы следовало понять, что необходимо реализовать его правильно… салют всем, кто сделал это возможным, особенном тем, кто пожертвовал немного мощности своего ЦП ;)"

[2]

команды "семейства" OR REG,REG (TEST REG,REG,ADD REG,0) воздействуют на флаги и к числу "совсем уж не значащих" никак не относятся;



и средств, направленных на затруднение


Обфускацией
(от английского obfuscation – буквально "запутывание") называется совокупность методик и средств, направленных на затруднение анализа программного кода. Существуют различные типы обфускаторов: одни занимаются интерпретируемыми языками типа Perl или PHP и "корежат" исходные тексты (удаляют комментарии, дают переменным бессмысленные имена, шифруют строковые константы etc), другие "перемалывают" байт-код виртуальных машин Java и .NET, что технически сделать намного труднее (РЕДАКТОРУ: уместно дать ссылку на статью по NET'у). Самые совершенные обфускаторы вламываются непосредственно в машинный код, "разбавляя" его мусорными инструкциями и выполняя целый ряд структурных (реже — математических) преобразований, изменяющих программу до неузнаваемости.
Вот об этом типе "запутывателей" мы и будем говорить. Фактически, это те же самые полиморфные генераторы, известные еще со времен Царя Гороха, только переоблаченные в новый термин, но сути дела это не меняет. Проблема в том, что полиморфный генератор может за считанные секунды сгенерировать хоть миллиард бессмысленных команд, перемешав их с несколькими килобайтами полезного кода — современные процессоры и жесткие диски это позволяют, пускай и с потерей эффективности, но на эффективность всем уже давно наплевать.
Удалять "мусор" в автоматическом режиме дизассемблеры еще не научились, а проанализировать мегабайты кода вручную методом "долота" — нереально. Нужны передовые методики реконструкции потока управления, расплавляющие "замусоренный" код и разделяющие его на "полезные" и "бесполезные" фракции, а их нет даже на уровне "теоретического понимания". И хотя кое-какие идеи на этот счет все же имеются (например, наложение маршрута трассировки на графы зависимостей по данным), до практической реализации еще далеко.


то совсем не про обфускацию,


Статья в общем- то совсем не про обфускацию, а про методики взлома "запутанных" программ, что совсем не одно и тоже. Будущее обфускации готовит хакерам совсем не радужные перспективы. С ходу можно назвать трансляторы Си-кода в байт-код Машин Тьюринга, Стрелок Пирса, Сетей Петри и многих других примитивных машин. Производительность современных процессоров это уже позволяет. В практическом плане это означает полный мрак стандартным методам анализа кода. Если вычистить мусор и удалить избыточность, внесенную "запутывателями" теоретически вполне возможно (но практически очень и очень сложно), то "распутать" байт-код Сетей Петри уже никак
невозможно! Это однонаправленный процесс и развернуть его на 180 градусов не сможет и сам Джа. Написать анализатор байт-кода, повышающий уровень абстракции— вполне возможно, вот только даже на таком уровне придется очень долго разбираться: что, как и куда.
Анализ типа "черного ящика" сулит намного большие перспективы, равно как и создание виртуальной машины, отрезающей защиту от внешнего мира. Дизассемблеры уже остановились в своем развитии и скоро вымрут как мамонты. В последних версиях IDA Pro не появилось ничего радикально нового, хуже того, наметилась признаки явной деградации, превратившие основное окно дизассемблера в… даже не знаю как "это" цензурно назвать. В общем смотрите сами (http://www.datarescue.com/idabase/5preview/index.htm, см. рис. 10).


Застенки виртуальной машины


Возвращаясь к разговору о триальных защитах, напомним, что мы имеем программу, которая запускается по меньшей мере один раз, а где один раз, там и два. Если пораскинуть мозгами и пошевелить хвостом, то можно создать такие условия, которые позволяет запускать программу неограниченное множество раз. Грубо говоря, мы как бы помещаем программу "под колпак" и подсовываем ей те данные, в которых она нуждается для продолжения своей  жизнедеятельности.

Известно, что виртуальные машины типа VMWare "автоматически" ломают триальные программы. Если программа введет счетчик запусков или запоминает дату инсталляции где-то внутри компьютера, то после прекращения работы она устанавливается на "чистую" виртуальную машину и продолжает работать как ни в чем ни бывало. Если дата окончания испытательного срока жестко прошита внутри программы, часы виртуальной машины переводятся "назад" и защита даже не подозревают, как жестоко ее обломали. Если программа "стучится" в Интернет, пытаясь подтвердить правоверность своей работы, виртуальная машина просто "отсекается" от Интернета. Короче говоря, виртуальные машины это хорошо, вот только… медленно, неудобно и громоздко.

app.exe|QueryValue|HKLM\Software\Licenses\{I5F218E3F24063708}|SUCCESS|0500000

app.exe|CreateKey |HKLM\Software\Licenses                    |SUCCESS|Key: 0xE132BB80

app.exe|SetValue  |HKLM\Software\Licenses\{I5F218E3F24063708}|SUCCESS|06000000

app.exe|CreateKey |HKLM\Software\Licenses                    |SUCCESS|Key: 0xE132BB80

app.exe|SetValue  |HKLM\Software\Licenses\{05F218E3F24063708}|SUCCESS|563EA80E0BA2A7A6