Рост коммерческого программного обеспечения под Linux, распространяющегося без исходных текстов, вызывает озабоченность сообщества Open Source, заинтересованного в создании открытых версий проприетарных продуктов. Основная сложность клонирования этих проектов состоит отнюдь не в кодировании, а в расшифровке протоколов обмена и форматов файлов. Эту часть работы берут на себя хакеры, свободно владеющие отладчиком, разбирающиеся в дизассемблировании и знающие массу эффективных приемов реверсинга. О некоторых из таких приемов здесь и пойдет речь.

Противопоставляя Linux продукции Microsoft, многие почему-то забывают, что, помимо операционных систем, Microsoft выпускает и программное обеспечение прикладного типа (например, тот же MS Office). И если Linux станет необычайно популярной, то Microsoft (а вместе с ней и другие производители) начнет переносить свои продукты на эту ось, только исходных текстов нам, естественно, никто не предоставит. Сейчас мы имеем свободную операционную систему со свободным ПО и небольшой примесью закрытых программ (например, Intel C++, Skype etc), но уже через несколько лет она может превратиться в псевдосвободную ОС с закрытыми драйверами, закрытым ПО и незначительной примесью открытых программ. Это
дискредитирует саму идею Open Source и отрицательно влияет на безопасность, поскольку закрытое ПО может содержать в себе что угодно: от непреднамеренных ошибок до умышленных закладок.

Microsoft уже заключила договор с Novell о переносе MS Office на SuSE, и это только начало. За Microsoft последуют и остальные. Чтобы предотвратить вторжение коммерческого ПО в плоскость свободной оси, необходимо создать некоммерческие клоны всех закрытых программ. Для этого нужно распотрошить двоичные файлы и вытянуть из них всю требуемую информацию о форматах файлов/протоколов, на основании которой кодеры создадут совместимый свободный продукт (он же клон).

 

Чем мы будем действовать

Основным инструментом хакера является дизассемблер. Лучшим дизассемблером был и остается IDA Pro, поддерживающий ELF-формат, распознающий большое количество библиотечных функций и обладающий замечательной системой навигации по исследуемому файлу. Короче говоря, равных IDA Pro нет. Остальные дизассемблеры плетутся где-то в хвосте и годятся лишь для анализа небольших программ.

Техника дизассемблирования двоичных файлов под Linux мало чем отличается от анализа Windows-файлов, только вместо API-функций мы будем иметь дело с функциями стандартных библиотек. Системные вызовы встречаются намного реже, так как они по-разному реализованы в различных версиях Linux и производитель, заботящийся о совместимости, ни за что не будет к ним прибегать. Упаковщиков исполняемых файлов под Linux практически нет, и большинство коммерческих программ распространяется в неупакованном виде, что значительно упрощает анализ. Однако большинство - это еще не все, и тот же Skype активно использует многоуровневую динамическую шифровку кода, на снятие которой уходит огромное количество
времени и пива (подробнее о технике борьбы с упаковщиками можно прочитать в подборке статей nezumi.org.ru/unpack-pack.zip).

Инструмент номер два — отладчик, в роли которого обычно выступает gdb, хотя в некоторых случаях удобнее пользоваться отладчиком, интегрированным в IDA Pro. И тот, и другой основаны на системной функции ptrace, с которой разработчики защит уже давно научились бороться и против которой существует целый легион эффективных антиотладочных приемов. Поэтому в хакерском арсенале должны быть и другие отладчики, не использующие ptrace, например, ALD, linice (желающих узнать подробнее о linux-отладчиках и антиотладочных приемах мы отсылаем к подборке статей: nezumi.org.ru/linux-debug-pack.zip, а также к мыщъх'иной книге «Техника отладки программ
без исходных текстов»).

С перехватом системных вызовов неплохо справляются штатные утилиты truss и ktrace, первая из которых работает в прикладном режиме, вторая — на уровне ядра. Для сбора сетевого трафика, как правило, используется tcpdump, входящий в комплект поставки большинства дистрибутивов.


Консольная версия IDA Pro под Linux

 

Как мы будем действовать

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

Достаточно просто понять, что делает та или иная функция, а уж за конкретной реализацией дело не станет (существуют десятки алгоритмов Фурье-преобразования, заточенные под различные типы процессоров, и создатели открытого клона могут использовать более компактный/быстродействующий вариант без нарушения совместимости).

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

 

Реконструкция формата на микроуровне

