CODE-RIP: искусство выдирания чужого кода - Часть I. Теория

Дата публикации 30 июн 2005

CODE-RIP: искусство выдирания чужого кода - Часть I. Теория — Архив WASM.RU

Rip, гл. - разрезать, распарывать, рвать (одним быстрым движением), раскалывать (дрова);
сленг - воровать, красть, грабить.

Abbyy Lingvo

Аннотация

Описываются основные принципы и методы выдирания чужого кода для последующего использования при написании своих программ.

Кому это нужно

В этом разделе дано мое личное видение проблемы, поэтому прошу "в пианиста не стрелять".

При написании программы с нуля или добавлении новых функций в существующие продукты разработчики часто могут найти такие функции в разработках от других компаний (конкурентов). Времени на разработку своими средствами, конечно же, нет. Более того, существуют такие потаенные алгоритмы (конвертация экзотических форматов и т.п.), которые продаются только определенным фирмам и только за круглые суммы.

Что же делать? Выход из данной ситуации на самом деле один - украсть J.

В конце-концов, у любого найдутся свои буйные, шокирующие фантазии по использованию подобных грязных приемов в быту.

Теория

Знаете ли Вы что…

Одним из переводов каждого из слов, имеющих к крэкерской тематике непосредственное отношение - hack, rip и nag, является слово "кляча" :smile3:

Итак, в статье попытаюсь описать основные идеи и принципы, заложенные в таком специфическом направлении искусства крекинга, как code-rip. Информацию в Интернете найти не удалось. Единственное, что выдаст пресловутый google на запрос "code rip" - некая программа по выдиранию html-кода с web-страниц. Однако, это совершенно из другой оперы и нам ни к чему.

Что, в конечном итоге, означает "рипнуть код"? В общем случае - создание из исполнимого модуля программы-жертвы библиотеки, экспортирующей необходимые взломщику функции.

Итак, минимальный набор инструментов, которым стоит обзавестись code-ripper-у:

  • Дизассемблер IDA: в случае обычного взлома всегда можно обойтись и без дизассемблера, используя только отладчик, однако для rip-анья кода обойтись без дизасмера будет крайне проблематично, в некоторых случаях - практически невозможно.
  • Мощный текстовый редактор: вот такого Вы еще точно нигде не встречали в описаниях инструментов ко взлому. Не удивляйтесь - в текстовом редакторе придется провести чуть ли не большую часть всего времени.
  • Отладчик Ollydbg: в процессе rip-а гораздо удобнее SoftICE-а. Из раздела "Практика" станет понятно, почему.

Пожалуй, это все.

Процесс rip-анья кода можно условно разделить на пять этапов:

  1. Оптимизация трудозатрат по времени
  2. Дизассемблирование
  3. Генерация файла с исходным кодом
  4. Редактирование
  5. Компиляция
  6. Отладка

Для достижения реально работающего результата 3-6 этапы придется повторять N раз. В некоторых случаях N может перевалить за сотню.

Практика

Приступим. Перед нами программа, вернее, ее исполнимый модуль, функции из которого нам так нужны для создания собственной библиотеки.

Оптимизация трудозатрат по времени

Фактор времени играет первостепенную роль в rip-е. Чем большим набором знаний в самых различных областях (компиляция, редактирование текстов, отладка…) Вы обладаете, тем быстрее Вам удастся rip-нуть нужный код. Ситуация, когда несколько дней бьешься над решением проблемы, решаешь или не решаешь ее, и вдруг случайно обнаруживаешь обходной, минутный путь встречается здесь так часто, как нигде более.

Итак, вернемся к подготовке почвы к оптимизации по времени. Часто бывает так, что необходимые нам функции содержатся в программах от различных фирм. Наша первоочередная задача - выбрать ту программу, исполнимый модуль которой меньше по размеру. Также следует учесть размер исполнимого модуля в разных версиях одной и той же программы, он может отличаться в несколько раз. Естественно, нужные нам функции могут коренным образом измениться в новых версиях, поэтому это тоже нужно отслеживать - ведь, в конечном счете, ваш продукт должен составить конкуренцию на рынке.

Выбрав наименьший необходимый модуль можно приступать к его анализу. Обычно исполнимый модуль бывает запакован какой-нибудь гадостью. Распаковав его (рекомендую воспользоваться готовыми решениями) мы скармливаем его IDA-е.

Дизассемблирование

Перед дизассемблированием советую снять "галочку" с Create Stack Variables в Kernel Options 1, также в Processor Oprions выставьте опцию "Disassemble Zero Opcode Instructions".

После дизассемблирования Вам необходимо превратить все абсолютные смещения в относительные (для этого выделите весь код и нажмите "o", далее выставить Apply to all operands и снять галочку с Apply only if possible - спасибо Infern0). Это важно, особенно если программа была упакована. В некоторых случаях процесс может занять многие дни, т.к. придется пройтись буквально по каждой строке исходника в поисках этих самых абсолютных смещений.

