Методы детектирования сендбоксов так же востребованы у разработчиков защит, как и методы антиотладки: если программа работает в изолированной среде, это может означать, что ее поведение хотят исследовать реверсеры или вирусные аналитики, либо это означает, что наша программа изолирована защитными средствами решений internet security. Так или иначе, определение факта изоляции программы — полезный навык, который помогает и разработчикам защит, и вирмейкерам. В этой статье я покажу, как распознавать sandbox-изоляцию и запуск под гипервизорами разных типов.

INFO

Sandbox (песочница) — изолированная программная среда, в которой задается и контролируется набор ресурсов для запущенной внутри нее программы. Как правило, ограничиваются и фильтруются вызовы WinAPI, которые отвечают за доступ к оборудованию, процессорным ядрам, определение размера памяти, а также доступ к сети и привилегированным средствам операционной системы. Сендбоксинг часто используется для запуска небезопасного кода и для анализа программ.

Есть ли надежные средства детекта песочниц и виртуалок?

Загрузка ... Загрузка ...
 

Проверяем запущенные процессы

Если виды песочниц нам приблизительно известны, то это облегчит детектирование. Можно просто поискать сендбоксы в списке выполняющихся процессов. Для этого используем функцию, которая перечислит все процессы и определит PID нужного по названию.

DWORD getPIDproc(char * pProcName)
{
    HANDLE pHandle = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);

    if(pHandle == NULL) return 0;

    PROCESSENTRY32 ProcessEntry;
    DWORD pid;
    ProcessEntry.dwSize = sizeof(ProcessEntry);
    bool Loop = Process32First(pHandle, &ProcessEntry);

    if(Loop == NULL) return 0;

    while (Loop)
    {
        if (strstr(ProcessEntry.szExeFile, pProcName))
        {
        pid = ProcessEntry.th32ProcessID;
            CloseHandle(pHandle);
            return pid;
        }
        Loop = Process32Next(pHandle, &ProcessEntry);
    }
    return 0;
}

Детект песочницы Comodo Internet Security:

if(getPIDproc("cmdvirth.exe")) std::cout << "Comodo sandbox detected!\n";

Процесс cmdvirth.exe обеспечивает виртуализацию в Comodo Internet Security. По тому же принципу можно задетектить песочницу Sandboxie:

if(getPIDproc("SbieSvc.exe")) std::cout << "Sandboxie detected!\n";

Я думаю, что принцип понятен. 🙂 Если процесс не будет найден, функция вернет 0, и условие if не будет выполнено. Также функция вернет 0, если возникнут проблемы с получением первого процесса в системном снапшоте функцией Process32First либо с получением самого снапшота функцией CreateToolhelp32Snapshot.

 

Проверяем подключенные модули в нашем адресном пространстве

Другая интересная идея — просмотреть подключенные модули в адресном пространстве нашей программы на предмет известных модулей песочниц. Сделаем это напрямую из нашего процесса при помощи WinAPI-функции GetModuleHandle:

BOOL checkLoadedDll(LPCWSTR pDllName)
{
    HMODULE hDll = GetModuleHandle(pDllName);
    if(hDll) return TRUE;
}

Проверка на песочницу Comodo Internet Security:

if (checkLoadedDll(L"cmdvrt64.dll")) std::cout << "Comodo sandbox detected!\n";

Или проверим на Sandboxie:

if (checkLoadedDll(L"sbiedll.dll")) std::cout << "Sandboxie detected!\n";

Функция GetModuleHandle() проверяет наличие DLL в адресном пространстве вызывающего процесса. Если функция не находит модуль, то возвращает ноль, условие не срабатывает.

Чтобы узнать список подключенных модулей в стороннем процессе, нужно получить его хендл, вызвав функцию WinAPI OpenProcess, затем перечислить все подключенные к процессу модули с помощью функции EnumProcessModules (в нее следует передать полученный хендл) и, наконец, получить название модуля с помощью функции WinAPI GetModuleFileNameEx.

 

Человеческий фактор

