Жанр: Учеба

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

сторінка №3

м достаточно.

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' будут равны. Естественно это увеличи

Список сторінок

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

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

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

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