div.main {margin-left: 20pt; margin-right: 20pt} Как сделать Linux программы меньше
Red Plait
Данный опус описывает два способа уменьшения размера ELF программ под Линух, однако
эти способы не специфичны для Линукса, и работают также под FreeBSD (возможно,
они будут работать и под некоторыми другими Unixes на платформе Intel x86).
В наши дни почти никто не задумывается о размере программ. И это очень печальное
последствие монополии Микрософт - вместо того, чтобы думать об эффективном
использовании имеющегося оборудования, эти парни пишут всё более прожорливые
(но вовсе не всё более лучшие) монстрообразные программы, вынуждая конечных
пользователей каждый год покупать всё более новые компьютеры (более мощные и дорогие).
Меня уже давно тошнит писать программы под "операционные системы" этой
фирмы - да и глупо заниматься оптимизацией программ для платформы, которая
предназначена исключительно для того, чтобы заставить пользователя купить
более мощную машину. Так что мы будем рассматривать альтернативные платформы -
Linux & FreeBSD. Поэтому Вы должны иметь машину с установленным на ней
одним из этих дистрибутивов. На этом требования и заканчиваются.
В самом деле, один из самых простых способов уменьшить размеры программы - писать
её на ассемблере. Я лично считаю одним из лучших ассемблеров под Unixes на
платформе x86 Nasm, взять можно на http://www.cryogen.com/Nasm/.
Однако при создании ELF-файлов (самый распространённый формат исполнимых файлов
под Unix) он вставляет в каждый объектный файл секцию с комментариями, что
данный файл был произведён Nasmом версии такой-то. Но ведь я и так знаю это !
Зачем мне нужна в каждом объектном файле такая секция ? Кроме того, содержимое
всех этих секции аккумулируется при линковке в секции комментариев исполнимого
файла ! В общем, я "доработал" файл outelf.c из версии 0.98
(последняя на сегодняшний момент), отвечающий за генерацию объектных
файлов в ELF формате, так что теперь туда не помещается секция с комментариями.
Я надеюсь, парни из команды разработчиков Nasmа на меня не сильно за это
обидятся - мы и так знаем, что их продукт лучший, а теперь он стал даже ещё
лучше - он генерирует файлы меньшего размера ! Но если Вам всё-таки нравится
иметь строку с copyrights, вы можете закомментировать строчку
#define RP_NO_COMMENT
и всё будет как раньше.
Кроме того, в выходных файлах есть и ещё кое-какая лишняя информация, а именно -
информация о локальных символах модуля. Вы всё равно не можете их использовать
в процессе линковки - они помещаются в таблицу символов только для корректного
создания relocations, и в дальнейшем могут быть безболезненно удалены. Более
того, если Вы создаёте разделяемую библиотеку или готовый исполнимый файл,
Вы можете удалить всю символьную таблицу ! Но если Вы создаёте простую библиотеку,
нет инструмента для удаления информации о локальных символах - она будет совершенно
бесполезно занимать место на Ваших дисках (впрочем, мне думается, что создание такого
инструмента вполне возможно с помощью библиотеки bfd из пакета binutils.
Возможно, я даже когда-нть займусь этим). Так что я совешил ещё некоторое насилие
над Nasmом - если Вы определите макрос (уже определён в моём патче)
#define RP_ONLY_GLOBAL
то перед записью ELF объектника будет перестроена таблица символов (и строковая
таблица) чтобы не включать в себя локальные символы.
Установка патча очень проста - переписываете исходный файл моим и перекомпилируете
- всё...
Платформы: любой Unix на платформе Intel x86 процессоров (естественно, если Вы
сможете собрать на нём сам Nasm). Проверялось на Nasm 0.98 на
FreeBSD 3.1 & 3.4
Red Hat Linux 5.1 & 6.1
Caldera OpenLinux 2.2
UnixWare 7.1
SCO OpenServer 5.0.5
PS: можете считать это моим началом работы над проектом модификации Nasmа (см. раздел
Projects)
Что-то нигде в Сети я ещё не видел документации, описывающей, как создавать
kernel modules не с помощью ассемблера, а целиком на ассемблере - вообще без
использования компилятора C (правда, нам потребуется линковщик). Тем не менее
я обнаружил, что это вполне возможно. Для проверки был написан (на Nasmе) абсолютно
бесполезный модуль, который умеет только сообщать о факте своей загрузки и
выгрузки. Чтобы пересобрать его для Вашей версии кернела, Вам потребуется
найти в файле заголовков /usr/include/linux/version-up.h значение
макроса UTS_RELEASE (версия кернела) и вписать его в определение
константы kern_ver моего файла a.asm. Собственно, в этом нехитром
действии и кроется причина отсутствия модулей кернела, написанных целиком на
ассемблере - все файлы заголовков, определяющих структуры и функции кернела,
расcчитаны на использование языка C (хотя мне попадалась пара экспериментальных
модулей, написанных на C++). Полностью отсутствует инфраструктура, необходимая
для полноценного использования ассемблера.
Для сравнения результатов тот же самый модуль был написан более традиционным
способом - на C. Результаты таковы: размер Cишного модуля 1064 байта, размер
ассемблерного модуля 748 байт. Комментарии излишни...
Хм, а как быть с уже скомпилированными исполнимыми файлами ? Действительно ли
ELF файлы не содержат ничего лишнего ? На изучение этого вопроса я потратил
около двух недель (в основном просматривая исходный код загрузчика ELF файлов
на исполнение в Linux кернеле и ELF interpretorа). И в результате выяснились очень
интересные вещи. ELF формат имеет два типа секций - одни обычные, используемые
линковщиком и иже с ним, а другие - так называемые программные, т.е. которые
используются кернелом при загрузке файла на исполнение. Более того, если, скажем,
для объектных файлов и разделяемых библиотек необходимы первые (символьные) - по
достаточно очевидным причинам, ведь эти файлы предназначены для их дальнейшей
обработки (линковка или динамическая загрузка), то для обычных исполнимых
файлов необходимы ТОЛЬКО программные секции. Ни код запуска ELFов, ни ELF interpretor
в своей работе не используют символьных секций ! Тем не менее, линковщик честно
помещает их в каждый генерируемый файл. Это цель для приложения усилий номер раз.
Я написал сначала программку elf_dump, которая просто распечатывает ELF заголовок и
заголовки секций обоих типов (я так и не смог заставить objdump показывать
все секции файла, включая программные секции). И выяснились ещё более забавные вещи -
оказывается уже упомянутая секция с комментариями, а также секции с символьной
таблицей (та, что удаляется, когда Вы делаете strip) вообще не грузятся
кернелом для исполнения. Т.е. программа может легко обойтись без них. Это наша цель
номер два.
Для проверки концепции за два часа на свет появилась другая программа - elf_compact.
Назначение её очень просто - она открывает файл, проверяет, что открытый файл - ELF,
что он исполнимый и отрезает у него всё лишнее, переписывая всё нужное в файл
с таким же именем, что и основной, с суффиксом ".cmpt". И она работает !
Более того, она работает не только на Linux, но и с программами от FreeBSD (на что я,
честно говоря, даже не рассчитывал).Я даже собрал обе программы под Win32 с помощью
Visual C++ 6.0 - получилось что-то вроде cross-optimizatorа. Она может также
служить заменой strip. И при всех её достоинствах исходный код имеет
размеры всего 5 Kb ! По всем законам жанра такая хакерская утилита просто
обязана быть написана в машинных кодах, в крайнем случае на ассемблере, но,
поскольку я очень ленив, придётся Вам довольствоваться переносимым C
Оказалось, что, несмотря на то, что выходные файлы отлично запускаются и работают,
и вообще кернел не испытывает каких-либо неудобств от ампутации мусора, ни отладчик
gdb, ни утилиты из binutils не могут жить без символьных секций
и отказываются иметь с такими файлами дело. Т.е. совершенно непроизвольно на
свет появился также простейший ELF protector (насколько я знаю, первый в своём роде -
по крайней мере ни я, ни те люди, которым я дал эту программу для тестирования,
ничего аналогичного под Linux не знают). Это тем более иронично, что до этого я в основном
ломал программы под Linux/Unix ! Краткий перечень сломанного прилагается (IMHO,
далеко не полный - что-то с памятью в последнее время стало совсем плохо - весна,
однако...):
SCO OpenServer 5.0.x (практически все версии - 5.0.2, 5.0.4, 5.0.5) - сломал
всё, что было на их инсталляционных дисках
UnixWare 7.0 & 7.1 - отломано ограничение на максимальное количество сетевых
подключений
Oracle 8.0.5 for UnixWare - на предмет максимального числа подключений
StarOffice 5.0 for Linux - ещё когда StarDivision не купила Sun. Инсталлировался
на любой серийный номер
InterBase 5.6 for Linux - написан генератор лицензий
YardSQL for FreeBSD - написан генератор файлов лицензий
SOLID for FreeBSD & for Linux - отломлена проверка на окончание trial-периода
и на максимальный размер базы и число подключений
Yandex Lite - аж целых три версии for Linix и одну for FreeBSD - устранение всех
ограничений демо-версии
C-Forge for Linux (кстати, хорошая среда разработки) - снятие trialа
VmWare 1.0 for Linux - как давно это было...
Jaguar v3.4 & v4.0 for Linux - прога для химических расчётов
Chili!Soft ASP for Linux
Fujitsu C/C++/Fortran Express v1.0
И вот на старости лет угораздило написать защиту. Впрочем, я не совсем болен
на голову, и уже на следующий день нашёл способ, как загружать такие "оптимизированные"
исполнимые файлы в IDA Pro - я написал для них свой loader. Он грузит такие
файлы правильно - т.е. не так как описано в спецификации ELF формата, а так,
как это делает Linux кернел.
Но ! когда я обратился к Гильфанову на предмет получить исходные коды его
загрузчика ELFов, в ответ мне было сказано, что
"папуасам и хакерам никогда я исходные коды не дам, даже не проси !"
Так что
Я обиделся на Гильфанова (не только из-за этого факта - подобных проявлений
истинного дружелюбия с его стороны уже было множество)
Loader вряд ли обладает всей функциональностью оригинального ELF loaderа,
в частности, понимает ТОЛЬКО x86 файлы. Впрочем, у меня и нет другого оборудования
Поскольку я затратил слишком много времени, нервов и ресурсов на выяснение сначала
как это всё работает, на бесплодную переписку с Ильфаком и Eric Youngdale (автор
кода загрузки ELFов в Linux кернеле, и по совместительству автор ELF interpretorа),
а затем ещё и на reverse engeneering творений Ильфака, мой loader
не будет выложен как OpenSource, как все прочие инструменты. Если он Вам нужен - обратитесь
к Ильфаку. Или ко мне - у меня будет дешевле (ведь мне не нужно поддерживать
бельгийский уровень жизни, как мне Ильфак однажды сказал :-)
Я же планирую написать несколько более усложнённый вариант настоящей защиты
под Linux (кто же лучше крякера сможет это сделать ?), возможно с применением
модулей в кернеле и есть ещё пара идей (пока не скажу) - в общем, что-то вроде
VBoxа под Linux...
Когда Вы будете источать праведные вопли о том, что якобы тоже самое, что и
ELF compact, умеют делать утилиты из пакета binutils, перечитайте
ещё раз предыдущую главу, где написано чёрным по белому русскими буквами - что
утилиты из binutils не умеют общаться с выходными файлами ELF compactа.
Даже если быть полным ламером, но мыслить логически - как утилиты binutils
могут так исправить файлы, что потом сами не смогут с ними работать ? Кроме того,
настоятельно рекомендую почитать документацию на ELF - возможно, Вы наконец
поймёте разницу между символьными и программными секциями...
Netwide Assembler home page
Linux Assembly Project
- проект нашего соотечественника Константина Болдышева по программированию на
ассемблере под Linux. Содержит множество полезной информации и ссылок
ELF Kickers. Обратите
особенное внимание на утилиту ssrtip - да, оказывается я не оригинален :-(
Описание ELF формата
Как сделать
Ваши Linux-программы ещё меньше :-)
Assembly Programming Journal
Исходные тексты программ, упоминаемых в статье
можно отправлять автору по адресу redplait@usa.net.
На глупые вопросы типа "Что такое FreeBSD ?" или
"где мне взять что-либо из описанного в этой статье ?" я не отвечаю.
Если же Вы нашли bug, имеете конструктивные идеи или уверены в
моей неправоте - всегда открыт к общению. Также большая просьба - не присылайте
мне десятимегабайтных (притом несжатых) файлов в качестве доказательства чего бы
то ни было.
Ну и если Вы хотите защитить свои программы под Linux/FreeBSD (или наоборот, сломать
чужие :-) - теперь Вы знаете, к кому обратиться...
Кроме того, автор не несёт ответственности за последствия работы его программ...
#include <std_disclaim.h>
|