Что делать, если мы не знаем даже примерно, какая именно программа-песочница будет использована? Надо сказать, что очень сложно на 100% задетектить грамотно написанную песочницу, ведь она будет работать на уровне ядра, может прятать все свои процессы, подключенные модули, может даже скрывать собственный драйвер. Техники, которые будут перечислены далее, я рекомендую использовать по принципу экспертной системы для анализа результатов: чем больше будет срабатываний, тем более вероятно, что мы находимся в изолированной среде.

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

BOOL mouse_motion()
{
    int count = 0;

    POINT mouse_coordinate1 = {};
    POINT mouse_coordinate2 = {};

    GetCursorPos(&mouse_coordinate1);

    Sleep(1500);

    GetCursorPos(&mouse_coordinate2);

    if ((mouse_coordinate1.x == mouse_coordinate2.x) && 
        (mouse_coordinate1.y == mouse_coordinate2.y))
        ++count;

    GetCursorPos(&mouse_coordinate1);

    Sleep(1500);

    GetCursorPos(&mouse_coordinate2);

    if ((mouse_coordinate1.x == mouse_coordinate2.x) && 
        (mouse_coordinate1.y == mouse_coordinate2.y))
        ++count;
    if(count > 0) return TRUE;
        else return FALSE;
}

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

 

PEB → NumberOfProcessors

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

Код для архитектуры x64:

PULONG procNum = (PULONG)(__readgsqword(0x60) + 0xB8);   // DWORD NumberOfProcessors;

Для x86:

PULONG procNum = (PULONG)(__readfsdword(0x30) + 0x64);   // DWORD NumberOfProcessors;

Этот код получает содержимое поля NumberOfProcessors из PEB (Process Environment Block). Теперь легко проверить число процессоров:

if (*procNum < 2)  std::cout << "NumberOfProcessors == 1, may be sandboxed!\n";
 

Выясняем размер оперативной памяти

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

BOOL check_memory()
{
    MEMORYSTATUSEX mem_stat = { 0 };    

    statex.dwLength = sizeof(mem_stat);
    GlobalMemoryStatusEx(&mem_stat);

    if(mem_stat.ullTotalPhys < (1024LL * (1024LL * (1024LL * 1LL)))) return TRUE;
    else return FALSE;
}
 

Проверяем свободное место

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

BOOL check_freespace()
{
    LPCWSTR lpDirectoryName = NULL;
    ULARGE_INTEGER lpTotalNumberOfBytes;

    BOOL bStat = GetDiskFreeSpaceEx(lpDirectoryName, NULL, &lpTotalNumberOfBytes, NULL);
    if (bStat) 
    {
        if (lpTotalNumberOfBytes.QuadPart < (30ULL * (1024ULL * (1024ULL * (1024ULL)))))
            return TRUE;
        else return FALSE;
    }
}
 

Простые тайминг-атаки

Песочницы зачастую весьма требовательны к ресурсам и нередко сильно замедляют работу программы. Мы можем использовать тайминг-атаки для того, чтобы понять, используется эмуляция оборудования или нет. Один из вариантов — выполнять какой-нибудь системный вызов, который на чистой системе будет работать моментально, а в эмулируемой среде замедлен из-за драйвера песочницы. Если разница велика, то можно предположить, что используется песочница. На такой трюк ловится, например, песочница Sandboxie.

Что может дать ложноположительный детект песочницы при выполнении тайминг-атак?

Загрузка ... Загрузка ...
BOOL checkTiming1()
{
    unsigned __int64 counter1, counter2, counter3;

    int i = 0;

    do
    {
        counter1 = __rdtsc();

        GetProcessHeap();

        counter2 = __rdtsc();

        CloseHandle(0);

        counter3 = __rdtsc();

        // Замеряем отношение времени выполнения CloseHandle и GetProcessHeap()
        if ( ( LODWORD(counter3) - LODWORD(counter2) ) / 
            ( LODWORD(counter2) - LODWORD(counter1) ) >= 10)
            return TRUE;
    } while (i = 0; i < 10; i++);

    return FALSE;
}

Отношение времени выполнения функции CloseHandle и GetProcessHeap() должно быть около 1 к 10. Если отношение меньше, делаем вывод о наличии фильтрации вызовов в драйвере песочницы.

