Unveiling Crypto Miner’s Stealthy Tactics: The Rise of Indirect Syscalls for Evasion

Recently we got our hands on a set of samples which had a big data section with high entropy and had fake executable information like WinRar, Chrome, CustomRP, etc. Out of curiosity we analysed one but we weren’t able to find any interesting Win32APIs used by this sample. When we reversed the sample we came to know that it was using Indirect Syscall.

Indirect Syscalls aren’t new, it’s been around for a while which is mainly employed by offensive teams and some C2 frameworks, but we have seen a gradual growth in this technique being used by common malware and loaders. 

The main objective of this technique is to bypass any user mode hooks placed by security products. Security products usually place their hooks in the DLLs like kernel32.dll, ntdll.dll to monitor the arguments passed and decide if it is malicious or not after doing a set of checks.

The sample we obtained even had an invalid WinRAR certificate, with fake binary properties. In this blog we are going to cover this stager rather than focusing on its Coin Miner payload. 

Fig 1. File Info

Indirect Syscall Implementation

In the sample we observed an abnormally higher number of recurrent mov call instructions as depicted in Fig 2,

Fig 2. List of recurrent MOV CALL instructions

As seen in the image all the calls correspond to the same function. Upon further analysis we found that the function itself had three more calls to other functions as depicted in Fig 3 which does the following,

  1. The first call contains code to obtain the SSN (System Service Number) for a particular NTDLL API
  2. The second call contains code to obtain the Syscall address in the NTDLL
  3. The last call is made to a global variable which now holds the Syscall address obtained from the previous call.
Fig 3. Syscall wrapper function to parse and invoke a Sycall

Now let’s look into each of these functions in detail

Finding the SSN of the required API in NTDLL

Instead of calling LoadLibrary and GetProcAddress to find the required API’s address, this malware instead parses the PEB of the current process like most of the malwares out today.

It is interesting to note here that this malware tries to evade pattern based signatures by obfuscating the constants. For instance, for accessing the PEB, malware generally uses the special purpose register gs:[60] for 64bit which will be monitored by certain EDR products. This malware does the same but instead of fixing values like 60, it rather does this by the instruction gs:[eax] where the eax value of 60 will be obtained by a hardcoded value passed into a function (i.e add with 0x439B5D18 as depicted in Fig 4.). It will then be added with another hardcoded value (i.e 0xBC64A348 as depicted in Fig 4.) resulting in the required value. This technique of addition will be employed elsewhere within the malware wherever a hardcoded value needs to be obfuscated.

Fig 4. PEB parsing the Loader Data(LDR_DATA)

It first tries to find the NTDLL entry in the InMemoryOrderModuleList from the LDR_DATA. This contains the list of loaded modules for the current process. This is a doubly linked list, where each list points to a structure LDR_DATA_TABLE_ENTRY which inturn would hold the necessary information for the loaded module like its DllBase, FullDllName, etc.

For each loaded module found in the list, the malware parses the export directory and checks if the export dll name is ‘ntdll’. Once a match is found it then proceeds to parse all the APIs starting with ‘Zw’ from the export directory which are considered as the lowest level of the user mode Win32Apis.

For each API found it will hash 2bytes(WORD) with a constant value that was discussed previously and ROR24 with the resultant.

Fig 5. Hashing code for Zw APIs

The values obtained for each API would be stored in the structure shown below:

Struct findSSN {

DWORD Hash_of_API;

QWORD RVA_of_API_in_NTDLL;

}

These structures would be sorted in an ascending order of their RVA_of_API_in_NTDLL, after this the Hash_of_API from the above structure will be compared with the hardcoded hash for each of the hash from the structure and a counter will be used to track the number of structures parsed.

Fig 6. Comparing hardcoded hash to get SSN

If there is a hash match the counter value is considered to be the SSN number for that API. To note here in NTDLL the SSN will be allotted for each Nt/Zw APIs in the order of their occurrence. The returned SSN will be stored in a global variable.

This technique is similar to the Syswhisper3 technique that is available in the wild. It is to be noted that popular C2 framework tools use this same technique to call native APIs without getting caught in the hooks placed by security products.

Finding Syscall Instruction’s Address in NTDLL

