coverage
взлом через покрытие
крис касперски ака мыщъх, no-email
мыщъх делится ### в этой статье описывается ### быстрый и эффективный метод взлома, основанный на анализе сравнения трасс программы до и после срабатывания защитного механизма, что позволяет мгновенно локализовать его дислокацию даже во многомегабайтной программе.
введение
Самое сложное во взломе — это «запеленговать» защитный код (зачастую представляющий собой тривиальный CALL CheckReg/TEST EAX,EAX/Jx unregistered), все остальное — дело техники. В хакерском арсенале имеются и точки останова, и перекрестные ссылки и многие другие противозащитные приемы, однако, не всегда они оказываются эффективными и тогда приходится напрягать мозги в поисках новых путей, один из которых мыщъх выносит из своей норы, отдавая его на растерзание читателям.
руководящая идея
Рассмотрим воображаемую программу с ограниченным сроком использования (trail'ом), которая некоторое время исправно работает, а потом выбрасывает мерзкий диалог с требованием о регистрации или выплевывает не менее грязное ругательство и завершает работу. Очевидно, что если мы найдем код, выводящий его на экран, нам останется только скорректировав Jx или добавив несколько NOP'ов. Но вот как что именно нужно корректировать? Можно, конечно, поставить точку останова на API-функцию или пройтись по перекрестным ссылкам на ругательные строки, но… это недостаточно эффективно. Существуют десятки API-функций, ответственных за чтение текущей даты/создание диалоговых окон, а текстовые строки часто бывают зашифрованы или хранятся в ресурсах…
А что если сравнить трассы программы до и после истечения испытательного срока? Код, выводящий окно на экран, не выполняется в первом случае, но выполняется во втором! Таким образом, взлом сводится к анализу покрытия (coverage), измерением которого занимаются профилировщики и сопутствующие им утилиты. Покрытым называется код, хотя бы однажды получивший управление и, соответственно, наоборот.
Покрытие позволяет взламывать и другие типы защитных механизмов. Например, nag-screen'ы, выводимые через случайные или регулярные промежутки времени. Запускаем программу и тут же выходим из нее, опережая nag-screen, а в следующем прогоне терпеливо ждем его появления и сравниваем результаты покрытия (правда, это не спасет, если nag-screen выводится до запуска программы).
Защиты, основанные на ключевом файле или серийном номере, легко ломаются через покрытие при наличии хотя бы одно-единственного валидного ключа. Некоторые могут спросить: а зачем ломать защиту, если ключ уже есть? Очень просто! Многие программы пытаются подтвердить подлинность ключа через Сеть, и, если запросы на подтверждение сыплются с разных IP-адресов, ключ объявляется «пиратским» и программе посылается сигнал дезактивации. Покрытие позволяет нам мгновенно определить где именно происходит проверка и блокировать ее. (Во многих случаях проблема решается брандмауэром, но некоторые программы уже научились определять наличие сети, например, вызовом API-функции InternetGetConnectedState и, если Сеть есть, защита нагло требуют отключить брандмауэр для активации ключа).
Аналогичным образом обстоят дела и с электронными ключами. Сравнивая трассу прогона программы с ключом и без ключа мы видим все проверки, после чего либо «убиваем» их, либо пишем свой собственный эмулятор, отлаживаемый так же путем сравнения трасс.
Покрытие дает в наши руки _надежный_ способ взлома, позволяя выявить _все_ проверки, даже если они идут из разных мест и с разной вероятностью. Мы получаем как бы «рельефный слепок» с программы, легко обнаруживающий то, что без него обнаруживается только путем утомительной трассировки или кропотливого дизассемблирования.
выбор инструментария
Анализом покрытия занимаются многие утилиты: как коммерческие, так и бесплатные. К явным фаворитам относятся IntelCoverageTool, NuMegaTrueCoverage и т. д. вплоть до MS profile.exe, из комплекта VisualStudio. Однако, все они ориентированы на работу с программами, имеющими исходные тексты, а «голый» двоичный файл обрабатывать не в состоянии. К счастью, их достаточно легко обмануть, сгенерировав всю необходимую информацию на основе дизассемблерного листинга, полученного с помощью IDA Pro.
Рисунок 1 Intel VTune Performance Analyzer – одинизмощнейшихкоммерческихпрофилировщиков, работающийвпаресутилитой Intel Coverage Tool, определяющийпокрытие
Фактически, профилировщику нужна всего лишь информация о номерах строк и именах функций. С исходным текстом он _не_ работает и для решения задачи нам достаточно добавить в ломаемый файл отладочную информацию, правда, для этого сначала придется разобраться с ее форматом. Некоторые профилировщики переваривают тривиальный map (который можно автоматически сгенерировать той же IDA Pro), но большинство работают со своими собственными форматами (как правило, недокументированными), завязанными на свои же собственные компиляторы (в случае IntelCoverageTool это IntelFortranCompiler или IntelC++ Compiler). К тому же IntelCoverageTool требует обязательного наличия IntelVTunePerformanceAnalyzer, который весит свыше 200 Мбайт и плохо работает под VMWare.
Рисунок 2 NuMega True Coverage – отличное средство для определения покрытие, но, увы, ориентированное сугубо на драйвера
NuMega, не имеющая собственных компиляторов, выглядит в этом плане более соблазнительной, однако, версия True Coverage, входящая в состав Driver Studio, поддерживает работу _только_ с драйверами, а прикладной версии в Сети найти не удалось (а ведь раньше, пока NuMega еще не продалась Compuware, она ведь была!).
Рисунок 3 MS Profile.exe – простейший профилировщик, умеющий в том числе определять и покрытие, но работающий только с перемещаемыми программами
MS Profiler.exe способен профилировать только перемещаемые программы, (т. е. такие, чья FIXUPTABLE не пуста), а большинство двоичных файлов неперемещаемы и хотя существует множество эвристических алгоритмов, восстанавливающих перемещаемые элементы (в частности, их можно встретить в дамперах), возможности MS Profile.exe не стоят того, чтобы с ним трахаться.
алгоритмы определения покрытия
Существует по меньшей мере два алгоритма определения покрытия: трассировка и точки останова. Исполнение программы в пошаговом режиме дает нам полную трассу прогона, «разоблачая» адреса всех выполняемых инструкций. К сожалению, защищенные программы как правило не позволяют себя трассировать, к тому же это медленно и непроизводительно.
Другой алгоритм заключается во внедрении программных точек останова в начало _всех_ машинных инструкций. Каждая, однажды сработавшая точка останова, снимается, а соответствующий ей код объявляется покрытым. Именно так большинство профилировщиков и работает (с той лишь разницей, что они внедряют точки останова в начало _группы_ инструкций, представляющей ту или иную строку исходного кода — вот зачем им нужна информация о строках!). Быстродействие (за счет снятия точек останова) намного выше, чем при трассировке, особенно в циклах, но… программную точку останова _очень_ легко обнаружить, поскольку она представляет собой байт CCh, записываемый поверх инструкции и выявляемый простым подсчетом контрольной суммы, что делает его малопригодным для анализа защитных механизмов.
К сожалению, аппаратных точек останова всего четыре и потому единственным надежным способом определения покрытия остается полная эмуляция процессора (чтобы не писать эмулятор с нуля можно воспользоваться уже готовым, например, BOCHS, распространяемом в исходных текстах — легким взмахом «напильника» мы переделаем его во что угодно!). Как вариант, можно использовать грубый метод покрытия по страницам — помечаем все страницы процесса как недоступные, а потом ловим исключения и определяем покрытие. При условии, что защитный код расположен в отдельной процедуре (как чаще всего и происходит), мы с определенной степенью вероятности ее «запеленгуем», а, быть может, и нет. Все зависит от того сколько места защитный код занимает. Во всяком случае, одиночные jx'ы данный метод не обнаруживает в принципе, но при желании его можно доработать.
Усовершенствованный алгоритм помечает все страницы недоступными как и прежде, но вот при возникновении исключения, мы не снимаем атрибут PAGE_NOACCESS, а смотрим на EIP, декодируем машинную инструкцию, устанавливаем аппаратную точку останова за ее концом (если это условный переход, мы либо анализируем флаги, определяющие условия его срабатывания, либо устанавливаем две аппаратные точки: за концом перехода и по адресу, на который он указывает). Временно снимаем атрибут PAGE_NOACCESS, выполняем инструкцию на живом процессоре, помечая ее покрытой, а затем вновь возвращаем PAGE_NOACCESS на место, который окончательно снимаем только тогда, когда покрываются все инструкции, принадлежащие данной странице. Такой подход обеспечивает приемлемую скорость выполнения и не слишком сложен в реализации (фактически нам достаточно реализовать дизассемблер длин инструкций). Правда, ломаемая программа может легко обнаружить и аппаратные точки, и атрибуты PAGE_NOACCESS, но большинство защит этим не занимаются.
как мы будем действовать
Поскольку, написание своего собственного профилировщика отнимает много сил и времени, на первых порах можно ограничиться использованием отладчика, поддерживающего трассировку в лог и обходящего большинство типовых защит. На эту роль хорошо подходит знаменитый OllyDbg для которого существует множество плагинов, скрывающих присутствие отладчика от глаз практически всех защитных механизмов.
Запуская программу до и после срабатывания защиты, мы получаем два лога трассировки. Считываем адреса машинных команд, загоняем их в массив «своего» лога, сортируем, убираем повторы, а затем сравниваем оба массива. Легко поискать, что сравнение сводится к поиску, который в отсортированном массиве быстрее всего осуществляется методом «вилки», причем сравнивать нужно не только первый массив со вторым, но и второй с первым, для удобства выводя рядом с адресами мнемоники машинных команд, которые можно вытащить прямо из лога.
Ниже приведен листинг упрощенной версии анализатора логов, не использующей сортировку и выполняющий сравнение путем последовательного поиска, а потому объемные логи обрабатывающий довольно долго (впрочем, с учетом быстродействия современных процессоров времени потребуется не так уж и много):
#defineXL1024 максимальная длина одной строки лога #defineNX(1024*1024) максимальное количество строк лога
добавить новый адрес в массив, если он не встречался ранее
addnew(unsigned int *p, unsigned int x)
{
int a;
for (a = 1; a<*p; a++) if (p[a] == x) return 0; if (a == NX) return -1;
p[0]++; p[a] = x; return 1;
}
вывод результатов на экран PRINT(unsigned int x, FILE *f) { char *z; char buf[XL]; while(fgets(buf,XL-1,f)) if1)break; } сравнение двух массивов адресов на предмет различий
diff(unsigned int *p1, unsigned int *p2, FILE *f)
{
int a, b, flag;
for (a = 1; a<*p1; a++)
{
for(b = 1, flag = 0;b<*p2; b++) if 2)





