Skip to Main Content
June 19, 2018

Enumerating Anti-Sandboxing Techniques

Written by Hans Lakhan
Fighting/writing malware is very much a cat and mouse game. One of several techniques used by Anti-Virus/EDR solutions is to detonate payloads in a sandbox and watch what happens. To combat this, malware writers (and pentesters) have been including checks in their payloads to identify when running in a sandbox to evade detection. However, these evasion techniques themselves are often being caught. So, to that end, I wondered if there might be a way to enumerate what anti-sandboxing checks are stealthier than others. To test this, I created several benign payloads, each with a different anti-sandbox check, and uploaded them to Virus-Total, Hybrid Analysis, and MetaDefender. Doing this and recording the results can disclose what sort of anti-sandboxing checks are most easily detected and by whom. However, before we get started, a few notes:
  1. Not all anti-sandboxing techniques are the same. For example, one technique, wherein the payload counts the number of CPU’s and only detonates if there are more than 3, can be coded in several different ways. For these tests, I went with the most basic approach. If a technique is caught, then we know which approach needs more investigation, and which should simply be avoided altogether.
  2. Security solutions on VirusTotal and MetaDefender are configured by the vendor and may be configured differently in your environment.
  3. There are plenty of anti-sandboxing techniques to use/choose from, I just selected ten out of ease.
  4. Rules and definitions are updated constantly, therefore these results might vary a month from now. All testing was performed in June 2018.
  5. My coding skills are weak and mostly consist of stack overflow and duct tape.
Step 1. Create a Completely Benign Payload For this step, I created a simple program, which when executed displays a random fact about cats.
#include <stdio.h>

#include <windows.h>

#pragma comment(linker, "/SUBSYSTEM:windows /ENTRY:mainCRTStartup")

void send_pop_up(char *fact)
{
         MessageBox(NULL, fact, "Test", MB_OK);
}

void getCatFact()
{
            char catFacts[20][100];
            strcpy(catFacts[0], "There are cats who have survived falls from over 32 stories (320 meters) onto concrete.");
            strcpy(catFacts[1], "A group of cats is called a clowder.");
            strcpy(catFacts[2], "Cats have over 20 muscles that control their ears.");
            strcpy(catFacts[3], "Cats sleep 70% of their lives.");
            strcpy(catFacts[4], "A cat has been mayor of Talkeetna, Alaska, for 15 years. His name is Stubbs.");
            strcpy(catFacts[5], "Cats can not taste sweetness.");
            strcpy(catFacts[6], "Owning a cat can reduce the risk of stroke and heart attack by a third.");
            strcpy(catFacts[7], "Wikipedia has a recording of a cat meowing because why not?");
            strcpy(catFacts[8], "The world's largest cat measured 48.5 inches long.");
            strcpy(catFacts[9], "Adult cats only meow to communicate with humans.");
            strcpy(catFacts[10], "A cat usually has about 12 whiskers on each side of its face.");
            strcpy(catFacts[11], "All cats have claws, and all except the cheetah sheath them when at rest.");
            strcpy(catFacts[12], "Approximately 1/3 of cat owners think their pets are able to read their minds.");
            strcpy(catFacts[13], "In the 1750s, Europeans introduced cats into the Americas to control pests.");
            strcpy(catFacts[14], "A 2007 Gallup poll revealed that both men and women were equally likely to own a cat.");
            strcpy(catFacts[15], "A cats heart beats nearly twice as fast as a human heart, at 110 to 140 beats a minute.");
            strcpy(catFacts[16], "Cats spend nearly 1/3 of their waking hours cleaning themselves.");
            strcpy(catFacts[17], "A female cat is called a queen or a molly.");
            strcpy(catFacts[18], "Rome has more homeless cats per square mile than any other city in the world.");
            strcpy(catFacts[19], "The richest cat is Blackie who was left 15 million by his owner, Ben Rea.");

            time_t result = time(NULL);
            send_pop_up(catFacts[result % 20]);
}

int main(void)
{
            getCatFact();
}
  I submitted the baseline app to Virus Total and surprisingly 5 out of 67 AV vendors think it’s malicious (or they just straight-up hate cats). Hybrid Analysis gives it a score of 36/100 and Meta Defender gives it a 2/31. While this is less than ideal for a simple harmless app, it does provide a baseline for our next steps. Step 2: Add In Sandbox Detection Checks The following checks were tested: With each check, a separate executable was generated and submitted to Virus Total/Hybrid Analysis and MetaDefender. These sites were chosen as they provide a wide array of AV/EDR products to test against.