Объясню, зачем это нужно. Дело в том, что процесс распаковки состоит из нехитрого дампа программы из памяти, с последующей корректировкой секции импорта и т.п. мелочами. Однако после dump-а все смещения внутри абсолютны, и валидны лишь в том случае, если база модуля соответствует базе сдампленного образа. Теперь внимание - мы собираемся компилировать код в DLL, при загрузке в память она никогда не ляжет на базу EXE (0x400000), поэтому все смещения окажутся недействительными. Мы, конечно, можем явно указать базу для DLL на этапе компиляции (ключ BASE), однако и в этом случае смещения окажутся невалидными - при компиляции MASM будет генерировать машинные коды для ассемблерных мнемоник такого размера, который во многих случаях не совпадает с оригинальными (как известно, одну и ту же команду можно представить разными машинными кодами, об этом the Svin лучше расскажет). Следовательно, все абсолютные смещения опять окажутся недействительными.

Одним словом, абсолютные смещения для rip-а не допустимы.

Есть один вариант, когда абсолютные смещения допустимы. Вместо DLL и LoadLibrary использовать CreateProcess и оригинальный EXE. Тут свои нюансы и в конечном итоге трудозатраты такие же, как при создании DLL.

Итак, теперь важно не забыть избавиться от лишних секций. Как правило, это ресурсы и импорт. Они могут занимать до половины всего исходного кода, чем будут очень мешать при редактировании, а секция импорта вообще вызовет приступы эпилепсии у MASM-а. Избавление от сегментов происходит в окне Segments IDA-ы.

Следующий шаг - создание исходного файла (Produce asm-file).

После создания исходника наступает кульминационный момент. Реально существует два пути дальнейшего продвижения:

  1. rip-ать только необходимые функции
  2. rip-ать весь модуль целиком

Первый метод подразумевает copy/paste в текстовом редакторе кода нужных функций и, безусловно, с первого взгляда покажется более привлекательным - результирующий объем кода на порядок меньше, откуда следует целый букет преимуществ: легкость правки, скорость компиляции и т.д.

Под вторым я понимаю перекомпиляцию всего кода целиком, без разбору. У него, кажется, одни только недостатки - огромный размер полученного в результате кода (миллионы строк для модуля 5-6 Мб), сложность анализа, да и вообще - топорно как-то.

Однако не следует забывать о ЦЕЛЯХ, поставленных нами с самого начала: быстро и качественно получить возможность вызова чужих функций из своих программ. Так вот опытным путем установлено, что в большинстве случаев второй метод позволяет добиться желаемого результата на порядок БЫСТРЕЕ, чем первый. Более того - в некоторых случаях, первый метод вообще не применим. Объясню почему.

Рассмотрим простейший пример. Допустим, у нас есть небольшая функция test_func, которая в листинге IDA занимает пару сотен строк. Казалось бы, какие проблемы - copy/paste и брюки превращаются… Однако, первое что должен сделать настоящий code-ripper -посмотреть граф этой "небольшой" функции (chart of xrefs from) в IDA. Теперь засекайте время на генерацию этого графа. Если прошло более пяти секунд то можете остановить процесс - дальше ждать не имеет смысла. От полученного "черного пятна" не будет абсолютно никакого толку. Если Вам совершенно нечем заняться - можете считать, что Вы нашли занятие на ближайшие месяцы. Процесс перетаскивания "пятна" из общего листинга в отдельный - целый ритуал. Уже после сотой вложенной функции, перетащенной вручную, Вы наверняка вспомните про второй метод.

На этапе редактирования необходимо как минимум "не забыть" повыметать из кода все упоминания об истинном владельце (обычно каждая добропорядочная фирма норовит насовать таких меток в десятках мест). В редакторе ищем все эти метки и убираем, или, если совсем с совестью не дружим - меняем на свои.

Компиляция

Этап компиляции - отдельная песня. Здесь придется особенно тяжко потрудиться.

Приколов и нюансов встретиться немерянно, причем для каждой программы они свои. Например, если Вы собрались компилировать сгенеренный IDA-ой исходник MASM-ом, то Вам непременно понадобиться версия 8.0 (спасибо q_q и bogrus), хотя бы потому, что более ранние версии некорректно выводят номера строк с ошибками в файлах, содержащих более 65535 строк, а именно номера строк с ошибками нам и нужны больше всего. Тут следует учесть время на компиляцию - исходник в 1 млн. строк на 1700MHz компилируется больше 10 минут. С учетом того, что на этапе отладки Вы сразу же будете находить ошибки, и тут же перекомпилировать весь код заново, процесс rip-а может затянуться на долгие годы. Это одна из основных причин, по которой на первом этапе следует выбрать наименьший по размеру модуль.