Техника отождествления функций требует определенной математической подготовки и программистского опыта. Знакомые функции зачастую распознаются с первого взгляда, незнакомые не распознаются вообще. Можно сколько угодно ковыряться в дизассемблерном листинге, но это ни на йоту не приблизит нас к разгадке, какую цель преследуют все эти операции и какой смысл несет преобразование массива чисел X в Y. В этом случае остается только одно -  построчно переписать код функции с ассемблера на Си (или на любой другой язык высокого уровня). При этом мы теряем шанс выбрать более эффективную реализацию и подходим к опасной черте, отделяющей независимую свободную реализацию от воровства двоичных
модулей. И хотя код, сгенерированный компилятором, будет значительно отличаться от дизассемблируемого кода, нас могут поймать на «водяных знаках» - dummy-коде, не несущем полезной нагрузки, но неизбежно сохраняющемся в открытом клоне при построчном переносе функций, что является достаточно убедительным доказательством плагиата. Обнаружить «водяные знаки» возможно только после отождествления алгоритма функции. Не стоит надеяться, что это будет тривиальный мусор в стиле MOV EAX,ECX/MOV ECX,EAX, скорее всего, производитель использует избыточные преобразования данных, которые в грубом приближении выглядят так: x=f(y)/1. Очевидно, что операция деления на единицу лишняя, но для ее исключения
требуется понять, что это именно деление, а не что-то другое.

Существует три пути отождествления функций: а) анализ алгоритма; б) сопоставление обрабатываемых данных с возвращенным результатом; в) уникальные константы. Ну, с анализом алгоритма все понятно. Если мы знаем тот или иной алгоритм (неважно чего: быстрой сортировки, обхода дерева, вычисления квадратного корня), то сможем отождествить его на любом языке: хочешь - на Паскале, хочешь - на ассемблере. Проблема в том, что никакой отдельно взятый человек не в состоянии удержать в голове все алгоритмы (или хотя бы самые популярные из них), особенно если дело касается математики, поэтому в хакерской команде должны быть узкие специалисты по криптографии, сжатию цифрового аудио/видео и т.д.

Для ускорения (и упрощения) анализа широко используется метод черного ящика: вместо того чтобы изучать код функции, мы перехватываем передаваемые ей данные и смотрим на возвращаемый результат. Сопоставляя первое со вторым, пытаемся постичь суть. Алгоритмы поиска или сортировки распознаются сразу же. С упаковкой/распаковкой дела обстоят несколько сложнее. Сам факт упаковки/распаковки обнаруживается с первого взгляда, но вот их алгоритм остается загадкой. Если только функция не работает с общепринятыми форматами типа gzip, то для создания совместимого клона необходимо полностью реконструировать ее алгоритм.


Linice – альтернативный отладчик под Linux

К счастью, многие алгоритмы оперируют уникальными константами - стандартные полиномы позволяют отождествить методику шифрования/расчета контрольной суммы буквально за несколько секунд, а референсные матрицы квантования легко разоблачают аудио-/видеокодеки, причем операция отождествления легко поддается автоматизации. В частности, для IDA Pro существует несколько хороших плагинов, распознающих большое количество алгоритмов шифрования (их можно найти на www.idapro.com).

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

 

Реконструкция формата на макроуровне

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

Допустим, программа читает первые N байт от начала файла, затем прыгает по смещению X, считывает еще K байт, прыгает по смещению Y… Что это может означать? Если формат не зашифрован/упакован, то, скорее всего, где-то в заголовке содержится ссылка на индексы (смещение позиции X, задаваемое, как правило, либо от начала файла, либо от конца заголовка), где лежат указатели на подчиненные структуры данных, одна из которых и расположена по смещению Y (здесь может находиться текст, подготовленный к выводу на экран).

Для реконструкции неупакованного/незашифрованного файла вполне достаточно утилит truss, ktrace и hex-редактора. Все индексы и данные там будут лежать в открытом виде, как есть, и, располагая одной лишь последовательностью вызовов seek и read, несложно разобраться, где какое поле лежит и чему принадлежит. Однако открытые форматы встречаются достаточно редко, поэтому к hex-редактору добавляется дизассемблер и отладчик.

В отладчике мы устанавливаем точки останова на все функции ввода/вывода (как файловые, так и сетевые) и ведем тот же протокол, который создают truss и ktrace, с той лишь разницей, что мы ставим точки останова на блоки памяти, возвращенные функцией read. При первом же обращении к ним отладчик всплывает, позволяя нам «запеленговать» искомую функцию. На первом этапе мы лишь записываем адреса функций-обработчиков и наблюдаем за возвращаемыми ими данными, устанавливая на них дополнительные точки останова и пытаясь понять, чем именно они занимаются (в частности, функции-распаковщики распознаются практически сразу).

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


Hexedit – простой hex-редактор под Linux