Также песочницы могут оптимизировать течение времени — например, игнорировать вызовы функции Sleep(). Если сделать замеры времени выполнения до вызова Sleep() и после, то мы это увидим.

BOOL check_sleep()
{
    // Инициализируем пустые метки
    DWORD counterStart = 0;
    DWORD counterEnd = 0;
    DWORD difference = 0;

    counterStart = GetTickCount();  // Засекаем время до вызова Sleep();

    Sleep(100000); // Засыпаем на 100 секунд

    counterEnd = GetTickCount();  // Проверяем время после вызова

    difference = counterEnd - counterStart;  // Сравниваем временные интервалы
    if difference > 99000) // Корректируем на одну секунду, допуская погрешность
        return FALSE;
    else    return TRUE;
}
 

Быстрый детект гипервизоров

Мы рассмотрели детектирование известных и неизвестных программ-песочниц, но нельзя ли определить гипервизор? Здесь нам поможет инструкция __cpuid. Она запрашивает у процессора его тип и функции, которые он поддерживает. На выходе мы получаем заполненную структуру cpuInfo, которая состоит из четырех чисел, передаваемых в регистрах процессора EAX, EBX, ECX и EDX. На вход нужно подать чистую структуру cpuInfo и число function_id, которое говорит команде, какая именно информация нас интересует. Функция поддерживает как x64-, так и x86-архитектуры. Ее прототип выглядит следующим образом:

void __cpuid(  
    int cpuInfo[4],  
    int function_id  
); 

Если передать число 1 в поле function_id и посмотреть, выставлен ли 31-й бит в регистре ECX структуры cpuInfo, то можно узнать о присутствии гипервизора. Итак, код:

BOOL check_cpuid()
{
    INT cpuInfo[4] = { -1 }; // Объявляем структуру cpuInfo и инициализируем ее

    __cpuid(cpuInfo, 1); // Запрашиваем данные

    if ((cpuInfo[2] >> 31) & 1) 
        return TRUE; // Проверяем нужный бит в структуре cpuInfo
}

Помимо этого, известные гипервизоры детектируются так же, как и песочницы: можно просто посмотреть их процессы или характерные записи в реестре. Давай попробуем задетектить гипервизор Virtual PC при помощи функции, которую мы написали выше, — DWORD getPIDproc(char * pProcName). Просто передадим в нее имя нужных процессов, характерных для Virtual PC:

if(getPIDproc("VMSrvc.exe") || getPIDproc("VMUSrvc.exe")) std::cout << "Virtual PC detected!\n";

Так же легко найдем гипервизор Citrix Xen:

if(getPIDproc("xenservice.exe")) std::cout << "Citrix Xen detected!\n";

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

BOOL check_wine_registry_key()
{
    HKEY phkResult = FALSE;

    if (RegOpenKeyEx(HKEY_CURRENT_USER, _T("SOFTWARE\\Wine"), NULL, KEY_READ, &phkResult) == ERROR_SUCCESS)
        {
            RegCloseKey(phkResult);
            return TRUE;
        }
 };

Какие еще есть характерные признаки песочницы или виртуалки?

Загрузка ... Загрузка ...
 

Заключение

Вот мы и разобрали, как определять известные изолированные среды разными способами. Это не все из возможных методов, но от такого набора заготовок можно будет отталкиваться, конструируя свои методы определения (а возможно, и обхода) сендбоксинга.

4 комментария

  1. Аватар

    0ri0n

    27.02.2018 в 13:42

    Интересно. запустить приложение с названием Idapro64.exe — на сколько это будет эффективнее антивирусников )))

  2. Аватар

    john_

    12.03.2018 в 15:16

    Годные статьи под хайдом)

  3. Аватар

    haje

    01.06.2018 в 01:45

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

    Не знаю можно ли тут ссылки прикреплять, гуглить по запросу «Анти-песочница на базе статистического анализа»

    • Аватар

      haje

      01.06.2018 в 01:46

      пысы, читать статью по линку там где много комментов — это оригинал. Там и куча других идей в комментариях.

Оставить мнение