С какими сложностями придется столкнуться? По большей части это инструкции, сгенерированные IDA-ой, которые не понимает MASM. Например, это почти все инструкции, содержащие префикс переопределения сегмента. Как поступать в данном случае? Проще всего - посмотреть в той же IDA-е в окне "Hex view" машинные коды "непонятных" команд, а затем воспользоваться функцией "Replace all" вашего любимого текстового редактора. Здесь главное не переусердствовать. Если инструкция выглядит слишком вызывающе (какой-нибудь bound esp, [ebp+6Ch]) то, скорее всего, IDA просто принял данные за код, в этом случае лучше вернуться в IDA, сделать "Undefine" участка подозрительного кода, преобразовать его в данные ("d"), и, как Вы уже догадались, повторить пункты 3-6.

В заключении этапа компиляции хотел еще описать небольшие хитрости по оптимизации размера rip-нутого кода. Например, как узнать какие его участки можно выкинуть? Опять же, берем отладчик, текстовый редактор и, прогоняя код в отладчике (с заходами во все процедуры), выделяем красным цветом весь код, по которому была совершена трассировка. Незакрашенные участки можно смело выкидывать :smile3:.

Отладка

Самый увлекательный этап. Здесь без OllyDbg просто не обойтись. На словах все выглядит очень просто - запускается две сессии отладки (именно поэтому SoftICE здесь нам не помощник), в одной из них мы прогоняем код оригинала, в другой, прогоняем код нашего rip-нутого кода. Т.е. в первой сессии мы трассируем код, и тут же в другой, после каждого шага, повторяем тот же самый шаг во второй сессии, после чего сравниваем содержимое регистров и других структур на соответствие оригиналу.

Для максимальной экономии времени предлагаю очевидную вещь: трассировать код оригинала без захода в процедуры, сверять возвращаемые результаты с процедурами из rip-нутого кода и повторять трассировку с заходом в процедуры только в случае отличающихся результатов. После захода в процедуру трассируем опять по тому же принципу - т.е. мы делаем обход всего дерева структуры программы.

На практике имеет место быть т.н. "лавинный" эффект - при исправлении ошибки в одном, "правильном" месте она исчезает в сотнях других. Структура любой компьютерной программы такова, что сотни различных процедур используют одну и ту же подпроцедуру, т.е. процесс поиска и исправления багов происходит лавинообразно; если на секунду представить, что каждая процедура использовала бы свои собственные подпроцедуры, но на отладку пришлось бы затратить пол жизни. Кстати, существует аналогичный эффект на этапе компиляции - MASM может выдать сотню ошибок, но стоит сделать 2-3 исправления и все они исчезают.

Подводные камни:

  1. В новых версиях ПО в нужных взломщику функциях, вероятно, могут быть произведены изменения, исправлены ошибки. Поэтому, выбирая более старую и компактную версию Вы можете потерять в функциональности и надежности.
  2. Часто бывает так (да практически по другому и не бывает), что "нужная функция" использует данные, инициализированные "другой функцией", причем эта "другая функция" норовит инициализировать их в самых неподходящих и неожиданных местах. Это означает, что мало просто передать управление на точку входа в "нужную функцию", необходимо выполнить ряд предварительных, инициализирующих действий (т.е. тащить в либу все инициализирующие функции).
  3. Иногда нужная функция может также использовать функции из других библиотек. В этом случае придется либо а). Тащить нужные функции в конечный модуль из других библиотек, со всеми прелестями пунктов 3-6 для последних, либо б). Воспользоваться программой MoleBox (спасибо bogrus). Данная утиль может собрать все DLL и исполнимый файл в один модуль, дальше Вы будете работать с ним как с одним целым.

Zаshitа

Как самому не стать жертвой коварных Джеков-потрошителей? (Джек-потрошитель - Jack The Ripper, прим. автора). Что можно предпринять для защиты своих кровных, рожденных в муках уникальных функций? Проблема заключается в том, что для защиты от rip-а стандартные методики не прокатят. Обычно, из-за лени, код рабочих функций программы защитой не затронут, т.е. проверки идут либо в функции при запуске программы либо в функциях, предшествующих вызову рабочих функций. В любом случае - rip-ер не будет даже вникать в логику работы таких защит - ему нужно лишь верно распознать начало рабочей функции и экспортировать ее при компиляции DLL.

Очевидно, что для защиты от rip-а нужно каким-то образом защищать каждую функцию в отдельности. Но как? Вообще функции должны быть все время зашифрованы, и в памяти и на диске и расшифровываться по мере вызова, по одной. Это, возможно, доставит rip-еру некоторые неудобства, но не более того.

В идеале самой защищенной функцией будет та, которая физически отсутствует как в исполнимом модуле, так и в оперативной памяти. Но возможно ли такое? Разумеется - например, разработка компании K-medulla позволяет физически вынести код функций в отдельное устройство. Само устройство - тема отдельной статьи ☺.

Во второй части мы рассмотрим практический пример применения приведенных методик. © Broken Sword


0 40.141
archive

archive
New Member

Регистрация:
27 фев 2017
Публикаций:
532