Revisiting the latest version of Andromeda/Gamarue Malware

Andromeda Malware aka Gamarue Malware has been prevalent since it came into limelight a couple of years ago. Also, the author keeps it well updated ever since. With respect to its earlier avatars, it has gone through several changes from anti-analysis to a change in protocol format. Some excellent write-ups have already been made on it [1][2] previously, but in this blog we will revisit and analyze the latest version.

Andromeda-Gamarue hides itself though many layers and its default one.

Since its inception it has made use of many techniques to defeat extraction of embedded configuration (url, keys, etc.), such as using a fake encryption, fake urls, config encryption and many more.

Meanwhile we also found a sample which had obfuscation techniques such as opaque predicates to hinder static-analysis.



Andromeda-Gamarue consists of two payloads, a default unpacker and a main payload. We are going to cover both in this post.

It starts with loading up some of the native functions identified by hashes using a simple hashing algorithm and stores the API address in stack variables.



To get a basic overview of the binary we will generate a run time dynamic call graph to help us understand the functionality to some extent.


It shows some calls to LdrProcessRelocateBlock(), which gives us an indication about where and how the payload is unpacked. The binary consist of a data blob in the .rdata section of a PE file which holds information regarding the unpacked payload. It has the following structure:


The integrity of the payload is checked against a hard coded crc32 hash value and, if the hash is verified, it further proceeds to decrypt and decompress the payload using a 16 byte rc4 key and APLIB decompression. This chunk is copied to an allocated heap region which is purposely created by using MEM_COMMIT or MEM_TOP_DOWN, which might be used to bypass some scanning engine or dumpers.


The base relocations are applied on that memory region using the RelocationTableOffset field.


Another block of executable memory region of size 1000h is allocated, which will later on be used for copying stolen API code. Then, Dll and Imports are parsed. Dll names can again be found as hashes.

The first instruction is copied from an API location to this particular memory region and a succeeding jump is placed after that to the original instructions. This is done to bypass API hooking. It consists of an x86 instruction parsing subroutine.


Subroutine calls and unconditional jumps follow, subsequent instructions are copied and a jump to OEP is made.


Main payload

The main payload consists of an installer and a primary payload responsible for communicating to the command and control centre. Let’s take a look at the call graph of the installer part:


It starts by getting serial number for the root drive (which will later on be used as a part in the c2 request). It also has a function to check for the presence of certain processes and if they are found it goes in an infinite loop. These checks are bypassed if a registry key “is_not_vm” is found in HEY_LOCAL_MACHINE software\\policies. The key has to be equal to VolumeSerialNumber. An environment variable is created from xoring VolumeSerialNumber with 0x737263, which is assigned to the module file name. This environment variable acts as an indicator for the previous instance of binary. It also sets up an event named after xoring VolumeSerialNumber xoring with 0x696E6A63.


This payload is injected inside “msiexec.exe” by changing the entry point to push <base of injected code> ret and waits for the event to be triggered by the main payload.

The main payload nulls the packer PE headers and sections.


Following this, it adjusts the privileges, sets TaskbarNoNotification, and disables UAC, Windows Action centre, as well as some security related services (only if the “bb” parameter is not set). Explained below:


If necessary privileges are not found, it will try to elevate the privileges by using the “Runas” verb.


C2 servers are encrypted and stored using a crc32 hash of PE data and an incremental XOR value.

After that, it makes connection to each c2 with the following json request:


ID = VolumeSerialNumber

BID = botnetID

OS = OSVersion

LA = Local IP address

RG = isprivileged?

BB = islocalized (Russia, Ukraine, Belarus and Kazakhstan)

This request is encrypted using a 32 bytes rc4 key and the response is also decrypted using the same rc4 key (earlier versions would have used 4 bytes ID as a response key). The request also comes in a JSON format now. It consists of a json parser compiled from https://github.com/udp/json-parser/.

The return value from jsonparser is represented this way:


The above JSON structure is expressed in the following format:

[next_request_sleeptime (minutes) ,{Unimplemented_object}, [TaskID, RequestType, ‘URL’-N/A ]……]

The first item in the array is the next request sleep time. It is the time frame in minutes when next iteration of calling c2 is performed.

The second in the list is an unimplemented / unused type. When this object is found, it is simply skipped.

The rest are single or multiple arrays which may consist of a url payload. TaskID is the UID of a task provided by the c2 server. This ID is sent back in a following request. The request type is an identifier of the task type of an eg download url, plugin download or delete bot.


These urls can either be exe or plugins. Plugins are encrypted and compressed with RC4 and APlib. After completing the specified task, another request is sent back to the c2 server which has the following format:


ID: VolumeSerialNumber


ERR: Error Level on task completion (0 – no error starting from 0x10)

W32: Error Number from GetLastError()



Raashid Bhat

Malware Analyst

Demo Free Trial MSSP