Number of CPU’s:

This will check the number of processors on the system. If there are less than two processors, exit.
SYSTEM_INFO sysinfo;
GetSystemInfo(&sysinfo);
int numCPU = sysinfo.dwNumberOfProcessors;

if (numCPU < 2)
{
          return 0;
}
Results: Virus Total (20/67), Hybrid Analysis (78/100), MetaDefender (5/31).

Sleep 60 Seconds:

The program will wait 60 seconds before continuing.
std::this_thread::sleep_for(std::chrono::seconds(60));
Results: Virus Total (15/58), Hybrid Analysis (100/100), MetaDefender (4/31).  

Number of Temp Files (Admin)

This will check for the number of temporary files in a directory normally written to if the user has local administrative privileges. If the count is less than three, exit.
int file_count = 0;

DWORD dwRet;   
LPTSTR pszOldVal;
pszOldVal = (LPTSTR)malloc(4096 * sizeof(TCHAR));
WIN32_FIND_DATA data2;
HANDLE hFind2 = FindFirstFile("C:\\Windows\\Temp\\*", &data2);

if (hFind2 != INVALID_HANDLE_VALUE) {
          do {
                     file_count++;
          } while (FindNextFile(hFind2, &data2));
          FindClose(hFind2);
}
if (file_count < 3) {
         return 0;
}
Results: Virus Total (3/67), Hybrid Analysis (35/100), MetaDefender (1/31).  

Number of Temp Files (User)

This will check for the number of temporary files in a directory normally written to if the user does not have local administrative privileges. If the count is less than three, exit.
int file_count = 0;
DWORD dwRet;   
LPTSTR pszOldVal;
pszOldVal = (LPTSTR)malloc(4096 * sizeof(TCHAR));
dwRet = GetEnvironmentVariable("TEMP", pszOldVal, 4096); // get path from environment variable

std::string stdstr = pszOldVal;
stdstr += "\\*";

LPSTR s = const_cast<char *>(stdstr.c_str());

WIN32_FIND_DATA data;
HANDLE hFind = FindFirstFile(s, &data);

if (hFind != INVALID_HANDLE_VALUE) {
            do {
                      file_count++;
            } while (FindNextFile(hFind, &data));
            FindClose(hFind);
}

if (file_count < 3) {
         return 0;
}
Results: Virus Total (18/68), Hybrid Analysis (79/100) and Metadefender (9/31).  

Is a Member of a Domain

This will check to see if the host machine is a member of a domain. If it’s not a member, exit.
bool is_member_of_domain()
{
            bool ret = false;
            DWORD dwLevel = 100;
            LPWKSTA_INFO_100 pBuf = NULL;
            NET_API_STATUS nStatus;
            LPWSTR pszServerName = L"localhost";
            nStatus = NetWkstaGetInfo(pszServerName, dwLevel, (LPBYTE *)&pBuf);
            if (nStatus == NERR_Success)
            {
                        char response[500];
                        wcstombs(response, pBuf->wki100_langroup, 500);

                        char workgroup[] = "WORKGROUP";

                        if (strcmp(response, workgroup)) // returns 0 if identical
                        {
                                    return true;
                        }
                        else
                        {
                                    return false;
                        }
            }
            return ret;
}

            if (!is_member_of_domain()) {

                        return 0;

            }
Results: Virus Total (4/66), Hybrid Analysis (42/100) and Metadefender (2/31).  

Uptime

This will check to see how long the host machine has been active. If it’s been up for less than one hour, exit
DWORD startTime = GetTickCount();
if (startTime < 3600000) {
           return 0;
}
Results: Virus Total (3/67), Hybrid Analysis (35/100) and Metadefender (1/31).  

AV Process Names