Once the structure is created, the RVA of the first Zw function is obtained from the structure and adds this RVA to the dllbase then goes to that specific location in memory where it will continuously parse the instructions to check if it matches 0x0F05 (syscall instruction) until 0x40 bytes if not found it moves to the next function’s address and repeats the process.

If the syscall instruction is found it will take the number of bytes parsed as the offset to and considers the syscall instruction will be in that offset for all other APIs in NTDLL.

The interesting part here is that instead of getting the syscall address from the respective API in NTDLL there is another code segment which will return the syscall address from random API. The code mainly uses a counter and a hardcoded value as address for this random calculation. 

Fig 7. Code responsible for getting random syscall address

This is mainly done to spoof the ETW tracing functionalities of EDRs, this technique is also similar to the idea shared by @Elephantse4l.

Call to the obtained Syscall instruction

Now, the malware has both the SSN and Syscall address needed to invoke the needed API in ntoskrnl, as depicted in Fig 3. it will first move the SSN into EAX register then a call is made directly to the global variable where the obtained syscall address has been stored.

The thing to note here is that an EDR tracing the return address from ntoskrnl would be fooled because the return address will be pointed to the random API in NTDLL while in reality the SSN number for the needed API is already passed to the kernel in EAX register and the ntoskrnl will execute the API for which the SSN number is mapped to.

Now a question may arise. What about the parameters needed by the APIs how are they transferred to these API functions if they are directly called using syscalls, to answer the question let’s take the first call made by the malware through indirect syscall approach which is calling the ZwCreateMutant to create a named object Mutex.

Fig 8. Parameters for ZwCreateMutant

From the Fig 8 we can see that the parameters are hardcoded for the APIs. In some cases, the APIs may need some values which are dynamic in nature like the handle and attributes for the above API, in those cases it would be supplied using the stack. 

Fig 9. Creation of Mutant

Now back to where we have seen the consecutive MOV CALL instruction as depicted in Fig 2. Once the syscall has been executed it will return back to the next mov call instruction which will get executed with wrong parameters (the old parameters pushed into register or stack) if the flow is not diverted.

For this reason, as depicted in Fig 2, the malware pops out the return instruction whenever there is a call to the indirect syscall function, then inside this function before the return instruction it will push the return address of the function which called this indirect syscall function. Now the flow is diverted and the next intended code would run.

HashAPI
0x5e1a9239ZwCreateFile            
0xf6f1359fZwWriteFile             
0xc190a439ZwReadFile              
0x77e8b9adZwDeleteFile            
0x3b9c3639ZwClose                 
0x66d5968bZwOpenFile              
0x99dbd2f4ZwResumeThread          
0x64cdc2bcZwGetContextThread      
0xd338302eZwSetContextThread      
0xfb0cbd23ZwAllocateVirtualMemory 
0xca8ceca2ZwWriteVirtualMemory    
0xd7e42b97ZwFreeVirtualMemory     
0xa90b0813ZwDelayExecution        
0xf34b5b75ZwOpenProcess           
0x43b25659ZwCreateUserProcess     
0x5dc3dce4ZwOpenProcessToken      
0x644ef57dZwWaitForSingleObject   
0x110c64cdZwQueryInformationFile  
0xbbdb50a4ZwCreateMutant          
0x4f404ecaZwAdjustPrivilegesToken 
0xc2d671cbZwQuerySystemInformation
0xd4267d98ZwQueryInformationToken 
0xae4206ebZwOpenKey               
0x90a59f02ZwCreateKey             
0xaa18c1cbZwEnumerateKey          
0xebe0f679ZwQueryValueKey         

The above table shows the list of APIs that will be used by this malware using Indirect syscall, the hash value may change from one stager to another but the API remains the same.

Obfuscation of Strings

The malware primarily uses the XMM registers for deobfuscating its strings from the .data section. This is done as:

1.Copying the obfuscated string’s bytes into XMM1 register and adds it with repeated 0xCD00CD00 bytes (to note this value may change for other string deobfuscation)

2.The resultant value will be then AND with 0xFF00FF00 bytes.

Fig 10. Deobfuscation of strings snippet

Diving into the Initialization stage

The malware uses its data section to hold the deobfuscated configuration strings as global variables. The below image shows the interesting deobfuscated strings which will be used in the later part of its execution.

