What is the coinminer whispering to us? Simplifying SysWhispers2 Usage Analysis

Introduction

When analyzing malware aimed at cryptocurrency mining, it is most interesting to investigate exactly loaders (loaders or droppers) of miners than the miners themselves, since it is in the loaders that the techniques aimed at detour means of protection And counter detection.

So, analyzing sampleinvolved in the Monero miner distribution campaign, I stumbled upon a well-known yet interesting malware mechanism.

This mechanism is based on direct call to Windows OS system functions bypassing the use of the standard path through calling library functions kernel32.dll And ntdll.dll. This approach allows malware authors to bypass user-space hijackings of system functions by EDR agents, thus reducing the risk of malware detection.

One of the problems with implementing this approach is that for each OS version and build, the system call number may be different. A good resource to see these changes is table from j00ruwhich shows how the numbers for specific features change from version to version.

The syscol number is determined its location in ntdll.dlli.e. syscall with number 0 will be located at the smallest address in the .text section compared to other similar functions.

At the same time, all exported system functions in ntdll.dll are a wrapper over the syscall instruction (or int 2Eh interrupts), which initiates the execution of a system function at the kernel level:

An example of the contents of an exported function in ntdll.dll

An example of the contents of an exported function in ntdll.dll

Since the syscol numbers may differ in each OS version, the malware must either have complete list system calls for all versions of interest, or have another mechanismwhich helps to understand exactly which syscol numbers are relevant for this version.

After we have refreshed our knowledge of the technique of direct calling of syscols, we will proceed to the analysis of the sample itself, or rather, the mechanism of interest to us.

Analysis

Under analysis malicious sample At the initial stages, we meet the following function:

Function that uses syscall

Function that uses syscall

Which contains an instruction atypical for conventional software syscallbut since we are analyzing malware, something atypical may, on the contrary, be quite appropriate in this context of malicious functionality.

syscall accepts system call number from the register rax/eaxsince the preceding code does not see the entries in raxit looks like the system call number is returned by the function sub_4018f1 (let’s call her ResolveSysCallbecause it is pointed to by several xrefand we will definitely meet her again).

Inside ResolveSysCall it can be seen that the value of the argument in the loop is compared with the values ​​of some arrayand in case of a match, the index of the array element is returned, that is system call number.

Content of ResolveSysCall
Loop in ResolveSysCall

Loop in ResolveSysCall

Thus, it becomes unequivocally clear that this malicious module uses the technique of using system calls directly.

SysWhispers2

In ResolveSysCall, all the magic happens in the sub_4014b6 function, let’s rename it to ComputeSysCallsList and move on to its analysis.

In this function, we meet the following, yet not very clear, content:
The contents of the ComputeSyscallsList function

The contents of the ComputeSyscallsList function

Which will become more accessible after recognizing the structures used and renaming the corresponding variables.

Namely
Fragment 1

Fragment 1

Fragment 2

Fragment 2

In some places, due to the view of the decompiler, it becomes not entirely clear what exactly is happening in this section of code, but if you dive into the disassembled view in parallel, then such a mechanism as moving through PE64 format structures becomes more accessible:

PE header parsing section in disassembled and decompiled representations

PE header parsing section in disassembled and decompiled representations

Personally, I was not able to understand that the highlighted line of code was looking up the address of the export table until I looked at exactly what offsets were used in the native code. Here, once again, the old truth is confirmed that the decompiled representation helps when you need to analyze something quickly, but when studying some sections in detail, it is better to look into the disassembler window, not relying only on the decompiler.

From everything seen, it becomes clear that these are the components of the tool SysWhispers2. For example, very conspicuous pattern to search for functions based on a comparison with the characters “Zw”, which matches the same comparison section in the loop with this substring in the decompiled representation:

Similar sections of code in sources and decompiler

Similar sections of code in sources and decompiler

SysWhipsers2 solves the problem with differences in numbers in each version by analyzing the version loaded into memory ntdll.dll and compiling a table of correspondence of hashes of function names to their numbers, which is presented in the code above.

