How malware evades sandboxes with Visual Basic

Every day at JSOC CERT we encounter events from various sandboxes that function as part of our customers' AntiAPT solutions and let thousands of files from web and email traffic pass through them. It is worth noting that modern Sandbox-systems in their development went much further than simply intercepting system calls in Kernel Mode and API functions in User Mode. Increasingly, they use their own hypervisor, a system for emulating user activity, dynamic instrumentation, hashing and clustering over sections of code, analysis of code coverage, etc. Such a variety of technologies creates the illusion that if a file does not work in the sandbox and does not show its “true face”, then this is probably APT or an innovative technology for detecting a virtual environment, which the IB community is not yet aware of. But…









Since we do not know the internal features of the work of commercial sandboxes, in some cases we do double-check - manually analyze the samples that passed the test. Recently, we have come across several times that some commercial sandboxes (for objective reasons, we cannot say which ones) did not detect certain malicious files during dynamic analysis, and if the static analyzer was also silent, the file was skipped altogether.



The sandbox scan managed to bypass such well-known malware families as Pony, Loki and Hawkeye. Only one thing united them - they were covered by a packer written in Visual Basic.



Given that these HPE families have long been nothing new, the “positive” sandbox verdict is very depressing. Therefore, we decided to describe the general principle of operation of this packer and the observations made by us over some time.

The general scheme of the packer’s work is conditionally divided into 4 stages and is shown in the diagram below.







The entry point of the malicious file looks typical for Visual Basic applications:







We met different variants of this packer, and the VB Wrapper code changed frequently, but the task performed remained the same: transfer control to the Stage 1 code. In earlier samples, control was transferred using the Enum * class API functions (for example, EnumWindows, EnumCalendarInfo, etc.). e) for which the address Stage 1 of the code was indicated as a parameter. Recently, we observe that control is transferred directly.



Stage 1



The control receives the code Stage 1. This code is not encrypted, but obfuscated. Obfuscation methods vary from sample to sample, but the general operation algorithm does not change:



  1. A cycle with many (including garbage) instructions, which generates the key necessary for decoding the Stage 2 code. The peculiarity of this code fragment is that there are no Sleep-functions, but due to the large number of iterations, its execution takes an average of 1-2 minutes.
  2. Decryption (regular XOR) and transfer of control to Stage 2 code.


The screenshot below shows examples of obfuscation methods used:







2 stage



The main task of the code in Stage 2 is to check the environment and implement anti-debugging methods. Some sections of the code are encrypted (decrypted before execution, and after that, encrypted back with the same XOR algorithm) to make it difficult to detect by signatures. After decryption, the characteristic features are visible, according to which the Stage 2 code can be recognized by manual analysis.







The list of checks is quite large and differs in different versions of the packer, so we will give several methods that were found in all versions, with screenshots, and at the end we list the entire list in the table.



1) GetTickCount + Sleep



The current timestamp is taken, Sleep is called for 2 seconds, after which another timestamp is immediately taken.



After that, the difference between the marks is checked (whether 2 seconds actually passed).







2) SetErrorMode



Checks the correct operation of the SetErrorMode API call. The function is called twice in a row with parameters 0x800 and 0x0, after which the result of the second call is checked: it must be equal to 0x800.







3) SetLastError



First, SetLastError is called with the parameter 0x5, after which it is checked that the value of Last error code in the TEB is correctly set (that is, it is 0x5).







4) Checking cursor movement



The code enters an endless loop waiting for the mouse to move.







5) DbgBreakPoint and DbgUiRemoteBreakin



These functions are modified to prevent the debugger from connecting to the process.





Technics







A comment







GetTickCount + Sleep







Checking Timestamps







SetErrorMode







Checking the function is working correctly







SetLastError







Checking the function is working correctly







GetCursorPos







Check cursor movement







Dbgbreakpoint







Modification of the function to prevent debugger attachment







DbgUiRemoteBreakin







Modification of the function to prevent debugger attachment







Hook deletion







The first 5 bytes of functions are restored in ntdll.dll in case there are hooks







NtSetInformationThread







Parameter 0x11 (ThreadHideFromDebugger)







GetThreadContext + check DR







Debug registers DR0-DR3, DR6, DR7 are checked.







Check breakpoints







The instructions INT3 (0xCC), int 3 (0xCD 0x03) and ud2 (0x0F 0x0B) at the beginning of some functions are checked







cpuid (EAX = 0x0)







Registers EAX, ECX, EDX are checked







cpuid (EAX = 0x40000000)







Registers EAX, ECX, EDX are checked







cpuid (EAX = 0x1)







31st ECX bit checked







PEB (BeingDebugged)







Checks value 0x1







PEB (NtGlobalFlag)







Checked value 0x70







NtQueryInformationProcess







Called with the flags ProcessDebugPort (0x7), ProcessDebugFlags (0x1F), ProcessDebugObjectHandle (0x1E)







Process name check







The strings “sample”, “sandbox”, “virus”, “malware”, “self.” Are checked









If all the techniques of stage 2 are completed, the command line is checked for compliance with the special format. If the verification fails, the following actions are performed:



1) The CreateProcess function is called with the CREATE_SUSPENDED flag to restart the current process. In this case, the command line has the required format.

2) Using the GetContextThread and SetContextThread functions, the entry point is changed to a new one, which is located in the Stage 1 code.

3) Repeat steps 1 and 2 (including a long cycle and all checks). This time, the command line check is successful and the process proceeds to the next step.



3 stage



At this stage, the body of the main virus is decrypted and the Process Hollowing technique is performed on the current process, after which control is transferred to the entry point of the main virus.



Lesson learneded



We cannot say for sure that in this case it causes problems for a particular sandbox, but I want to believe that the ability to use the malware described in the article has long been provided by vendors, and the problem lies only in the long time delay at the first stage of the packer’s work .



Despite the fact that modern sandboxes are for the most part positioned as part of protection systems against APT attacks, our observations suggest that even malicious families well known to the community penetrate the infrastructure with enviable constancy. Since there are no guarantees that the sample that bypassed the sandbox will not have a couple of antivirus bypass techniques in its arsenal, you cannot rely only on this bunch of protective solutions. In such cases, a properly constructed monitoring process, including information security events from end hosts, can ensure timely response and minimize potential damage.



All Articles