Fig 11. Deobfuscated Strings

The strings SYSTEMROOT= , APPDATA= , TEMP= were used to find the system reserved paths, these strings will be compared with the system information structure obtained by calling ZwNtQuerySystemInformation. These paths will then be used to create a new suspended process from Windows root directory, drop a self-copy in AppData, and drop Winring0.sys file in Temp folder (which will later be used by the Coin Miner). 

It uses ZwCreateUserProcess for creating new process using strings like “powercfg.exe /x -hibernate -timeout -ac 0, and -standby -timeout -ac 0” to disable the hibernation of user machine for uninterrupted miner activity. It also executes reg.exe, sc.exe for setting up persistence which is discussed later in this blog. We can observe the execution of other executables depicted in Fig 15.

Payloads

This malware acts as a stager to the Coin Miner while analysing the data section of this stager we found a huge chunk of encrypted data. Digging deeper revealed that there was not just one but two payloads, one is a second stager and the other one is the Coin Miner

Fig 12. Coin Miner Information

The Coin Miner is an XMRig miner which is packed using UPX. In the data section both the payloads are in an encrypted form which are decrypted similar to how the strings were decrypted but will be added with different hex values, once done it gives an output of base64 string which will then further XORed with a key (the key itself will be in an encrypted form in data section) depicted below,

Fig 13. Resolving XOR key and decrypting the payload

PE Injection

Once the Coin Miner payload is decrypted a new process of svchost.exe is created in a suspended mode using the ZwCreateUserProcess API, a new virtual space will be created in the remote process at the location 0x140000000 using the ZwAllocateVirtualMemory where the decrypted Coin Miner payload is injected using ZwWriteVirtualMemory, it then overwrites the ImageBase value in the PEB of the remote process to point to 0x140000000 and then uses ZwSetThreadContext to change the thread entry to the newly injected Coin Miner’s entry point. Once all is done it uses ZwResumeThread to initiate the malware.

Fig 14. Injected code in svchost.exe

Persistence

Persistence is achieved using three methods: creating a Run registry, creating a service entry, and finally persistence via memory.

The malware copies itself to the user’s %appdata% folder and then adds a registry entry pointing to the self-copied executable as depicted in Fig 11.

If it’s given elevated privileges it will create a service C:\Windows\system32\sc.exe create “EHKHFYFV” binpath= “C:\ProgramData\diofjoocysky\zeuxyrjalpyd.exe” start= “auto” 

Fig 15. Creating persistence and other malicious activities

The second stager, mentioned earlier, will be injected in a similar manner into a newly created process of conhost.exe. This injected second stager will be responsible for keeping the main stager safe in the memory.

This is achieved by reading the dropped main stager contents from the %appdata% and holding it in the injected process’s memory, so whenever the malware is deleted manually from %appdata% location, the injected stager will drop the main stager back in the same location from its memory.

Fig 16. Injected conhost.exe

To note here the stored stager will be encrypted using the same algorithm which was used to encrypt the other payloads in the main stager. This is mainly done to bypass the memory scanners implemented by EDRs.

From what we have seen between the two stagers, is that both of them look similar on how they invoke an API. The only difference is the constant, XOR key, hash values. We believe that this is some kind of an unknown loader created using a builder. We have also come across similar samples which only differ by the condition above.

The adoption of indirect syscall execution by recent loaders such as pikabot, as well as its use by unwanted programs like Coin Miners, indicates a trend towards stealthy access. This shift implies that we should anticipate an increase in malware variants that quickly adopt these techniques for evading security products.

IOCs

HashDetection Name
64B2AC072D69299CA063190DE16D3230(Stager)Trojan (005ae0191)
31038BE145328B898BA4084AA1170CB0(Stager 2)Trojan ( 005af2041 )
C52DB71D016305B83F064D7199C2C57B (Coin Miner)Adware (005300251)
D7f2CF68AEFA2FF0DA96560E2D389EE9(Stager)Trojan ( 005af85d1 )
58DA2EC09E4E7F099E3A049032AA6775(Stager)Trojan ( 005af85d1 )
CBD74C3B2BEBA60CC6586FA720D5408F(Stager)Trojan ( 005af85d1 )