Apart from SysWhispers2 malware authors may use other versions or similar techniques (for example, Hell’s Gate or Halo’s Gate).

Now, having found the original project, you can view it to compare the results of your research of the relevant parts of the malicious module with the original code.

A bit of automation

Because SysWhispers2 may occur from time to time during the analysis of various modules, you can write a script to simplify the analysis of such malware, which will also calculate hashes names of all system functions and indicate the names of the functions in the appropriate sections. You can also add the compilation of a similar table of correspondence of siskol numbers to their names, which will suddenly come in handy in the future (especially since we already have the C source code for such logic).

Final script using IDAPy takes the following form (of course, if someone sees flaws in the code that they cannot put up with, I apologize):

Code
import idautils
import idaapi
import pefile


# вычисление хеша
def hash(name):
    position = 0
    # seed-фраза для каждой сборки своя
    seed = 0x7d895397
    while name[position]:      
        seed ^= (((seed << 24) | (seed >> 8)) + (int.from_bytes(name[position:position+2], "little"))) & 0xffffffff
        position += 1
    return(seed)

  
# поиск по номеру сискола соответствия между хешом и именем сискола
def get_func_name(hashes, syscalls, hash_value):
    syscall = list(hashes.keys())[list(hashes.values()).index(hash_value)]
    return(syscalls[syscall])

  
# объявляем словари: временный для полученных сисколов,
# словарь с упорядоченными сисколами,
# и словарь соответствия номера сискола хешу    
SysCallsTableTmp = {}
SysCallsTable = {}
SysCallsTable_hashes = {}


# возьмем ntdll системы, так как в каждой версии ОС
# номера сисколов могут отличаться,
# далее заполняем словарь именами сисколов и их адресами
pe = pefile.PE("C:\\Windows\\System32\\ntdll.dll")

for entry in pe.DIRECTORY_ENTRY_EXPORT.symbols:
    try:
        if b"Zw" in entry.name:
            SysCallsTableTmp[entry.name] = hex(pe.OPTIONAL_HEADER.ImageBase + entry.address)
    except:
        continue

        
# так как номер сискола прямо пропорционально соответствует его адресу
# (сискол с номером 0 будет расположен по самому наименьшему адресу),
# упорядочиваем новый словарь согласно адресам,
# то есть получаем непосредственно номера сисколов и хеши их имен.
# Если бы нам не нужны были номера сисколов, сортировкой можно не заниматься,
# а просто определить соответствие имен и их хешей
SysCallsTable_sorted = sorted(SysCallsTableTmp.items(), key = lambda syscall: syscall[1])


# допишем нулевой байт, чтобы это укладывалось в логику хеширования
for i in range(len(SysCallsTableTmp)):
    SysCallsTable[hex(i)] = SysCallsTable_sorted[i][0]
    SysCallsTable_hashes[hex(i)] = hash(SysCallsTable[hex(i)] + b"\x00")

    
# используем поиск всех вызовов функции поиска сисколов ResolveSyscalls
# по адресу 0x4018F1 в нашем случае, чтобы получить искомые хеши 
# и расставить комментарии с полученными именами функций
xrefs = XrefsTo(0x4018F1)
for i in xrefs:
    ea = prev_head(i.frm)
    if "ecx" in generate_disasm_line(ea, 0):
        name = get_func_name(SysCallsTable_hashes, SysCallsTable, get_operand_value(ea, 1) & 0xffffffff)
        set_cmt(ea, name.decode("utf-8"), 1)

After running the script, we will get the following view of the sections where siscols are used (with the name of the function that will be called):

Example 1

Example 1

Example 2

Example 2

Thus, we have received a small toolwhich allows using SysWhispers2 by the hash of the syscol name, determine what kind of system function will be called. Now, if we again come across a sample that uses SysWhispers2we will be able to understand the functionality of the analyzed malicious module much faster.

Similar Posts

Leave a Reply

Your email address will not be published. Required fields are marked *