Жанр: Учеба

Главы книги о взломе

Kris Kasperski 2:5063/61.8
Главы книги о взломе
Хаккеры
- Инструменты управления государством
всегда должны быть остро отточены и
готовы к употреблению. Власть держится
на страхе".
Ф. Херберт "Мессия Дюны"
За последние годы трактовка "хакер" сильно изменилась. Это в первую
очередь связано с появлением "хакерв-вандалов". Точнее совсем не хакеров,
в изначальном понимании этого слова, но называющими себя именно так.
Персонализация компьютера привела к тому, что доступ к последним получили
практически все желающие независимо от их возраста, наклонностей, уровня
знания и психологической уравновешенности.
Иными словами, если в высших учебных заведениях были (к сожалению
действительно, именно были) преимущественно люди культурные и образованные,
а на производствах по крайней мере по-просту серьезные, и любого вандала
легко было уволить, лишив его этим доступа к ЭВМ, то персональный компьютер
попадал в руки к самым разным людям.
Среди них были не только увлеченные фанаты, но и всевозможные маньяки.
К чему это привело мы как раз и наблюдаем сегодня. Компьютерный мир из
доброго сообщества быстро превратился в кальку нашего общества, где
незамкнутые ворота грозили грабежом, а за каждым темным углом мог стоять
человек с ножом.
Появились не только компьютерные воры, но и просто хулиганы, которым
приносило удовольствие доставлять пакость ближнему своему. Они уничтожали
информацию, блокировали компьютерные системы и писали так называемые
"троянские программы". Незащищенная персоналка оказалась питальной средой
для всех этих деяний. Но тогда еще никому и в голову не могла
придти мысль о разграничении доступа на однопользовательских маломощных
персональных компьютерах. Да и если бы она и пришла реализовать последнюю
без поддержки со стороны микропроцессора не предствалялось возможным.
Операнионная система просто не могла бы препятствовать обращению
прикладной задачи к портам ввода\вывода и своему коду. Из это следовало,
что злоумышленник мог как угодно модифицировать код операционной системы,
отключая все защитные механизмы или напрямую работать с оборудованием
уничтожая или модифицируя любые сектора на диске.
В то время компьютерный мир еще не знал ни вирусов, ни вандалов.
Сама Intel не верила, что ее процессоры будут использоваться в "серьезных"
машинах (уж слишком смехотворными казались их вычислительные способности).
Конечно, большой ошибкой со стороны IBM было продвигать ничем не защищенный
компьютер на рынок машин для среднего и малого бизнеса. Впрочем, в то время
никто не мог предствавить к чему в конечном счете приведет.
Парк IBM-совместимых машин стремительно рос и когда компьютерный мир
осознал какого он джина выпустил из бутылки, что-то менять стало уже
поздно. Ситуацию могла спасти только принципиально новая архитектура
оперционной системы и микропроцессора. Но в этом случае обесценилось все
существующие программное обеспение, а на такие жертры пойти никто не мог.
Впрочем со временем появились защищенные операционные системы типа
WINDOWS NT, но они не получили ожидаемой популярности именной по причинам
плохой совместимости с уже существующим программным обеспечением.
Большинство до сих пор работает на ничем не защищенной WINDOWS 95\WINDOWS
98. Впрочем, надо признать, что последняя хоть и с большим запозданием, но
все же имеет некоторые защитные механизмы, предотващающие некоторые
деструктивные действия. К сожалению последнее достигается ценой ухудшенной
совместимости. Многие программы могут не работать или работаь не правильно.
Действительно, в этом мире за все нужно платить.
Но оставим дискуссию о проблемме незащищенности "народной" операционной
системы WINDOWS 9x - словами горю не помощешь, и если вам требуется
безопасность, то переходите хотя бы на WINDOWS NT. Это не решит всех
проблемм, но по крайней мере позволит спать спокойно.
Рассмотрим причину всего этого - людей, умышленно пишущих деструктивные
программы и похищающих не принадлежащую им информацю. Какие же мотивы
толкают на это? Современная психология утверждает, что во всем виновата
жажда власти или превосходства над другими, что впрочем одно и то же.
Компьютерный вандал ну ничем ни отличается от уличного хулигана и причины
их поведения сходны - неудовлетворенность, агрессивность, озлобленность...
Таких людей можно только пожалеть. В какой-то мере именно общество
формирует последних. И именно оно же и пожинает плоды своих деяний. К
сожалению при этом страдают ни в чем невинные люди.
Компьютер позволяет ощутить превосходство над окружающими. Власть над
простыми пользователями, не спосбодными распозать и адекватно
прореагировать на зловредную программу кому-то доставляет изрядное
наслаждение.
К счастью подавляющие большинство последних обладают весьма смутными
познаниями в программировании и они бы не предстваляли никакой угрозы, не
будь пользователи так беспечны в отношении собственной безопасности. По
разным оценкам ущерб от вандалов составляет от 5 до 10 процентов от общего
числа случаев потери информации (при этом более 50% инцендентов происходит
по вине ошибок или неверных действий пользователя).
Последнее, кстати, служит отличным самооправданием для злоумышленников,
называющих себя "санитарами компьютерного леса". Они создают программы
наподобии "кракера интернета" (а на самом деле форматирующими жесткий
диск) и этим пытаются "лечить" так называемых "ламеров" (т.е.
низкоквалифицированных пользователей) от компьютерной глупости, а заодно и
жадности. Другими словами "компьтерный крестовый поход" во благо святого
дела.
Неграмотных пользователей конечно нужно учить, но не такими же приемами!
Бесполезность террора уже многократно доказана историей, но он с завидным
упорством вспыхивает не только в компьютерном, но к сожалению, и в реальном
мире. Очень маловероятно, что бы в течении ближайших ста лет хоть
что-нибудь изменилось. Проблемма вандалов (и не только компьютерных) не в
них самих, а в обществе, т.е. в нас всех.
Взломанные системы и отформатирование винчестеры это месть в никуда за
свою поломанную судьбу. Никакие законы, тюрьмы ничего не смогут изменить.
На место одно пойманного и наказанного вандала завтра придет десяток таких
же. Точнее не придет, а выпадет из оттолкнувшего их общества. Трудно судить
озлобленного и ожесточенного человека, садящегося после этого за
клавитатуру и пришущего злобного вируса, призваного наказать обидившее его
человечество.
Силой невозможно противостоять силе. И нетерпимсть окружающих к
вирусописателям и стремлениее "начистить последним функель" или
"разобраться" только подливает масла в огонь. Человек, отвечающий агрессией
на действя вирусописателя уже не может заслуживать жалости. Чем отличается
он, обиженный и готовый идти и избивать первого подозреваемого
вирусописателя не особо утруждая себя выяснением насколько тот виноват?
Фактически вандалов гораздо больше, и то что часть из них не пишет ни
вирусов, ни троянских программ - чистая случайность. Просто они еще не
дошли до этой грани, но может ли кто утвеждать, что этого не будет в
будущем? Очень сомнительно...
Нет, я не собираюсь здесь никого оправдывать, но всем своим авторитетом
призываю остановится и не вершить самосуд, а просто внимательнее относится
ко всем проходящим через ваши руки програмам. Альтруизм, конечно, не
наставит вирусописателя на путь истинный, но по крайней мере не породит
новой агрессии и новых злоумышленников. Вообще вандализм всега имеет
глубокие социально-писихологиские корни и прична озлобленности на общество
у каждого своя.
Невозможно повести ни какого обобщения. Каджый случай требует
отдельного рассмотрения. Человеком могут двигать разные мотивы а не только
месть. Быть может он одинок и хочет внимания, которому ему так не хватает?
Конечно, садится и писать вредоносные программы это не выход, но не
забывайте, что разговор идет о людях с тем или иными психическими
отклонениями, в которых они сами ни в чем ни виноваты.
Исправить вандалов невозможно. Можно перевосписать (или проще сломать
- чем и занимается Евгений Касперский с особо "доставшими" его
вирусописателями) одного человека, но ведь на его место завта придет
другой. Вандалы - это следствие болезни нашего общества и только исправляя
последнее можно добиться хоть каких-то результатов. Пока же мы боремся не с
причиной, а со следствем. На ум приходит извествных анекдот "бороться с
коммунистической партией под ее руководством".
В самом деле компьютерные преступники как это не парадоксально, но
принесли больше пользы, чем вреда и в экономическом и социальном плане. Они
обеспечили рабочие места специалистам по антивирусам и вообще компьютерной
безопасности. При этом оборот компьютерынх фирм неизмеримо превысл убытки
от вирусных атак. Вандалы стали приносить ошутимый доход и оказалсь в
самом деле весьма полезными. Забавно, но при этом на борьбу с ними ни у
кого до сих пор не находится денег.
К счастью человеку довльно скоро надоедает делать мелкие пакости
окружающим. При этом одни просто "уходят со сцены", но есть и такие,
которые отваживаются на глубокое изучение тонкостей операцинных систем,
компьютерного "железа", криптографии, математики с тем, что бы проникать в
действительно хорошо защищенные системы, писать сложно-шифруемые вирусы, т.
е. иными словами наносить уже значительный и крупномашстабнй вред. При этом
мы сталкиваемся с таки интересным явлением как "воспитание образоанием".
Образование неразрывно связано с культурой и духовым
воспитанием. При этом человек заметно добреет и мстить никому уже не
хочется. К сожалению, он все еще остается духовно искалечен и от объявления
войны обществу его отделяет очень тонкая грань, которую он может в любую
секнуду перешагнуть.
Могу с полной отвественностью заявить, что просто чудо, что до сих пор
такого инцедента еще не произошло. Впрочем, оно и понятно, знания дают
чувство уверенности в себе и стремление что-то кому-то доказывать пропадает
само собой.
Все сказанное выше навряд ли относится к хакерам - по определению
профессионалам. Среди последних за редкими исключениями практически не
встречается вандалов или злых шутников. Однако, профессионалами не
рождаются ими становятся. При этом немногим удается избежать "вирусной
болезни" в смысле увлечения написанием вирусов. Впрочем, написание еще не
подразумевает распространения.
Ну а мелким кракерстом занимаются почти все. Действительно, на
некотором этапе изучаения ассемблера и системного программирования трудно
найти более достойное применение своим знаниям. Для серьезных вещей еще не
хватает мастрерства, а текстовой редактор большинству писать все же скучно.
Впрочем, вреда от таких экспериментов было немного, если он вообще и был. В
вирусах оказалось действительно что-то притягательное. Конечно,
спекулятивно считать их хоть каким-то подобием живого огранизма, но зато
писать очень и очень интересно.
То же самое можно сказать и о взломе программ. И в том и другом случае
многих сознательно или подсознательно привлекает власть. Вряд ли последнее
утверждение нуждается в поясннении.
Kris Kasperski 2:5063/61.8 13 Mar 99 15:16:00
Очередной фрагмент из книги
Отречение: за неимением времени я предоставляю неотредактированный
вариант главы "Логическая защита\ограничение возможностей". Прошу извинить,
если допушенные ошибки кого-то раздражают. Все претензии высказывать на
KPNC@Null.ru конструктивые предложения KPNC@Usa.net
ОГРАНИЧЕНИЕ ВОЗМОЖНОСТЕЙ
Многие незарегестрированные версии отличаются тем, что часть
возможностей последних заблокирована. Если программа предусматривает
регистрацию, то обычно больших проблемм при взломе не возникает. Совсем
другое дело, когда регистация не предусмотрена и в наше распоряжения дана
DEMO-версия с ограниченными возможносями. Иначе говоря есть две программы,
никак не связанные между собой - полная и демонстационная версия. Строго
говоря, очень верятно, что взлом последней окажется невозможным, поскольку
код, выполняющий некоторые функции, в программе физически отсутствует.
Часто это оказывается безнадежным, но не всегда. Если нет никаких
других путей для получения легальной копии (например, ее автор
имигрировал в Штаты или запросил такое колличество денег, за которые лучшее
ее самому написатть), то можно решиться на такой отваждый шаг, как
воссоздать самостоятельно недостающий код. Это сложный и трудоемкий
процесс, требующий тщательного изучения алгоритма взаимодействия остального
кода с остутствующим.
Однако, чаще всего код все же физически присутствует, но просто не
получает управления. Например, просто заблокированы некоторые пункты меню,
как в file://CD/SRC/CRACK0D/Crack0D.exe Такое действительно встречается
очень часто и легко программируется. Все что нужно сделать программисту это
в редакторе ресурсов пометить некоторые элементы управления или меню как
'Disabled'. Но все что просто делается, так же просто и ломается.
Необходимо воспользоваться любым редактором ресурсорв. Я предпочитаю
пользоваться 'Symantex ResourceStudio 1.0', однако неплохо подойдет и любой
другой. Загрузим в него наш файл. Дальнейшие действия зависият от
интерфейса выбранной программы, и не должны вызвать затруднений, за
исключением тех ситуаций, когда выбранный редактор не поддерживает
используемого формата ресурсов или некоректно работает с ними. Например, с
помощью Borland Resource WorkShop мне так и не удалось выполнить эту
операцию. Он необратимо портил ресурс диалога, хотя с разблокированем меню
справился отлично.
Что бы разблокировать элементы управления или меню, необходимо вызвать
свойства объекта и снять пометку 'Disabled' или 'Grayed', после чего
сохранить изменения. Запустим программу, что бы проверить нашу работу.
Получилось! Не исправив ни одного байта кода и даже не прибегая к помощи
дизассемблера и отладчика мы вломали это!
Удивительно, что такие защиты до сих пор существуют и не так уж и
редко встречаются. Психология разработчиков это воистину великая тайна.
Очень трудно понять на что они расчитывают. Однако, некоторые уже, видимо,
начинают догадываться, что нет ничего проще и приятнее, чем редактировать
ресурсы в исполняемом файле, поэтому прибегают к явным вызовам API типа
EnableWindow(false). Т.е. блокируют элементы управления непосредственно во
время работы. Разумеется, можно перехватить этот вызов отладчикам и удалить
защитный код. Именно так и поступит любой хакер и даже кракер. Рядовой же
пользователь остановит свой выбор на программе, подобной Customizer,
которая позволяет "налету" менять свойства любого окна, а в последствии
делать это и автоматически.
Таким образом необходимо усилить реализацию защиты, так что бы ее
вскрытие не было доступно широкому кругу пользователей. Достаточно ввести
некоторую перемнную типа 'Registered' и проверять при нажатии на кнопку ее
значение. Если Registered равна нулю, а пользователь каким-то загадочным
образом все же ухитрился нажать заблокированную кнопку, то повторно
блокируем кнопку или завершаем работу, мотивируя это несанкционированныи
действиями пользовтеля.
Например, именно так и реализована защита в crack0E. Откроем файл
редактором ресурсов и убедимся, что все элементы разблокиpованы.
Выключаются они позже, на стадии иницилизации диалога, функциями API.
Поробуем разблокировать их инструментом типа customizer-а. С первого
взгляда кажется, что это сработало. Но попробуем нажать кнопку "hello".
Защита сообщает о незарегистрированной версии и вновь блокирует кнопку.
Для простого пользователя такой барьер можно уже считать непреодалимым.
Однако, для знакомых с ассемблером и отладчиком, нет ничего трудного
нейтрализовать подобную защиту.
Обратимся к MSDN и введем в строке поиска "Disable Window". Среди
полученных функций будет только одна, непосредственно относящиеся к win32
API - EnableWindow. Можно загрузить отладчик и установить на последнюю
точку останова или поискать перекресные ссылки на нее же в дизассемблере.
Но этому я, надеюсь, уже научил читателя. Давайте усложним себе задачу и
попробует обойтись без этих чудес прогресса. В конечном счете гораздо
интереснее работать головой, чем техникой.
Очевидно, что сообщение "Это незарегистрировнная копия" выдает защитный
механизм. Для этого он должен передать поцедуре AfxMessageBox смещение этой
строки. Разумеется речь идет о смещении в памяти, а не в файле. Однако для
PE файлов его легко узнать, например, с помощью HIEW. Эта утилита
единственная из всех мне известных шестнадцатиричных редкторов, позволяющая
просматривать локальные смещения для PE файлов.
Находим строку "Это незарегестрированная копия", не забыв сменить
кодировку, и переключаем Hiew в режим отображения локальных смещений. В
нашем случаи это будет 0х00403030. Не забывая про обратный порядок
байтов в слове, ищем последовательность '30 30 40 00'. Если все сделать
правильно, то получии только одно вхождение. Дизассемблируем прямо в hiew-е
найденный код:
.00401547: 8B4660 mov eax,[esi][00060]
.0040154A: 85C0 test eax,eax
.0040154C: 7516 jne .000401564 -------- (1)
.0040154E: 6830304000 push 000403030 ;" @00"
^^^^^^^^^
.00401553: E8C2020000 call .00040181A -------- (2)
.00401558: 6A00 push 000
.0040155A: 8D4E64 lea ecx,[esi][00064]
.0040155D: E8B2020000 call .000401814 -------- (3)
.00401562: 5E pop esi
.00401563: C3 retn
Обратим внимание на условный переход. Несомненно, он ведет к нужной нам
ветке программы. Однако, не будем спешить его изменять. Это нам ничего не
даст. Все элементы останутся по-прежнему заблокированными, и нажать на них
мышкой не будет никакой возможности. Можно, конечно, найти соответствующие
вызовы WindowEnable, но это утимительно и не гарантирует того, что хотя бы
один мы не пропустим.
Найдем перемнную, которая управляет выполнением программы. Очевидно,
что [esi+0x060] это она и есть. Необходимо найти код, который управляет ее
значением. Если его изменить на противоположное, то программа автоматически
зарегистрируется.
Давайте сделаем смелый шаг, предположим, что esi указывает на экземпляр
класса и переменная иницилизируется в этом же классе. Тогда любой код,
манипулирующий с ней, будет адресоваться аналогичным образом. На самом деле
это действительно смелый шаг, потому что никто нам не гарантирует, что не
будет иначе, особенно для оптимизирующих компиляторов. Однако, это
настольно часто срабатывает, что нет нужды искать другие пути, пока не
попробывать этот. В худшем случае мы ничего не найдем или получим ложные
срабатывания.
На этот раз, нам везет и hiew выдает следующий любопытный фрагмент:
.004013D3: 8B4C240C mov ecx,[esp][0000C]
.004013D7: C7466000000000 mov d,[esi][00060],00000
.004013DE: 5F pop edi
Это есть ни что иное, что самое сердце защиты. Обратите внимание, что
приложение не предусматирает явной регистрации. Переменная иницилизируется
одним и темже значением, ни от чего не зависящим. Т.е. демонстационная и
коммерческая версии это по сути дела разные программы. Но, отличающиеся
всего одним байтом. Попродуем присвоить этой переменной ненудевое значение-
.004013D7: C7466000000000 mov d,[esi][00060],00001
И перезапустим программу. Это сработало! Нам не пришлось даже
анализировать алгоритм защиты. Изменив только один байт (перемнную-флаг)
остальное мы возложили на плечи самой защиты. Ни в коем случае нельзя
сказать, что мы нейтрализовали или модифицировали ее. Разумеется нет.
Защита все еще жива и коректно функционирует.
Однако, изменив флаг, мы ввели ее в заблуждение и заставили нас
признать зарегистрированными пользователями. Это довольно универсальный
и широко распростаненный способ. Гораздо легче передать защите поддельные
вхдные данные, чем анализировать много килобайт кода в поисках ее
фрагментов, разбросанных по всей программе.
Впрочем, разработчики далеко не всегда ограничиваются одним флагом.
Таких перемнных может быть несколько, и одна не обязательно будет связана с
другой. Это усложнит задачу взломщика, особенно если защита проверяет, что
бы все флаги были идентичны. Тогда не остается ничего, кроме тщательного
анализа. В худщих реализациях бывает, что несоответствие флагов регистрации
не приводит к вызову сообщений об ошибках, а искажению алгоритма работы
таким образом, что программа внешне работает, но работает неправильно.
Это может выглядеть так.
return SomeResult*(!FlagReg1 ^ FlagReg2);
Если два флага не равны друг другу, то в реультате получится ноль!
Функция вернет неверный результат. Если такое, например, случится в
программе расчета зарплаты, то последствия не заставят себя ждать. Самое
печальное, что флаги регистрации могут одновременно являтся и рабочими
переменными программы. Обычно при этом флагу выделяют младший бит, а все
остальное под нужды какой-нибудь функции. Тогда без тщательного анализа
всего кода невозможно быть уверенным, приложение функционирует коректно.
К счастью, программисты часто оказыаются слишком ленивы, что бы
детально проработать эту архитектуру. И рождат перлы типа Crack0F.
Рассмотрим этот защитный механизм. Перед нами две заблокированных кнопки.
Очевидно, для локализации защиты, нужно найти вызовы EnableWindow.
j_?EnableWindow@CWnd@@QAEHH@Z proc near ; CODE XREF: sub_0_401360+D4.p
; .text:004015CF.p
jmp ds:?EnableWindow@CWnd@@QAEHH@Z
j_?EnableWindow@CWnd@@QAEHH@Z endp
Их всего два. Как раз по числу элементов упавления. Пока защита не
предвещает ничего необычного и ее код выглядит вполне типично:
.text:0040142A mov eax, [esi+68h]
.text:0040142D lea ecx, [esi+0ACh]
.text:00401433 push eax
.text:00401434 call j_?EnableWindow@CWnd@@QAEHH@Z ;
и аналогично другой фрагмент:
.text:004015C8 mov eax, [esi+60h]
.text:004015CB lea ecx, [esi+6Ch]
.text:004015CE push eax
.text:004015CF call j_?EnableWindow@CWnd@@QAEHH@Z ;
Попробуем найти, как уже было показано выше, '46 60', т.е. [esi+60] и '46
68'- [esi+68]. Полученный результат должен выглядеть следующим образом -
.00401385: C7466001000000 mov d,[esi][00060],000000000
и
.004012CC: C7466801000000 mov d,[esi][00068],000000000
Кажется, что защита использует два независимых флага. С первого взгяда
их нетрудно и изменить на ненулевое значение. Ожидается, что это заставит
защиту работать. ну чтож, попытаемся это сделать.
Как будто-бы все работает, не правда-ли? Но попробует нажать на левую
кнопку:

