cat _posts/2021-03-09-xcry-ransomware-en.md

malware analysis

Analysis of the XCRY ransomware written in Nim

XCRY is a 32-bit ransomware family written in Nim. It was publicly reported by MalwareHunterTeam.

VirusTotal

SHA-256: e32c8b2da15e294e2ad8e1df5c0b655805d9c820e85a33e6a724b65c07d1a043

Download sample

The sample is not packed and contains extensive debug information. The ransomware is named after the extension it adds to encrypted files.

There is already a brief description of the sample, but I will examine it in more detail. Debug information left in the binary gives us readable function names. This helps us understand the structure of compiled Nim programs and recognize important functions in later samples that lack debug symbols. The image below compares XCRY with another Nim sample compiled without debug information and shows the similarities between their functions.

For information on reducing the size of Nim binaries, see this article.

The PreMainInner() function initializes the libraries.

Among them is nim-libsodium, a wrapper around libsodium that provides the ransomware’s cryptographic primitives.

PreMainInner() is especially useful because it reveals the actual imports.

Nim uses a custom string type called NimString. These strings are dynamically sized, include a length field, and are null-terminated. A 0x40 byte also appears immediately before the string data.

Nim strings can be allocated as objects with newObjRC1

or directly on the heap.


proc boehmAllocAtomic(size: int): pointer {.

  importc: "GC_malloc_atomic", boehmGC.}

Nim programs resolve function addresses dynamically. nimLoadLibrary is a wrapper around LoadLibrary.

Here, add ecx, 8 advances to the actual kernel32 string at offset 8 in the object shown above.

The sample uses the Winim library to call Windows API functions directly. OffensiveNim provides examples of its use.

Much of this code is generated by the compiler, so the static import table provides an incomplete picture. This may make Nim attractive to malware authors. For example, CreateProcessW is absent from the imports even though the sample resolves and uses it at runtime, as it does FindFirstFile for file traversal.

To avoid running twice, XCRY checks whether lock_file exists. It creates this file after completing its work.

XCRY contains a public key used by libsodium. The key is stored in memory as a string and converted to a byte array with libsodium’s hex2bin function.

On startup, XCRY copies itself into APPDATA under a random name.

The name is generated with libsodium’s randombytes function.

Interestingly, the ransomware does not enumerate connected drives. Instead, it uses a hard-coded list of possible drive letters beginning with Z, which is commonly assigned to network drives.

Each detected drive is encrypted in a separate thread created with Nim’s spawn function.

Files are encrypted with XSalsa20, which uses a 192-bit nonce instead of Salsa20’s 64-bit nonce. First, crypto_stream_keygen generates a secret key.

The key is then encrypted with the embedded public key using crypto_box_seal.

The resulting value is 0xA1 bytes long.

The sample then passes this value to crypto_shorthash, producing a 16-byte ShortHash.

It constructs a record with the following format:

Short Hash + space + private key + byte 0x0A.

This record is saved as encryption_key in the APPDATA directory.

The attackers ask the victim to provide this file before paying for decryption. Interestingly, XCRY generates a new secret key for every 1,000 (0x3E8) files and appends each key record to encryption_key.

Below is a Kaitai Struct definition for the encryption_key file.

meta:
  id: xcry_encryption_key
  file-extension: xcry_encryption_key
seq:
 - id: enc_key
   type: enc_key_struct
   repeat: eos
types:
  enc_key_struct:
    seq:
     - id: short_hash_key
       size: 16
     - id: inner_delimeter
       size: 1
     - id: private_key
       size: 0xa0
     - id: delimeter
       size: 1

Each victim file is read and encrypted in chunks of 0x2710 (10,000) bytes.

The main encryption function is crypto_secretbox_detached, which uses the previously generated random key.

The corresponding ShortHash is inserted at the beginning of each encrypted file so that the decryptor can select the correct key. The screenshot below shows an encrypted file containing the ShortHash.

The same ShortHash appears in the encryption_key file.

The original file is deleted without first being overwritten, which may make recovery possible. By comparison, the open-source ransomware GonnaCry overwrites files before deleting them.

XCRY also leaves Volume Shadow Copies intact.

That is all. I wanted to see what compiled Nim programs look like internally and how ransomware could be implemented in the language. I hope this analysis made both points clear, and thank you for reading.

For another example of ransomware written in Nim, read about the attack on the IObit forum and download the corresponding sample.

TOP