Техника и философия хакерских атак II (фрагмент [2/3])
kk@sendmail.ru
классификация защит по стойкости к взлому
Всемогущи ли хакеры? Всякую ли защиту можно взломать? При всем своем многообразии защитные механизмы, окружающие нас, делятся на два типа: криптозащиты (называемые так же защитами Кирхгофа) и логические защиты.
Согласно правилу Кирхгофа, стойкость криптозащит определяется исключительно стойкостью секретного ключа. Даже если алгоритм работы такой защиты становится известен, это не сильно упрощает его взлом. При условии правильного выбора длины ключа, защиты Кирхгофа неломаемы в принципе (если, конечно, нет грубых ошибок в их реализации, но криптозащиты с подобными ошибками в категорию защит Кирхгофа просто не попадают).
Стойкость логических защит, напротив, определяются степенью секретности защитного алгоритма, но отнюдь не ключа, вследствие чего надежность защиты зиждется на одном лишь предположении, что защитный код программы не может быть изучен и/или изменен.
Конечно, для рядовых пользователей, абсолютно ничего не смыслящих ни в дизассемблерах, ни в отладчиках, совершенно все равно каким путем осуществляется проверка вводимого ими регистрационного номера. Защищенное приложение с их точки зрения представляет собой "черный ящик", на вход которого подается некоторая ключевая информация, а на выходе: "success" или "fuck out, shit mother fucker!". Хакеры - другое дело. Если регистрационный номер используется для расшифровки критически важных модулей программы, - дело дрянь и, если процедура шифрования реализована без ошибок, единственное, что остается - найти рабочую (читай - легально зарегистрированную) программу и снять с нее дамп. Если же защита тем или иным путем сравнивает введенный пользователем пароль с заложенным в нее эталонным паролем, - у хакера есть все шансы ее сломать. Как? Исследуя защитный код хакер может:
а) найти эталонный пароль и "подсунуть" ее программе как ни в чем не бывало;
б) заставить защиту сравнивать веденный пароль не с эталоном, а: с самим собой!
в) выяснить какой именно условный переход выполняется при вводе неверного пароля и скорректировать его так, чтобы он передавал управление не на ругательное сообщение, а на "легальную" ветку программы;
Подробный разговор о конкретной технике взлома ждет нас впереди, пока же просто учтем, что такой тип защит действительно может быть взломан. Причем, не просто "взломан", а "очень быстро взломан", - порой расправа с защитой занимает всего лишь несколько минут и только сильно навороченным защитам удается продержаться под осадой день - два.
Возникает вопрос: если логические защиты и вправду настольно слабы, то почему же их так широко используют? Во-первых, большинство разработчиков программного обеспечения совершенно не разбираются в защитах и просто не представляют во что именно компилятор "перемалывает" исходный код (судя по всему, машинный код им представляется таким дремучим лесом, из которого живым никто выбраться не сможет). Во-вторых, в ПО массового назначения надежность защитных механизмов все равно ничего не решает. Как было сказано выше, при наличии хотя бы одной-единственной зарегистрированной копии, хакер просто "снимет" с программы дамп и все! Защита, даже не успев сказать "мяу", отлетит в мир иной (туда - где, находится тот самый Сервер, на который попадают все деинсталируемые программы без исключения). В-третьих, основной доход от продаж ПО приходится на долю тех страх, граждане которых законопослушны и защиты на ломают. Что же до нас, - россиян - мы программы вообще не покупаем. Даже если защитный механизм окажется хакерам не по зубам, акты легальной покупки программы будут носить единичный характер.
Таким образом, несмотря на то, что все программы в принципе ломаемы, "хакнуть" демонстрационную программу, скаченную из Сети или купленную на CD-диске возможно далеко не всегда. Если критические участки приложения зашифрованы (или, - что еще хуже - физически удалены из демонстрационного пакета), то:вылезай, приехали!
классификация защит по роду секретного ключа
Одни защиты требуют ввода серийного номера, другие - установки ключевого диска, третьи же "привязываются" к конкретному компьютеру и наотрез отказываются работать на любом другом. Казалось бы - что может быть между ними общего? А вот что: для проверки легальности пользователя во всех трех случаях используется та или иная секретная информация, известная (и/или доступная) только ему одному. В первом случае, в роли пароля выступает непосредственно сам серийный номер, во втором - информация, содержащаяся на ключевом диске, ну а в третьем - индивидуальные характеристики компьютера, представляющие с точки зрения защитного механизма точно такую последовательность чисел, как и "настоящий" секретный пароль.
Правда, между секретным паролем и ключевым диском (компьютером) есть принципиальная разница. Вводимый им пароль пользователь знает явно и, при желании может поделиться им с друзьями без ущерба для себя. Ключевым диском (компьютером) пользователь обладает, но совершенно не представляет себе, что именно этот диск содержит. При условии, что ключевой диск не копируется автоматическими копировщиками, пользователь не сможет распространять такую программу до тех пор, пока не выяснит характер взаимодействия защиты с ключевым диском (компьютером) и не разберется как эту защиту обойти. Имеются по меньшей мере три пути:
а) защитный механизм нейтрализуется (в особенности это относится к тем защитам, которые просто проверяют ключевой носить на наличие неких уникальных характеристик, но реально никак их не используют);
б) ключевой носитель дублируются "один к одному" (весьма перспективный способ защит, которые не только проверяют ключевой носитель на его наличие, но и некоторым сложным образом с ним взаимодействуют, скажем динамически расшифровывая номерами сбойных секторов некоторые ветви программы);
в) создастся эмулятор ключевого носителя, обладающий всеми чертами оригинала, но реализованный на совершенно иных физических принципах (актуально для тех случаев, когда скопировать ключевой носитель на имеющимся у хакера оборудовании невозможно или чрезвычайно затруднительно и, вместо того, чтобы послойно сканировать на электронном микроскопе всем хорошо известный HASP, хакер пишет специальную утилиту, которая с точки зрения защитного механизма ведет себя как настоящий HASP, но при этом ее можно свободно копировать).
Очевидно, что защиты, основанные на знании, полагаются исключительно на законодательство и законопослушность пользователей. Действительно, что помешает легальному пользователю поделиться паролем или сообщить серийный номер всем желающим? Конечно, подобное действие квалифицируется как "пиратство" и с недавнего времени преследуется по закону. Но точно также преследуются (и наказываются!) все нелегальные распространители контента, охраняемого авторским правом, вне зависимости от наличия/отсутствия на нем защиты. Тем не менее, несмотря на резко ожесточившуюся борьбу с пиратами, нелегальное программное обеспечение по-прежнему свободно продается как в центральных магазинах, так и на радиорынках. Практически под любую программу, распространяемую через Internet как share-ware, в том же самом Интернете можно найти готовый "кряк" или ее бесплатный аналог (и нечего тут смеяться!).
В этих условиях "спасение утопающих - дело рук самих утопающих". Наивно, конечно, думать, что количество легальных продаж прямо пропорционально крутизне вашей защиты, но: share-ware программа без защиты рискует перестать продаваться вообще (даже американские "зомби" предпочитаю не платить за программу, которая каждый день об этом им не напоминает). В первом издании настоящей книги я писал "Самые распространенные сегодня защиты - это пароли и серийные номера". Изменилось что ни будь за истекшие четыре года? Анализ программ, прилагаемых к журналу "Компьютер Пресс" на CD показал, что многие разработки наконец-то вняли советам хакеров и убрали пункт "Registers" из меню и теперь программа требует для регистрации: неизвестно что. Это может быть и ключевой файл, и запись в реестре, и некоторая последовательность "вслепую" нажимаемых клавиш, и: еще много всего! Так же исчезли текстовые сообщения о успешности/не успешности регистрации, в результате чего локализация защитного механизма в коде исследуемой программы значительно усложнилась (при наличии текстовых сообщений нетрудно по перекрестным ссылкам найти кто именно их выводит, после чего защитный механизм можно легко "раскрутить").
Из качественно новых отличий мне хотелось бы отметить лишь одно: использование Интернет для проверки "чистоты" лицензионности программы. В простейшем случае, защитный механизм периодически ломиться в сеть, где на специальном сервере хранятся более или менее полная информация о всех, зарегистрированных клиентов. Если регистрационный номер, введенный пользователем, здесь действительно присутствует, то все ОК, в противном случае защита дезактивирует флаг "зарегистрированности" программы, а то и удаляет сама себя с диска. Естественно, разработчик программы может по своему желанию, удалять из базы регистрационные номера тех пользователей, которые ему не понравились (либо же по его мнению были растиражираваны пиратами). Другие защиты нагло (и зачастую скрытно!) устанавливают на компьютере TCP-/UDP-сервер, предоставляющий ее разработчику те или иные возможности удаленного управления программой (обычно - дезактивацию ее нелегальной регистрации).
Тем не менее, такие защиты очень просто обнаружить и еще проще устранить. Обращение к Интернету не может пройти незаметным, - сам факт такого обращения легко распознается даже штатной утилитой NET STAT, входящий в комплект поставки операционных систем Windows 9x/NT, ну а эстеты могут воспользоваться TCPVIEW Марка Русиновича. Локализовать код защитного механизма так же не составит большого труда, - достаточно пойти по следу тех самых API-функций, которые, собственно, и демаскируют защиту, причем, все известные мне защиты этого типа пользовались исключительно библиотекой WINSOCS и ни одна из них не отважилась взаимодействовать с сетевым драйвером напрямую, да, впрочем, это все равно не усложнило бы взлом:
шаг первый - создаем защиту и пытаемся ее сломать
Предложим, что мы хотим оградить некоторую программу от доступа посторонних. Как это можно сделать? Самое простое, что приходит нам в голову - сразу же после запуска программы затребовать у пользователя пароль и сравнить его с эталоном. Затем, в зависимости от результата сравнения, либо послать пользователя к черту, либо продолжить нормальное выполнение программы. ОК, на словах все выглядит хорошо, но как это реализовать программно?
"Глупый вопрос!" - воскликните вы, - "даже начинающие программисты знаю, что сравнение строк осуществляется функцией strcmp (если мы говорим о Си) или даже просто оператором равенства в Дельфи и Паскале). Убедиться в правильности пароля - плевое дело, вот, пожалуйста, держите программу! (за отсутствие контроля длины вводимого пароля большая просьба нас не пинать, ведь это всего лишь пример)".
#define legal_psw "my.good.password" main() { char user_psw[666]; cout << "crackme 00h\nenter passwd:"; cin >> user_psw; if (strcmp(legal_psw, user_psw)) cout << "wrong password\n"; else cout << "password ok\nhello, legal user!\n"; return 0; }
Листинг 1 C5F11EA6h пример простейшей парольной защиты
Откомпилируем crackme.C5F11EA6h.cpp и запустим его на выполнение. Ага, программа требует ввести пароль. Чтобы сравнить введенным пароль с эталонным, последний должен как-то храниться в программе, так? А тестовые строки, между прочим, никак не уродуются компилятором и в откомпилированном файле хранятся в своем "естественном" виде!
Для того чтобы найти правильный пароль, достаточно лишь просмотреть дамп программы и отыскать все текстовые строки, которые могут быть паролем. Ошибка разработчика защиты состояла в том, что он по своей наивности понадеялся, что взломщик не найдет открыто хранящийся пароль в дампе программе. Как это ни странно, но даже вполне профессиональные программисты защищают свои программы именно так (и игры, русифицированные фирмой Акела, - яркое тому подтверждение).
Для просмотра дампа подойдет любой hex-вьювер (например, всем известный HIEW), а при его отсутствии вас выручит знаменитая утилита dumpbin входящая в штатный комплект поставки подавляющего большинства Windows-компиляторов.
Причем, незачем просматривать весь дамп исследуемой программы целиком (как это рекомендовалось в первом издании настоящий книги). За прошедшее время в компьютерном мире очень многое изменилось: MS-DOS программы отошли в мир иной, а вместе с ними ушли и те уродливые компиляторы, что любили размещать константные строки в сегменте кода (больше всех этим славились ранние компиляторы фирмы Borland). Сегодня данные всегда
Однако, просматривать весь дамп целиком (особенно для больших файлов) - слишком утомительно и возникает желание хоть как-то автоматизировать этот процесс. Как это сделать? Существует огромное множество алгоритмов распознавания строк, вот, например, самый простейший из них: извлекаем очередной символ из файла и смотрим: может ли он быть строкой или нет? (строки и особенно пароли в подавляющем большинстве случаев состоял лишь из читабельных символов, т. е. тех, что могут быть введены с клавиатуры и отображены на экране). Читабельные символы накапливаются во временном буфере до тех пор, пока не кончится файл или встретится хотя бы один нечитабельный символ. Если количество символов, накопленных в буфере, дотягивается по крайней мере до пяти - шести, то перед нами с большой степенью вероятности "настоящая" ASCII-строка, в противном случае, это скорее всего двоичный "мусор", не представляющий никакого интереса, и мы, очистив временный буфер, начинаем накапливать читабельные символы сначала.
Пример готовой реализации программы-фильтра можно найти на прилагаемому к книге компакт-диску (см. каталог etc со всякой всячиной), но лучше попрактиковаться в ее написании самостоятельно.
Итак, если все сделано правильно, то мы должны получить следующий результат:
------------> смещение в файле ¦ ¦ -------> текстовая строка ¦ ¦ 00007D11:LCMapStringW 00007D1F:KERNEL32.dll 0000805C:crackme 00h 0000806A:enter passwd: 0000807D:my.good.password 0000808F:wrong password 0000809C:password ok 000080AF:hello, legal user! 000080C2:.?AVios@@ 000080DE:.?AVistream@@ 00008101:.?AVistream_withassign@@ 0000811E:.?AVostream@@ 00008141:.?AVostream_withassign@@ 00008168:.?AVstreambuf@@ 0000817E:.?AVfilebuf@@ 000081A0:.?AVtype_info@@
Листинг 2 результат автоматической фильтрации двоичного тела программы
Рассмотрим полученный листинг. Обратим внимание на строку "my.good.password", находящуюся по адресу 807Dh. Не правда ли, она могла бы быть паролем? Чаще всего (но необязательно) искомая строка располагается близко к тексту "введите пароль". Ниже (80AFh) мы видим еще одного "кандидата". Давайте проверим, подойдет ли хотя бы один из них?
> crackme. C5F11EA6h.exe enter passwd:my.good.password password ok hello, legal user!
Листинг 3 скармливание программе первого пароля-кандидата. ответ защиты красноречиво свидетельствует о ее полной и безоговорочной капитуляции
Несмотря на простоту, данный метод не лишен недостатков. Самый главный из них - то, что успешный взлом не гарантирован. Если разработчик не дурак, то в открытом виде пароля не окажется. Более надежным (но, увы, и более трудоемким) способом взлома является дизассемблирование программы с последующим анализом алгоритма защиты. Это трудоемкая и кропотливая работа, требующая не только знаний ассемблера, но и усидчивости, а также немного интуиции. Однако, глаза страшатся, а руки делают:
шаг второй от EXE до CRK
Бесспорно, среди существующих на сегодняшний день дизассемблеров лучшим является IDA Pro. Особенно идеально она подходит для взлома и изучения защищенных программ. Очевидно, что crackme.C5F11EA6h не является таковой в полном смысле этого слова. В нем нет ни шифрованного кода, ни "ловушек" для дизассемблеров. SOURCER или любой другой справился бы с этой задачей не хуже. Поэтому окончательный выбор я оставляю за читателем (кстати, четвертая версия ИДЫ с некоторого времени начала распространяться бесплатно).
После того как дизассемблер завершит свою работу и выдаст километровый листинг, неопытный читатель может испугаться: как войти в эти дебри непонятного и запутанного кода? Сотни вызовов функций, множество условных переходов... Как во всем этом разобраться? И сколько времени потребуется на анализ? К счастью, нет никакой нужды разбираться во всем дизассемблированном листинге целиком. Достаточно изучить и понять алгоритм защитного механизма, ответственного за сверку паролей. Единственная проблема как найти этот механизм в бескрайних степях дизассемблерного кода? Можно ли этого добиться иначе, чем полным анализом всей программы? Разумеется, можно! Давайте, например, попробуем воспользоваться перекрестными ссылками на ASCII-строки типа "неверный пароль", "пароль ОК", "введите пароль", прямым текстом содержащиеся в программе. Чаще всего код, ответственный за их вывод на экран, находится непосредственно в гуще защитного механизма или, на худой конец, расположен где-то поблизости.
Сами же строки в подавляющем большинстве случаев находятся в сегменте данных, именуемом ".data". (В старых программах под DOS это правило часто не соблюдалось. В частности, компилятор Turbo Pascal любил располагать константы непосредственно в кодовом сегменте). Для перехода в сегмент данных в IDA нужно в меню "View" выбрать пункт "Segments" и среди перечисленных в появившемся окне сегментов отыскать сегмент с именем "data". Прокручиваем экран дизассемблера на несколько страниц вниз и, - вот они наши строки, сразу же бросающиеся в глаза даже при беглом просмотре:
.data:00408050 aCrackme00hEnte db 'crackme 00h',0Ah ; DATA XREF: sub_401000+D^o .data:00408050 db 'enter passwd:',0 .data:0040806A align 4 .data:0040806C aMy_good_passwo db 'my.good.password',0 ; DATA XREF: sub_401000+2A^o .data:0040807D align 4 .data:00408080 aWrongPassword db 'wrong password',0Ah,0 ; DATA XREF: sub_401000+62^o .data:00408090 aPasswordOkHell db 'password ok',0Ah ; DATA XREF: sub_401000+7A^o .data:00408090 db 'hello, legal user!',0Ah,0 .data:004080B0 dd offset off_4071A0
Листинг 4 текстовые строки и перекрестные ссылки
Смотрите, - IDA автоматически восстановила перекрестные ссылки на эти строки (т. е. опередила адрес кода, который к ним обращается) и оформила их в виде комментария (в приведенном выше листинге они выделены жирным шрифтом). Каббалистическая грамота типа "DATA XREF: sub_40100+62" расшифровывается как "перекрестная ссылка [X - References] на данные [DATA], ведущая к коду, расположенному по смещению 0x62 относительно начала функции sub_40100". Для быстрого перехода в указанное место достаточно лишь подвести курсор в границы "sub_401000+62" и долбануть по <ENTER'у> или же дважды щелкнуть мышем. Через мгновение судьба нас заносит сюда:
.text:00401000 sub_401000 proc near ; CODE XREF: start+AF p .text:00401000 .text:00401000 var_29C = byte ptr -29Ch .text:00401000 .text:00401000 sub esp, 29Ch .text:00401006 mov ecx, offset dword_408A50 .text:0040100B push ebx .text:0040100C push esi .text:0040100D push offset aCrackme00hEnte ;"crackme 00h\nenter passwd:" .text:00401012 call ??6ostream@@QAEAAV0@PBD@Z ; ostream::operator<<(char const *) .text:00401017 lea eax, [esp+2A4h+var_29C] .text:0040101B mov ecx, offset dword_408A00 .text:00401020 push eax .text:00401021 call ??5istream@@QAEAAV0@PAD@Z ; istream::operator>>(char *) .text:00401026 lea esi, [esp+2A4h+var_29C] .text:0040102A mov eax, offset aMy_good_passwo ; "my.good.password" .text:0040102F .text:0040102F loc_40102F: ; CODE XREF: sub_401000+51 j .text:0040102F mov dl, [eax] .text:00401031 mov bl, [esi] .text:00401033 mov cl, dl .text:00401035 cmp dl, bl .text:00401037 jnz short loc_401057 .text:00401039 test cl, cl .text:0040103B jz short loc_401053 .text:0040103D mov dl, [eax+1] .text:00401040 mov bl, [esi+1] .text:00401043 mov cl, dl .text:00401045 cmp dl, bl .text:00401047 jnz short loc_401057 .text:00401049 add eax, 2 .text:0040104C add esi, 2 .text:0040104F test cl, cl .text:00401051 jnz short loc_40102F .text:00401053 .text:00401053 loc_401053: ; CODE XREF: sub_401000+3B j .text:00401053 xor eax, eax .text:00401055 jmp short loc_40105C .text:00401057 ; ------------------------------------------------------------------ .text:00401057 .text:00401057 loc_401057: ; CODE XREF: sub_401000+37 j .text:00401057 ; sub_401000+47 j .text:00401057 sbb eax, eax .text:00401059 sbb eax, 0FFFFFFFFh .text:0040105C .text:0040105C loc_40105C: ; CODE XREF: sub_401000+55 j .text:0040105C pop esi .text:0040105D pop ebx .text:0040105E test eax, eax .text:00401060 jz short loc_40107A .text:00401062 push offset aWrongPassword ; "wrong password\n" .text:00401067 mov ecx, offset dword_408A50 .text:0040106C call ??6ostream@@QAEAAV0@PBD@Z ; ostream::operator<<(char const *) .text:00401071 xor eax, eax .text:00401073 add esp, 29Ch .text:00401079 retn .text:0040107A ; --------------------------------------------------------------------- .text:0040107A .text:0040107A loc_40107A: ; CODE XREF: sub_401000+60 j .text:0040107A push offset aPasswordOkHell ;"password ok\nhello, legal user!\n" .text:0040107F mov ecx, offset dword_408A50 .text:00401084 call ??6ostream@@QAEAAV0@PBD@Z ; ostream::operator<<(char const *) .text:00401089 xor eax, eax .text:0040108B add esp, 29Ch .text:00401091 retn .text:00401091 sub_401000 endp
Листинг 5 результат дизассемблирования файла crackme.C5F11EA6h.cpp, местоположение курсора выделено инверсным цветом
Судя по ссылкам на текстовые строки "enter password", "wrong password" и "password ok", сосредоточенных на небольшом участке кода, - функция sub_401000 - тот самый заветный защитный механизм и есть. Согласитесь, что проанализировать сотню строк дизассемблерного кода (а именно столько функция sub_401000 и занимает), совсем не то же самое, что разобраться более чем двенадцати тысячами строк исходного файла!
Главная цель разработчиков защиты - спроектировать защитный механизм так, чтобы не оставить никакой избыточной информации, касающихся аспектов его функционирования. Проще говоря, - не оставляйте за собой следов! Рассматриваемый же нами пример наследил по самое не хочу. Текстовые строки, сообщающие пользователю о неправильном вводе пароля, - это самый великолепный след, который хакером доводилось когда либо видеть. Куда он ведет? Очевидно, к коду, которую эту строку выводит! В свою очередь этот "ругательный" код ведет к коду, который его при тех или иных обстоятельствах вызывает. Короче, в конце своего пути, след выведет нас на тот код, который и принимает решение о корректности введенного пароля, - самое сердце защиты (или, выражаясь военной терминологией, "штаб-квартира главнокомандующего"). В порядке затруднения взлома, это место следовало бы получше скрыть!
Впрочем, своей крутизной нам еще рано гордится. Ведь защитный код нашли не мы, а интеллектуальный анализатор дизассемблера IDA. А как быть тем несчастным у которых этого дизассемблера просто нет? Что ж, тогда можно воспользоваться любым подручным hex-редактором (пусть для определенности это будет HIEW), ну и конечно своими собственными руками и головой. Постойте! - воскликнет иной читатель. - Но какой черт мы будем возиться с HIEW'ом, загружая свою голову не весь чем, когда можно приобрести IDA, избавляя тем самым от необходимости вникать во все премудрости ручного анализа! Что ж, - отвечу я. Свой жизненный путь каждый из нас выбирает сам. И если вам в первую очередь важен конечный результат, а на понимание сути происходящего вы готовые плевать - пожалуйста, идите этим путем. Действительно, большинство защит вскрываются стандартными приемами, которые достаточно заучить как "отче наш", и которые не требуют понимания "как это работает". Далеко не каждый кракер обладает глубокими знаниями того, что он ломает. Мой тезка и в каком-то смысле коллега (широко известный среди спектрумистов уже едва ли не десяток лет) однажды сказал "Умение снимать защиту, еще не означает умения ее ставить". Это типично для кракера, ломающего программы за деньги, а не на интерес. Хакеры же в свою очередь больше интересуются именно принципом функционирования защитного механизма и взлом для них вторичен. Взломать программу, но не понять ее, - для хакера все равно, что ничего вообще не взломать. Взлом он ведь разный бывает: можно, например, просто подобрать пароль методом тупого перебора, а можно бросить защите интеллектуальный вызов и победить ее или проиграть, но как проиграть! Горечь поражение компенсирует приобретенный опыт и он же дает пищу для последующих размышлений, делает нас выше, лучше, умнее! А тупой перебор нам ничего кроме как щенячьей радости от победы не добавляет.
Итак, если вы хакер, - ваши пальцы быстро набивают на клавиатуре заветное: "hiew crackme.C5F11EA6h.exe". Теперь, вызывая диалог контекстного поиска по <F7> мы пытаемся найти по какому адресу в файле расположена строка "wrong password" (обратите внимание: именно адресу, а не смещению, - hiew несмотря на свою кажущуюся простоту, в порядке собственной инициативы анализирует заголовок PE-файла и автоматически переводит смещения в виртуальные адреса, т. е. те адреса, которые данные ячейки получат после загрузки файла в память):
.00408080: 77 72 6F 6E-67 20 70 61-73 73 77 6F-72 64 0A 00 wrong password0 .00408090: 70 61 73 73-77 6F 72 64-20 6F 6B 0A-68 65 6C 6C password ok0hell .004080A0: 6F 2C 20 6C-65 67 61 6C-20 75 73 65-72 21 0A 00 o, legal user!0 .004080B0: A0 71 40 00-00 00 00 00-2E 3F 41 56-69 6F 73 40 аq@ .?AVios@ .004080C0: 40 00 00 00-00 00 00 00-A0 71 40 00-00 00 00 00 @ аq@
Листинг 6 определение адреса текстовых строк, выводимых защитой при вводе неправильного пароля
Если верить HIEW'у, то строка "wrong password" расположена по адресу 00408080h. Запоминаем (записываем его на бумажке) и, не забыв переместиться в начало файла, давим <F7> еще раз и в поле "hex" вводим адрес строки, записанный задом наперед: "80 80 40 00". Почему задом наперед?! Да потому что в x86 процессорах младшие байты всегда располагаются по меньшему адресу и, соответственно, наоборот. Если сказанное вам не очень-то понятно, обратитесь к любому учебнику по ассемблеру (или к документации на x86 процессоры наконец).
HIEW быстро находит первое вхождение, которое приходится на следующий и, между прочим, уже знакомый нам машинный код:
.0040105E: 85C0 test eax,eax .00401060: 7418 je .00040107A -------- (2) .00401062: 6880804000 push 000408080 ;" @ИИ" .00401067: B9508A4000 mov ecx,000408A50 ;" @SP" .0040106C: E884040000 call .0004014F5 -------- (2) .00401071: 33C0 xor eax,eax .00401073: 81C49C020000 add esp,00000029C ;" O?" .00401079: C3 retn .0040107A: 6890804000 push 000408090 ;" @И?" .0040107F: B9508A4000 mov ecx,000408A50 ;" @SP" .00401084: E86C040000 call .0004014F5 -------- (3) .00401089: 33C0 xor eax,eax .0040108B: 81C49C020000 add esp,00000029C ;" O?" .00401091: C3 retn
Листинг 7 результат поиска кода, выводящего строку "wrong password" на экран, положение курсора выделено инверсным цветом
Сравните его с дизассемблерным листингом IDA, не правда ли, результат работы HIEW'а несколько менее информативен? Однако мы отвлеклись. И возращение к нашим баранам мы начнем с изучения прототипа функции ostream::operator<<(char const*) (она же - функция .0004014Fh в HIEW'е). Компилятор языка Cи заносит в стек все аргументы справа налево, поэтому 0x408080 и будет тем указателем на строку (*str), которую эта функция и выводит. Таким образом, мы находимся в непосредственной близости от защитного механизма. Сделаем еще один шаг, переместив свой взор на несколько строк назад (т. е. в область меньших адресов), видите:
.0040105E: 85C0 test eax,eax .00401060: 7418 je .00040107A -------- (2)
Листинг 8 тот самый заветный условный переход, который отличает всех правильных пользователей от неправильных
Выводу строки "wrong password" предшествует условный переход JE .00040107A, который в случае нулевого значения регистра EAX "перепрыгивает" через функцию вывода строки "wrong password", т. е. другими словами, передает управление на "правильную" ветку программы, - именно ту, которая выводит "password ok"!
Пришло время немного "похулиганить" и изменить ту заветную пару байт, которая мешает нелегальным пользователям (а так же всем легальным, но забывшим пароль) получить доступ к программе. Достаточно очевидно, что если изменить условный переход JE .0040107A на безусловный JMP short .0040107A любой введенный пароль защита станет воспринимать как правильный. Переводим HIEW в режим редактирования, нажав <F3> и подведя курсор к строке с этим самым условным переходом, меняем "JE" на "JPMS". Теперь сохраняем изменения в файле <F9> и выходим.
Запустим программу и попробуем ввести любое слово (желательно из нормативной лексики), пришедшее нам на ум. Если все было сделано правильно, на экране победно загорается надпись "password ok". Если же программа зависла, значит, мы где-то допустили ошибку. Восстановим программу с резервной копии и повторим все сначала.
Если же взлом прошел успешно, то можно попробовать придумать какую-нибудь шутку. Вот, например, подумаем, что произойдет, если заменить JE на JNE? Ветви программы поменяются местами! Теперь, если будет введен неправильный пароль, то система воспримет его как истинный, а легальный пользователь, вводя настоящий пароль, с удивлением прочитает сообщение об ошибке.