Издательский дом ООО "Гейм Лэнд"ЖУРНАЛ ХАКЕР #105, СЕНТЯБРЬ 2007 г.

Антиантивирусные технологии

Deeoni$ (DeeoniS@gmail.com)

Хакер, номер #105, стр. 112

Кое-что о своевременной технической поддержке зловредного программного обеспечения

Эта статья предназначена в первую очередь для людей, интересующихся принципами работы антивирусных сканеров. Материал не содержит каких-либо инструкций по созданию вредоносного программного обеспечения и не является призывом к действию. Все описанное ниже не противоречит УК РФ и служит исключительно для благих целей. Имена всех персонажей вымышлены, их совпадения с реальными случайны.

Наверное, каждый, кто когда-нибудь пробовал себя в такой нелегкой области программирования, как вирусописательство, создав свой первый зловред, сразу же отсылал его в какую-нибудь антивирусную лабораторию. Повзрослев и став умнее, вирусописатель, наоборот, начинает стремиться к тому, чтобы его творение как можно дольше оставалось незамеченным. А если этого достичь невозможно, он стремится к максимально быстрому и удобному выпуску стерильной версии.

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

«Легкие деньги!» - подумают некоторые. И будут правы, но при одном но. Нужен правильный подход, а точнее, технология.

Немного теории

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

Теперь немного о том, как выглядит процесс идентификации вируса в лаборатории. В общем случае файл проходит несколько этапов. Первый – это проверка подозрительной программы на как можно большем числе антивирусных сканеров других производителей. Если несколько антивирусов идентифицируют программу как вирус, то компьютер автоматически получает сигнатуру файла и добавляет его в базу. Если сканеры третьих производителей ничего не показали, то в дело может включиться полуавтоматический анализатор. Это специальное ПО, которое запускает потенциального зловреда в особой «песочнице» и отслеживает все его действия. На основании этих действий сигнатура файла также может быть добавлена в базы. Если и эта процедура ничего не дает, за файл берется самый совершенный на сегодняшний день программно-аппаратный комплекс — человеческий мозг. Другими словами, несколько специально обученных дятлов тыкают на разные кнопки клавиатуры, возят по столу мышкой и через несколько минут выносят свой вердикт. Если программа окажется слишком хитрой, оригинальной и т.д., то она попадет к профи, которые разберут ее по косточкам и точно скажут, что это такое.

Большинство вирусов и прочей нечисти отсеивается на первых двух этапах без всякого участия человеческого интеллекта. Те немногие, что пробились через кордоны автоматики, застревают на вирусных аналитиках. Таким образом, труд нашего программиста под ником Coder будет уничтожен буквально через несколько минут после попадания в антивирусные лаборатории. Но не просто же так ему платят по 1k американских денег в месяц? Coder должен бдить и на первый же писк антивируса реагировать модификацией своего творения.

Ближе к практике

Теперь, когда Coder знает, как работают антивирусные сканеры, он может смело заняться анализом своего кода с целью выявления мест, которые вызывают подозрения у антивирусного ПО. Но как же это сделать? Многие опытные вирусописатели могут посоветовать сделать свой код максимально похожим на тот, который генерируется компиляторами (если зловред написан на ассемблере). Но каждый раз просматривать исходник и пытаться наугад определить, что же именно вызвало такую бурную реакцию у антивируса, совсем неправильно. Есть метод, который позволяет с математической точностью определить, какой именно байт в файле является сигнатурой для антивируса.

Как известно, все исполняемые файлы в win32 имеют PE-формат. У них есть заголовок, секция кода, секция данных, импорта, экспорта и т.д. Для начала надо определить, где в файле находится каждая секция и сколько байт она занимает. Когда это сделано, затираем нулями по одной секции. Советую начать с секции данных и закончить секцией кода.

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

Допустим, секция, в которой находится злосчастный байт, определена. Далее методом деления пополам (философский прием «дихотомия» - примечание Лозовского) мы выясним, что именно так раздражает аверов. Метод этот применительно к нашему случаю будет выглядеть примерно так: нужную нам секцию мы условно делим на две половины. Затем сначала трем первую половину и проверяем сканером, если он ругается, то восстанавливаем первую часть, трем вторую и опять проверяем. Если антивирус молчит, то теперь уже делим ту часть, которую только что затерли, и забиваем нулями уже ее половины... предварительно, конечно, восстановив содержимое. Двигаясь дальше по этому алгоритму, мы в конце концов найдем тот самый байт, который так не нравится антивирусному сканеру.