¬
+ ¦
¦ ¦
¦ ¦
¦ ¦
¦ ¦
¦ ¦
¦ ¦
¦ рисунок pe ¦
L
Пустой диалог выглядит странно, не так ли? Похоже, что защита взломана
некоректно и приложение работает неверно. И дело не только в том, что
сложно найти то место, где код ведет себя неправильно (это не так уж и
проблематично по большому счету). Главная сложность убедится в
работоспособности (неработоспособности программы). В данном примере это
тривиальная задача, но она не будет такой в банковских, научных, инженерных
приложениях. Если неправильно работает только одна редко вызываемая ветка,
то тестирование поломанного приложения дело безнадежное.
Однако, разработчики защит часто упускают из виду, что компилятор мог
расположить все флаги близко от друг друга, значительно упрощая кракеру
поиск. В самом деле, в нашем примере фигурируют две перемнные типа DWORD -
[esi+60] и [esi+68]. Нетрудно заметить, что между ними образовалась "дырка"
ровно в двойное слово. Может быть эта переменная - еще один флаг защиты?
Попробуем найти '46 64':
.004015B3: C7466400000000 mov d,[esi][00064],000000000
Что будет если ноль заменить на единицу? Попробуем, и... сработало!
Ранее пустой диалог теперь приветствует нас "Hell0, Sailor!". Защита пала!
Очевидно, что разработчик использовал по крайней мере три флага и
конструкцию типа:
s0.SetAt(0,s0[0]*(!RegFlag_1 ^ RegFlag_3));
Но кто может гарантировать, что нет четвертого или пятого флага? На
самом деле, число переменных класса ограничено и не так трудно
проанализировать их все. Кроме того, обычно флаги регистрации это
глобальные перемнные. Последних же в грамотно спроектированной программе на
объективно-ориентированном языке очень и очень немного.
Конечно, подобные технологии взлома построены на допущении ленивости
разработчиков защит. Тщательно продуманную защиту подобного типа
практически невозможно обнаружить даже при детальном анализе кода. Но,
такие случаи пока остаются экзотикой и часто не встречаются.
Блокирование элементов управленя не единстенно возможный вариант.
Многие демонстационные приложения при попытке выполения некоторой операции
(например, записи в файл) выдают диалоговое окно, информирующие об
отстутствии данной возможности в ограниченной версии. Иногда эта
возможность (вернее код, реализующий ее) действительно физически
отстутствует, но чаще он просто не получет управления.
Варианты блокировки больше рассматираться не будут, что бы избежать
повторения. Это слишком просто и элементарно ломается. Гораздо интереснее
искать пути выхода из ситуации, когда кода, реализующего данную операцию
по-просту нет. Конечно, чаще легче переписать программу полностью заново,
чем разобраться в взаимодействии с недостающим кодом, и воссоздать его.
Это столько сложная тема, что просто не может быть исчерпывающие
рассмотрена в рамках данной книги.
Рассмотрим достаточно простой пример подобной защиты: fiel:
//CD/SRC/CRACK10/Crack10.exe Это простой текствой редактор, который при
при попытке сохранения отредактированного файла выводит диалогове окно,
информирующие об отсутствии такой возможности в демо-версии.
Найдем этот вызов и дизассемблируем его:
.text:00401440
.text:00401440 push 0
.text:00401442 push 0
.text:00401444 push offset unk_0_404090
.text:00401449 call j_?AfxMessageBox@@YGHPBDII@Z
.text:0040144E xor eax, eax
.text:00401450 retn 4
.text:0040144E NagScreen endp
.text:0040144E
Допустим, можно удалить вызов j_?AfxMessageBox@@YGHPBDII@Z, но чего мы этим
добъемся? Однозначно, что код, обрабатывающий запись файла на диск
отсутствует. Впрочем, есть ненулевая вероятность, что он находится сразу
после retn или где-нибудь поблизости. Это происходит в случае использования
следующих конструкций:
BOOL CCRACK10Doc::OnSaveDocument(LPCTSTR lpszPathName)
{
AfxMessageBox("Это ограниченная версия. Пожалуйста, преобретайте полную");
return 0;
return CCRACK10Doc::OnSaveDocument(lpszPathName);
}
Однако, оптимизирующие компиляторы в таком случае просто удаляют
неиспользуемый код. Таким образом, вероятность, что сохранится код, не
получающий управления близка к нулю. Это здорово помогает разработчикам
защит, но печально для кракеров.
Впрочем, в нашей ситуации написать недостающий код легко. Мы можем
получить указатель на текствой буффер и просто сохранить его на диске. Все
это укладывается в десяток строк и может быть написано за несколько минут.
Передаваемые параметры можно узнать если установить на эту процедуру точку
останова и заглянуть отладчиком не вершину стека. Засланное в стек значение
очень похоже на указатель (а чем бы еще могло являться такое большое число?
) и в действительности является указателем на имя файла, что можно легко
проверить, взглянув на дамп памяти, расположенный по этом адресу.
Однако, гораздо большей проблеммой станет не написание своего кода, а
его внедрение в уже откомпилированный exe-файл. Под MS-DOS эта проблемма
уже была хорошо изучена, но Windows обесценила большую часть прошлого
опыта. Слишком велика оказалась разница между старой и новой платформами. С
другой стороны windows принесла и новые возможности такой модификации.
Например, помещение кода в DLL и простой вызов его оттуда. Подробное
рассмотрение таких примеров требует целой отдельной книги, поэтому
рассматриваемый здесь прием заведомо упрощен.
Вернемся к защите. Перейдем по единственной перекрестной ссыле, что бы
узнать кто вызывает этот код.
.rdata:00403644 dd offset j_?OnOpenDocument@CDocument
.rdata:00403648 dd offset sub_0_401440
^^^^^^^^^^^^^^^^^^^
.rdata:0040364C dd offset j_?OnCloseDocument@CDocument
Что предствавляют собой перечисленные смещения? Программисты, знакомые с
MFC, безошибочно узнают в них экземпляр класса CDocumnet. Это можно
подтвердить, если прокрутить экран немного вверх и перейдя по одной из двух
перекресных ссылок, посмотеть на следующий фрагмент:
401390 sub_0_401390 proc near
401390 push esi
401391 mov esi, ecx
401393 call j_??0CDocument@@QAE@XZ
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
401398 mov dword ptr [esi], offset off_0_4035C8
40139E mov eax, esi
4013A0 pop esi
4013A1 retn
4013A1 sub_0_401390 endp
В таком случае становится ясно, что sub_0_401440 это виртуальная
функция CDocument::OnSavеDocument()! Но разработчик не передает
управления последней, а выводит диалогове окно и откразывается от записи.
А что если заменить sub_0_401440 на вызов функции по умолчанию
OnSaveDocument? Для этого сначала необходмио узнать импортируется ли
эта функция программой или нет. Воспользуемся для этой цели IDA и изучим
секцию rdata. К глубокому нашему сожалению OnSaveDocument в таблице импорта
отстутствует. Можно, конечно, вызвать любую функцию из DLL непосредственно,
загрузив ее LoadLibray. Это, конечно, потребует немалого места для
размещения нового кода в файле. Но благо, оно там с избытком имеется.
Компилятор выравнивает прологи всех функций по границе 0x10 байт для
оптимизации выполнения программы, поэтому остается много "дыр", которые
можно использовать взломщику для своих целей.
Это действительно очень просто, достаточно иметь минимальные навыки
программирования под windows. Однако, при первая же поптыка реализации
сталкиват с серьезной трудностью. Что бы вызвать функцию по адресу,
необходимо наличие GetProcAddress, а приложение не импортирует ее.
Печально на первый взгляд, но легко исправимо. Достаточно лишь слегка
изменить таблицу импорта, что бы включить недостающий вызов.
Обычно компияторы всегда оставлябт в файлах много пустого места, что бы
можно было немного расширть таблицу импорта. Что бы это сделать нужно знать
формат PE файла, который описан, например, в MSDN. Покажем на примере как
это можно сделать. Скопирум файл crack10.exe в myfile.exe Теперь запустим
HIEW 6.x (не ниже) и перейдем в секцию ипорта. В самом ее начале
расположен массив IMAGE_IMPORT_DESCRIPOR. Подробности о его структуре можно
подчерпнуть в SDK или MSDN. Двойное слово стоящее в начале это RVA
(relative virtual address) указатель на структуру IMAGE_THUNK_DATA. Вот
он-то нам и нужен. Преобразовать rva в локальное смещение внутри PE файла
можно сложив последний с image base, которую можно узнать из заголовка
файла.
Что собой предстваляет IMAGE_THUNK_DATA? Это массив указателей на
RVAFunctionName. Наглядно это предствавить можно если изучать это
структуру в любом подходящем для вас шестнадчатиричном редакторе,
например hiew. Что может быть интереснее, чем копание в PE файле вручную, а
не готовым инструментом просмотра. Конечно, последнее намного проще и даже
может быть приятнее, но не дает никаких полезных навыков. Хакер не должен
расчитывать на технику, а только на свои руки и голову. Кракер же может не
особо утруждаясь воспользоваться готовым редактором для таблиц
экспорта\импорта (например PEKPNXE Криса Касперски) и всего лишь
отредактировать одну строку, что не требует дополнительных объяснений.
Напротив же - ручаня работа с PE файлами пока еще не достаточно хорошо
описана и сам формат лишь отрывочно документирован. Едиинственным маяком в
мире WINDOWS был и остается заголовчный файл WINNT.H, который содержит все
необходимые нам структуры, но, увы, не содержит комментариев к ним.
Поэтому назначение некоторых полей придется выяснить самостоятельно.
Для начала загрузим исследуемый файл в hiew. Можно было бы сразу, вызвать
секцию импорта, но первый раз попытаемся для интереса найти ее вручную.
Заголовк PE файла начинается не сначала файла. Вместо этого там
расположена DOS-овская заглушка, которая нам совсем не интересна. Сам же PE
файл начинается с одноименной сингатуры. Двенадцатое (считая от нуля)
двойное слово это image base, который нам потребуется для вычислений,
связанных с RVA, в нашем случае равен 0x400000, что типично для win32
файлов.
Теперь нам необходимо найти адрес таблицы импорта . Он будет вторый в
директории (первый таблица экспорта). Под директорией здесь понимается
структура, расположенная в конце OPTIONAL HEADERа и содержащая необходиую
нам информацю. Я не привожу точного описания ее формата, отсылая читателя к
MSDN и wintnt.h Все же это книга не предназначена для пересказа
существующей документации и бессмыслено бы было тратить на это десятки
страниц. Попутно замечу, что на страдии подготовки книги это вызвало
некотрое возражение у тех людей, которые не владели английским даже на
уровне чтения технических текстов со словарем и не позаботились приобрести
даже электонной документации, которая свободно поставляется в любм
компилятором и даже доступна в Интернете на сайтах производителей и в первю
очередь, MicroSoft. Ну что на это можно сказать? Тогда не покупайте эту
книгу, а лазерный диск с сотнями тысяч готовых "краков" - очевидная
экономия времени и денег.
Итак, предположим, что мы уже выяснили, что таблица импорта
располагаетется по адресу 0x40000+0x3A90=0x43a90. Перейдем к ее
расмотрению, а точнее к рассмотрению IMAGE_THUNK_DATA, которое мы уже
затронули выше. Формат его данных очевиден из содержания:
.00403E30: F6 3E 00 00-02 3F 00 00-16 3F 00 00-C6 3E 00 00
.00403E40: E6 3E 00 00-26 3F 00 00-44 3F 00 00-BE 3E 00 00
.00403E50: 6A 3F 00 00-A8 3E 00 00-9A 3E 00 00-86 3E 00 00
.00403E60: 36 3F 00 00-56 3F 00 00-D8 3F 00 00-00 00 00 00
Поразмышляв над ним минутку-другую можно догаться, что его элемнты -
двойные слова - это RVA указатели. На эту мысль наталкивает самое значение,
например 0x3EF6 - находится в недалеко от текущей позиции, глубоко в
таблице импорта. Кроме того, все близкие к друг другу и однонаправленно
возорастающие значения элементов очень похожи на типичный массив
указателей.
Загляную в документацию, мы можем убедиться с правильности нашей
догадки. Попытаеся теперь, не образаясь к документации, угадать это
указатели НО ЧТО? Логично предположить, что вероятно, на
непосредственно имортируемые функции. Все еще не обращаясь к документации
перейдем по одному из указателей:
0403F00: 6D 00 83 00-5F 5F 73 65-74 75 73 65-72 6D 61 74 m Г __setusermat
^
0403F10: 68 65 72 72-00 00 9D 00-5F 61 64 6A-75 73 74 5F herr Э _adjust_
0403F20: 66 64 69 76-00 00 6A 00-5F 5F 70 5F-5F 63 6F 6D fdiv j __p__com
0403F30: 6D 6F 64 65-00 00 6F 00-5F 5F 70 5F-5F 66 6D 6F mode o __p__fmo
Это действительно имена функций, а слово стоящее перед ними, очевидно,
ординал! Однако, мы едва не упустили одну важную деталь - ведь существуют
функции, которые экспортируются только по ординалу и символьная информация
по просту не доступна. Не ужели тогда ДВОЙНЫЕ СЛОВА - указатели будут
расточительно указывать на СЛОВА ординалы? Разумеется нет, фирма MicroSoft
в стремлении к оптимизации предусмотрела такой вариант. В этом случаи все
элементы IMAGE_THUNK_DATA предстваляют собой не указатели, а
непосредственно ординалы функций. Что бы загрузчик мог распознать такую
ситуацию старший бит двойного слова равен единице. В результате, получается
массив наподобии следующего:
.00403B00: B2 10 00 80-86 11 00 80-FA 09 00 80-D0 09 00 80
.00403B10: 63 16 00 80-52 0F 00 80-41 04 00 80-4F 14 00 80
.00403B20: 5C 09 00 80-12 0D 00 80-B4 14 00 80-B6 14 00 80
.00403B30: A5 0A 00 80-EF 0F 00 80-5A 12 00 80-BB 14 00 80
.00403B40: A9 14 00 80-52 16 00 80-A6 0B 00 80-4B 0C 00 80
Любопытно, что в оптимизации WINDOWS NT MicroSoft опередила сама себя и
в системеных модулях все элементы выщеуказанного массива даже не ординалы,
а непосредственные смещения импортируемых функций. Это блестящее решение
MicroSoft заслуживает неприменного уважения. Действительно, загрузчкику
почти совсем не остается работы, что экономит не одну сотню тактов
процессора. И лишний раз подтвержает, что "решение от MicroSoft" чаще все
же ирония, чем горькая правда. И хотя windows в целом оставляем мрачное
впечателение, в ее недрах спрятано не мало интересных "конфеток". И в само
деле - ведь над ней работали весьма не глупые люди.
Четкое понимание структуры таблицы импорта необходимо для серьезных
манипуляций связанных с перемещением и добавлением новых элементов в
последнюю. Действительно, импортировать еще одну функцию очень просто.
Достаточно прописать ее ординал (или имя) в таблицу. Общее число элементов
нигде не учитывается, а конец таблицы же определятся завершающим нулем.
Взглянем на наш файл. RVA адрес первой структуры IMAGE_THUNK_DATA
равен 0x3B00. Учитывая, что image base 0x400000, получаем локальное
смещение 0x403B00. Как узнать из какого модуля импортируются эти функции?
Для этого заглянем в поле Name IMAGE_IMPORT_DESCRIPTOR, (четвертое двойное
слово от начала). В нашем случае оно указывает на стоку 'MFC42.DLL' Именно
в эту таблицу мы и должны добавить запись для OnSaveDocument. Разумеется,
что в таблице не будет свободного места и за ее концом находится начало
следующей. Кажется ситуация неразрешимая, однако давайте подумаем. На
каждую IMAGE_THUNK_DATA указывает всего одна ссылка. А что будет если мы
переместим одну из них в другое свободное место (которое навярняка
найдется) и в освободившеся простанство внесем новую запись?
Очевидно, что нам нужно освободить место в конце таблицы. В нашем
случае так находится небольшой массив из нескольких элементов. Очевидно,
это везение, инчаче бы пришлось перемещать и менять местами гораздо больше
массивов, что не было бы так наглядно.
Благодаря выравниванию адресов на границе секций данных и ресурсов
практически всегда есть бездна никем не занятого пространства.
Переместим выделенную структуру, например, по адресу 0х404110. Для этого
нужно скопировать блок с адреса 0х403E24 по 0х403E6B и записать его на
новое место. Теперь освободившеся место можно использовать по своему
усмотрению. Но прежде неоходимо сокрректировать ссылку на перемещенный
фрагмент. Для этого найдем в IMAGE_IMPORT_DESCRIPTOR прежний RVA адрес и
испрравим его на новый.
Запустим файл, что бы убедиться, что мы все сделали правильно и он
работает. Приступим к ручному импортированию функции из файла. Это
достаточно утомительный, но познавательный процесс, вынуждающий заглянуть
"под капот" PE файла, и понять как он загружается и работает. Для начала
изучим массив имортирумых функций:
.00403B00: B2 10 00 80-86 11 00 80-FA 09 00 80-D0 09 00 80
^^ ^^ ^^ ^^
.00403B10: 63 16 00 80-52 0F 00 80-41 04 00 80-4F 14 00 80
.00403B20: 5C 09 00 80-12 0D 00 80-B4 14 00 80-B6 14 00 80
.00403B30: A5 0A 00 80-EF 0F 00 80-5A 12 00 80-BB 14 00 80
.00403B40: A9 14 00 80-52 16 00 80-A6 0B 00 80-4B 0C 00 80
Видно, что все они импортируются по ординалу. И нам необходимо только
добавить еще один. Находим в файле MFC42.map функцию OnSaveDocument и на
основе полученного смещения определяем ординал с помошью dumpbin или любой
другой аналогичной утилиты получаем, что ее ординал 0x1359. Дописываем ее в
конец таблицы. Запускаем dumpbin, что бы удостовериться, что он заметил
проделанные изменения. Однако, это далеко не конец нашей работы, а скорее
ее только начало. Что нам даст новая запись в IMAGE_THUK_DATA? Честно
говоря ничего. Нам нужно узанть адрес функции после загрузки, а как это
сделать? Для этого сущестует еще одно поле в IMAGE_IMPORT_DESCRIPTOR -
это пятое двойное слово, указывающие адрес массива, в каждый элемент
которого загрузчик операционной системы запишет реальный адрес
импортируемой функции. В нашем случае для MFC42.DLL такая структура
расположена по адресу 0x40300C. Рассмотрим ее поближе, но сначала обратим
внимание, что адрес 0x40300C находится за пределами секции импорта и
уже пренадлежит секции .rdata Это обстоятельство на самом деле очень важно,
т.к. иначе бы загрузчик просто бы не смог получить доступа к памяти на
запись, а следовательно изменить значение. Поэтому эта таблица перемещаема
только в пределах .rdata Но что она собой представляет? Гораздо проще и
быстрее выяснить это самостоятельно, чем искать в докуменации среди
множества бесполезной для нас сейчас информации. Рассмотрие ее поближе:
.00403000: 8C 3F 00 00-78 3F 00 00-00 00 00 00-B2 10 00 80
^^
.00403010: 86 11 00 80-FA 09 00 80-D0 09 00 80-63 16 00 80
.00403020: 52 0F 00 80-41 04 00 80-4F 14 00 80-5C 09 00 80
.00403030: 12 0D 00 80-B4 14 00 80-B6 14 00 80-A5 0A 00 80
.00403040: EF 0F 00 80-5A 12 00 80-BB 14 00 80-A9 14 00 80
Не правда ли эти таблицы идентичны? И та и другая перечисляет ординалы.
Однако существенная разница между ними все же есть. Первая сохраняется
неизменной на всем протяжении работы, а последняя замещается реальными
адречами импортируемых функций уже на стадии загрузки. И именно ее
приложение использует для вызовов типа CALL DWORD PTR [0x403010].
Очевидно, что в случае ипорта по имени все элементы таблицы будут
указателями на ASCIIZ-строки с именем и ординалом функции. Заглянув в
MSDN можно с гордостью констатировать тот факт, что мы ни где не ошиблись в
наших предположениях. Со временем большинство исследователей недр windows
все реже и реже заглядывают в документацию, поскольку многое и так
достаточно очевидно и нет никаой нужды в разъяснении.
Печально, что служит примером для подражания начинающих и неопытных
хакеров, которые воспринимают это как повод для отказа от документации
вообще. И в результате тычустся в слепую или начинают задавать глупые
вопоосы наподобии "какой функцией windows открывет файл. Я установил на
OpenFile точку останова, а она не сработала. Почему?" Действительно общий
объем докуменатции для разработчика win32 такой, что даже беглый просмотр
заголовков отнимет не один месяц времени. Это все так. Еще под window 3.1
говорили, что нужно не меньше года обучения, что бы стать полноценным
программистом под эту платформу. Насколько же все усложнилось с тех пор!
Самое обидное, что на этом фоне делается основной упор на каркасные
библиотеки типа MFC, технологии OLE и ActiveX и системному программированию
просто не остается место - ни в умах разработчиков не в документации.
Лозунг - все что Вас нужно уже сделано компанией MicroSoft в самом деле
сейчас очень популярен, но многих людей (включая и меня) он приводит в
ярость. Программисты старого поколения до сих пор любят все делать своими
руками и не передаю выполнения своей программы чужому коду, пока не изучат
последний.
Полноценным системщиком можно стать лишь отказавшить от MFC и C++,
а попробовать написать несколько серьезных приложений на старом добром Си.
Даже не ассемблере, а простом выскоуровневом языке. Непосредственное
общение с win32 может с первого взгляда показаться пугающим, но это
единственно возможный вариант почуствовать архитектуру системы.Без этого
говорить о хакерстве просто смешно.
Однако мы ушли далеко всторону. Но не будем возрашаться назад.
Признаюсь, я вас обманул и завел в тупиковый путь. Надеюсь, что осознание
это факта уже дошло до читателя. В самом деле, что бы добавить еще одну
запись в вышеуказанную секцию надо ее чуточку разжвинуть и... получиь GPF.
В самом деле, на нее указыает множество ссыклок из разных частей кода.
Изменить из все не предствавляется возможным, особенно там, где
использовалась косвенная адресация.
Впрочем, если быть до конца честным, в большинство компиляторов
генерируют хорошо всем известные:
.004018D0: FF25C4314000 jmp MFC42.4612
.004018D6: FF2590314000 jmp MFC42.4610
.004018DC: FF2594314000 jmp MFC42.6375
.004018E2: FF2510304000 jmp MFC42.4486
.004018E8: FF2514304000 jmp MFC42.2554
.004018EE: FF2518304000 jmp MFC42.2512
.004018F4: FF251C304000 jmp MFC42.5731
.004018FA: FF2520304000 jmp MFC42.3922
.00401900: FF2524304000 jmp MFC42.1089
Таким образом, на каждый элемент имеется всего одна ссылка, которая к
тому же легко может быть найдена и скоректирована. Выходит, я дважды
обманул читателя. На самом деле это не тупик, а просто хлопотный, но
очевидный, путь. Я встречал многих хакеров, которе соблазнились отпавиться
по нему и для коррекции ссылко даже писали специальную программу или скипт
к IDA.
Однако, можно пойти более короткой дорогой. Кто нас заставляет
добавлять элемнт в существующую таблицу, когда можно создать свою и
разместить ее где угодно! Это в самом деле очень просто.
Поскольку сразу за концом IMAGE_IMPORT_DESCRIPOR следует
IMAGE_THUNK_DATA, то очевидно, что добавить еще одну запись, можно только
переместив одну из двух на свободное место. Первая несравненно короче,
поэтому и шансов найти бесхозного пространства для нее побольше. Строго
говоря нам необходимо разместить ее в пределах таблицы импорта и никто не
разрешит ее перемещать с секцию .data - получится перекрывание секций, и
последсивия не застаят себя ждать... hiew "заругается" на такой файл. И,
пожалуй, все. Действительно, если изучить код загрузчика windows становится
ясно, что ему глубоко все равно в какой секции расположена таблица импорта
и более того, совершенно безразличен размер последней, а точнее его
соответствие с реальным. Конец определяется null-записью.
На самом деле, необходимо отдавать себе отчет в зыбкости таких
рассуждений. Никто не гарантирует, что в будущем MicroSoft не перепишет
загрузчик, который будет делать такие проверки или не появится прикладных
программ (в частоности антивирусов) которые не контролировали бы
корректность заголовка.
С другой стороны, работа хакера почти всегда базируется на отклонении
от документации и рекомендаций сопутствующих к ней руководств. В противном
же случае ничего не остается, как сидеть бездействовать или
перекомпилировать полученный ассемблером и исравленный текст в исполняемый
файл, что сопряжено с многочисленными проблеммами и трудозатратами.
Скопируем IMAGE_IMPORT_DESCRIPOR в любое свободное место секции данных
и изменим на нее ссылку в Import Directory. Теперь нам необходимо создать
новую запись в ней. Начнем с четвертого двойного слова, указывающего на имя
функции. Можно состлаться на уже существующую строку 'MFC42.DLL', а можно
созадть свою и указать на нее. Последнее нам дает больше свободы и
независимости. Поэтому поступим именно так:
.004041D0: 4D 46 43 34-32 2E 44 4C-4C 00 00 00-00 00 00 00 MFC42.DLL
Хорошо, имя экспортируемого модуля мы уже записали. Теперь необходимо
создать массив IMAGE_THUNK_DATA, точнее, "массив" громко сказано, всего
лишь одну запись.
.004041E0: 59 13 00 80-00 00 00 00-00 00 00 00-00 00 00 00 Y. А
Понятно, что 0x1359 это и есть ипортируемая функция OnSaveDocument, а
старший бит 0x8000 указывает, что последняя ипортируется по ординулу.
Остается создать таблицу адресов, точнее таблицу создавать нет никакой
необходимости. Не смотря на то, что каждый ее элемент по теории должен
ссылаться на соотвествующую фукцию, оптимизация загрузчка привела к тому,
что он никак не использует начальные значения таблицы адресов, а вносит
записи в том порядке, в котором они перечислены в таблице имен
(IMAGE_THUNK_DATA). Поэтому достаточно лишь найти незанятое пространство и
установить на него указатель в последнем поле IMAGE_IMPORT_DESCRIPOR.
Однако, тут мы наталкиваемся на серьезные ограниченя. Загрузчику на
запись доступна только .rdata, в которой так скажем свободным местом не
густо. Более того, ни один элемент нельзя перемещать, поскольку ссылки на
него разбросаны по всему коду программы. Остается только надесятся, что в
результате выравнивания в конце таблицы найдется немножко пространства для
наших целей. И действительно, несколько десятков байт свободно. Для нас это
более, чем достаточно.
0403FC0: 57 69 6E 64-6F 77 00 00-55 53 45 52-33 32 2E 64 Window USER32.d
0403FD0: 6C 6C 00 00-AA 01 5F 73-65 74 6D 62-63 70 00 00 ll к._setmbcp
0403FE0: 00 00 00 00-00 00 00 00-00 00 00 00-00 00 00 00
0403FF0: 00 00 00 00-00 00 00 00-00 00 00 00-00 00 00 00
Остается только скорректировать IMAGE_THUNK_DATA. Финальный вариант может
выглядеть так -
0404160: E0 41 00 00-00 00 00 00-00 00 00 00-D0 41 00 00 pA ¦A
0404170: E0 3F 00 00-00 00 00 00-00 00 00 00-00 00 00 00 p?
Убедимся с помощью dumpbin, что это исправно работает.
MFC42.DLL
403FE0 Import Address Table
4041E0 Import Name Table
0 time date stamp
0 Index of first forwarder reference
Ordinal 4953
Если заглянуть отладчиком по адресу 0x403FE0, то там мы обнаружим готовый
к употреблению адрес функции OnSaveDocument. Проверим, что это
действительно так. Для этого дизассемблирует (командой u в soft-ice) этот
регион памяти. При этом отлачик должен вывести в прологе ординал функции.
Это убеждает нас, что все работает. Остается эту функцию всего лишь
вызвать. Для этого вернемся далеко назад, когда мы нашли перекрытую функцию
OnSaveDocument. Очевидно нам стоит переписать ее. Рассмотрим код еще раз:
.00401440: 6A00 push 000
.00401442: 6A00 push 000
.00401444: 6890404000 push 000404090
.00401449: E812070000 call AfxMessageBox
.0040144E: 33C0 xor eax,eax
.00401450: C20400 retn 00004
Очевидно, что ее нужно периписать например следующим образом:
.00401440: FF742404 push d,[esp][00004]
.00401444: 90 nop
.00401445: 90 nop
.00401446: 90 nop
.00401447: 90 nop
.00401448: 90 nop
.00401449: 2EFF15E03F4000 call d,cs:[000403FE0]
.00401450: C20400 retn 00004
Для понимания этого обратимся к SDK. Вот какой протитип имеет функция
virtual BOOL OnSaveDocument( LPCTSTR lpszPathName );
Отсюда выткакет стока push dword [esp][00004], остается объяснить вызов
функции. Как мы помним, загрузчик по в ячейку 0x403FE0 записал ее адрес,
вот и был он использован для вызова. И это все! Мы дописали недостающий
код. Этот момент очень важен. Поспешный читатель меня может упрекнуть в
искусстерности ситуации. Действительно ли часто встречаются подобные
примеры в жизни? Даже применительно к MFC используемая функция с большой
степенью вероятности может быть перекрыта функцией разработчика. Как быть
тогда?
Но не спешите, пусть функция перекрыта, тогда положение осложняется лишь
тем, что хакеру сперва нужно будет понять ее алгоритм, а затем воссоздать
недостающий код и... поместить его в собственную DLL, а от туда уже
аналогичым образом сделать вызов. При этом нет надобности изоощряться и
втискивать код в скудные клочки пустого места, беспорадочно разбросанные по
файлу. Можно выбрать любое симпатичное средство разработки (например, MS
VC) и написать на нем недостающую функцию, используя всю мощь MFC и
объективно-ориентированного Си++. Это гораздо легче и кроме того по-просту
удобно и приятно.
Для модификации старых exe для MS-DOS обычно использовался только
ассемблер. С одной стороны это было приятно (разумеется, для поклонников
этого языка), а с другой утомительно. Кроме того, в windwos гораздо легче
понять взаимодействие различных фрагментов программы, т.к. очень много
избыточной информации, а объективно-ориентированные языки (которые
доминируют последнее время) оперируют в основном с локальными структурами и
переменными. Тем боле тех ужастных глобальных объектов общего
использования, которые непонятно для кого предназначены и как используются.
Особенно тогда, когда программист в погоне к минимализации требуемой памяти
использовал одну и ту же перемнную повторно, если предыдущей процедуре она
уже была не нужна. Допустим, при старте программы пользователь ввел пароль,
который был сравнен с некотрой эталонной строкой. Ясно, что во время работы
программы эта область памяти может быть отведена под нужды других процерур,
если пароль сравнивается только один раз. Из этого следует, что мы получим
множество перекрестных ссылок и долго будем чесать репу, в размышлениях "а
чего это так с паролем-то интенсивно работают".
Одним словом, под windows стало настолько просто дописывать недостающий
код непосредственно в исполняемом файле, что даже начинающим кодокопателями
это по плечу. Поразительно, но очень немного кракеров берутся дописывать
недоастающий код в таких случаях, а просто лениво пожимают плечами и
рекомендуют обраться к автору за полной версией. Впрочем, их можно понять,
гораздо легче и выгоднее отламывать хаспы и присать генераторы серийных
номеров, чем в дописывать несуществующий код.
Вернемся к нашему примеру. Попробуем его запустить. Появляется другое
диалогове окно, с сообщением об ограниченности версии. Выходит, что автор
защиты предусмотрел двойную проверку. Выкинул-ли он еще кусок кода или
только вернул управление? Что бы это выяснить, необходимо изучить
вызывающий это сообщение код. Не будем прибегать к столь можному
инструменту как IDA, а воспользуемся компактным и шустрым hiew-ом.
Достаточно всего лишь найти сслку на строку, смещение которой можно узнать,
заглянув в сегмент данных. После чего нетрудно будет найти следующий
фрагмент:
.00401410: 8B442404 mov eax,[esp][00004]
.00401414: 8B5014 mov edx,[eax][00014]
.00401417: F7D2 not edx
.00401419: F6C201 test dl,001
.0040141C: 7411 je .00040142F
^^^^^^^^^^^^^^^^^^^
.0040141E: 6A00 push 000
.00401420: 6A00 push 000
.00401422: 6854404000 push 000404054 ; << строка
.00401427: E834070000 call AfxMessageBox
.0040142C: C20400 retn 00004 ;"
.00401430: 8B4130 mov eax,[ecx][00030]
.00401433: 8B4808 mov ecx,[eax][00008]
.00401436: E81F070000 call Serialize
.0040143B: C20400 retn 00004
MFC-программстам будет нетрудно понять как он работает. Если происходит
запись файла, то edx становиться равно единице, если чтение то нулю. Именно
на этом и построена защита. В оригинале это могло выглядить приблизительно
так:
void CCRACK10Doc::Serialize(CArchive& ar)
{
// CEditView contains an edit control which handles all serialization
if (ar.IsStoring())
{
AfxMessageBox("Это ограниченная версия. Пожалуйста, приобретайте полную");
return;
}
((CEditView*)m_viewList.GetHead())->SerializeRaw(ar);
}
Все, что требуется сделать для ее ликвидации это заменить условный переход
на безусловный. Или в качестве альтернативного варианта удалить ret. Тогда
защита по-прежнему будет "ругаться", но начнет записывать файлы. По
отношению к разработчику это даже будет более често. Пользователь получит
необходимый ему сервис, однако постоянно раздражаемый nag-screen-ом он с
ненулевой вероятностью может приобрести коммерчесую версию. С другой
стороны по отношению к пользователю со стороны кракера это будет выглядеть
издевательсвом. Особенно если он начнет выводить в диалоговом окне свои
копирайты.
Попробуем убрать ret, заменив его, скажем на nop. С первого взгляда это
не отразится на работоспособности программы. Однако, запустив программу и
попытавшись сохранить файл, мы получаем до боли знакомый GPF - "программа
выполнила некорректную операцию и будет завершена". В чем же дело? С
первого взгляда этот вопрос нас ставит в тупик, поэтому воспользуемся
отладчиком и внимательно потрассируем измененный фрагмент. Причина
обнаруживается достаточно быстро. Функция AfxMessageBox не сохраняет
регистров eax и ecx, а код, расположенный ниже их использует, никак не
предпологая, что их содержимое было изменено. Следовательно, забота о
сохранении, точнее о написании соответствующего кода, ложится на плечи
взломщика. Это не трудно, и даже не утомительно - добавить пару команд push
и pop, но как-то неаккуратно выходит. Действительно, между условным
переходом и вызовом функции нет свободного пространста. Можно, конечно,
сместить всю функуцию немного вниз, для чего свободного места
предостаточно, но может быть можно найти решение с изменением меньшего
числа байт? В самом деле, если убрать not, а jz заменить на jnz мы получим
два байта, как раз столько, что бы сохранить пару регистров. Однако, это
потребует коррекции точки перехода, поскольку команды push расположены
"вышее" ее. В итоге мы получим такой вариант:
.00401417: 50 push eax
.00401418: 51 push ecx
.00401419: F6C201 test dl,001
.0040141C: 750E jne .00040142C
.0040141E: 6A00 push 000
.00401420: 6A00 push 000
.00401422: 6854404000 push 000404054
.00401427: E834070000 call .000401B60
.0040142C: 59 pop ecx
.0040142D: 90 nop
.0040142E: 90 nop
.0040142F: 90 nop
.00401430: 8B4130 mov eax,[ecx][00030]
.00401433: 8B4808 mov ecx,[eax][00008]
.00401436: E81F070000 call .000401B5A
.0040143B: C20400 retn 00004
Удостовертесь, что он действительно работает! Однако, это еще не предел
и есть множество более изящных решений. Попробуйте найти их и вы получите
истинное удовольствие.
Итак, мы проделали большой путь - научились не только снимать
ограничения с программ, но и дописывать недостатющий код. Конечно, все что
показано в этой главе это только начало еще большего пути, который
открывается перед нами. Что он сулит? Модификация программ непосредственно
в исполняемом коде не только заменной пары байт, а внесением принципиальных
изменений в код и добавлением новых возможностей воистуну великая вещь!
Читатель, вероятно, понял, что для этого не хватило бы и отдельной книги,
не то что одной главы. Но и в этом случае от него потребовались бы
собственные исследования и копания в коде.
Навыки хакера не возникают просто так. Это длительный и упорный труд.
Порой он становится неинтересен и скучен. Но когда-то приходится делать и
такую работу, что бы потом можно было справляться с последней
автоматически.
Kris Kasperski 2:5063/61.8 12 Feb 99 00:51:00
Масочная атака
Данный вариант атаки я еще не видел описанным в доступной литературе,
посему могу считаь его чисто своим, однако он позволяет атакавать многие
криптосистемы не имея открытого текста, а лишь зная короткие
последовательности, встречающиеся в шифротексте.
Это очень рульно, если не сказать больше. Более того последний вариант
данного алгоритма "берет" криптоситемы, даже без никаких зананий об
исходном тексте! Только исходя из предположения, что встречаемые символы не
равновероятны (а разве часто бывает иначе?) причем саму вероятность или
соотношение знать не нужно! Применительно к arj этот алгоритм позволяет
находить пароль любой длины за ~2^24 интераций, т.е. за ~17.000.000
вариантов можно найти любой пароль. Скорость перебора в программе без
оптимизации на MS VC около 30.000 вариантов/сек. Теоритически можно бы
пароль было найти за 600 секунд (10 мин), но алгоритм требует зверских
расходов памяти и моих позорных 64 мег уже не хватает :( Начинается своп и
пароль находится не раньше, чем за сутки.
Посему я колеблюсь - стоит ли описывать полный алгоритм, или это будет
скучно и не интересно?
Я так чую, что книга растягивается. О криптографии для начинающих не
написано и половины от задуманного, а уже 200 кил. Много :( Если смаковать
каждую фишку, то это будет не книга, а порнография какая-то. И так Павел
Семьянов меня укоряет, что мыслей по дереву растекаюсь. Так что теперь я начал
все жать и скипать не существенное. А чуть позже призадумался - а может не стоит
критику слушать? Павел человек мной, конечно, уважаемый, да только если все
формулировать кратко, то будет не интересно и бесполезно. Новички пожмут плечами
и не поймут, "монстры" уже половину и так будут знать, а всем остальным это
вообще окажется не интересно...
По отзывам и пожеланиям я понял, что нужна книга в первую очередь для
начинающих. Или это просто остальные молчат?
Так вот, если продолжать в том же духе, то получиться книга исключительно
про атаки на криптосистемы. Все остальное (хаспы, дебаги, сети) придется
отложить на потом. Или можно кратко - но обо всем?
2.2 Простейшие системы шифрования.
" - Высказана мысль или нет, она
существует и имеет свою власть,-
сказал Туек.
- Ты можешь обнаружить однажды,
что грань между жизнью и
смертью у Свободных слишком
тонка."
Ф. Херберт. "Дюна"
Данная глава является кратким обзором проблемы для неподготовленного
читателя. Остальные ее могут без потери для себя смело пропустить.
Неоправданно популярный способ:
if (!IsValidUser)
{
Message("Invalid user! Aborting...");
Abort;
}
транслируется компилятором приблизительно в следующий код:
CALL IsValidUser
OR AX,AX
JZ continue
^^^^^^^^^^^^^
PUSH offset str_invalid_user
CALL Message
CALL Abort
continue: ; нормальное продолжение исполнения программы
и может быть легко взломан хакером изменением всего одного байта.Поменяв
выделенную строку на JZ continue на JMP continue злоумышленник получает
работоспособную копию программы. Не зависимо от алгоритма функции
IsvaldUser - будь то проверка ключевого диска или ввод серийного номера
совершается безусловный переход на ветку нормального продолжения исполнения
программы. На языке Си исправленная программа будет выглядеть так:
IsValidUser;
if (!true)
{
Message("Invalid user! Aborting...");
Abort;
}
Т.е. Ветка {...} никогда не получит управления! На самом деле все не
так просто, поскольку в исполняемом файле нужно еще найти эту инструкцию
перехода. К тому же разработчики защиты это всячески пытаются затруднить.
Запутывают алгоритм, используют самомодифицирующийся код, применяют
недокументированные вызовы операционной системы... Однако, такие
препятствия хакера не смущают. Технологии противодействия заметно обгоняют
эволюцию систем защиты. А с появлением дизассемблера IDA, отладчика
Soft-Ice и распаковщика cup386 копание в чужом коде не только
превратилось в удовольствие, но и стало доступно широкому кругу лиц.
Десяток лет назад, всего выше перечисленного богатства еще не было,
процессор работал только в реальном режиме и не было никакой возможности
уберечь отладчик от разрушающего воздействия со стороны изучаемой
программы, хакеры "старого поколения" все ломали в основном карандашом,
листком бумаги и головой. Зачастую код приходилось дизассемблировать в
уме, а недокументированные вызовы изучать, погружаясь в недра операционной
системы. Это не только требовало высокого профессионализма, но и огромного
количества свободного времени, которое ничем более полезным занять не
приходилось.
Россия в этом выгодно преуспела. Кадровые специалисты, скучающие на
работе, восприняли защиты как увлекательную головоломку. Так или иначе но с
той поры мы настолько привыкли к бесплатному поломанному программному
обеспечению, что до сих пор поддаемся соблазну купить пирацкий диск, а не
платить вдесятеро большую сумму фирме-производителю.
Отсюда и возникло твердое убеждение, как бы это разработчик не защищал
все равно сломают. На самом деле существуют надежные алгоритмы делающие
взлом по меньшей мере неэффективным. Основанные на криптографии они
обеспечивают не только надежную, но и математически обоснованную степень
защиты.
Допустим, пусть пароль, введенный пользователем, расшифровывает рабочий
код программы. Мне могут возразить, что это не спасает от банального
воровства пароля. Что помешает одному пользователю использовать пароль
другого? На самом же деле пароль должен браться из менее явных
источников, например, электронных ключей.
Кроме того, даже использование в качестве пароля ключевого диска или
файла требует по крайнем мере одной легальной копии для взлома. Поэтому за
время пока такая копия попадет к руки хакера и будет нелегально
распространена разработчик успевает продать достаточное число экземпляров
продукта. Кроме того, каждый уважающий себя кодокопатель считает долгом
чести самостоятельно написать генератор ключей, а не использовать уже
существующий. Это позволит поддержать объем продаж какое-то время.
Впрочем, организационные проблемы выходят за рамки данной книги и на
этом рассмотрение их и закончим. Гораздо интереснее рассмотреть
популярные криптосистемы и возможные атаки на них.
Большинство защит сегодня используют шифровку своего кода в целях
затруднения анализа и модификации кода. Ключ, используемый в
расшифровщике, хранится непосредственно в последней, поэтому теоретическая
криптостойкость подобной системы равна нулю. Впрочем, это не важно, т.к.
преследуются совсем другие задачи. Кроме IDA ни один известный мне
дизассемблер не может работать с шифрованным кодом. Отладчик не сможет
функционировать, если декодер использует необходимые ему ресурсы.
Наконец, непосредственная модификация кода становиться невозможна. При
этом сам алгоритм шифра и его криптостойкость не играют ни какой роли!
Действительно, если пароль, используемый декодером, известен, то
использовать криптостойкие алгоритмы бессмысленно!
Поэтому, наиболее популярными являются криптостемы на основе логической
операции xor. Одним из ее свойств является зеркальность. Повторное
шифрование результат восстановит исходный текст. Шифровщик и дешифровщик
устроены одинаково, что упрощает и сокращает код. Докажем, что
a xor b xor a = b.
Для этого просто перечислим все возможные значения a и b в следующей
табличке:
a\b ¦ 0 ¦ 1
-----+
+
0 ¦ 0 xor 0 xor 0 == 0 ¦ 0 xor 1 xor 0 == 1
¦ ¦
1 ¦ 1 xor 0 xor 1 == 0 ¦ 1 xor 1 xor 1 == 1
¦
Заметим, что xor это битовая операция. Аргументы a и b могут иметь только
два значения 0,1. Однако, никто не запрещает проводить ту же операцию для
последовательности битов. Команда процессора XOR word, const на самом деле
представляет собой не word xor const, а последовательность операций
над каждой парой битов двух переменных.
Теперь обратим внимание, что a xor 0 == a; a xor 1 == !a. Т.е.
значащими в маске шифрования являются только единичные биты. Поэтому
рекомендуется выбирать такую маску, в которой единичные и нулевые биты
равномерно перемешаны. К примеру, 00001111b (0xF) будет плохой маской, т.к.
оставляет неизменными четыре старшие бита в каждом символе шифротекста, что
позволяет (как будет показано ниже) успешно атаковать шифр. Маска 01010011
полностью уничтожает вероятностное распределение исходных символов в
шифротексте, поэтому считается хорошей.
Возможна шифровка двух видов - статическая и динамическая. В первом
случае дешифровщик работает только один раз, после чего исходный текст
может быть полностью восстановлен. Или иными словами, существует такой
момент, когда не осталось ни одного зашифрованного фрагмента.Такой подход
имеет очень простую реализацию, но крайне неэффективен. Хакер может снять
дамп с памяти в момент окончания работы расшифровщика и записать его на
диск. После чего полученный файл можно будет изучать штатными средствами.
Такое невозможно выполнить для динамической расшифровки, когда ни в какой
момент код не будет расшифрован полностью. При вызове процедуры она
расшифровывается, а при выходе зашифровывается опять. Технически можно
написать декодер, который расшифрует весь код в автономном режиме, но это
довольно сложно, и требует тщательного анализа защиты.
Покажем это на примере самошифрующийся программы. Традиционно такие
программы выполняются на ассемблере, но не более сложно реализовать это на
Си, Паскале и подобных языках, даже не используя ассемблерных вставок, а
работая с памятью через указатели.
Рассмотрим простейший пример (file://CD:/SRC/Crypt00.asm).
LEA SI,beginCrypt ; Расшифровываем с этого адреса
Repeat: ; <
¬
XOR Byte ptr [SI],077h ; Расшифровать очередной байт ¦
INC SI ; Переместить указатель ¦
CMP SI,offset endCrypt ; ?Конец достигнут ¦
JNA Repeat ; --{SI<=offset endCrypt}-------
Что бы полученная программа оказалась работоспособна необходимо вручную
зашифровать фрагмент [offset beginCrypt,offset endCrypt] по xor 0x77. Для
этого можно воспользоваться утилитой HIEW, скриптом IDA или написать
процедуру, которая это сделает автоматически.

¬
¬
¦ ¦ ¦ ¦
¦ рисунок p1 ¦ ¦ рисунок p2 ¦
L
L
Теперь сравним два дампа до и после шифровки. Шифровка исказила
исходный дамп до неузнаваемости, исчезла текстовая строка "Hello,Wordl!".
Этот прием может использоваться злоумышленником для сокрытия текстовых
фрагментов в вирусах, троянских программах и т.д.
Шифровка затруднила и изучение программы. Вот что выдаст дизассемблер в
нашем случае.
1AEF:0100 BE0D01 MOV SI,010D
1AEF:0103 803477 XOR BYTE PTR [SI],77
1AEF:0106 46 INC SI
1AEF:0107 81FE2401 CMP SI,0124
1AEF:010B 76F6 JBE 0103
1AEF:010D C3 RET ; < отсюда все зашифровано
1AEF:010E 7ECD JLE 00DD
1AEF:0110 62 DB 62
1AEF:0111 76BA JBE 00CD
1AEF:0113 56 PUSH SI
1AEF:0114 B43F MOV AH,3F
1AEF:0116 121B ADC BL,[BP+DI]
1AEF:0118 1B18 SBB BX,[BX+SI]
1AEF:011A 5B POP BX
1AEF:011B 57 PUSH DI
1AEF:011C 2018 AND [BX+SI],BL
1AEF:011E 051356 ADD AX,5613
1AEF:0121 7A7D JPE 01A0
1AEF:0123 53 PUSH BX
Как разобраться в этой дикой мешанине кода и данных? Что делать или как с
этим жить?
Тут на помощь приходит уникальный дизассемблер IDA, поддерживающая
встроенный Си-подобный язык. Следующий скрипт (file://CD:/SRC/crypt00.
idc) выполнит все автоматически. Что бы его запустить на выполнение нужно
дать команду : idax -Scrypt00.idc crypt00.com
Рассмотрим как он работает:
for (a=0x10D;a<0x124;a++) // Цикл дешифровки {
c=Byte(MK_FP(0x1000,a)); // Взять байт
c = c ^ 0x77; // Расшифровать
PatchByte(MK_FP(0x1000,a),c); // Записать результат
}
Фактически мы копируем алгоритм расшифровщика, с точностью до
реализации. Приведенный код расшифровывает загруженный образ файла, которой
потом IDA будет в состоянии дизассемблировать. Вот за эту возможность она
горячо любима всем хакерами. В самом деле, не нужно выходить из уютной и
привычной среды дизассемблера в агрессивную среду отладчика. Дожидаться
окончания расшифровки и записывать дамп на диск (а еще не всякий отладчик
обеспечивает такую возможность). Загружать полученный образ в дизассемблер
и если что не так, повторять все вновь.
Выше мы отмечали, что использование 32 битного ключа дает
0x100000000 вариантов и потребует около трех минут перебора. А если длина
ключа все 64 бита?0x10000000000000000 вариантов потребует ~30000 секунд или
почти восемь часов перебора (и еще больше выдаст ложных срабатываний).
Если бы мы могли достоверно знать хотя бы одну шестнадцати
байтовую последовательность, присутствующую в исходном тексте... Крайне
маловероятно, что мы располагает такой информацией! Однако на самом деле
положение вовсе не безнадежно и у нас по прежнему хорошие шансы найти даже
такой длинный ключ. Сначала покажем, что минимально необходимый фрагмент
открытого текста в действительности равен длине ключа плюс единица. В таком
случае фрагменты A и A' будут равны. Естественно это увеличит число ложных
срабатываний, но не так много, как кажется на первый взгляд. Представим для
начала, что нам известен лишь фрагмент А. Какова вероятность того, что он
совпадет с A'?

¬
L
,
ТАТ-------------ТАТ----- - - -
+
+------- - - -
¦ ¦
¦<---- L ------>¦
Давайте представим себе последовательность AA. Естественно, что она
будет "вмещать" в себя A^2 элементов. У скольких элементов левая "половина"
равна правой? Обозначим левую часть как L, а правую как R. Легко видеть что
для каждого L существует только один равный ему R. Число совпадающих
вариантов равно множеству элементов А. Тогда вероятность совпадения
произвольных А и А' равна #A/#A^2 == 1/#A, где # - число элементов
множества.
Однако, нас интересуют только те совпавшие числа, которые находятся
строго на расстоянии L друг от друга, где L как видно равна длине ключа.
Задачу подсчета данной вероятности я оставлю любопытным читателям решить
самостоятельно, здесь же только отмечу что она на несколько порядков ниже
от общего числа ключей, которое нам бы пришлось перебирать в противном
случае. Даже если #A равна одному биту (!) у нас не плохие шансы. И гораздо
более высокие, чем показал расчет любознательных читателей. Точнее говоря
они МОГУТ СТАТЬ несравненно выше. Используемая нами математическая модель
исходила из предпосылки равновероятности всех символов. Это очень упрощает
расчеты, но не соответствует действительности. Как правило, нам известно
хотя бы приблизительное распределение вероятности встречаемых символов. Это
поможет отбросить часть вариантов, как заведомо ложные или (что более
правильно) начать перебор с наиболее вероятных ключей к менее вероятным.
Вышесказанное звучит захватывающе, однако так и не подсказывает где нам
взять хотя бы 64+1 битовую последовательность для атаки по открытому
тексту. Ассемблерских команд такой длины и даже устойчивых их сочетаний не
существует. Но так уж в самом деле не существует ли? Может плохо искали?
Например, все языки высокого уровня используют стандартные
библиотеки, сингатуры которых известны, а число пренебрежительно мало (по
сравнению с числом возможных ключей, разумеется). Это применимо далеко не к
каждой программе, но к значительному их числу.
Допустим, автор защиты это учел и использовал неизвестный доселе
компилятор или полностью реализовал весь код на ассемблере и отважился
выбрать ну очень длинный ключ, настолько длинный, что даже не будем
уточнять какой точно. Восторжествует ли он на этот раз? Увы (или ура - в
зависимости от ориентации читателя). Злоумышленник применил масочную атаку!
Суть ее сводится к следующему, да пусть нам не известно сколь-нибудь
длинной стоки из зашифрованного текста, но мы наверняка знаем много
коротких! И при этом с некоторой достоверностью известно расстояние L между
ними.
Алгоритм атаки следующий - пусть s0 одна из существующий в шифротекст
последовательностей. Применим атаку по открытому тексту и в результате
получим ОГРОМНОЕ множество подходящих, но ложных ключей. Что ни один из не
верен это ясно из того, что длина каждого равна #s0. По условию s0 короче
пароля, следовательно ни один ключ не является законченным паролем. Однако,
ясно, что настоящий пароль включает в себя некоторые элементы полученного
множества. Возьмем другую известную последовательность s1 и повторим
аналогичную операцию. Теперь выберем общие для f(s0) и для f(s1)
элементы. Вероятнее всего, что именно из них и составлен пароль. С каждой
интерацией число символов, общее для всех последовательностей стремительно
уменьшается а вместе с ним и число вероятных паролей. Когда все
последовательности исчерпаны, выберем только те, которые разнесены в
границах предполагаемого расстояния (если такая информация имеется).
Существует множество вариантов получения пароля из заданного множества
фрагментов, однако нет нужны перебирать их все. Я доставлю читателю
удовольствие самому решить несложную задачку уменьшения числа возможных
вариантов. Привычка получать все в готовом виде в самом деле очень вредна.
А для хакера более того в корне неприемлема! В моем понимании хакер - это
человек который в любой ситуации привык рассчитывать только на себя.
Готовые технологии и знания это непозволительная роскошь.
Однако наводящую подсказочку я все же дам. Пусть нам неизвестна
никакая вероятность всех встречаемых в шифротексте символов, но для
аждой последовательности Sn, вероятность образующих ее элементов
звестна наверняка!
Как ломать crackMe03 (фрагмент книги)
При атаке на шифр считается, что криптоалгоритм известен с точностью до
реализации, и требуется найти пароль. В качестве примера к этому
рассмотрим программу crackme0.com (file://CD:/SRC/Crackme.com).
Алгоритм расшифровки ничем не защищен и его легко можно изучить. Однако,
это нам не дает никакой информации об исходном пароле.
CalcCRC: ; <
¬
LODSB ; Читаем байт введенного пароля ¦
ADD AH,AL ; Суммируем ¦
LOOP CalcCRC ; --{CX}
Эта процедура вычисляет CRC с введенного пароля. CRC очень простой и с
плохим рассеиванием. Множество паролей будет иметь одно и то же CRC,
поэтому нет никакой возможности предсказать на основе всего восьми-битного
числа исходный пароль.
LEA SI,Crypt ; На начало зашифрованных данных
Decrypt: ; <
¬
XOR [SI],AH ; Расшифровать байт ¦
INC SI ; Следующий байт ¦
CMP SI,offset Buffer; ?Мы уже все расшифровали ¦
JB Decrypt ; --{SI Шифротекст расшифровывается значением контрольной сумы от пароля!
Причем всего может существовать 0x100 (256) вариантов расшифровки! Нетрудно
было бы перебрать их всех найти исходный. Даже если бы использовалось слово
(0x1000) то вариантов было бы все равно не много!
Между прочим, это довольно распространенный примем в некоммерческих
программах. Удивительно, как это не учитывают авторы защит! Если
перебирать не пароли, а ключи (т.е. сами хеш-значения), то можно достичь
скорость порядка нескольких сотен ключей тысяч ключей в секунду. Таким
образом не спасет даже использование двойного слова (0x100000000). Оценим
наихудшее время, необходимое для перебора тридцати-двух битного ключа.
Исходя из средней скорости сто тысяч ключей в секунду, мы найдем результат
не более чем за 167 секунд, или приблизительно три минуты. Практически
мгновенно, не так ли? Все дело в высокой скорости перебора ключей,
обусловленной простотой алгоритма.
Автоматического перебор ключей опирается на возможность контроля
идентичности расшифрованного текста и оригинального. Программа, очевидно,
должна была контролировать правильность ввода пользователя и подсчитывать
CRC либо пароля, либо расшифрованного текста. Рассмотрим следующих код:
CmpCRC: ; <
¬
DEC SI ; Следующий байт ¦
ADD CL,[SI] ; Суммировать ¦
CMP SI,offset Crypt ; ?Конец ¦
JNB CmpCRC ; --{SI>offset Crypt}
CMP CL,0C3h ; ?Значение CRC верно
JZ Crypt ; --CRC OK-->
; СRC FAIL
Очевидно проверяется контрольная сумма исходного текста. Мы можем
исследовать хеш-алгоритм и хеш-сумму исходного текста. В нашем случае она
равна 0xC3.
Однако, сколько существует неправильных вариантов расшифровки, при
которых тем не менее их контрольные суммы совпадают? Очевидно, что больше
одного! И с ростом числа вариантов расшифровки количество "ложных" ключей
стремительно растет! Так, уже при 32-битных ключах основную проблему будет
представлять не поиск подходящий ключей, а выборка единственного верного
исходного текста, среди расшифрованных вариантов! При этом у нас никаких
достаточно строгих критериев по которым можно было бы автоматически
отличить ложные варианты. Разработчик защиты добился-таки своего!
Вероятностью, что пользователь введет неправильный, но подходящий пароль,
достаточно мала, что бы ей было можно пренебречь. Действительно,
предположим, что хотя бы 1% паролей будет давать ложные срабатывания, тогда
для 0x10000 (65536) ключей это составит 655 вариантов, а для 0x10000 уже
42.949.672! Обратим внимание, что с точки зрения криптографии все эти
варианты равноценны!
Разумеется, мы в любом случае имеем хотя бы косвенное представление об
исходном тексте. Но как его использовать? Можно по типу данных
предугадать вероятность того или иного символа, проверить на совпадение со
словарем, поискать некоторые закономерности... Но как же все это будет
медленно работать! И в любом случае (особенно на коротких фрагментов) нет
никакой гарантии, что даже самый надежный алгоритм всегда распознает
исходный текст.
Разработчики защиты могут торжествовать! Они заставили хакера
призадуматься. Хакеры же пошли другим путем. Криптографам был давно
известен метод атаки по открытому тексту. В самом деле, если злоумышленник
обладает хотя бы частью открытого текста, то он может сделать обратную
шифрованию операцию и найти колюч! Вспомним свойство операции xor:
X xor Key = Y, Y xor Key = X, но X xor Y = Key!
В нашем примере с восьмибитным ключом достаточно достоверно знать всего
лишь один байт открытого текста, что бы получить Key, который можно
применить ко всему шифротексту в целом. Но откуда же можем знать какой-то
байт чужого текста да еще и достоверно? Оказывается, можем, да еще как!
Конечно, не достоверно, но с определенной степенью вероятности мы можем
ПРЕДПОЛОЖИТЬ его наличие!
Весьма вероятно, что в приведенном шифротексте встретиться инструкция
INT 0x21. Ее двух-байтовый опкод 0x21CD (мы ведь помним про обратный
порядок байт в слове?) превышает длину ключа, а значит делает его на этом
промежутке неслучайным. В самом деле в уравнении X xor Y = key мы не знаем
ни X, ни key, при выбранном Y они могут быть любыми. Если #Y > #Key, то мы
получим X1 xor Y1 == X2 xor Y2 == key!
Может это объяснение покажется туманным, поэтому давайте приведем
практический урок, который поможет это усвоить. С помощью утилиты hiew
попробуем расшифровать секретный фрагмент применяя операцию XOR 0x21CD.
--------------¬
¦ рисунок 4 ¦
L--------------
00000030: C3 74 08 B4-09 BA BE 01-CD 21 C3 B0-B5 E4 BB A9
00000040: 00 20_20 2E-0C E7 51 8C-72 9E 76 82-73 89 21 A2
00000050: 4A CC^01 E7-25 7D 01 CD-21 CD 00 00-00 00 00 00
00000060: 00 00¦00 00-00 00 00 00-00 00 00 00-00 00 00 00
¦
Обратим внимание на последовательность 0x2020. С большой вероятностью в
оригинальном тексте здесь находилось 0x21CD, зашифрованное ключом 0x20!
Следует учитывать, что данный метод допускает ложные срабатывания. Поэтому
предпочтительнее использовать по возможности более длинные
последовательности. В этом случае мы получили только один возможный ключ, а
что бы мы стали делать если бы их оказалось несколько? Если мы знаем
частоту появления выбранной последовательности (а именно такие и стоит
выбирать), то мы можем определить истинный ключ простым сравнением! Однако,
допустим, два или более ключей имеют очень близкую к ожидаемой вероятность
появления. Как быть тогда? В этом случае необходимо попробовать поискать
другую последовательность, ожидаемую в шифротекст. В нашем примере это
может быть 0xA0D - перенос стоки. Весьма вероятно, что пример выводит
какую-то строку, которая вероятно завершается возвратом каретки. Оставим
читателю провести этот эксперимент самостоятельно, а сами обратим внимание
на следующий момент - в самом деле редкая программа обходится без вывода
текстовых сообщений. Но ведь в любом языке не так уж много слов, а тем
более распространенных! Это очень уязвимое место подобных систем. В самом
деле даже не потребуется утомительно перебора. Поищем такие стоки, как ©,
Copyright, OK, Cancel, Yes, No... Хорошо действует двойной пробел, пробел+
запятая и т.д.
Кроме того, не забываем, что CRC исходного текста нам известен,
следовательно можно быстро определить какой ключ из нескольких -
правильный. Я не буду приводить расчетов вероятности появление ложных
паролей скажу только, что при использовании последовательностей из двух и
более символов она уже достаточно невелика. Таким образом криптостойкость
данного шифра при атаке по открытому тексту (даже не зная точного
расположения последнего) равна нулю!
В данном примере использовался ключ 0x20. Внешне этот ключ абсолютно
ничем не примечателен, но взгляните за зашифрованный фрагмент:

¬
¦ ¦
¦ рисунок 5 ¦
L
И это ЗАШИФРОВАННЫЙ текст? Как же такое могло произойти? Магическое
свойство xor 0x20 переводить английский текст в противоположный регистр
сыграло с автором защиты очень злую шутку. У каждого алгоритма есть
некоторые число СЛАБЫХ паролей, которые в разной степени снижают его
криптостойкость. Об этом мы говорили выше. А теперь если вспомнить, что
0x20 - 100000b, то нетрудно убедиться, что такой ключ меняет всего один бит
на каждый байт! Т.е. шифротекст будет мало чем отличаться от исходного.
Хорошие криптосистемы делают проверку на слабые ключи, плохие - нет.
Учитывая, что число слабых ключей у иных шифров не так мало, как это
кажется на первый взгляд, с вероятностью использования такого ключа стоит
считаться.
ORC
¦ От переводчика.Это очень неплохое руководство местами содержит неточности
¦ которые я "осмелился" исправить. Так же некотореы вопросы я отваживаюсь
¦ углубить для более ясного понимания материала. Все ругательные обороты
¦ невозможно достоверно воспроизвести в силу разницы культур и языков,
¦ поэтому в ущерб точности я постарался передать сам дух автора.
L= 12-02-99 = 23:04:07 =
HOW TO CRACK, by +ORC, A TUTORIAL
=================================
[УРОК 1: ВВЕДЕНИЕ] [PoolDemo.exe]
"""""""""""""""""" """"""""""""""
Лучший путь для изучения кракинга (т.е. прекрасно понять, точно
определить и изганть (приостановить, отодвинуть) одну или больше схем
защиты внутри программного обеспечения без наличия исходных текстов)
начать самостоятельные хакерские эксперементы, используя СТАРЫЕ приложения,
которые имеют СТАРЫЕ системы защиты.
В этом случае вы быстро усвоите основую технику хакерской професси. Не
забывайте, что развите систем защиты шло не единственным путем... строго
говоря это даже не было развитием: вы неприменно обнапужите много новых
"умных" трайков {от trick - хитрость, трюк. Здесь и дальше в фигурных
скобках примечания переводчика}, но большее время придется откапывать
только избитые подражания прошлым (и хорошо изученным) идеям. Это не
удивительно: НАСТОЯЩИЕ знания "коммерческих" программистов (называющие сами
себя "протекционистами" {игра слов -"protectionists" это и сторонник
протекционизма и "защитник" программ} очень часто ограничены: онм склонны
использовать старые методы (иногда немного измененные, временами даже
усовершенстованные) взамен разработки новых технологий. Это типичная
"коммерческая" дегенерация, случающаяся с всякий раз с людьми,
работающими ради денег, вместо творения ради самого процесса или для
своего удовольствия. Эта "коммерческая" мода безрасудно возноситься
дурацким, бакс-ореенитрованным обществом, в котором мы вынуждены жить.
Поэтому я начинаю "настольную" часть (начинающуюся с урока 3),
использующу для примера немного "старых" приложений и немного "старых"
защит. Позднее мы сможем переключиться на новейшие защиты с целью понять
их. И вы научитесь как обламывать и этот вид гадости то же. Я так же
объясню ГДЕ вы сможете найти кучу программ для лома и сохранения
своих денег и КАК вы должны приступать к работе.
Это учебник для начинающих кракеров. Быть может просто раздумывающих
"а не захачить ли мне что-нибудь" или уже имевших попытки с переменным
результатом. Я не могу обещать, что вы получите что хотите, но я буду
стараться изо всех сил. С другой стороны, если вы уже вырубали немного
работающего кода на ассемблере и уже вскрывали много различных защит, тогда
этот туторал вероятно покажется элементарным для вас. (Если вы хотите
освежить основы, и не имеете более занимательного объекта време
препровождения, можете оставаться).
Для успешного взлома вам потребуется следующие основыне понятия:
v хорошее знания языка ассемблера (чем больше вы заете, тем лучше и
быстрее ломаете)
v Чуть-чуть интуиции
v Немного помощи более опытных кракерв { например, моей :) }
Приложения, что вы будете использовать для обучения могут быть поделены на
категории:
v изувеченные паролем (простейшие для взлома)
v изувеченные счетчиком дней или запусков (доволько простые для взлома)
v приложения, ограниченные датой исполозования (простые для взлома)
v приложения с некоторыми заблокированными функциями ( иногда простые
иной раз сложные)
v защищенные Дисковым доступом (которые сейчас считают устаревшими)
v привязанные к CD-ROМУ (очень простые для взлома)
v КРИПТОГРАФИЧЕСКИЕ (т.е. вариации из вышеупомянутых защит, но с добавкой
самомодифицирующегося кода (XOR & SHL) (довольно просты для взлома)
v ни какая из вышеперечисленных. (часто тяжелы для взлома)
ГДЕ ПОИМЕТЬ STUFF]
""""""""""""""""""
Получившие широкое распростанение "DEMO" сидиромки настоящая
сокровищница для кракера! {У нас такие то же есть. Например "Российский
СОФТ 97" - целый сидюк демок и дешево} Немного погодя после их выпуска вы
получите все оставшиеся копии, что остались непроданными практически
бесплатно {увы но не у НАС. Рынок, еще мать его...} Демонстационные CR-ROМы
позволяют вам хапунть сразу кучу приложений (старых и новых), что часто
каким-либо методом покуверканы (иногда интересными методами). Действительно
чудесный простор возможностей для кракеров! Гы! Нахаляву мы можете поиметь
целый диск до отказа набитый LOTUS {це фирма такая} приложениями (или M$
или WordPrefect) на "триал в течении 30 дней" или "20 запусков" редакций.
Вы действительно насладитесь взламывая их, используя каждое и каждого и\или
снисходительно-благосклонно поместите их на web для бедных ламеров, что не
имеют ни $$$ ни мозгов.
ИГРЫ определенно не будут больше выпендриваться! Они очень интересны
для будующих кракеров, т.к. часто "перезащищены" Я имею ввиду, что они
имеют защитные схемы действительно ОЧЕНЬ высокого уровня, скрытые внутри
файлов, которые относительно малы. Теперь, смотрите, это намного проще и
легче захачить и поскипать защитную схему внутри одинокого 35.000-байтного
исполняемого файла, чем локализовать ее внутри сборища множества длинных
DDLей, которые раздуты до 2.000.000 байт каждый. Ленивая куча
"современных" программистов, систематически релизяцих защитные схемы на
этой "скрытой желчи в недрах десерта" логике. Фактически они больше не
способны программировать на ассемблере: они больше и больше опираются на
"толстых" монстров яка Выжел Бацик, Делфы или Visual C++ {ну MS VC на мой
взгляд не плохой полигон. Тем паче, что можно кодить и без MFC} (Не
беспокойтесь... Я все же научу как быстро ломать эти огромные приложения)
Другой причиной выбора "рабочим материалом" игр, взамен приложениям
является часто ТОЧНО ТА ЖЕ защитная схема, которую вы можете
обнаружить во многих простых (и компактных) шаровых игр, позднее была
использована для "защиты" многих "могучих" и крайне экспенсивных
графических приложений.
По этой причине в моем туторале мы будем часто вскрывать защищенные
игрушки, даже если мы позднее применим эти знания в основном к коммерческим
схемам защиты или краку защит доступа к удаленным серверам (BBS или даже
ATM) {ATM вряд-ли, т.к. АвтоматическихТорговыхМашин у нас еще нету. Мож в
Москве и есть уже - не знаю, но считаем что нету}
Давайте теперь вместе взломаем как вступительный пример "time-protect"
защиту. Мы изучим позднее (-> Урок 4) что все приложения, что "взведены" на
время (т.е. "как много раз" вы используете их или "как долго") базируются
на сходных принципах (иногда с огромным числом вариаций по этому поводу)
v они могут иметь счетчик с "кликми" : НАЙДИТЕ ИХ и ВЫРУБИТЕ ИХ
v они могут вызывать таймерный прерывания : ПЕРЕХВАТИТЕ ИХ САМИ
v что-то-там сравнивать с переменной : NOOPим ИХ
v проверять дату ваших файлов на диске : НАЙДИТЕ СРАВНЕНИЕ и JMP!
Я хочу начать с современным примером защиты "счетчика кликов", просто дать
вас почуствовать крак, и я выбрал широко распростаненную дему: вы должны
найти ее сравнительно легко. С целью показать некоторые, подстерегающие нас
проблеммы мы сломаем для примера "неверно" (вы научитесь как ломать
эффективно в "настольных" уроках).
ПРИМЕР: [ARCADE POOL]
"""""""""""""""""""""
Демонстрационная версия для PC by East Point Software Ltd, © Team 17
Software Ltd 1994. Эта дема была опубликована многими журналами на их
сидюках в 1995 году {и где же сейчас ее искать?}
Эта прекрасный бильярд имеет "time" узуверки. Вы можете играть только 2
минуты, после чего "nag"-разражатель напомнит где и почем вы сие творение
можете купить {малось ругательств поскипна без извращения сути}
Ну, что бум делать? Здесь вы можете сделать (но необязательно должны):
взть [Soft-Ice] и загрузить его в ваш config.sys (Как сие сделать вам
раскажет "Инструменты нашей професси" и аналогичное "Инструменты, которые
мы вибираем by KPNC") Версия 2.6 Софт-Айса была взломана MARQUIS DE SOIREE
и легко может быть найдена на Web {или www.dore.da.ru - там лежит soft-ice
2.8 - последний из серии для DOS}
v сохраним все вектора перед загрузкой нашей малышки
v пшла, залетная! ldr pooldemo.exe
v сравним вектора и распечатаем перехваченные
v {...скипнуто, как длинное и не существенное...}
v рассмотрим карту памяти
v теперь дампим, занятую PoolDemo память на диск
v секуду-другую ничего не делаем
v дампим память еще раз
!IDA DEBUGER!
А вы пользуетесь отладчиком IDA? Нет? А зря. Очень рульная штука. И
удобная. К тому же мощная!
Идея пришла неожиданно. У нас же есть готовая интерактивная среда и
встроенный язык плюс дешифратор команд! Написать эмулятор только особо
ленивый не сможет. Конечно, встроенного языка в версии 3.6 не хватит даже
для игрушки, поэтому нам нужны более поздние версии. Там можно на Си
написать DLL и подключить. Скорость будет обеспечена. Курсор перемещать мы
может, горячие клавиши можно реализовать через макосы (вызовы новых функций
с консоли).
В чем новизна и удобство идеи? А в том, что можно сделать не полный,
а _контекстный_ отладчик! Что это такое? Обычный дебарег отлаживает всю
программу целиком. Это хорошо, но чаще всего нас интересуют только
выбранные фрагменты. В IDA можно подогнать курсор к нужному месту, задать
начальные значения регистров и пусть на выполнение эмулятор. Такой подход
прежде всего упорщает задачу, т.к. тут скорость не трубуется. А как удобно!
Можно видеть работу кода в динамике! И не ломать голову какие будут
значения регистров\флагов на выходе из такого и такого фрагмента и куда
метнеться условный переход. Можно просто прогнать чисто локальный кусок с
любой его точки.
Это сразу легко позволит делать расшифрвку - подводим курос - пошли
его расшифровывать. Причем можно автоматически анализирвать расшифрованный
код, что бы все выглядело цивильно! И бряк-поинты можно организовать!
Обычно IDA и ICE используются в связке ради уточнения некотрых
мелочей. Когда неясно как работает данный код и что он делает - загружае
ICE (можно прямо из IDA) и смотрим. Потом опять в IDA и продолжить поиски.
Так вот, удобнее было бы никуда не метаться, а прямо в IDA все это и
дебагить.
Реализовать это все просто. Осталость найти время. Или заинтересовать
людей и всем миром - каждый со своей локальной задачей - это решить! Идей
есть - это главное.

Поділитися у соцмережах

☏ Замовлення реклами: +3805044-68-872

© Асоціація електронних бібліотек України

☝ Усі матеріали сайту (зокрема статті, зображення, рекламні оголошення тощо) призначені лише для попереднього ознайомлення. Усі права на публікації, представлені на сайті, належать їхнім законним власникам. Просимо Вас не зберігати копії інформації.