Analysing a low-prevalence loader written in Go

No language is safe from malware authors and Golang is no exception. In this blog post, I'll cover a low-prevalence loader that has not been publicly reported on to my knowledge. It displays a message-box about an updated version of Java, but downloads and executes a payload behind the scenes.

The loader arrives as an MPRESS-packed 64-bit PE binary and can be executed normally. According to this Reddit post, this loader was distributed to victims via malicious advertisements. Unpacking this binary by following this swell guide presents us with a Golang binary.

Using GoReSym on the unpacked binary recovers a lot of the original metadata about the binary, including the address of the original main function. While Go malware can sometimes be cumbersome to reverse-engineer, I was able to recover much of the symbol information using the PCLNTAB alongside SentinelOne's AlphaGolang scripts

The following files were examined during analysis:

FilenameSubmission Date
Setup_Chrome-394010981.exe2023-09-05
Java_Edg-1207792131.exe2021-09-27

The following file is likely another sample of this loader, but I was not able to download it:

FilenameSubmission Date
JavaPlayerChrome-1157890851.exe2020-08-21

String Decryption

 

Although the loader is not particularly obfuscated, it does make use of the gobfuscator library to encrypt its static strings.

Writing an automatic string decryptor for this is relatively straight-forward. We can make use of the fact that this pattern of encryption calls the runtime:slicebytetostring() function to return the decrypted contents. Let's use a YARA-rule to identify this runtime function in the binary and trace any references that are made to it.

    import yara

    yara_rule = """
    rule runtime_slicebytetostring
    {
        strings:
            $chunk_1 = {
                48 83 F? 01
                7? ??

                [0-150]

                48 85 C?
                74 ??
                48 83 F? 20
                7? ??

                [10-150]
                E9 ?? ?? ?? ??
                CC CC CC CC
            }

        condition:
            any of them
    }
    """

    # Compile the YARA rule
    compiled_rule = yara.compile(source=yara_rule)

    # Scan the bytearray with the compiled rule
    matches = compiled_rule.match(data=data)

We can then work backwards from the call instruction to extract the original one-time pads and decrypt the strings ourselves:

I have uploaded the string decryption script to GitHub. The 2021 sample of the loader starts off by contacting several hard-coded .local domains, though with no apparent purpose. The newer sample from 2023 no longer exhibits this behavior.

Next, it shows the Java pop-up message to the user:

At the same time, the main loader functionality is executed. 


Evasion

 

First, the loader extracts its own filename and attempts to execute a regular expression against it, similar to the below pseudocode. 

import sys
import re

binary_filename = sys.argv[0] # C:\Users\Donald\Downloads\go-loader(sample).exe
binary_filename_stem = binary_filename.split("\\")[-1].split(".exe")[0].split("-")[1]
binary_filename_stem = re.sub(r"\([^()]*\)", "", binary_filename_stem) # loader

if (len(binary_filename_stem) > 0):
    execute()
else:
    "System failed"

Assuming that the loader's filename is of the right format, it then performs rudimentary anti-VM checks and attempts to contact any of its pre-configured C2 domains.

The anti-VM checks include enumerating over the registered display adapters and reading the DriverDesc and MatchingDeviceId values to compare their contents against the following hard-coded driver and vendor names:

StringVendor
VEN_8086Intel
VEN_10DENVIDIA
VEN_1002AMD
VEN_102BMatrox
VEN_1A03Aspeed
DISPLAY 
IDDCX 
DGLVRKDOD 
IDDBUS2 

These are legitimate device vendor IDs for display adapters. If the values on the host machine do not match any of the listed vendors, the payload will not execute any further. For reference, a typical VMWare analysis machine will contain the string VEN_15AD whereas QEMU may contain VEN_1234, neither of which are contained in the list.

In addition, the loader attempts to open the HKLM\SYSTEM\CurrentControlSet\Control\Session Manager\Memory Management\PrefetchParameters registry key and read the BootId value. If this value does not exist or it is less than 5, the loader will refuse to execute. According to this answer on StackOverflow, the BootId value specifies the number of times that a Windows machine has rebooted. It would follow that a detonation machine might periodically restore a fresh snapshot and thus fail this check.


Communication

 

If the host machine is suitable for execution, the loader begins to contact its pre-configured addresses in search of an active C2 server. It does so by attempting to retrieve the file hosted at https[:]//<C2_URL>/robots.txt and refusing to execute if no such file is hosted at the server or if the file is empty. 

  • api.maxiscn[.]com
  • api.mx-share[.]com
  • api.mx-disk[.]com
  • api.octabx[.]club
  • api.oclabxc[.]xyz
  • api.kakz[.]info
  • api.4share[.]icu
  • api.picoh[.]xyz
  • api.mylinks[.]pw
  • api.octavity[.]xyz

Next, the loader will attempt to send a GET request to the address https[:]//<C2_URL>/app/1 in order to read a string from the C2. If this string is not equal to "1", the loader will refuse to execute.