Теперь дело за малым – надо всего лишь чуть модифицировать код, чтобы этого байта там не было. Это, конечно, легче сделать, если зловред написан на ассемблере, но можно вбить машинные команды и прямо в бинарник.

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

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

Пишем код

Для работы с файлом в формате PE нам понадобится класс-описание:

Интерфейс класса для работы с PE-файлами

class CPEFile

{

public:

virtual VOID WriteDOSHeader(VOID);

virtual UINT ReadDOSHeader(VOID);

virtual BOOL CheckAccess(VOID);

virtual BOOL IsPEFile(VOID);

CPEFile();

virtual ~CPEFile();

virtual UINT ReadObjectEntry(VOID);

virtual VOID WriteObjectEntry(VOID);

virtual LONG SeekToObjectEntry(VOID);

virtual LONG SeekToPEHeader(VOID);

virtual UINT ReadPEHeader(VOID);

virtual VOID WritePEHeader(VOID);

virtual VOID Close(VOID);

virtual BOOL Open(LPCTSTR szPEFile, BOOL ReadOnly = FALSE);

DOSHeader DOSHdr;

PEHeader PEHdr;

ObjectEntry ObjEntry;

CStdioFile m_hPEFile;

protected:

DWORD dwOffsetToPEhdr;

};

Функция ReadDOSHeader считывает в буфер DOS-заголовок PE-файла и возвращает количество записанных байт. CheckAccess проверяет у PE-файла атрибут ReadOnly. Если файл только для чтения, то функция вернет FALSE. IsPEFile проверяет, является ли файл исполнимым в формате PE. Если это так, то результатом работы функции будет TRUE. Посмотрим на код этой функции:

Код метода lsPEFile()

BOOL CPEFile::IsPEFile(VOID)

{

DWORD tmp;

m_hPEFile.SeekToBegin();

m_hPEFile.Read(&DOSHdr, sizeof(DOSHeader));

if(DOSHdr.Signature != MZ_SIGN) return FALSE;

dwOffsetToPEhdr = DOSHdr.OffsetToPEHeader;

m_hPEFile.Seek(dwOffsetToPEhdr, CFile::begin);

m_hPEFile.Read(&tmp, sizeof(DWORD));

if(tmp != PE_SIGN) return FALSE;

return TRUE;

}

Здесь функция ReadObjectEntry считывает описание секции из заголовка в буфер. Результатом ее работы будет количество считанных байт. WriteObjectEntry записывает описание секции в заголовок. SeekToObjectEntry перемещает указатель для работы с файлом на начало описания секции в заголовке, а SeekToPEHeader перемещает указатель на начало PE-заголовка. ReadPEHeader считывает PE-заголовок, а Close и Open соответственно закрывают и открывают файл.

Функция Open принимает два параметра. Первый - это szPEFile: имя файла, который следует открыть, а второй – ReadOnly: следует ли открывать файл только для чтения.

DOSHeader, PEHeader и ObjectEntry - это структуры, описывающие соответствующие части PE-файла. С некоторыми из них можно ознакомиться ниже.

Описание структур DOSHeader и ObjectEntry

typedef struct

{

WORD Signature; //Сигнатура 'MZ'

WORD PartPag; //длина неполной последней страницы

WORD PageCnt; //длина образа (+заголовок) в 512-байтниках

WORD ReloCnt; //число элементов в Relocation Table

WORD HdrSize; //длина заголовка в 16-байтниках

WORD MinMemory; //минимум требуемой памяти

WORD MaxMemory; //максимум требуемой памяти

WORD ReloSS; //сегмент стека (относительно RootS)

WORD ExeSP; //указатель стека

WORD ChkSum; //контрольная сумма

WORD ExeIP; //счетчик команд

WORD ReloCS; //сегмент кода (относительно RootS)

WORD TablOff; //позиция в файле первого элемента Relocation Table

WORD Overlay; //номер оверлея

BYTE Reserved[32]; //зарезервировано

DWORD OffsetToPEHeader; //зарезервировано

} DOSHeader;

typedef struct

{

BYTE ObjectName[8];

DWORD VirtualSize;

DWORD SectionRVA;

DWORD PhysicalSize;

DWORD PhysicalOffset;

BYTE Reserved[10];

DWORD ObjectFlags;

} ObjectEntry;

