Вирусы. Вирусы? Вирусы!, Безопасность в сети
|
HugoBo-SS
тут-та-ту
[SoftoRooMTeaM]
Группа: Модераторы Сообщений: 10.906 Регистрация: 3.04.2008 Из: Russia SPb Пользователь №: 827.869
Респектов: 4350
| Вирусы. Вирусы? Вирусы! Часть 1
Поговорим о компьютерных вирусах? Нет, не о том, что вчера поймал ваш антивирус. Не о том, что вы скачали под видом инсталлятора очередного Photoshop. Не о rootkit-e, который стоит на вашем сервере, маскируясь под системный процесс. Не о поисковых барах, downloader-ах и другой малвари. Не о коде, который делает плохие вещи от вашего имени и хочет ваши деньги. Нет, всё это коммерция, никакой романтики… Мы поговорим о компьютерных вирусах, как о коде, который способен порождать собственные копии, изменяясь от поколения к поколению. Которому, как и его биологическим собратьям, необходим файл-носитель, работоспособный, и остающийся работоспособным, чтобы давать жизнь новым поколениям вируса. Которому для размножения необходима благодатная среда, много вкусных исполняемых файлов, а также, много глупых и активных пользователей, чтобы они их запускали. » Нажмите, для открытия спойлера | Press to open the spoiler « Так что название «вирус» не просто красивый ярлычок для описания вредоносной программы, компьютерный вирус, в его классическом понимании, является сущностью весьма близкой к его биологическому аналогу. Человечество, как это не раз доказывалось, способно создавать весьма изощренные решения, особенно когда дело касается создания чего-нибудь наносящего вред другим людям. Итак, давным-давно, после того, как DOS пришел к людям, и у каждого программиста появилась своя маленькая вселенная, где адресное пространство было единым, а права на файлы были всегда rwx, появилась мысль о том, может ли программа копировать сама себя. «Конечно, может!», – сказал программист и написал код, который копирует собственный исполняемый файл. Следующая мысль была «а могут ли две программы объединиться в одну?». «Конечно, могут!», – сказал программист и написал первый инфектор. «Только вот зачем?» – подумал он, и это стало началом эпохи компьютерных вирусов. Как оказалось, гадить на компьютере и всячески пытаться избежать обнаружения очень весело, а создание вирусов является очень интересным с точки зрения системного программиста делом. Кроме того, появившиеся на рынке антивирусы предоставляли создателям вирусов серьёзный вызов их профессионализму. В общем, для статьи вполне достаточно лирики, перейдем к делу. Я хочу рассказать о классическом вирусе, его структуре, основных понятиях, методах детектирования и алгоритмах, которые используются обеими сторонами для победы. »» Нажмите, для закрытия спойлера | Press to close the spoiler «« Анатомия вируса
» Нажмите, для открытия спойлера | Press to open the spoiler « Мы будем говорить о вирусах, живущих в исполняемых файлах форматов PE и ELF, то есть о вирусах, тело которых представляет собой исполняемый код для платформы x86. Кроме того, пусть наш вирус не будет уничтожать исходный файл, полностью сохраняя его работоспособность и корректно инфицируя любой подходящий исполняемый файл. Да, ломать гораздо проще, но мы же договорились говорить о правильных вирусах, да? Чтобы материал был актуальным, я не буду тратить время на рассмотрение инфекторов старого формата COM, хотя именно на нем были обкатаны первые продвинутые техники работы с исполняемым кодом. Основными частями кода вируса являются infector и payload. Infector – это код, который ищет подходящие для заражения файлы и внедряет в них вирус, стараясь максимально скрыть факт внедрения и при этом не повредить функционалу файла. Payload – это код, который выполняет собственно необходимые вирмейкеру действия, например, рассылает спам, DoS-ит кого-нибудь, или просто оставляет на машине текстовой файлик «Здесь был Виря». Нам совершенно непринципиально, что там внутри payload, главное, что вирмейкер всячески старается скрыть его содержимое. Начнём со свойств кода вируса. Чтобы код удобней было внедрять, разделять код и данные не хочется, поэтому обычно используется интеграция данных прямо в исполняемый код. Ну, например, так: » Нажмите, для открытия спойлера | Press to open the spoiler « jmp message the_back: mov eax, 0x4 mov ebx, 0x1 pop ecx ; со стека будет взят адрес «Hello, World» mov edx, 0xF int 0x80 ... message: call the_back ; после исполнения на стеке будет лежать адрес «возврата», т.е. адрес «Hello, World\n» db "Hello, World!", 0Dh, 0Ah »» Нажмите, для закрытия спойлера | Press to close the spoiler «« Или так: » Нажмите, для открытия спойлера | Press to open the spoiler « push 0x68732f2f ; “hs//” push 0x6e69622f ; “nib/” mov ebx, esp ; в ESP теперь адрес строки «/bin/sh» mov al, 11 int 0x80 »» Нажмите, для закрытия спойлера | Press to close the spoiler «« Все эти варианты кода при определенных условиях можно просто скопировать в память и сделать JMP на первую инструкцию. Правильно написав такой код, позаботившись о правильных смещениях, системных вызовах, чистоте стека до и после исполнения, и т.д., его можно внедрять внутрь буфера с чужим кодом. Предположим, вирмейкер имеет возможность написать код вируса в таком стиле, и теперь ему надо внедрить его в существующий исполняемый файл. Ему необходимо позаботиться о двух вещах: Куда положить вирус? Необходимо найти достаточно места, чтобы вирус туда поместился, записать его туда, по возможности не разломав файл и так, чтобы в области, в которой вирус окажется, было разрешено исполнение кода. Как передать управление на вирус? Просто положить вирус в файл недостаточно, надо еще совершить переход на его тело, а после завершения его работы вернуть управление программе-жертве. Или в другом порядке, но, в любом случае, мы же договорились не ломать ничего, да? Итак, разберемся с внедрением в файл. Современные исполняемые форматы для платформы x86 в Windows и Linux – это PE (Portable Executable) и ELF (Executable and Linkable Format). Вы без проблем найдёте их спецификации в системной документации, и если будете заниматься вопросами защиты исполняемого кода, то точно не пропустите. Исполняемые форматы и системный загрузчик (код операционной системы, который занимается запуском исполняемого файла) являются одним из «слонов», на которых стоит операционная система. Процедура запуска .exe файла является очень сложным алгоритмически процессом с кучей нюансов, и рассказывать об этом можно в десятке статей, которые вы обязательно найдете сами, если тема вас заинтересует. Я ограничусь простым рассмотрением, достаточным для базового понимания процесса запуска. Чтобы в меня не кидались помидорами, далее под компилятором я буду иметь в виду весь комплекс программ, превращающий исходный код в готовый исполняемый файл, то есть, фактически, компилятор + линкер. Исполняемый файл (PE или ELF) состоит из заголовка и набора секций. Секции – это выровненные (см. ниже) буферы с кодом или данными. При запуске файла секции копируются в память и под них выделяется память, причем совсем необязательно того объёма, который они занимали на диске. Заголовок содержит разметку секций, и сообщает загрузчику, как расположены секции в файле, когда он лежит на диске, и каким образом необходимо расположить их в памяти перед тем, как передать управление коду внутри файла. Для нас интересны три ключевых параметра для каждой секции, это psize, vsize, и flags. Psize (physical size) представляет собой размер секции на диске. Vsize (virtual size) – размер секции в памяти после загрузки файла. Flags – атрибуты секции (rwx). Psize и Vsize могут существенно различаться, например, если программист объявил в программе массив в миллион элементов, но собирается заполнять его в процессе исполнения, компилятор не увеличит psize (на диске содержимое массива хранить до запуска не нужно), а вот vsize увеличит на миллион чего-то там (в runtime для массива должно быть выделено достаточно памяти). Флаги (атрибуты доступа) будут присвоены страницам памяти, в которые секция будет отображена. Например, секция с исполняемым кодом будет иметь атрибуты r_x (read, execute), а секция данных атрибуты rw_ (read,write). Процессор, попытавшись исполнить код на странице без флага исполнения, сгенерирует исключение, то же касается попытки записи на страницу без атрибута w, поэтому, размещая код вируса, вирмейкер должен учитывать атрибуты страниц памяти, в которых будет располагаться код вируса. Стандартные секции неинициализированных данных (например, область стека программы) до недавнего времени имели атрибуты rwx (read, write, execute), что позволяло копировать код прямо в стек и исполнять его там. Сейчас это считается немодным и небезопасным, и в последних операционных системах область стека предназначена только для данных. Разумеется, программа может и сама изменить атрибуты страницы памяти в runtime, но это усложняет реализацию. Также, в заголовке лежит Entry Point — адрес первой инструкции, с которой начинается исполнение файла. Необходимо упомянуть и о таком важном для вирмейкеров свойстве исполняемых файлов, как выравнивание. Для того чтобы файл оптимально читался с диска и отображался в память, секции в исполняемых файлах выровнены по границам, кратным степеням двойки, а свободное место, оставшееся от выравнивания (padding) заполнено чем-нибудь на усмотрение компилятора. Например, логично выравнивать секции по размеру страницы памяти – тогда ее удобно целиком копировать в память и назначать атрибуты. Даже вспоминать не буду про все эти выравнивания, везде, где лежит мало-мальски стандартный кусок данных или кода, его выравнивают (любой программист знает, что в километре ровно 1024 метра). Ну а описание стандартов Portable Executable (PE) и Executable Linux Format (ELF) для работающего с методами защиты исполняемого кода – это настольные книжки. Так как адреса внутри всех этих секций связаны, просто шлепнуть кусок кода в середину секции, «перевязав» его JMP-ами не получится, исходный файл сломается. Поэтому популярными местами для внедрения кода вируса являются: основная кодовая секция (перезапись вирусом начала исполняемого кода начиная прямо с Entry Point). padding между окончанием заголовка и первой секцией. Там ничего нет и вполне можно уместить там небольшой вирус (либо его загрузчик) не сломав файл. новая секция, которую можно дописать в заголовок и разместить в файле после всех остальных. В этом случае никакие внутренние смещения не поломаются, и с местом проблем тоже нет. Правда последняя секция в файле, в которой разрешено исполнение, конечно же, обратит на себя внимание эвристика. padding между окончанием содержимого секции и ее выровненным концом. Это намного сложнее, так как сначала надо этот самый «конец» найти, и не факт, что нам повезет и места будет достаточно. Но для некоторых компиляторов это место можно обнаружить просто по характерным байтам Есть способы и похитрее, некоторые я опишу во второй статье. Теперь о передаче управления. Чтобы вирус отработал, его код должен каким-то способом получить управление. Самый очевидный способ: сначала управление получает вирус, а потом, после того, как он отработает – программа-хост. Это самый простой способ, но также имеют право на жизнь и варианты, когда вирус получает управление, например, после завершения работы хоста, или в середине исполнения, «замещая» исполнение какой-нибудь функции. Приведем несколько техник передачи управления (термин Entry Point или EP, используемый далее, – это точка входа, то есть адрес, на который системный загрузчик передаст управление после того, как подготовит исполняемый файл к запуску): JMP на тело вируса замещает первые байты, находящиеся в Entry Point файла. Затёртые байты вирус сохраняет в своём теле, и, по окончании собственной работы, восстанавливает их и передает управление на начало восстановленного буфера. Способ, похожий на предыдущий, но вместо байтов вирус сохраняет несколько полных машинных инструкций в Entry Point, тогда он может, ничего не восстанавливая (проследив только за корректной очисткой стека), выполнить их после окончания собственной работы и передать управление на адрес инструкции, следующей за «сворованными». Как и в случае с внедрением, есть способы и похитрее, но мы их тоже рассмотрим ниже, или отложим до следующей статьи. Всё это – способы сделать корректную вставку буфера с кодом в некоторый исполняемый файл. При этом п.2 и п.3. подразумевают функционал, позволяющий понять, какие байты являются инструкциями, и где находятся границы между инструкциями. Ведь мы не можем «разорвать» инструкцию пополам, в этом случае все сломается. Таким образом, мы плавно переходим к рассмотрению дизассемблеров в вирусах. Понятие принципа работы дизассемблеров нам понадобится для рассмотрения всех нормальных техник работы с исполняемым кодом, поэтому ничего страшного, если я немного опишу его сейчас. Если мы внедрим свой код в позицию точно между инструкциями, то сможем сохранить контекст (стек, флаги) и, выполнив код вируса, восстановить все обратно, вернув управление программе-хосту. Конечно, с этим тоже могут быть проблемы, если используются средства контроля целостности кода, антиотладка и т.п., но об этом тоже во второй статье. Для поиска такой позиции нам необходимо вот что: поставить указатель точно на начало какой-нибудь инструкции (просто так взять рандомное место в исполняемой секции и начать дизассемблирование с него нельзя, один и тот же байт может быть и опкодом инструкции, и данными) определить длину инструкции (для архитектуры x86 инструкции имеют разные длины) переместить указатель вперед на эту длину. Мы окажемся на начале следующей инструкции. повторять, пока не решим остановиться Это минимальный функционал, необходимый для того, чтобы не попасть в середину инструкции, а функция, которая принимает указатель на байтовую строку, а в ответ отдает длину инструкции, называется дизассемблером длин. Например, алгоритм заражения может быть таким: Выбираем вкусный исполняемый файл (достаточно толстый, чтобы в него поместилось тело вируса, с нужным распределением секций и т.п.). Читаем свой код (код тела вируса). Берем несколько первых инструкций из файла-жертвы. Дописываем их к коду вируса (сохраняем информацию, необходимую для восстановления работоспособности). Дописываем к коду вируса переход на инструкцию, продолжающую исполнение кода-жертвы. Таким образом, после исполнения собственного кода вирус корректно исполнит пролог кода-жертвы. Создаем новую секцию, записываем туда код вируса и правим заголовок. На место этих первых инструкций кладем переход на код вируса. Это вариант вполне себе корректного вируса, который может внедриться в исполняемый файл, ничего не сломать, скрыто выполнить свой код и вернуть исполнение программе-хосту. Теперь, давайте его ловить. »» Нажмите, для закрытия спойлера | Press to close the spoiler «« Анатомия детектора
» Нажмите, для открытия спойлера | Press to open the spoiler « Вдруг, откуда ни возьмись, появляется рыцарь на белом компе, в левой руке у него отладчик, а в правой – дизассемблер, программист антивирусной компании. Откуда он там взялся? Вы, конечно, догадались. С большой долей вероятности, он появился там из «смежной области». Антивирусная область в плане программирования весьма уважаема теми, кто в теме, ибо возиться этим ребятам приходится с весьма изощренными алгоритмами, причем в довольно стеснённых условиях. Сами посудите: у вас на входе сотня тысяч экземпляров всякой заразы и исполняемый файл, работать вы должны практически в реальном времени, а цена ошибки весьма высока. Для антивируса, как и для любого конечного автомата, принимающего бинарное решение «да/нет» (инфицирован/здоров), существует два типа ошибок – false positive и false negative (ошибочно признал файл заразным, ошибочно пропустил зараженный). Понятно, что общее число ошибок надо снижать в любом раскладе, но false negative для антивируса куда более неприятна, чем false positive. «После скачивания торрента, перед установкой игры отключите антивирус» — знакомо? Это и есть «false positive» – crack.exe, записывающий что-то в исполняемый .exe файл для достаточно умного эвристического анализатора (см. ниже), выглядит как вирус. Как говорится: «лучше перебдеть, чем недобдеть». Думаю, не надо описывать вам компоненты современного антивируса, все они крутятся вокруг одного функционала – антивирусного детектора. Монитор, проверяющий файлы на лету, сканирование дисков, проверка почтовых вложений, карантин и запоминание уже проверенных файлов – все это обвязка основного детектирующего ядра. Второй ключевой компонент антивируса – пополняемые базы признаков, без которых поддержание антивируса в актуальном состоянии невозможно. Третий, достаточно важный, но заслуживающий отдельного цикла статей компонент – мониторинг системы на предмет подозрительной деятельности. Итак (рассматриваем классические вирусы), на входе имеем исполняемый файл и один из сотни тысяч потенциальных вирусов в нем. Давайте детектировать. Пусть это кусок исполняемого кода вируса: » Нажмите, для открытия спойлера | Press to open the spoiler « XX XX XX XX XX XX ; начало вируса длиной N байт . . . 68 2F 2F 73 68 push 0x68732f2f ; “hs//” 68 2F 62 69 6E push 0x6e69622f ; “nib/” 8B DC mov ebx, esp ; в ESP теперь адрес строки «/bin/sh» B0 11 mov al, 11 CD 80 int 0x80 XX XX XX XX ; конец вируса длиной M байт . . . »» Нажмите, для закрытия спойлера | Press to close the spoiler «« Сразу хочется просто взять пачку опкодов (68 2F 2F 73 68 68 2F 62 69 6E 8B DC B0 11 CD 80) и поискать эту байтовую строку в файле. Если нашли – попался, гад. Но, увы, оказывается эта же пачка байт встречается и в других файлах (ну мало ли кто вызывает командный интерпретатор), да еще и таких строк для поиска «стотыщ», если искать каждую, то никакая оптимизация не поможет. Единственный, быстрый и правильный способ проверить наличие такой строки в файле – это проверить ее существование по ФИКСИРОВАННОМУ смещению. Откуда его взять? Вспоминаем «смежную область» — особенно места про то, куда вирус себя кладет и как передает себе управление: вирус внедряется в padding между заголовком и началом первой секции. В этом случае можно проверить существование этой байт-строки по смещению «длина заголовка» + N (где N – число байт от начала вируса до байт-строки) вирус лежит в новой, отдельной секции. В этом случаем можно проверить существование байт-строки от начала всех секций с кодом вирус внедрился в padding между концом кода и концом кодовой секции. Можно использовать отрицательное смещение от конца секции, типа «конец кодовой секции» — М (где M — число байт от конца байт-строки до конца кода вируса) – «длина байт-строки» Теперь оттуда же про передачу управления: вирус записал свои инструкции прямо поверх инструкций в Entry Point. В этом случае ищем байт строку просто по смещению «Entry Point» + N(где N – число байт от начала вируса до байт-строки) вирус записал в Entry Point JMP на свое тело. В этом случае надо сначала вычислить куда смотрит этот JMP, а потом искать байт-строку по смещению «адрес перехода JMP» + N(где N – число байт от начала вируса до байт-строки) Что-то я устал писать «байт-строка», она переменной длины, хранить ее в базе неудобно, и совершенно необязательно, поэтому вместо байт-строки мы будем использовать её длину плюс CRC32 от неё. Такая запись очень короткая и сравнение работает быстро, так как CRC32 алгоритм не из медленных. Гнаться за устойчивостью к коллизиям контрольных сумм смысла нет, так как вероятность коллизии по фиксированным смещениям мизерная. Кроме того, даже в случае коллизии ошибка будет типа «false positive», что не так уж и страшно. Обобщаем все вышеописанное, вот примерная структура записи в нашей антивирусной базе: ID вируса флаги, указывающие откуда считать смещение (от EP, от конца заголовка, от конца первой секции, от начала всех секций, от адреса перехода инструкции JMP в EP и т.п.) смещение (offset) длина сигнатуры (Lsig) CRC32 сигнатуры (CRCsig) Оптимизируем вход (оставляем только сигнатуры, которые «влазят» в данный файл, сразу из заголовка подготавливаем набор необходимых смещений) и далее: » Нажмите, для открытия спойлера | Press to open the spoiler « { # для всех подходящих записей - на основании флагов вычисляем базовое смещение в файле (начало кодовой секции, entry point и т.п.) - прибавляем к нему offset - читаем Lsig байт - считаем от них CRC32 - если совпало – мы поймали вирус } »» Нажмите, для закрытия спойлера | Press to close the spoiler «« Ура, вот наш первый антивирус. Он достаточно крут, так как при помощи достаточно полной базы сигнатур, нормально подобранных флагов и хорошей оптимизации этот детектор способен очень быстро ловить 95% всяких зараз (подавляющее большинство современного malware это просто исполняемые файлы, без всякой способности к мутации). Далее начинается игра «кто быстрее обновит базы сигнатур» и «кому раньше пришлют новый экземпляр какой-нибудь гадости». Сбор и каталогизация этой «гадости» является задачей весьма нетривиальной, но совершенно необходимой для качественного тестирования детектора. Сбор эталонной базы исполняемых файлов задача непростая: попробуйте найти все экземпляры зараженных файлов (для сложных случаев в нескольких экземплярах), каталогизировать их, перемешать с «чистыми» файлами и регулярно гонять по ним детектор с целью выявления ошибок детектирования. Такая база собирается годами, и является очень ценным активом антивирусных компаний. Возможно, я ошибаюсь, и её реально достать (всякие сервисы online-проверок на вирусы вполне в состоянии предоставить некоторый её аналог), но, когда я занимался этим вопросом, ничего похожего достать было нельзя (по крайней мере, под Linux). »» Нажмите, для закрытия спойлера | Press to close the spoiler «« Эвристический анализатор
» Нажмите, для открытия спойлера | Press to open the spoiler « Какое страшное слово – «эвристический анализатор», сейчас его и не увидишь в интерфейсах антивирусов (наверное, пугает пользователей). Это одна из самых интересных частей антивируса, так как в нее пихают все, что не укладывается ни в один из движков (ни сигнатурный, ни в эмулятор), и похож на доктора, который видит, что пациент кашляет и чихает, но определить конкретную болезнь не может. Это код, который проверяет файл на некоторые характерные признаки заражения. Примеры таких признаков: некорректный (испорченный вирусом, но работоспособный) заголовок файла JMP прямо в точке входа «rwx» на секции кода Ну, и так далее. Помимо указания факта заражения, эвристик может помочь принять решение – запускать ли более «тяжелый» анализ файла? Каждый признак имеет разный вес, от «подозрительный какой-то» до «не знаю чем, но файл заражен точно». Именно эти признаки дают большинство ошибок «false positive». Не забудем также о том, что именно эвристик может предоставить антивирусной компании экземпляры потенциальных вирусов. Сработал эвристик, но ничего конкретного не было найдено? Значит файл точно является кандидатом на отправку в антивирусную компанию. »» Нажмите, для закрытия спойлера | Press to close the spoiler «« Межвидовое взаимодействие и эволюция
» Нажмите, для открытия спойлера | Press to open the spoiler « Как мы увидели, для быстрого и точного сравнения детектору необходимы сами байты сигнатуры и ее смещение. Или, другим языком, содержимое кода и адрес его расположения в файле-хосте. Поэтому понятно, как развивались идеи сокрытия исполняемого кода вирусов – по двум направлениям: сокрытие кода самого вируса; сокрытие его точки входа. Сокрытие кода вируса в результате вылилось в появление полиморфных движков. То есть движков, позволяющих вирусу изменять свой код в каждом новом поколении. В каждом новом зараженном файле тело вируса мутирует, стараясь затруднить обнаружение. Таким образом, затрудняется создание содержимого сигнатуры. Сокрытие точки входа (Entry Point Obscuring) в результате послужило толчком для появления в вирусных движках автоматических дизассемблеров для определения, как минимум, инструкций перехода. Вирус старается скрыть место, с которого происходит переход на его код, используя из файла то, что в итоге приводит к переходу: JMP, CALL, RET всякие, таблицы адресов и т.п. Таким образом, вирус затрудняет указание смещения сигнатуры. Гораздо более подробно некоторые алгоритмы таких движков и детектора мы посмотрим во второй статье, которую я планирую написать в ближайшее время. Параллельно с развитием вирусных движков и противостоящих им детекторов активно развивались коммерческие защиты исполняемых файлов. Появилось огромное количество небольших коммерческих программ, и разработчикам нужны были движки, позволяющие взять EXE-файл и «завернуть» его в некоторый «конверт», который умеет защищенным образом генерировать валидный серийный номер. А кто у нас умеет скрывать исполняемый код, и внедрять его в исполняемые файлы без потери работоспособности? Правильно, те самые разработчики из «смежной области». Поэтому написание хорошего полиморфного вируса и навесной защиты исполняемого файла – это очень похожие задачи, с использованием одних и тех же алгоритмов и инструментов. Так же схожи и процесс анализа вирусов и создания сигнатур и взлом коммерческого ПО. В обоих случаях надо добраться до истинного кода и либо создать сигнатуру, либо достать из него алгоритм генерации серийного номера. В интернетах существуют несколько страниц по теме «классификация компьютерных вирусов». Но мы же договорились, вирус – это то, что умеет само себя воспроизводить в системе, и чему необходим файл-носитель. Поэтому всякие трояны-руткиты-malware – это не вирусы, а тип payload-кода, который вирус может таскать на себе. Для описываемых в статье технологий классификация компьютерных вирусов может быть только одна: полиморфные и неполиморфные вирусы. То есть меняющиеся от поколения к поколению, либо нет. Рассмотренный в статье детектор легко детектирует неполиморфные (мономорфными их назвать, что ли) вирусы. Ну а переход к полиморфным вирусам является отличным поводом, наконец, завершить эту статью, пообещав вернуться к более интересным методам сокрытия исполняемого кода во второй части. »» Нажмите, для закрытия спойлера | Press to close the spoiler «« Источник: habrahabr | |
| |
9.07.2014 - 18:04 |
vic171
профи!
Группа: Наши Люди Сообщений: 1.330 Регистрация: 20.10.2007 Из: Ставрополье Пользователь №: 562.373
Респектов: 587
| Очень интересная статья, возможно в некоторых моментах несколько непонятна простому пользователю, сталкивался, когда вирус подстраивается под антивирус, ну тут уже я делаю руки вверх, дело в том что при удалении антивируса у системы слетает голова, приходится тащить инфо с диска С ну и все убивать... | |
| |
24.10.2014 - 14:43 |
HugoBo-SS
тут-та-ту
[SoftoRooMTeaM]
Группа: Модераторы Сообщений: 10.906 Регистрация: 3.04.2008 Из: Russia SPb Пользователь №: 827.869
Респектов: 4350
| Вирусы. Вирусы? Вирусы! Часть 2
Продолжим рассмотрение вирусных движков. На этот раз речь пойдет о полиморфизме исполняемого кода. Полиморфизм для компьютерных вирусов означает, что каждый новый зараженный файл содержит в себе новый код вируса-потомка. Чисто теоретически, для антивируса это должно было бы означать настоящий кошмар. Если бы существовал вирус, который в каждом новом поколении на 100% менял бы свой код, причем по настоящему случайным образом, никакой сигнатурный анализ не смог бы его детектировать. » Нажмите, для открытия спойлера | Press to open the spoiler « Возможно, где-то есть супер-программист, который действительно написал такой код, и именно поэтому мы про него ничего не знаем. Мне не очень в это верится, и даже кажется, что математики, занимающиеся математическим обоснованием работы вычислительных систем, могли бы доказать, что не существует такого определенного алгоритма полиморфизма, результат работы которого нельзя было бы стопроцентно детектировать при помощи другого определенного алгоритма. Но мы — люди простые, нам просто интересна идея кода, который сам себя изменяет, а в свете «алгоритм против алгоритма», рассмотрение противостояния методов сокрытия исполняемого кода методам детектирования для программиста должно быть весьма интересным. »» Нажмите, для закрытия спойлера | Press to close the spoiler «« Вспоминаем и немного дополняем первую статью» Нажмите, для открытия спойлера | Press to open the spoiler « Вспомню наших героев из первой статьи: вирмейкера и программиста антивирусной компании и прицеплю к ним их кармических близнецов: разработчика навесной защиты и крэкера. Первые стараются скрыть исполняемый код и информацию о нем, вторые — получить доступ к характерному коду и его внутренним алгоритмам. В вирусной области превалируют автоматические методы (вирусный движок и антивирусный детектор), в области защиты — ручные (параметры навесной защиты контролируются вручную, процесс взлома софта также является ручной работой, несмотря на обилие вспомогательного софта). По итогам первой статьи у нас есть вирус, умеющий корректно заражать исполняемый файл (т.е. умеет при запуске файла отработать сам и корректно выполнить код самого файла) и антивирусный детектор, который знает, что вирус размещается в строго определённых местах файла, и что на некотором расстоянии от характерной точки (от точки входа, от начала секции, от конца заголовка) существует фиксированный набор байт, который характеризует данный вирус. Также, чтобы статья не съезжала в обсуждение тем «что плохого делает вирус», давайте договоримся, что payload вирусa ничего не делает. Так можно отсеять все обсуждения касательно характера действий рассматриваемого кода и сосредоточиться на методах детектирования и сокрытия. Продолжаем вспоминать и дополнять первую статью. Схему противостояния вируса и антивируса можно по аналогии рассмотреть с точки зрения взлома коммерческой программы. Вместо инфектора здесь работает собственно программа, «навешивающая» защиту на исполняемый файл. Она «портит» код самой программы, информацию, необходимую для его восстановления прячет в свое тело, и таким же хитрым способом, как и вирус, скрывая алгоритм своей работы, первым исполняет код защиты, который проверяет валидность серийного номера или время действия trial, и, затем, «починив» основную программу, передаёт ей управление. Крэкер в свою очередь исполняет роль антивируса, пытаясь добраться до внутреннего кода защиты и узнать его алгоритм. Получается, что он занимается тем же самым, что и авер, пытаясь найти (и сохранить) характерную часть кода. Разница лишь в том, что крэкер заберет весь оригинальный код и изготовит из него работоспособный файл, а авер найдет в нем характерный кусок и создаст сигнатуру. Самым популярным способом снять такую защиту является dump образа программы в памяти на диск. Крэкер ищет момент, когда защита отработала, расшифровала и «починила» основную программу, и в памяти находится работоспособный образ кода этой основной программы. Фактически он ищет возможность остановить программу на так называемой OEP (Original Entry Point) — «старой» точке входа защищаемой программы. В этот момент образ в памяти можно сохранить на диск. Он, конечно, будет неработоспособен, но его можно починить, «перенастроив» Entry Point исполняемого файла так, чтобы он указывал на OEP, и, если программа в этот момент работоспособна, такой образ будет работать просто пропуская защиту (там есть еще много манипуляций с восстановлением вызовов внешних функций, многократным дампом на случай, если программа расшифровывается не полностью, и вообще, это тема для десятка статей, но главный принцип именно такой). Другим популярным способом является найти кусок кода, генерирующий серийный номер и, если это возможно, «выкусить» его, и сделать маленький исполняемый файл, генерирующий валидные серийные номера (keygen). Как мы увидим ниже, похожий образ действий не чужд и антивирусному детектору. Еще мне нравится проводить аналогии с биологическими системами, я постараюсь сильно не обременять вас этим. Уж очень хочется поскорее увидеть искусственный разум и жизнь. »» Нажмите, для закрытия спойлера | Press to close the spoiler «« Дизассемблер и отладчик» Нажмите, для открытия спойлера | Press to open the spoiler « Понимание основных принципов их работы является важным для рассмотрения области защиты исполняемого кода. Вы наверняка об этом что-то знаете, раз читаете эту статью, но всё равно — либо немного потерпите, либо просто пропустите этот раздел. Дизассемблер принимает на вход либо исполняемый файл, либо абстрактный буфер с кодом и, что довольно важно, первый адрес, с которого нужно начать дизассемблирование. В случае исполняемого файла это, например, точка входа. Поставив указатель на первую инструкцию, дизассемблер, определяет, что это за инструкция, её длину в байтах, является ли она инструкцией перехода, какие регистры использует, на какие адреса в памяти ссылается и т.п. Если инструкция не является инструкцией перехода, дизассемблер переходит к следующей инструкции, переместив указатель вперед на длину инструкции. Если это безусловный JMP или CALL, дизассемблер перемещает указатель следующей инструкции туда, куда указывает адрес перехода. Если это условный переход (JZ, JNA и т.п.) то дизассемблер помечает следующими к рассмотрению сразу два адреса — адрес следующей инструкции и адрес, на который возможен переход. Если комбинация байт не распознается, процесс дизассемблирования данной ветви останавливается. Также необходимо упомянуть о том, что дизассемблер сохраняет информацию о том, какие инструкции ссылаются на данную(!), что позволяет определять вызовы функций, и, самое главное, кто их вызывает. Дизассемблер превращает последовательность байт в последовательность многосвязных структур, в которых хранится информация о каждом байте инструкции: является ли конкретный байт частью опкода (кода операции), данными, адресом, на который откуда-то совершается переход и т.п. Каждая структура может содержать ссылки на одну или две следующих структуры и при этом являться объектом, на который ссылается произвольное число других структур (например первая инструкция функции, которая вызывается множество раз). Также, умные дизассемблеры могут следить за указателем стека, или уметь распознавать и корректно помечать для дизассемблирования такие конструкции, как: mov eax, 0x20056789; call eax; Плюс распознавать характерные функции по набору инструкций, устанавливать начальные точки для дизассемблирования вручную, комментировать отдельные инструкции и сохранять результат дизассемблирования на диск, т.к. операция построения графа вызовов и разметки структур весьма затратна, ну а возиться с одним файлом можно днями. Но, как мы рассмотрели ранее, возможна ситуация, когда на диске в файле переход ведет на зашифрованный буфер, и дизассемблер в этом случае генерирует кашу из инструкций или останавливается. В этом случае, надо заполучить этот зашифрованный буфер прямо в runtime, когда он открыто лежит в памяти, а для этого требуется отладчик. Основная задача отладчика — остановить программу в самом интересном месте. Для этого используется несколько способов. Можно открыть память процесса, и вместо одной из инструкций вписать int 3 — в этом случае процессор, выполняя эту инструкцию сгенерирует исключение, а отладчик обработает его, откроет свое окно, восстановит оригинальную инструкцию и покажет, что находится в этой области памяти. Можно включить в процессоре флаг трассировки, и тогда процессор будет генерировать это исключение на каждой инструкции. Наконец, у процессора есть отладочные регистры, можно поместиь в них некоторый адрес, и процессор, получив доступ к памяти по этому адресу, остановится. Так что, например, поставив breakpoint на доступ к адресу начала зашифрованного буфера мы остановимся первый раз, когда декриптор начнет расшифрование и прочитает первый байт буфера, а второй раз, когда передаст туда управление. В этот момент содержимое буфера можно записать на диск, натравить на него дизассемблер и узнать все его секреты. В продвинутых защитах вообще не существует такого момента времени, когда в памяти лежит полный рабочий код программы — части кода расшифровываются кусками по мере надобности. В этих случаях реверсеру приходится собирать dump по кускам. »» Нажмите, для закрытия спойлера | Press to close the spoiler «« Защита от исследования кода» Нажмите, для открытия спойлера | Press to open the spoiler « Тема защиты исполняемого кода от исследования достойна десятка статей, поэтому в рамках рассматриваемого вопроса остановимся лишь на нескольких моментах. Статическая защита кода от исследования представляет собой различные методы запутывания исполняемого кода и шифрование буферов с важными участками кода с последующим расшифрованием в runtime. Запутывание кода можно реализовать при помощи специальных, обфусцирующих код компиляторов, а шифрование — при помощи рассматриваемых ниже полиморфных движков (к которым с точки зрения кода относятся и коммерческие защиты). Динамическая защита подразумевает, что программа может в runtime определить отлаживают ли её и предпринять в связи с этим некоторые действия. Например, прочитав буфер с собственным кодом программа может сравнить его контрольную сумму с эталонной, и, если отладчик вставил в код int 3 (см.выше), понять что её отлаживают или каким-то другим способом модифицировали её код. Но самый, пожалуй, надежный и переносимый способ понять, что тебя отлаживают — это измерение времени исполнения характерных участков кода. Смысл простой: измеряется время (в секундах, попугаях или тактах процессора) между инструкциями в некотором буфере, и, если оно больше некоторого порогового значения — значит в середине программу останавливали. Защита, поняв, что ее отлаживают, может, к примеру, игнорировать ветви, внутри которых может остановиться реверсер и тупо не срабатывать, а вирус, удалить себя из системы. Для борьбы с такими ситуациями реверсеры работают в контролируемых средах, которые можно легко воспроизвести — например в виртуальных машинах, для которых можно воспроизвести все, вплоть до параметров BIOS. Поэтому, исследуя код вируса или защиты необходимо помнить о том, что программа вполне может обнаружить факт исследования и сделать что-нибудь нехорошее. »» Нажмите, для закрытия спойлера | Press to close the spoiler «« Полиморфные движки или «код мельчал, движок крепчал»» Нажмите, для открытия спойлера | Press to open the spoiler « Вернёмся к вирусным движкам. В определенный момент развития DOS, после появления кучи мега-актуальных на тот момент упаковщиков, программисты, кроме файлов, начали упаковывать все, что упаковывается. А ".exe" файлы занимают кучу места, причем довольно большая часть такого файла — исполняемый код со стабильным распределением частот групп байт, который наверняка хорошо жмется правильным алгоритмом. Поэтому первыми шагами к полиморфным движкам стали упаковщики. Принцип работы упаковщика довольно прост: берем буфер с исполняемым кодом (кодовую секцию, например); упаковываем её; берем позиционно независимый код распаковщика и дополняем его правильными адресами начала и конца буфера с запакованным кодом; добавляем в конец распаковщика переход на OEP (первую инструкцию распакованного кода); размещаем распаковщик и буфер со сжатым кодом в исполняемом файле (правим размеры секций и/или EP). Получившийся файл по размеру получается намного меньше, чем оригинальный. После появления новых, крутейших винчестеров с объёмом 100Мб это стало не столь актуально, но, упаковка открыла вирмейкерам и разработчикам защит много новых возможностей: размер вируса (несмотря на наш крутейший винчестер в 100Мб) все таки важен. Если payload-код жирный и многофункциональный, то весь вирус будет труднее запихать в файл, особенно если используется что-то хитрее, чем дописывание в конец файла новой секции. Использование упаковки позволит почти весь большой и сложный код вируса упаковать в буфер, который в разы меньше оригинального размера буфер с упакованным кодом необязательно располагать в секции с флагом исполнения. Для продвинутых инфекторов это очень важный фактор, ведь основное тело вируса можно спокойно положить куда угодно. После распаковки распаковщик должен позаботиться, чтобы в области памяти, в которую был распакован код, было разрешено исполнение. Именно поэтому Windows API, работающие с атрибутами доступа к памяти (всякие VirtualProtect, VirtualProtectEx, VirtualOuery и VirtualQuervEx) неизменно привлекают внимание эвристиков ну и на сладкое, самое важное — вместо упаковки или после неё буфер с кодом можно зашифровать, а ключ положить в распаковщик. Теперь это будет не распаковщик, а декриптор. При каждом новом инфицировании (или навешивании защиты на исполняемый файл) буфер с кодом можно зашифровать при помощи нового ключа, и тогда буфер с кодом будет иметь совершенно новое содержание (разумеется при использовании хороших алгоритмов шифрования).:w В дальнейшем я не буду писать «упакован», но предполагаю, что упаковка может быть включена в процесс шифрования. Ну вот он, собственно, и есть — первый полиморфный движок. Распишем поподробней примерный алгоритм инфицирования: Генерируем новый ключ шифрования. Берем код декриптора (где и как — поговорим позже, в простейшем случае тупо достаем готовый код из нашего тела). Внедряем в него (в код декриптора) наш новый ключ шифрования. Внедряем в код вируса передачу управления из файла-жертвы и обратно (пока код еще не зашифрован). Зашифровываем новым ключом наш большой буфер с кодом. Бесхитростно укладываем зашифрованный буфер в файл-жертву (он существенно отличается от предыдущего, поэтому можно особо не прятаться). Добавляем переход на начало зашифрованного буфера в конец декриптора. Хитро (насколько это возможно) укладываем декриптор в файл-жертву. Посмотрим, что получилось: большая часть вируса (зашифрованный буфер) целиком меняется от файла к файлу, и неизменным остается только небольшой декриптор. Этот декриптор фактически содержит несколько адресов (меняющихся от файла к файлу), ключ расшифрования (также изменяющийся) и собственно код расшифровщика. Антивирусу теперь пришлось поднапрячься, характерные для данного вируса паттерны укрыты внутри зашифрованного буфера, и кусок кода для сигнатуры теперь приходится искать в декрипторе, а он небольшой и содержит в себе гораздо меньше характерных участков кода и данных. Такое упрощение задачи послужило причиной появления более продвинутых полиморфных движков, которые при заражении изменяют только код декриптора — ведь разобраться с небольшим куском кода гораздо проще, чем со всем payload-кодом. Радостные вирмейкеры и разработчики защит потирают руки и изучают способы спрятать маленький декриптор похитрее, а аверы и крэкеры чинят дизассемблеры, у которых после попыток дизассемблировать рандомные строки байт, на которые в коде присутствует JMP, едет крыша. »» Нажмите, для закрытия спойлера | Press to close the spoiler «« Эволюция вирусных движков» Нажмите, для открытия спойлера | Press to open the spoiler « Теперь авер тратит немного больше времени на создание сигнатур, т.к. работать приходится с небольшим объемом кода, в котором меньше характерных участков. А вирмейкер озабочен лишь мутацией довольно небольшого декриптора с довольно простым внутренним алгоритмом, и задача скрыть его от детектора теперь кажется более реальной. Учитывая, что антивирус сравнивает сигнатуру по фиксированному смещению, сначала вирмейкер пытается различными способами сдвигать код декриптора и, соответственно, дискредитировать характерную сигнатуру внутри него. »» Нажмите, для закрытия спойлера | Press to close the spoiler «« NOP зоны или «авось пронесёт»» Нажмите, для открытия спойлера | Press to open the spoiler « Первым, простым приёмом, который приехал в вирусы из эксплоитинга уязвимостей, являются NOP-зоны. Когда атакующему удалось успешно провести эксплойт какой-либо уязвимости, и заставить процессор совершить переход по заданному адресу, но при этом неизвестен точный адрес расположения shellcode в памяти, атакующий может сделать так: кучу пространства перед собственно кодом эксплойта заполнить NOP-ами: addr1: nop nop ;... еще очень много NOP-ов nop addr2: jmp addr3; shellcode pop esi; shellcode xor edx,edx shellcode ;... Теперь можно сделать переход «куда-то туда», в NOP-зону. Если известно только приблизительное расположение в памяти, этот прием позволяет успешно выполнить shellcode. Так же беcхитростно можно поступить и с декриптором, просто при инфицировании помещаем его в разные места длинной NOP-строки. А кое-где (там, где это не разломает переходы) можно напихать этих NOP-ов прямо в код. В этом случае все будет корректно работать, но смещения характерной сигнатуры всегда будут разные. Разумеется смещения для инструкций переходов придется пересчитывать. Слишком халявное решение не сильно напрягло авера, который просто добавил в базу признак «пропусти все NOP-ы при подсчете сигнатуры», но этот маленький шаг весьма примечателен тем, что впервые детектор начал рассматривать инструкции, а не байты. Но об этом позже. »» Нажмите, для закрытия спойлера | Press to close the spoiler «« Пермутация или «сложи как нибудь»» Нажмите, для открытия спойлера | Press to open the spoiler « Размышляя, как бы дискредитировать сравнение по сигнатуре, не разломав код декриптора, вирмейкер приходит к идее пермутации. Пермутация — это перестановка блоков кода в каждом новом поколении. Код состоит из некоторого количества блоков, эти блоки переставляются местами в каждом новом поколении вируса, и связываются JMP-ами. Как всегда, на бумаге всё просто, а проблемы начинаются в реализации. Внутри блоков есть условные и безусловные переходы и вызовы функций, поэтому такие, логические блоки должны оставаться целыми. При этом, чем толще блоки, тем меньше вариативность получившегося декриптора, а чем меньше размер блока, тем больше приходится добавлять переходов, раздувая код декриптора, и тем сложнее соблюсти целостность. Для выравнивания блоков по длине можно, например, использовать NOP-зоны. Вот пример алгоритма: в теле вируса храним уже готовый набор из блоков с разметкой (которая представляет собой номер блока и его длину). Затем берем рандомный блок, записываем его в буфер, и правим JMP в конце предыдущего блока. Дополняем результат JMP-ом на первый блок и буфер с рандомно переставленными блоками готов. В отличие от предыдущих игр, это уже достаточно серьезная серьёзная заявка, каждое новое поколение, пускай и за счет безусловных переходов, но все таки порождает, с точки зрения смещений, совершенно другой код. Вирмейкер с довольной улыбкой засыпает. [block 1] [block 2] [block 3] [...] [block N] [jmp block 1] [block 2] [jmp block 3] [block 1] [jmp block2] [block 3] [jmp block 4] [...] [block 4] [jmp block 5] Просыпается авер. Трассируя код нескольких поколений вируса, он понимает, что в декрипторе имеет дело с перестановкой блоков, и надо дорабатывать детектор, по возможности не лишив его производительности. Он решает написать быстрый автоматический дизасcемблер, который умеет бежать по инструкциям, останавливаться только на инструкциях перехода, вычислять адрес перехода и переходить к анализу инструкций, находящихся по адресу перехода. Теперь в антивирусной базе лежит следующее указание: начиная с точки входа шагай по инструкциям, совершая переходы соответственно встреченным JMP-ам, и, пройдя N инструкций, сравни сигнатуру. Если сигнатура находится в десятом блоке, придется пройти до десятого перехода, если внутри возможны условные переходы (JZ), то их условно можно считать двумя переходами — на следующую инструкцию и на адрес перехода, и, соответственно, разветвлять проход по инструкциям. Разумеется, никто не отменял и детектирование попроще, например если блоки у вируса фиксированной длины L и их N штук, можно просто провести N сравнений по сигнатуре по смещениям [0, (1 * L), (2 * L), ..., ((N-1) * L)]. Оценим трудоёмкость процесса поиска с использованием дизассемблера. Дизассемблер минимально должен обеспечивать определение длины инструкции и преобразование VA (Vitual Address) to RVA (Relative Virtual Address) (адрес, указанный в JMP в смещение в файле). Определение длины инструкции — это в принципе достаточно быстрый алгоритм (обращение к элементу массива и вычисление следующего шага на основе флагов, записанных в соотв. элементе массива), а преобразование адреса это пара элементарных операций сложения адресов, на основе информации о том, какой секции принадлежит адрес. Плюс немного ума для определения дешевых трюков для замены банального JMP next_block_address, таких, например, как: XOR eax,eax; JZ next_block_address; ; или PUSH next_block_address; RET; ; или MOV eax, next_block_address; CALL eax; Это не сильно страшные в плане производительности алгоритмы, но, тем не менее, на вычисление CRC32 от короткой строки по заданному смещению это уже совсем не похоже, и сердитый тестировщик ругается на то, что детектор уже полночи жуёт тестовую базу и сожрал весь процессор. Как это водится, если что-то, что включили, а оно тормозит, надо либо оптимизировать это, либо постараться не включать это без необходимости. Первый способ, увы, не катит — в простом дизассемблере много не наоптимизируешь, поэтому авер переходит к любимому месту всех антивирусов — эвристическому анализатору. »» Нажмите, для закрытия спойлера | Press to close the spoiler «« Эвристический анализатор или «вскрытие покажет»» Нажмите, для открытия спойлера | Press to open the spoiler « В первой статье мы уже коснулись эвристического анализа — действительно, существуют признаки, которые с разной степенью достоверности могут говорить, что в файл был инжектирован код. И тогда, авер действительно выделил некоторые из них, которые были подозрительными, но никак не тянули на право заявлять о 100% факте зараженности файла. Тогда он их просто закомментировал, т.к. потратил на них немало времени и удалять их совсем было жаль. Теперь на их основе можно принять решение — запускать ли более тяжелый, использующий дизассемблирование, анализ файла, или нет. Есть и еще одна проблема — т.к. эвристик реагирует на всё подозрительное, коммерческие защиты вызывают у него неподдельный интерес, поэтому аверу пришлось завести в базе сигнатур еще пару сотен «белых» под популярные навесные защиты — их трогать нельзя. Именно благодаря им мы все таки можем нормально запускать различные коммерческие софтины. А при написании собственного софта, использующего методы работы с исполняемым кодом, неплохо бы перед релизом прогнать все файлы своей программы на всех популярных антивирусах где нибудь на virustotal. За непопулярные можно сильно не волноваться, эвристический анализатор трудно утащить так же просто, как базы сигнатур и вряд ли анализатор малопопулярного антивируса будет так же крут, как то, который разрабатывался долгие годы. Стоит, конечно упомянуть и о попытках вирмейкера замаскировать свой вирус под популярную защиту. Для этого, нужна собственно сигнатура, и он начинает разбирать антивирусную базу, чтобы понять, куда бы положить нужные байты, чтобы антивирус принял его вирус за защиту. Да и вообще, изготавливая следующую версию вируса, неплохо бы ознакомиться с кодом, который детектирует текущую. Так что антивирусные базы тоже являются объектами реверс-инжиниринга, а код детектора также подвергается анализу со стороны вирмейкеров. Но вернёмся к нашему эвристическому анализатору, приведём несколько эвристических признаков: Точка входа в секции открытой для записи(rwx). Открытая для записи, исполняемая секция, в которую сразу передается управление, с большой вероятностью свидетельствует о наличии самомодифицирующегося кода, такие секции используются в подавляющем большинстве случаев вирусами и программными защитами. Инструкция перехода в точке входа. Особого смысла в размещении инструкции перехода в точке входа нет и такой признак указывает на наличие самомодифицирующегося кода в файле. Точка входа во второй половине секции. Вирусы, использующие расширение секции, в большинстве случаев располагаются в конце секции. Это нетипично для нормальных файлов, поэтому такая ситуация является подозрительной. Поломки в заголовке. Некоторые модификации заголовка после инфицирования оставляют файл работоспособным, но сам заголовок при этом содержит ошибки, которые линкер бы не допустил. Это тоже подозрительно. Нестандартный формат некоторых служебных секций. В исполняемых файлах есть служебные секции, такие как, например, .ctors, .dtors, .fini и т.п. Особенности этих секций могут использоваться вирусами для заражения файла. Нарушение формата такой секции также является подозрительным. … и еще сотня таких признаков Таких признаков может быть множество, они имеют разную степень опасности, некоторые могут быть опасными лишь в комбинации с другими, но это мощнейший инструмент для принятия решения о необходимости более тщательного анализа и о факте заражения. Обойти эвристик весьма непросто (я имею в виду сделать так, чтобы он не выдал даже предупреждения). Это либо всякие платформозависимые решения, использующие особенности определенных компиляторов или фреймворков (типа перезаписи стандартных конструкторов или деструкторов), которые довольно быстро попадают в эвристическую базу, либо использование реально больших и сложных инфекторов, умеющих действительно качественно расположить код в файле. Когда эвристические признаки говорят, что «файл 100% заражен», но тяжелый анализ ничего не нашел, антивирус пишет, что файл заражен вирусом с названием типа: «Generic Win32.Virus», или по-нашенски «Какой-то Win32 Вирус». Такие сообщения часто можно встретить на всяких кейгенах, лоадерах и т.п. В прошлой статье я уже говорил, что именно по этой причине в инструкциях по установке пиратского софта пишут «перед установкой отключите антивирус». Также я еще раз хочу обратить внимание на один из важнейших информационных активов антивирусных компаний — коллекцию исполняемых файлов достаточного объёма, чтобы на ней можно было бы тестировать анализатор, не боясь выпустить в мир версию, которая будет кидаться на легитимные файлы, которые туда обязательно добавляются. Обиженные кейгены и лоадеры наверняка возмущаются, что их туда не добавляют оперативно, но кто ж их, вирусню, слушает… Итак, поработав над эвристиком авер приходит к следующему общему алгоритму детектирования: Проверить файл обычным сигнатурным поиском. Если успешно — считать файл заражённым. Если найдена «белая» защита — выйти молча. Проверить файл эвристическим анализатором. Если не найдено ни одного признака — выйти. Если найдены признаки достаточного веса, запустить анализ, использующий дизассемблирование. При этом, если эвристические признаки достаточно серьёзны, чтобы говорить о заражении, считать файл зараженным вне зависимости от того, нашел анализатор что-либо, или нет. Работы проделано много, и антивирус теперь, пусть и не идентифицируя угрозу, но с очень высоким процентом достоверности распознает факты инфицирования. Поддержка тестовой базы исполняемых файлов позволяет без опаски добавлять новые эвристические признаки, как только появляются новые алгоритмы инфицирования, и, наконец-то, антивирус умеет реагировать на угрозы до того, как новая зараза успевает распространиться. Надо отметить, что если раньше тестировать антивирус на всех исполняемых файлах в мире казалось совершенно нереальным, то сейчас сейчас база всех возможных в WWW исполняемых файлов уже не кажется фантастикой. Исполняемый файл штука, требущая серьезных временных затрат, и мир производит их не так уж и много. Кроме того тестирование на этой огромной базе файлов легко распараллеливается, поэтому вполне реально дрессировать эвристик на огромных массивах возможных данных. Счастливый авер пьёт своё какао и ложится спать… »» Нажмите, для закрытия спойлера | Press to close the spoiler «« «Мутанты наступают». Метаморфизм» Нажмите, для открытия спойлера | Press to open the spoiler « Вирмейкер на этот раз решает не проводить манипуляции над уже существующим кодом, а генерировать новый код декриптора в каждом новом поколении. Это и есть метаморфизм — генерация нового кода в каждом новом поколении. В отличие от пермутации, в данном случае код не просто переставляет блоки внутри себя, а реально меняет своё содержание. В теории это должно означать безоговорочную победу вирмейкера над точным детектированием его вируса (эвристику-то никто не отменял). Теперь, сигнатура, сделанная для одного поколения вируса станет неактуальной для другого, а, даже если и продолжит детектировать вирус, то не даст гарантии работоспособности в следующем поколении. Что же представляет собой метаморфный генератор? Основой для генерации нового поколения декриптора является некий «базовый код», причем на каком языке он написан — несущественно. Он хранится внутри зашифрованного тела вируса, поэтому может быть постоянным. Там же, в теле вируса, лежит движок, который на основе каждой инструкции этого «базового» кода каждый раз генерирует новый, исполняемый, код. Это очень напоминает компилятор — на входе некоторые семантические конструкции, на выходе готовый к исполнению процессором код. Еще подобная генерация исполняемого кода на основе базового кода происходит в виртуальных машинах — в момент, когда на определённой платформе виртуальная машина исполняет подготовленный байт-код. Именно в этот момент «базовый» байт-код превращается в конкретный исполняемый, который понимает данный процессор. И, если каждую новую платформу считать новым поколением кода, то совокупность виртуальных машин под разные платформы является метаморфным генератором. Если вспомнить, что мы генерируем код декриптора, который максимально независим от того, где и когда он исполняется (не содержит системные вызовы, не обращается к сохраненному состоянию, не содержит сложные объекты), и работает с уже готовыми данными в памяти по известным смещениям, то задача кажется вполне себе разрешимой. На входе у генератора три основных параметра — адрес зашифрованного буфера, его длина и ключ. Ну, пусть будет еще seed для псевдослучайной генерации всяких констант, будущих ключей и т.п. Также декриптор содержит условные переходы, но только в пределах своего тела, что также немного упрощает задачу. »» Нажмите, для закрытия спойлера | Press to close the spoiler «« Генерация мусора» Нажмите, для открытия спойлера | Press to open the spoiler « Вирмейкер решает подойти к вопросу, используя генерацию множества лишних инструкций, и «размешивать» истинный код декриптора в них. Пусть даже исходные инструкции останутся неизменными, в куче других инструкций вычленить необходимые для сравнения по сигнатуре будет очень проблематично. Несмотря на невзрачное название, генератор мусора является самой сложной и интересной частью метаморфного движка, ведь мусор или не мусор, а сгенерировать надо исполняемый код, который не будет ломаться сам и не будет портить основной код декриптора. В процессе «замешивания» необходимо будет: — следить за смещениями характерных точек (адресов переходов, выходов из цикла и т.п.); — следить, чтобы мусорный код не испортил необходимые регистры и регистр флагов. Очень заманчивыми кандидатами на звание мусорных инструкций являются всякие MMX, SSE, floating-point инструкции, их можно легко сгенерировать сколько надо, главное — не трогать стек, не писать в регистры общего назначения и не ломать флаги, необходимые декриптору, и первый метаморфный код выглядит вот так: mov ecx, 100h; ; декриптор lbl0: mov eax, [esi + ecx] ; декриптор xor eax, edi ; декриптор mov [ebx], eax ; декриптор add ebx, 4h ; декриптор movd mm0,edx ; мусор movd mm1,eax ; мусор psubw mm1,mm0 ; мусор lbl1: jcxz lbl2 ; декриптор, выход из цикла psubw mm1,mm0 ; мусор movd mm3,ecx ; мусор jmp lbl0 ; декриптор, продолжение цикла lbl2: sub ebx, 100h ; декриптор Авер не сильно волнуется, т.к. эвристик всё-таки продолжает ругаться на заражённые файлы (работая над генератором, вирмейкеру неохота возиться с серьёзным инфектором), но точно идентифицировать конкретный вирус уже не может. Поэтому тёмной ночью аверу снится инфектор, который не поддается эвристику, и его навязчивой идеей становится необходимость задетектить гада со 100% точностью. Чтобы точно идентифицировать вирус, детектор надо дорабатывать — теперь необходимо, начав с точки входа, шагать по инструкциям, пропускать все мусорные и добавлять в анализируемые только значимые, а это означает, что дизассемблер в детекторе начинает расти. Если вы помните про NOP зоны в абзаце про пермутацию, то пропуск NOP-ов при набивании буфера для сравнения по сигнатуре, фактически, и есть первый подход к сняряду — детектор пропускает NOP-ы, как мусорные инструкции. Теперь авер вместо сравнения с 0x90 (опкод NOP) использует дизассемблер (чем быстрее, тем лучше), который: Сдвигает указатель на начало следующей инструкции (дизассемблер длин). Говорит, является ли данная инструкция мусорной (NOP, MMX, SSE и т.п.). Значимые инструкции добавляет в анализируемый буфер. В случае безусловного перехода помечает адрес перехода, как следующий анализируемый. В случае условного перехода помечает обе возможных ветки кода для дальнейшего анализа. Таким образом авер собирает буфер из инструкций, которые составляют основной код декриптора, и уже в нём может провести сравнение по сигнатуре. Это пока еще довольно быстрая процедура, но, программируя её, авер все больше волнуется: «всегда ли я смогу отличить мусорную инструкцию от значимой?» Вирмейкер, чувствуя это, дорабатывает свой генератор мусора. Теперь он зовет на помощь инструкции сохранения контекста: pushad/popad (положить или достать со стека все регистры общего назначения) и pushfd/popfd (то же самое для регистра флагов). mov ecx, 100h; ; декриптор lbl0: mov eax, [esi + ecx] ; декриптор xor eax, edi ; декриптор mov [ebx], eax ; декриптор add ebx, 4h ; декриптор
pushad ; сохраняем регистры pushfd ; сохраняем флаги mov eax, 12321h ; мусор
xor edx,edx ; делаем что хотим sub eax, esi ; продолжаем мусорить popfd ; восстанавливаем флаги popad ; восстанавливаем регистры lbl1: jcxz lbl2 ; декриптор, выход из цикла
pushad ; сохраняем регистры pushfd ; сохраняем флаги shr ebx, 4 ; мусор popfd ; восстанавливаем флаги popad ; восстанавливаем регистры
jmp lbl0 ; декриптор, продолжение цикла lbl2: sub ebx, 100h ; декриптор
Теперь дизассемблер анализатора должен следить не только за тем какие инструкции он анализирует, но и находятся ли они в области «делаем что хотим». А это означает, что у дизассемблера появляются глобальные переменные, хранящие информацию о том, в каком месте программы мы находимся. Все становится еще интереснее. Ну а вообще, инструкции сохранения контекста для любого реверс-инженера как красная тряпка для быка — при анализе исполняемых файлов любая встреча с такой инструкцией означает «скорее ставь сюда breakpoint!». Следующей итерацией в развитии метаморфного кода является генерация необходимого действия различными способами при помощи различных арифметических операций и всяких ассемблерных хитростей. Типа того: «базовая инструкция» сгенерированный код 1 сгенерированный код 2 virt_mov eax, 10h mov eax, 20h; sub eax, 10h; mov edx, 10h; mov eax, edx; virt_mov ecx, 08h xor ecx,ecx; add ecx, 08h;; mov ecx, 04h; add ecx, 04h; virt_sub eax, ecx neg ecx; add eax, ecx; mov edx, ecx; sub eax, edx; Например, так можно работать со всеми константами: предположим в «базовом коде» лежат две инструкции «virt_mov edx, 10h» и «virt_mov ecx, 100h». Тогда генерируя новый код, движок выбирает случайную константу, например, «50h», и использует ее для работы со всеми абсолютными значениями, и «virt_mov edx, 10h» мутирует в «mov edx, 50h; sub edx, 40h;», a «virt_mov ecx, 100h» в «mov ecx, 50h; add ecx, B0h». Различные константы порождают различные байт-паттерны, что вынуждает авера добавлять в дизассемблер всё больше логики, реализовывать wildcards в сигнатурах по инструкциям, делая для инструкций что-то наподобие «mov eax, ; ; mov ecx, ». Это уже не очень просто, и уже не очень быстро, и вообще пахнет жареным…
После анализа кода детектора, помимо констант, для модификации данных в инструкциях, вирмейкер теперь хочет менять и весь набор регистров, используемый в декрипторе. Чтобы позволить себе такое, необходимо использовать разделение регистров — часть регистров являются рабочими для декриптора, а остальные — для генератора мусора. В этом случае генератор мусора не трогает рабочие регистры, а также не портит регистр флагов. Например, весь декриптор может работать только с eax, edx и esi. Тогда все порождённые генератором инструкции должны работать только с ebx,ecx, edi и не менять флаги. При этом, набор регистров должен изменяться в каждом новом поколении вируса.
. . . mov eax, 10h ; декриптор
mov ebx, 20h ; декриптор, ebx - мусорный регистр, его можно испортить командой xchg xchg edx, ebx ; для того, чтобы загрузить 20h в регистр edx
xor ecx,ecx ; мусор inc ebx ; мусор add ecx,ebx ; мусор add eax, edx ; декриптор mov edx, [esi] ; декриптор xchg edi,ebx ; мусор cmp edx, 0 ; декриптор . . . ;
Генератор мусора, в общем-то, может менять и «запрещенные» регистры и флаги, но при этом возвращать их состояние обратно. В этом случае «истинные» инструкции декриптора можно внедрять не в любое место буфера с мусором, а только в те места, где значения в регистрах и флаги «чистые».
По-настоящему серьёзным испытанием становится изготовление сигнатуры по инструкциям, если используются различные ассемблерные хитрости, позволяющие реализовать необходимые действия при помощи совершенно непохожих паттернов, например:«базовая инструкция» сгенерированный код 1 сгенерированный код 2 virt_push eax sub esp, 04h; mov [esp], eax; mov edx, esp; sub edx, 04h; mov [edx], eax; virt_mov eax, ebx lea eax,[ebx]; push ebx; xchng eax,ebx; pop ebx;
Таких паттернов — огромное количество, при желании вы их легко найдёте. Теперь генератор становится намного сложней, т.к. сильно усложнаяется работа со смещениями, стеком, флагами и т.п. Но это еще один серьёзный шаг к идеальному генератору. Итак, для порождения кода, в котором детектору будет максимально сложно утверждать, является ли текущая инструкция мусорной, необходим генератор, способный генерировать код со следующими свойствами: содержать ходовые целочисленные инструкции с регулярными регистрами; не использовать инструкции сохранения-восстановления контекста для отделения мусорного кода от истинного; набор регистров как в мусорных блоках, так и в инструкциях декриптора в каждом поколении должен быть различным; базовые инструкции декриптора должны превращаться в блоки инструкций различной длины; байт-структура мутировавших инструкций должна быть максимально вариативной.»» Нажмите, для закрытия спойлера | Press to close the spoiler «« «Наш ответ Чемберлену». Эмуляция» Нажмите, для открытия спойлера | Press to open the spoiler « Предположим, что вирмейкер через 42 месяца работы написал-таки почти идеальный метаморфный генератор, и детектор не может фильтровать инструкции по принципу «мусор-не мусор» и, соответственно, не может собрать достаточно данных для сравнения по сигнатуре. Но и у авера в запасе есть ответ, столь же сложный в реализации, но способный справиться с детектированием конкретного вируса использующего даже столь продвинутые методы метаморфизма. В процессе противостояния всё новым генераторам и изготовления всё более сложных дизассемблерных сигнатур дизассемблирующий движок дошёл до состояния, когда помимо текущей инструкции он хранит также и все окружение данной конкретной инструкции, следя за изменениями в регистрах, флагах, указателем стека и т.п. Критически взглянув на получившийся код авер вдруг понимает, что, фактически, запрограммировал софтовую модель процессора. Проходя по инструкциям его код обновляет переменные, соответствующие регистрам, следит за флагами, чтобы предсказать условный переход, отслеживает верхушку стека и т.п., т.е. фактически исполняет читаемый код виртуально. Метод детектирования, использующий эмулирующий исполнение движок так и называется — эмуляция. Вспомним, как крекеры снимают навесную защиту исполняемых файлов, остановив ее на первой инструкции в момент, когда работоспособный распакованный код лежит в памяти (на Original Entry Point). Для понимания того, как эмулятор может помочь детектору добраться до вкусного, зашифрованного payload кода я коротко опишу один простой, но действенный способ остановить программу на OEP. Основан он на том, что в момент старта основной программы указатель стека должен быть установлен в своё изначальное значение, т.к. на стеке лежат данные, относящиеся к окружению программы, агрументы, переменные среды и т.п. Поэтому, можно быть уверенным, что после того, как защита отрботает, esp вернётся к тому значению, которое было установлено в начале. Крекер останавливает программу прямо в точке входа, запоминает значение указателя стека esp и ставит условный breakpoint, который остановит программу в тот момент, когда esp станет равным тому самому значению, которое было зафиксировано на момент старта. С большой вероятностью именно в этот момент он будет находиться на OEP (ну или в корне декриптора с точки зрения вложенности функций). Декриптор вируса (если он использует стек, конечно) также должен вернуть указатель стека на место, и код детектора, бегущий по инструкциям, может следить за этим указателем, заведя переменную cur_esp и изменяя её каждый раз, когда встречает инструкции, меняющие esp. . . . ; base_esp = cur_esp; push eax ; cur_esp -= 4; mov eax, 1h ; - push edx ; cur_esp -= 4; . . . ; - pop edx ; cur_esp += 4; pop eax ; cur_esp += 4; (cur_esp == base_esp) !!! . . . ; здесь возможно отработал декриптор или весь вирус Как раз в этот момент, когда стек восстановлен, в памяти находится расшифрованое тело вируса, а в нем вкусные данные для постоянной сигнатуры. В случае с вирусом даже необязательно дожидаться всей распаковки, наверняка внутри декриптора все таки есть постоянные данные типа длины ключа, длины блока, смещения (которое зависит от расмещения секция в файле). Другими словами, если идти по декриптору инструкция за инструкцией, то каким бы ни был порождённый метаморфным генератором код, обязательно наступит момент, когда где-то в памяти или в регистрах будут лежать характерные для именно этого вируса данные. Поймав этот момент, можно определить что это за вирус. Также идя таким образом по инструкциям можно ожидать опасные действия — вызовы подозрительных API, запись в подозрительные файлы и т.п. Остался пустячок — автоматизировать этот процесс. Как я уже упоминал, эмулятор — это софтовая модель процессора, исполняющего код нашего файла. Причем процессора-халявщика, потому что ему не надо писать в память, делать ввод-вывод и вообще, ему интересно только то, что позволит остановить программу в нужном месте. Он не умеет MMX, SSE и вообще, чем меньше он умеет (при этом выполняя свою функцию), тем лучше (т.к. халявщик он условный, и весьма тяжеловесен). Предположим, в какой-то момент декриптор кладет на стек строку «BANANAS», зная это авер может исполнять код на виртуальном процессоре постоянно проверяя верхушку стека на наличие этой строки. Движок эмулятора имеет в себе переменные, соответствующие регистрам, флагам, память под эмуляцию стека и т.п. Я намеренно оставил блоки между pushad/popad, чтобы продемонстрировать, что эмулятор может пропускать в том числе и блоки кода, а не отдельные инструкции, т.к. эмуляция — процедура не из простых. Вот как оно примерно работает (пусть по адресу в ESI лежит этот самый «BANANAS\0»). mov ecx, 0h; ; ecx_var = 0; lbl0: mov eax, [esi + ecx] ; esi и eax мы знаем (из прошлой эмуляции), ; поэтому загрузим из правильного места в памяти ; указатель на "BANANAS" xor eax, edi ; eax_var = eax_var XOR edi_var; push eax ; esp_var -= 4; *esp_var = eax_var; pushad ; включаем режим безделья pushfd ; skip mov eax, 12321h ; skip xor edx,edx ; skip sub eax, esi ; skip popfd ; skip popad ; выключаем режим безделья ; "качественные" мусорные инструкции ; эмулятор не знает об этом ; и вынужден исполнять их виртуально mov edx, 23h ; edx_var = 23h; or edx, eax; ; edx_var = edx_var OR eax_var; lbl1: inc ecx ; ecx_var++; cmp ecx, 8h; ; if (ecx_var == 8) { goto lbl2; }: pushad ; включаем режим безделья pushfd ; skip shr ebx, 4 ; skip popfd ; skip popad ; выключаем режим безделья jmp lbl0 ; goto lbl0 lbl2: sub ebx, 100h ; на стеке лежит "BANANAS" - попался! Разумеется, эмуляция требует обработки множества специфических ситуаций, никаких ресурсов не хватит, чтобы честно эмулировать каждую инструкцию, и при этом не забывать выполнять необходимые проверки окружения. Поэтому эмуляторы детектируют циклы, передают части кода на исполнение реальному процессору, в общем там, как и в метаморфных генераторах, простора для творчества хоть отбавляй. Итак, где-то в идеальном мире существует идеальный метаморфный генератор, порождающий абсолютно недетектируемый код. И там же, в противовес ему, существует идеальный эмулятор, на котором можно исполнить этот метаморфный код и продетектировать его. Есть ли еще развитие темы самомодифицирующегося кода? »» Нажмите, для закрытия спойлера | Press to close the spoiler «« Философские вопросы» Нажмите, для открытия спойлера | Press to open the spoiler « Попробуем расширить тему модификации исполняемого кода в каждом новом поколении. Ведь именно изменчивость каждого нового поколения лежит в основе эволюции, поэтому, тема самомодифицирующегося кода в аспекте информационных технологий попахивает уже креационизмом. Кто знает, возможно мы постепенно создаем новую вселенную и новую жизнь? Рассматривая шифрование буфера с основным кодом вируса мы говорили об изменении вируса с точки зрения байт — т.е. менялся набор байт, составляющих вирус. Это самый примитивный уровень, отдельный байт несет немного информации о свойствах вируса и изменениями на этом уровне не добиться вариативности поколений. Если проводить аналогию с развитием жизни, это напоминает многообразие простых химических соединений. Множество простых соединений, в различных комбинациях, вода, аммиак, углекислый газ, кислотные остатки и гидроксильные группы — в течение миллионов лет этот коктейль не мог породить ничего сложного. Но в конце концов удачные комбинации привели к появлению сложных органических молекул — основы жизни. Рассматривая мутацию декриптора с помощью метаморфного генератора мы в общем рассматривали вирус уже как набор инструкций, а не байт. Это важный факт, означающий, что теперь мы работаем с информационными элементами следующего порядка. Теперь мы работаем с «обнуляем eax» вместо того, чтобы вдаваться в подробности, как мы это делаем (xor eax,eax или sub eax, eax) и все наши дизассемблеры-детекторы — это замена детектирования по байтам детектированием по цепочке инструкций. Заменяя одну инструкцию другой, мы меняем в том числе и набор байт, т.е. этот уровень включает в себя предыдущий, и, с точки зрения эволюции видов, он куда более продвинут. Изменением набора инструкций можно добиться более целенаправленной вариативности, нежели тупым псевдо-случайным «перемешиванием» байт. Биологическим аналогом, наверное, могли бы выступить аминокислоты, каждая из которых уже сама по себе способна порождать некоторое биологическое действие, при этом они целостны, умеют комбинироваться в более сложные структуры и несут в себе больший квант информации, нежели простейшие соединения. Если продолжать аналогию, следующим уровнем, на котором вирус может изменяться от поколения к поколению, будут функции, т.е. сущности, состоящиие из набора инструкций. Это что-то типа огромной коллекции функций в нескольких экземплярах на каждый отдельный кусок функционала, например, множество функций, которые ищут файл для заражения, зашифровывают и расшифровывают тело вируса, инфицируют файл-жертву и т.п. В каждом новом поколении вируса набор используемых функций рекомбинируется, меняя весь внутренний функционал. Каждая функция написана отдельным способом, содержит в себе другой набор инструкций и байт, т.е. этот уровень также включает в себя все предыдущие. При возможности реализовать такую схему без архитектурных уязвимостей, такой вирус можно будет теоретически детектировать только детектором, умеющим работать с кодом также на уровне функций, т.е., фактически, оценивающим поведенческий сценарий вируса, а не отдельные инструкции. Не уверен, что сейчас существует что-то похожее на движок, умеющий мутировать на уровне функций, хотя в нескольких статьях читал про идеи, например, скачивания вирусом собственных частей с хоста вирмейкера и замещении собственного функционала, или генетических алгоритмах (когда два вируса меняются функциональными блоками между собой). Тем не менее, огромное количество различных высокоуровневых языков и фреймворков по идее должно способствовать появлению такого рода программ, но уже, как мне кажется, не в области вирусов (ниже расскажу почему). Наверное, биологической аналогией функций такого вируса могут быть белки. Внутриклеточные функции могут обеспечиваться различными типами белков, белки могут замещаться другими, при этом клетка останется целостной, и будет продолжать существование, несмотря на изменившиеся внутренние функции. Ну а выше только мутация на уровне общего алгоритма программы. Например вирус, который сейчас детектируется антивирусом в следующем поколении становится вообще другой программой, хорошей и пушистой, и антивирусу его просто не нужно детектировать, хехе. Хотелось бы растечься мыслью, как в прошлом абзаце, но… Это фантастика. »» Нажмите, для закрытия спойлера | Press to close the spoiler «« Бабло побеждает зло» Нажмите, для открытия спойлера | Press to open the spoiler « Ну и где это всё, спросите вы? Где тысячи страшных метаморфных генераторов, заполонивших компы пользователей, отправившие в психиатрические клиники сотни программистов антивирусных компаний, где жуткие вирусные эпидемии кладущие сеть на недели, где это всё? По моему мнению причин тут несколько. Первая причина, техническая — это ограничения среды. До NT-шного ядра, NTFS и Linux на домашних PC коду вирусов жилось очень привольно — пиши куда хочешь, исполняйся где хочешь. Сейчас использовать зараженную систему намного сложней и не так уж интересно. Нет, я не хочу сказать, что всё потеряно, но о былом могуществе осталось только мечтать и с каждым годом ситуация всё хуже — права на файлы и процессы, подписи файлов, online-валидация запускаемого софта — все это практически убило «чистокровные» компьютерные вирусы. Но кто знает, по отчетам рынок мобильных зараз растет весьма активно, не означает ли это, что мобильным разработчикам придется пройти по тем же граблям, что и разработчикам больших ОС? Очень надеюсь, что это не так, и технологии защиты в полной мере перекочевали на мобильные устройства. Ну а главная, как мне кажется, причина — это квалификация программиста. Если вы в состоянии написать хороший метаморфный генератор, с которым специалисты провозятся хотя бы несколько дней, или эмулятор детектирующий сигнатуру внутри качественного вирусного движка, или сделать качественный crackme, который публично зауважают, то… просто напишите мне. Я не рекрутер, но если вас будет много — сменю профессию, просто представляя вас компаниям, которые занимаются безопасностью. Будьте уверены, ваши доходы и стабильность по жизни во много раз перевесят то, что можно получить распространяя вирусы или взламывая софт. В этом и есть главная причина — продвинутые движки пишут очень немногие энтузиасты с очень хорошей подготовкой, и, в большинстве случаев, находят в жизни куда более привлекательные применения своему таланту. Ну а еще, по моему мнению, коммерческая часть современной заразы откровенно скучна, поэтому те, кто поддался соблазну получать ворованные деньги, надолго зависают в сугубо прикладных аспектах и бросают заниматься совершенствованием внутренних алгоритмов. »» Нажмите, для закрытия спойлера | Press to close the spoiler «« Эпилог Жалко, что коду, способному порождать собственные изменённые копии, пока не нашлось другого применения, кроме как прятаться от антивирусов и противостоять взлому. Я искренне желаю этим методам дальнейшего развития, желательно без уклона в зловредство. И пускай программы, порожденные человеческим разумом, станут немного ближе к тому, что мы называем жизнью. Источник: Habrahabr | |
| |
|
|