If a suitable C2 server is found, the loader will execute its main functionality and download a payload. To achieve this, the loader will attempt to download a file at https[:]//<C2_URL>/p.txt from a separate list of pre-configured C2 servers.

  • secured.mx-share[.]com
  • secured.mx-disk[.]com
  • secured.octabx[.]club
  • secured.oclabxc[.]xyz
  • secured.kakz[.]info
  • secured.4share[.]icu
  • codec.picoh[.]xyz
  • codec.mylinks[.]pw
  • codec.maxiscn[.]com
  • codec.octavity[.]xyz
  • 185.4.67[.]104
  • 130.0.232[.]145
  • 130.0.235[.]240
  • 188.116.25.241
  • 5.61.39.128

The content of this file is validated through an arithmetic expression. An example number that passes this validation for the 2021 sample is 24.

If this file exists and its contents can be validated by the loader, the C2 domain is selected for further communication. Next, the loader will attempt to download a payload from the C2 server using a pre-configured filename at the following URL constructed during runtime:

  • https[:]//<C2_URL>/update/SearchIndexer.exe

After successfully downloading the payload, the loader will attempt to verify its MD5 checksum by comparing it against the content of a text file hosted at the following URL:

  • https[:]//<C2_URL>/update/SearchIndexer.exe.txt

Finally, the loader will write this file to disk and execute it with a few command-line parameters:

  • The extracted loader name from before
  • A hard-coded float value 0.50
  • A hard-coded argument --ayooe

Following either successful or unsuccessful execution of the payload, the loader deletes itself using cmd.exe /C del "<loader_path>":


Payload

 

Based on several unobfuscated field names in the binary, it's likely that the payload was intended to be a bitcoin miner of some sort. 

  • config->MinerExeName = "SearchIndexer.exe"
  • config->CoinApiId = "1"
  • config->CoinSuffix = ""

In the sample at the center of this analysis, the "upx" configuration option was enabled rather than the "trtl" configuration option. The impact of this option changes the URL of the downloaded payload, which may indicate that the payload is expected to be either upx- or trtl-compressed.

Searching for files that match this description on various malware analysis platforms, this file stands out as a potential match. While the C2 infrastructure could not be contacted during analysis, it appears that this loader would have been used to serve an XMRig instance.


Indicators of Compromise

 

The following host IOCs were observed during analysis:

PathMode
C:\Windows\Temp\SearchIndexer.exeWrite
HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Session Manager\Memory Management\PrefetchParametersRead
HKEY_LOCAL_MACHINE\SYSTEM\ControlSet001\Control\Class\{4d36e968-e325-11ce-bfc1-08002be10318}\0000Read
HKEY_LOCAL_MACHINE\SYSTEM\ControlSet001\Control\Class\{4d36e968-e325-11ce-bfc1-08002be10318}\0001Read

The following network IOCs were observed during analysis:

URL
secured.mx-share[.]com
secured.mx-disk[.]com
secured.octabx[.]club
secured.oclabxc[.]xyz
secured.kakz[.]info
secured.4share[.]icu
codec.picoh[.]xyz
codec.mylinks[.]pw
codec.maxiscn[.]com
codec.octavity[.]xyz
185.4.67[.]104
130.0.232[.]145
130.0.235[.]240
188.116.25[.]241
5.61.39[.]128
api.maxiscn[.]com
api.mx-share[.]com
api.mx-disk[.]com
api.octabx[.]club
api.oclabxc[.]xyz
api.kakz[.]info
api.4share[.]icu
api.picoh[.]xyz
api.mylinks[.]pw
api.octavity[.]xyz
bull.mx-share[.]com

YARA rules

 

rule go_loader {
    meta:
        description = "Matches a Golang loader that displays a Java pop-up"

    strings:
        $config_coin_1 = "CoinApiId"
        $config_coin_2 = "CoinSuffix"
        $config_domain_1 = "APIDomains"
        $config_domain_2 = "SecuredDomains"
        $config_name_1 = "InstallerName"
        $config_name_2 = "MinerExeName"
        $config_path_1 = "TempPath"
        $config_path_2 = "System32Path"
        $config_path_3 = "InstallPath"
        $config_version_1 = "AppVersion"
        $config_domain_3 = "PostbackDomain"

        $mz = "MZ"

    condition:
        $mz at 0x0 and 8 of ($config*)
}
rule go_loader_insn {
    meta:
        description = "Matches a Golang loader that displays a Java pop-up"

    strings:
        $insn_p_txt_validation = {
            48 B9 AB AA AA AA AA AA AA AA
			48 0F AF C8
			48 B? A8 AA AA AA AA AA AA 2A
			48 01 ??
			48 C1 C? 3D
			48 B? AA AA AA AA AA AA AA 0A
			48 39 C?
        }

        $insn_compare_rax_five = {
            48 83 F8 05 
            7?
        }

        $insn_http_timeout = {
            48 B? 00 50 5C 18 A3 01 00 00
            48 89 ?? 28
        }

        $mz = "MZ"
    condition:
        $mz at 0x0 and all of ($insn*)
}