Используя этот класс, легко можно написать утилитку, которая будет помогать в определении плохого байта. Она сможет давать пользователю возможность выбрать, какую именно секцию тереть. Если забивание секции нулями помогло, то появляются две кнопки: «Затереть первую половину» и «Затереть вторую половину», а также третья, по нажатию на которую программа будет понимать, что дальше надо работать с той половиной, которую выбрал пользователь. То есть фактически программа будет выполнять лишь затирание нужных частей исполнительного файла, а его проверку и логику алгоритма половинного деления будет осуществлять пользователь. Таким образом поиск интересующий нас байт может стать делом нескольких минут.

Развитие идеи

Можно пойти и дальше. Лень, как известно, - двигатель прогресса, и весь процесс определения неугодного антивирусу байта можно свести к одному клику. Сделать это очень просто. Почти у каждой уважающей себя антивирусной компании есть консольная версия своего продукта. Так вот нажимать на кнопочки и анализировать результат работы сканера будет тоже программа. После каждого забивания нулями определенного участка файла тулза будет запускать консольную версию антивируса и сканировать бинарник с затертым куском, а затем парсить выдачу результатов. Делается это не очень сложно, а жизнь способно упростить до предела. Особо извращенные программисты могут попробовать распарсить результаты скана антивирусов с графическим интерфейсом.

Таким образом, получается, что для определения плохого байта достаточно запустить утилиту, выбрать, какой файл будет проверяться, нажать кнопку «СТАРТ» и, как нас учит компания Майкрософт, «откинуться на спинку кресла».

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

Очень часто антивирусы реагируют на текстовые строки в теле файла. Допустим, если вредоносная программа - это какой-нибудь троян-даунлоадер, то антивирус может реагировать на строку с линком к скачиваемому им файлу. Зная это, хитрый хакер способен пойти на небольшую уловку: зашифровав реальную строку, он оставит фейковую, отвлекающую внимание. В 90% случаев аверы будут детектить трояна по ней. То есть для выпуска следующей чистой версии достаточно изменить эту строку. Первое время фокус будет прекрасно работать, но в конце концов обман будет раскрыт. Чтобы оттянуть этот момент, надо придумывать фейковые строки, максимально приближенные к реальности.

Многие вирусописатели могут возразить мне, сказав, что антивирусы не детектируют зловредов всего по одному байту. Это в какой-то степени правда, но есть одна известная мудрость: «Прочность цепи определятся самым слабым ее звеном». В нашем контексте это значит, что достаточно изменить всего один, но самый «слабый» байт, и вся сигнатура станет бесполезна. Такой недостаток есть практически во всех популярных антивирусах. NOD32, Avast, Kaspersky, AntiVir и т.п. – все они страдают этим. Есть и одно исключение, о нем можно почитать во врезке.

Заключение

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

Думаю, наш программист под ником Coder воспользовался подобной технологией, исправно получает свои честно заработанные $1000 каждый месяц и чувствует себя счастливым человеком. Антивирусные компании тоже довольны... у них есть, чем пополнить свои базы, а значит, клиенты будут и дальше покупать их продукт. Счастлив и рядовой пользователь – он видит, что его любимый антивирус развивается и находит все новых и новых зловредов. Вот такая вот гармония :).

Microsoft OneCare

Чтобы скрыть зловреда от большинства антивирусов, достаточно изменить всего один байт, который является ключевым в сигнатуре. Но есть один продукт, который этого не боится, - это Microsoft OneCare. Да, да... Это именно тот антивирус, который занимает последние места в тестах и отчаянно матерится всем интернет-сообществом. Но те ребята, что создавали антивирусный движок, подошли к этому дело основательно.

Если OneCare детектит зловреда по секции кода, для его сокрытия недостаточно изменить один байт. Надо найти и переделать несколько инструкций, которые раскиданы по всему телу файла. Это очень усложняет процесс поиска, ведь для этого нам нужно затереть уже не один, а несколько плохих байт. Если хотя бы один байт мы не найдем, то изменение всех остальных нам не поможет. Мы даже не узнаем, правильно ли мы их нашли, ведь они зависимы и для их выявления надо разрабатывать специальный алгоритм. Он не очень сложен, но все же достаточно трудоемок.

Содержание
ttfb: 5.2428245544434 ms