Основная проблема в том, что аппаратных точек останова на x86 всего четыре и максимальная длина каждой из них составляет двойное слово. Аппаратных точек останова катастрофически не хватает, а невозможность установить точку останова на регион памяти вгоняет хакеров в глубокую тоску, граничащую с суицидом. Можно, конечно, попробовать установить точку останова на первый байт данных, возвращенных функцией read, надеясь на то, что разбор данных происходит с самого начала. В большинстве случаев именно так все и происходит, однако никаких гарантий этого у нас нет. К тому же, некоторые программы сразу считывают весь файл (или все служебные структуры файла) в память и в дальнейшем работают с ним
напрямую, не обращаясь к seek.

При исчерпании аппаратных точек останова отладчик gdb предлагает установить программную (их количество не ограничено), однако при этом он переходит в режим пошагового исполнения программы, проверяя каждую машинную инструкцию, в результате чего скорость выполнения программы катастрофически замедляется. Для анализа дисковых файлов это, в общем-то, некритично, а вот при реконструкции протокола обмена время прогона очередного куска под отладчиком может превысить время ожидания сервера. При этом нас вышибут по тайм-ауту, вынуждая приобретать более быстрый процессор или… эмулировать аппаратные точки останова путем выставления атрибутов страниц в PROT_NONE с помощью функции mprotect, которую
можно вызывать непосредственно из-под gdb (к слову, gdb, в отличие от SoftICE, позволяет вызывать любые функции). При обращении к странице памяти отладчик всплывает по исключению типа нарушения доступа, и нам остается только записать адрес дерзнувшей инструкции, вернуть атрибуты на место, выполнить инструкцию (или даже всю функцию целиком) и вызвать mprotect еще раз, забирая атрибуты обратно. При этом отладчик, естественно, должен быть настроен так, чтобы поглощать сигнал о нарушении доступа, не передавая его прикладной программе. О том, как это сделать, рассказано в статье, входящей в linux-debug-pack.zip.

Этот прием не накладывает никаких ограничений на количество точек останова, требуя лишь, чтобы их размер был кратен длине одной страницы памяти. Это достаточно жесткое требование. А что делать, если нам требуется установить точку останова на 300h байт памяти по адресу 8048123h? Очень просто! Ставим точку останова на страницу 8048000h, а затем вручную отбрасываем ложные срабатывания, находящиеся вне адресов 8048123h - 8048423h (если же регион памяти, на который необходимо установить точку останова, пересекает границу страниц памяти, приходится отнимать атрибуты доступа сразу у двух страниц). При желании эту работу легко автоматизировать, поскольку gdb поддерживает довольно развитую систему
макросов, на которых можно написать практически все что угодно.


Использование эмулятора Bochs для отладки программ

Таким образом, в нашем распоряжении окажется список адресов, откуда происходит вызов функций read и seek, а также список адресов, где осуществляется обработка данных. Исследуя окрестности этих адресов в дизассемблере, мы сможем реконструировать протокол обмена/формат файла за достаточно продолжительное, но все-таки конечное время. Причем эту фазу работы легко распараллелить между несколькими участниками, ведь на макроуровне связи между функциями уже ясны, и теперь пришла пора рыть вглубь, анализируя алгоритм работы каждой функции в отдельности.

 

Вертикальный лимит пределов и ограничений

Полная реконструкция формата файла/протокола обмена на основе наблюдений за живым обменом с помощью truss/ktrace/tcpdump невозможна в принципе, поскольку далеко не в каждом файле (сеансе обмена с сервером) задействуются все поля/команды. Открытый клон, созданный на основе таких данных, будет падать при открытии каждого N’го файла или впадать в ступор при получении от сервера непонятного сообщения. Ну и куда это годится? Пользователи тут же взвоют и расстанутся с открытым клоном в пользу оригинального продукта, даже если клон дешевле/удобнее.

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

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

 

Заключение

Свободные клоны коммерческих программ с закрытым протоколом обмена/форматом файлов заслужили репутацию нестабильных (OpenOffice открывает далеко не все документы, созданные MS Office, и это всего лишь один пример). Однако ими пользуется огромное количество людей по всему миру, выявляя все новые ошибки и несовместимости, которые позднее исправляют разработчики, в результате чего качество свободного клона неуклонно растет. Но, увы, производители закрытых программ тоже не сидят сложа руки и выпускают новые версии, поэтому открытые клоны обречены на пожизненное отставание от прогресса, а хакеры вынуждены расходовать огромное количество времени на исследование, однако другого выхода нет. Если
рынок уже захвачен несвободным продуктом, то писать что-то свое, пусть даже в десять раз лучшее, при отсутствии совместимости с уже имеющимся - бесполезно.

  • Подпишись на наc в Telegram!

    Только важные новости и лучшие статьи

    Подписаться

  • Подписаться
    Уведомить о
    0 комментариев
    Межтекстовые Отзывы
    Посмотреть все комментарии