This will search the current running list of processes. If any match our predefined list of known anti-virus vendors, exit.
bool process_check()
{
            string sandboxProcesses[numSandboxProcesses] = {
                        "vmsrvc.exe",
                        "tcpview.exe",
                        "wireshark.exe",
                        --- snip ---
                        "srvmon.exe",
                        "saHookMain.exe",
                        "bmrt.exe"
            };

            WTS_PROCESS_INFO* pWPIs = NULL;
            DWORD dwProcCount = 0;
            if (WTSEnumerateProcesses(WTS_CURRENT_SERVER_HANDLE, NULL, 1, &pWPIs, &dwProcCount))
            {
                        //Go through all processes retrieved
                        for (DWORD i = 0; i < dwProcCount; i++)
                        {
                                    for (int j = 0; j < numSandboxProcesses; j++) {
                                                string enum_process = pWPIs[i].pProcessName;
                                                string av_process = (string)sandboxProcesses[j];
                                                if (!enum_process.compare(av_process)) {
                                                            return true;
                                                }
                                    }
                        }
            }

            //Free memory
            if (pWPIs)
            {
                        WTSFreeMemory(pWPIs);
                        pWPIs = NULL;
            }
            return false;
}

if (process_check())
{
            return 0;
}
Results: Virus Total (16/67), Hybrid Analysis (100/100) and Metadefender (4/31).  

Ram Size:

This will check to see the total amount of installed RAM on the system. If it’s less than four gigabytes, exit.
bool ram_check() {
            MEMORYSTATUSEX status;
            DWORD max = 4294496257; // 4GB
            status.dwLength = sizeof(status);
            GlobalMemoryStatusEx(&status);
            if ((DWORD)status.ullTotalPhys < max) {
                        return true;
            }
            return false;
}

if (ram_check())
{
            return 0;
}
Results: Virus Total (4/67), Hybrid Analysis (11/100) and Metadefender(1/31).

Recent Files:

This will check the total number of recent files under the current user’s home directory. If the total number is less than five, exit.
int file_count = 0;

DWORD dwRet;
LPTSTR pszOldVal;
pszOldVal = (LPTSTR)malloc(4096 * sizeof(TCHAR));
dwRet = GetEnvironmentVariable("USERPROFILE", pszOldVal, 4096);

std::string stdstr = pszOldVal;
stdstr += "\\AppData\\Roaming\\Microsoft\\Windows\\Recent\\*";

LPSTR s = const_cast<char *>(stdstr.c_str());

WIN32_FIND_DATA data;
HANDLE hFind = FindFirstFile(s, &data);

if (hFind != INVALID_HANDLE_VALUE) {
           do {
                       file_count++;
           } while (FindNextFile(hFind, &data));
           FindClose(hFind);
}

if (file_count < 5) {
           return 0;
}
Results: Virus Total (15/67), Hybrid Analysis (17/100) and Metadefender (6/31).  

Disk Size:

This will check the total amount of disk space on the installed system. If it’s less than 60 Gigabytes, exit.
HANDLE drive;
BOOL result;
GET_LENGTH_INFORMATION size;
DWORD lpBytesReturned;

drive = CreateFile("\\\\.\\PhysicalDrive0", GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, 0, NULL);
if (drive == INVALID_HANDLE_VALUE) {
            // Someone is playing tricks. Or not enough privileges.
            CloseHandle(drive);
            return FALSE;
}
result = DeviceIoControl(drive, IOCTL_DISK_GET_LENGTH_INFO, NULL, 0, &size,
         sizeof(GET_LENGTH_INFORMATION), &lpBytesReturned, NULL);
CloseHandle(drive);
if (result != 0) {
           if (size.Length.QuadPart / 1073741824 <= 60) /* <= 60 GB */
                    return 0;
}
Results: Virus Total (13/67), Hybrid Analysis (100/100) and Metadefender (5/31).  

Overall Results:

Virus Total:

Table part 1/2

Table part 2/2

Hybrid Analysis: MetaDefender: Total Test: Conclusions: First off, we can tell that there are many products out there that do a great job of detecting anti-sandbox techniques. Granted, many of these techniques on their own are not malicious, so every instance where our payload is flagged as being bad is really a false positive. That said, this information does allow us to narrow down on what techniques are, in their simplest form, effective in reducing detections. Using the information above, I can see that using sleep, AV/EDR process names and disk sizes by themselves will most likely get us flagged. As an attacker, I could spend time focusing on reducing the detection rate there, but instead, why not focus on something like domain membership, Uptime, or Ram Size? If you’re interested in testing your own anti-sandbox techniques, I’d recommend checking out the examples at CheckPlease.