Различия

Здесь показаны различия между двумя версиями данной страницы.

Ссылка на это сравнение

articles:coverage [2017/09/05 02:55] (текущий)
Строка 1: Строка 1:
 +====== coverage ======
 +<​sub>​{{coverage.odt|Original file}}</​sub>​
 +
 +====== взлом через покрытие ======
 +
 +крис касперски ака мыщъх, no-email
 +
 +**мыщъх делится ### в этой статье описывается ### быстрый и эффективный метод взлома,​ основанный на анализе сравнения трасс программы до и после срабатывания защитного механизма,​ что позволяет мгновенно локализовать его дислокацию даже во многомегабайтной программе.**
 +
 +===== введение =====
 +
 +Самое сложное во взломе — это "​запеленговать"​ защитный код (зачастую представляющий собой тривиальный CALL CheckReg/​TEST EAX,​EAX/​Jx unregistered),​ все остальное — дело техники. В хакерском арсенале имеются и точки останова,​ и перекрестные ссылки и многие другие противозащитные приемы,​ однако,​ не всегда они оказываются эффективными и тогда приходится напрягать мозги в поисках новых путей, один из которых мыщъх выносит из своей норы, отдавая его на растерзание читателям.
 +
 +===== руководящая идея =====
 +
 +Рассмотрим воображаемую программу с ограниченным сроком использования (trail'​ом),​ которая некоторое время исправно работает,​ а потом выбрасывает мерзкий диалог с требованием о регистрации или выплевывает не менее грязное ругательство и завершает работу. Очевидно,​ что если мы найдем код, выводящий его на экран, нам останется только скорректировав Jx или добавив несколько NOP'​ов. Но вот как что именно нужно корректировать?​ Можно, конечно,​ поставить точку останова на API-функцию или пройтись по перекрестным ссылкам на ругательные строки,​ но… это недостаточно эффективно. Существуют десятки API-функций,​ ответственных за чтение текущей даты/​создание диалоговых окон, а текстовые строки часто бывают зашифрованы или хранятся в ресурсах…
 +
 +А что если сравнить трассы программы до и после истечения испытательного срока? Код, выводящий окно на экран, не выполняется в первом случае,​ но выполняется во втором! Таким образом,​ взлом сводится к анализу **покрытия** (//​coverage//​),​ измерением которого занимаются профилировщики и сопутствующие им утилиты. **Покрытым** называется код, хотя бы однажды получивший управление и, соответственно,​ наоборот.
 +
 +Покрытие позволяет взламывать и другие типы защитных механизмов. Например,​ nag-screen'​ы,​ выводимые через случайные или регулярные промежутки времени. Запускаем программу и тут же выходим из нее, опережая nag-screen, а в следующем прогоне терпеливо ждем его появления и сравниваем результаты покрытия (правда,​ это не спасет,​ если nag-screen выводится до запуска программы).
 +
 +Защиты,​ основанные на ключевом файле или серийном номере,​ легко ломаются через покрытие при наличии хотя бы одно-единственного валидного ключа. Некоторые могут спросить:​ а зачем ломать защиту,​ если ключ уже есть? Очень просто! Многие программы пытаются подтвердить подлинность ключа через Сеть, и, если запросы на подтверждение сыплются с разных IP-адресов,​ ключ объявляется "​пиратским"​ и программе посылается сигнал дезактивации. Покрытие позволяет нам мгновенно определить где именно происходит проверка и блокировать ее. (Во многих случаях проблема решается брандмауэром,​ но некоторые программы уже научились определять наличие сети, например,​ вызовом API-функции InternetGetConnectedState и, если Сеть есть, защита нагло требуют отключить брандмауэр для активации ключа).
 +
 +Аналогичным образом обстоят дела и с электронными ключами. Сравнивая трассу прогона программы с ключом и без ключа мы видим все проверки,​ после чего либо "​убиваем"​ их, либо пишем свой собственный эмулятор,​ отлаживаемый так же путем сравнения трасс.
 +
 +Покрытие дает в наши руки _надежный_ способ взлома,​ позволяя выявить _все_ проверки,​ даже если они идут из разных мест и с разной вероятностью. Мы получаем как бы "​рельефный слепок"​ с программы,​ легко обнаруживающий то, что без него обнаруживается только путем утомительной трассировки или кропотливого дизассемблирования.
 +
 +===== выбор инструментария =====
 +
 +Анализом покрытия занимаются многие утилиты:​ как коммерческие,​ так и бесплатные. К явным фаворитам относятся **Intel****Coverage****Tool**,​ **NuMega****TrueCoverage** и т. д. вплоть до **MS**** ****profile****.****exe**,​ из комплекта VisualStudio. Однако,​ все они ориентированы на работу с программами,​ имеющими исходные тексты,​ а "​голый"​ двоичный файл обрабатывать не в состоянии. К счастью,​ их достаточно легко обмануть,​ сгенерировав всю необходимую информацию на основе дизассемблерного листинга,​ полученного с помощью IDA Pro.
 +
 +{{coverage_Image_0.png?​553}}
 +
 +Рисунок 1 Intel VTune Performance Analyzer – одинизмощнейшихкоммерческихпрофилировщиков,​ работающийвпаресутилитой Intel Coverage Tool,​ определяющийпокрытие
 +
 +Фактически,​ профилировщику нужна всего лишь информация о номерах строк и именах функций. С исходным текстом он _не_ работает и для решения задачи нам достаточно добавить в ломаемый файл отладочную информацию,​ правда,​ для этого сначала придется разобраться с ее форматом. Некоторые профилировщики переваривают тривиальный map (который можно автоматически сгенерировать той же IDA Pro), но большинство работают со своими собственными форматами (как правило,​ недокументированными),​ завязанными на свои же собственные компиляторы (в случае IntelCoverageTool это IntelFortranCompiler или IntelC++ Compiler). К тому же IntelCoverageTool требует обязательного наличия IntelVTunePerformanceAnalyzer,​ который весит свыше 200 Мбайт и плохо работает под VMWare.
 +
 +{{coverage_Image_1.png?​552}}
 +
 +Рисунок 2 NuMega True Coverage – отличное средство для определения покрытие,​ но, увы, ориентированное сугубо на драйвера
 +
 +NuMega, не имеющая собственных компиляторов,​ выглядит в этом плане более соблазнительной,​ однако,​ версия True Coverage,​ входящая в состав Driver Studio,​ поддерживает работу _только_ с драйверами,​ а прикладной версии в Сети найти не удалось (а ведь раньше,​ пока NuMega еще не продалась Compuware, она ведь была!).
 +
 +{{coverage_Image_2.png?​553}}
 +
 +Рисунок 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)) if((strtol(buf,&​z,​16)==x)&&​(printf("​%s",​buf)|1))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 ((p1[a]==p2[b]) && ++flag) break;
 +
 +if (!flag) PRINT(p1[a],​f);​
 +
 +}
 +
 +}
 +
 +main(int c, char **v)
 +
 +{
 +
 +int f=0; char buf[XL]; FILE *f1, *f2; unsigned int *p1, *p2; char *x;
 +
 +
 +
 +if (c < 3) return printf("​USAGE:​ log-coverage-diff.exe file1 file2\n"​);​
 +
 +
 +
 +p1 = (unsigned int*) malloc(NX*4);​ p2 = (unsigned int*) malloc(NX*4);​
 +
 +
 +
 +f1 = fopen(v[1],"​rb"​);​if (!f1) return printf("​-ERR:​ open %s\x7\n",​v[1]);​
 +
 +f2 = fopen(v[2],"​rb"​);​if (!f2) return printf("​-ERR:​ open %s\x7\n",​v[2]);​
 +
 +
 +
 +fgets(buf, 1023, f1); fgets(buf, 1023, f2);
 +
 +
 +
 +while(f<​2 && !(f=0))
 +
 +{
 +
 +if (fgets(buf, XL-1, f1)) addnew(p1,​strtol(buf,&​x,​16));​ else f++;
 +
 +if (fgets(buf, XL-1, f2)) addnew(p2,​strtol(buf,&​x,​16));​ else f++;
 +
 +}
 +
 +
 +
 +if (fseek(f1, 0, SEEK_SET)) return printf("​-ERR:​ seek %s\x7\n",​v[1]);​
 +
 +if (fseek(f2, 0, SEEK_SET)) return printf("​-ERR:​ seek %s\x7\n",​v[2]);​
 +
 +
 +
 +printf("​\ndiff p1 -> p2\n"​);​diff(p1,​p2,​f1);​
 +
 +printf("​\ndiff p2 -> p1\n"​);​diff(p2,​p1,​f2);​
 +
 +
 +
 +return 0;
 +
 +}
 +
 +Листинг 1 листинг программы log-coverage-diff.c,​ сравнивающий протоколы трассировки отладчика OllyDbg и определяющий разницу в покрытии
 +
 +Компиляция осуществляется как обычно,​ то есть с ключами по умолчанию,​ в частности,​ при использовании MicrosoftVisualC++ командная строка выглядит так: "​cl.exe log-coverage-diff.c",​ а для создания оптимизированного варианта — "​cl.exe /​Ox log-coverage-diff.c"​ Только не надо выделять весь текст и копировать его в среду разработки,​ а потом нажимать F7 (Build) и удивляться почему это программа не компилируется! Она и не должна компилироваться,​ поскольку по умолчанию MicrosoftVisualStudio создает проект на Си++, а это Си. Как говорится,​ почувствуйте разницу!
 +
 +{{coverage_Image_3.png?​553}}
 +
 +Рисунок 4 результат компиляции log-coverage-diff.c в MicrosoftVisualStudioIDE – 3 ошибки,​ 6 предупреждений
 +
 +{{coverage_Image_4.png?​553}}
 +
 +Рисунок 5 а из командной строки компиляция проходит нормально!
 +
 +На всякий случай (вдруг у кого под рукой не окажется компилятора) готовый исполняемый файл идет на прилагаемом диске.
 +
 +===== демонстрационный пример взлома =====
 +
 +Давайте,​ в качестве упражнения напишем простейшую триальную защиту и попробуем ее взломать методом сравнения покрытий до истечения испытательного срока и после. Исходной код подопытной программы может выглядеть,​ например,​ так, как показано в листинге 2. Срок "​годности"​ задается константами ye (год), me (месяц),​ md (день), которые должны быть установлены в соответствии с текущей датой (так, чтобы программа еще работала).
 +
 +int expired=0;
 +
 +#define md 3
 +
 +#define me 8
 +
 +#defineye 2006
 +
 +foo()
 +
 +{
 +
 +SYSTEMTIMEsy;​
 +
 +
 +
 +GetSystemTime(&​sy);​
 +
 +if ((sy.wYear&​0xF)*sy.wMonth*sy.wDay > (ye&​0xF)*me*md) expired=1;
 +
 +}
 +
 +main()
 +
 +{
 +
 +printf("​coverage trial\n"​);​
 +
 +foo();
 +
 +if (expired)
 +
 +{
 +
 +printf("​trial has expired!\n"​);​
 +
 +}
 +
 +else
 +
 +{
 +
 +printf("​trial ok\n"​);​
 +
 +}
 +
 +}
 +
 +Листинг 2 листинг программы coverage.c, защищенной испытательным сроком,​ которую мы будем ломать
 +
 +Компиляция осуществляется так же, как и в предыдущем случае,​ но лучше использовать готовый файл с диска — это гарантирует совпадение всех смещений и если вдруг в ходе взлома возникнут затруднения,​ всегда будет ясно где лыжи и откуда копать.
 +
 +Запускаем coverage.exe,​ убеждаемся,​ что работает (программа пишет: trial ok), а затем переводим дату за конец испытательного срока и запускаем вновь. Ага! trial has expired! Значит,​ надо ломать!
 +
 +{{coverage_Image_5.png?​428}}
 +
 +Рисунок 6 демонстрационная программа coverage.exe,​ с ограниченным сроком использования
 +
 +Возвращаем дату на прежнее место и загружаем coverage.exe в OllyDbg. Затем в "​View"​ выбираем "​Runtrace"​ и нажимаем <​SHFT-F10>​ для вызова контекстного меню в котором говорим "​logtofile"​ и в появившемся диалоговом окне вводим имя файла — "​coverage1.txt"​. После чего нажимаем <​CTRL-F11>​ (TraceInto) и дожидаемся завершения работы программы. На диске образуется файл coverage1.txt размером ~3 Мбайта. В том же самом окне "​Runtrace"​ нажимаем <​SHIFT-F10>​ еще раз и закрываем лог-файл ("​closelogfile"​).
 +
 +Перезапускаем программу по <​CTRL-F2>,​ переводим системную дату вперед (из командной строки командной date или щелкнув по часам в правом нижнем углу), возвращаемся в окно "​RunTrace",​ нажимаем <​SHIFT-F10>,​ "​logtofile"​ и вводим имя "​coverage2.txt"​. Запускаем трассировку по <​CTRL-F11>​ и по завершении выполнения программы закрываем log-файл (<​SHIFT-F10>,​ "​closelogfile"​).
 +
 +{{coverage_Image_6.png?​552}}
 +
 +Рисунок 7 определение покрытия (RunTrace) с помощью отладчика OllyDbg
 +
 +Теперь у нас есть два log-файла,​ которые можно скормить утилите log-coverage-diff.exe,​ чтобы увидеть различия в покрытии до истечения испытательного срока и после:
 +
 +$log-coverage-diff.exe coverage1.txt coverage2.txt
 +
 +diffp1 -> p2
 +
 +00401076 MainPUSHcoverage.00406054
 +
 +0040107BMainCALLcoverage.00401085
 +
 +00401080 MainADD ESP,4
 +
 +diff p2 -> p1
 +
 +0040103B MainMOV DWORD PTR DS:​[4068F0],​1
 +
 +00401067 MainPUSH coverage.00406040
 +
 +0040106C MainCALL coverage.00401085
 +
 +00401071 MainADD ESP,4
 +
 +00401074 MainJMP SHORT coverage.00401083
 +
 +Листинг 3 результат сравнения покрытия программы до и после истечения испытательного срока
 +
 +Проанализировав полученный результат (см. листинг 3),​ даже начинающий хакер может долгаться,​ что двойное слово по адресу 4068F0h представляет собой глобальный флаг истечения испытательного срока, а команда "​MOV DWORD PTR DS:​[4068F0],​ 1"​ — как раз и есть та редиска,​ которая его взводит,​ когда испытательный срок заканчивается. Чтобы заполучить программу в бессрочное использование достаточно заменить "​MOV DWORD PTR DS:​[4068F0],​ 1"​ на "​MOV DWORD PTR DS:​[4068F0],​ 0"​.
 +
 +Загружаем coverage.exe в hiew, дважды нажимаем <​ENTER>​ для перехода в дизассемблерный режим, давим <F5> (Goto) и вводим адрес команды MOV, предварив его символом точки, чтобы указать hiew'​у,​ что это именно адрес, а не смещение от начала файла — "​.40103B",​ затем нажимаем <F3> для активации режима редактирования и <​ENTER>​ для ввода ассемблерной команды. hiew автоматически копирует текущую инструкцию в строку редактирования и нам остается всего лишь заменить "​1"​ на "​0",​ нажать <F9> для сохранения изменений в файле и после выхода из hiew'​а его можно запускать невзирая на текущую дату.
 +
 +{{coverage_Image_7.png?​552}}
 +
 +Рисунок 8 взлом программы coverage.exe в hiew'e
 +
 +Как вариант,​ можно открыть coverage.exe в IDA Pro и посмотреть,​ что находится по адресам,​ выданным утилитой log-coverage-diff.exe и вокруг них. Возможно,​ мы найдем более быстрый путь взлома:​
 +
 +0401000 sub_401000proc near
 +
 +0401000pushebp
 +
 +0401001movebp,​ esp
 +
 +0401003subesp,​ 10h
 +
 +0401006leaeax,​ [ebp+SystemTime]
 +
 +0401009pusheax;​ lpSystemTime
 +
 +040100Acallds:​GetSystemTime
 +
 +0401010movecx,​ dword ptr [ebp+SystemTime.wYear]
 +
 +0401013andecx,​ 0FFFFh
 +
 +0401019andecx,​ 0Fh
 +
 +040101Cmovedx,​ dword ptr [ebp+SystemTime.wMonth]
 +
 +040101Fandedx,​ 0FFFFh
 +
 +0401025imulecx,​ edx
 +
 +0401028moveax,​ dword ptr [ebp+SystemTime.wDay]
 +
 +040102Bandeax,​ 0FFFFh
 +
 +0401030imulecx,​ eax
 +
 +0401033cmpecx,​ 90h
 +
 +0401039jleshort loc_401045
 +
 +**__040103Bmovdword_4068F0,​ 1__**
 +
 +0401045 loc_401045:
 +
 +0401045movesp,​ ebp
 +
 +0401047popebp
 +
 +0401048retn
 +
 +0401048 sub_401000endp
 +
 +0401048
 +
 +0401049 _mainproc near
 +
 +0401049pushebp
 +
 +040104Amovebp,​ esp
 +
 +040104Cpushoffset aCoverageTrial ; "​coverage trial\n"​
 +
 +0401051call_printf
 +
 +0401056addesp,​ 4
 +
 +0401059callsub_401000
 +
 +040105Ecmpdword_4068F0,​ 0
 +
 +0401065jzshort loc_401076
 +
 +**__0401067__****____****__pushoffset aTrialHasExpire ; "trial has expired!\n"​__**
 +
 +**__040106C__****____****__call_printf__**
 +
 +**__0401071addesp,​ 4__**
 +
 +**__0401074jmpshort loc_401083__**
 +
 +0401076 loc_401076:
 +
 +**__0401076pushoffset aTrialOk ; "trial ok\n"​__**
 +
 +**__040107Bcall_printf__**
 +
 +**__0401080addesp,​ 4__**
 +
 +0401083
 +
 +0401083 loc_401083:
 +
 +0401083popebp
 +
 +0401084retn
 +
 +Листинг 4 фрагмент дизассемблерного листинга ломаемой программы coverage.exe,​ различия в покрытии до и после истечения испытательного срока выделены подчеркиванием
 +
 +Как можно видеть,​ сердце защитного механизма сосредоточено в процедуре sub_401000, которая сравнивает текущую дату с жестко прошитой (hard-coded) константой,​ после чего делает jle на loc_401045 (испытательный срок еще не истек) или… не делает и тогда выполняется команда mov dword_4068F0,​ 1,​ устанавливающая глобальный флаг истечения испытательного срока, проверяемый лишь в одном-единственном месте — по адресу 040105Eh, за которым идет условный переход 0401065 jz short loc_401076,​ выбирающий какое из двух сообщений выводить на экран (в реальных программах флаг регистрации обычно проверяется многократно). Если мы заменим jle short loc_401045 на jmp short loc_401045,​ флаг истечения испытательного срока никогда не будет взведен!
 +
 +{{coverage_Image_8.png?​553}}
 +
 +Рисунок 9 наглядное отображение расхождения в покрытии исследуемой программы в IDA Pro, выполненное специальным скриптом и оформленное в виде комментариев
 +
 +Для наглядности можно написать несложный скрипт на IDA-Си, считывающий результат работы log-coverage-diff.exe и отмечающий различия в покрытиях каким ни будь символом,​ например,​ "​*1*"​ будет означать,​ что данная машинная команда выполнялась только в первом прогоне,​ а "​*2*"​ – только во втором (см. рис. 9). Подробности о синтаксисе IDA-Си и технике написания скпитов — в книге "​образ мышления — IDA PRO"​ электронную копию которой можно бесплатно скачать с мыщъх'​иного сервера ftp://​nezumi.org.ru
 +
 +===== заключение =====
 +
 +Подведем итог — мы познакомились с новым способом взлома,​ написали несколько полезных утилит и обогатились свежими идеями. В общем, неплохо провели время и теперь можем уверенно двигаться вперед,​ переходя к взлому реальных защит!
 +
 +