Analyzing What Appears to be GNNCRY's macOS Test Build

macOS “GNNCRYS” ransomware PoC is a Linux port with predictable Blowfish keys, harmless but instructive.

Analyzing What Appears to be GNNCRY's macOS Test Build

GonnaCry is a ransomware variant primarily targeting Linux systems. It emerged around 2017 as an open-source proof-of-concept project on GitHub, created by developer Tarcísio Marinho for educational purposes to demonstrate ransomware mechanics, and encryption techniques. However, by 2021, malicious linux variants had appeared in the wild, encrypting user files and demanding cryptocurrency ransoms, leading to detections by antivirus firms.

A review of VirusTotal submissions confirms that the Linux variants still circulating are consistently flagged as ELF-ransomware.

Fig 01: Linux variants of GonnaCry

While hunting for benign files, I came across a sample that exhibited properties closely matching those of GonnaCry, yet it had been compiled specifically for macOS.

Fig 02: MacOS variant of GonnaCry

Like any other researcher, checking the open-source project, I could not find any recent commits or information indicating the development of a macOS variant. This seems interesting, if the build was not officially planned, then who created it?

Fig 03: GonnaCry github repository

Initial Triage and Sample Overview

Getting a copy of the sample, we perform standard triage using file and otool to inspect the headers. The results confirmed it as a Mach-O arm64 executable.

Fig 04: File metadata

Running strings revealed a hardcoded list of target file extensions covering various document types, images, archives, and more, alongside a cleartext ransom note.

Fig 05: Sample headers and extracted strings

Static Analysis

We then moved onto static analysis and found the flow to be straightforward, almost exactly like the original Linux PoC, just wearing a macOS coat.

Main execution flow

At first glance, the sample reveals a clear-text path variable exposing the developer's environment, indicating where the malware was either created or tested:

/home/tarcisio/tests/

This path suggests the original author of GonnaCry may have been developing or testing a macOS variant.

Fig 06: Execution Flow (from main function)

The ransomware begins with environment path discovery:

  • _get_home_enviroment() → Gets user's home directory
  • _get_desktop_enviroment() → Finds Desktop folder
  • _get_username() → Gets current username
  • _get_trash_path() → Constructs trash directory path
  • _get_media_path() → Constructs media directory path
Fig 07: Execution Flow (from main function)

The sample then performs targeted file enumeration by invoking _find_files() three times, scanning the home, trash, and media directories. Discovered files are stored in a linked list for processing.

Once enumeration is complete:

  • Files are encrypted using _encrypt_files()
  • A ransom note is generated on the Desktop via _create_files_desktop()

After encryption, the malware performs resource cleanup, freeing all dynamically allocated memory to minimize forensic traces.

File discover

The _find_files() function performs a recursive directory traversal, hunting for files with specific extensions that have been hard coded into the binary. The full list of targeted extensions is extensive and spans common document, image, archive, and database formats:

doc docx xls xlsx ppt pptx pst ost msg eml vsd vsdx txt csv rtf wks wk1 pdf dwg onetoc2 snt jpeg jpg docb docm dot dotm dotx xlsm xlsb xlw xlt xlm xlc xltx xltm pptm pot pps ppsm ppsx ppam potx potm edb hwp 602 sxi sti sldx sldm vdi vmdk vmx gpg aes ARC PAQ bz2 tbk bak tar tgz gz 7z rar zip ba
Fig 08: Hard coded file extension

Upon closer inspection, the function begins by parsing this space-delimited string using strtok() to isolate individual extensions. For each file encountered during traversal, it extracts the extension via _get_filename_ext() and compares it against the tokenized list using _strcmp(). When a match is found, the full file path is appended to a linked list through _append().

Fig 09: File discovery function

The traversal logic is straightforward yet robust. Regular files (type 8) trigger the extension check, while directories (type 4) prompt a recursive call into the sub-directory. To avoid infinite loops, entries named "." and ".." are explicitly skipped.

Notably, the sample includes several hardcoded paths such as, .local/share/Trash/ and /media/, which indicate that this binary was cross-compiled from a Linux-originating codebase.

Despite the developer path suggesting macOS testing (/home/tarcisio/tests/), these internal path references align closely with the original Linux variant, implying that a dedicated macOS port was never fully implemented.

Fig 10: Searches for Linux trash folder
Fig 11: Searches for Linux media folder

Encryption

The ransomware sample then generates a 16-byte initialization vector (IV) using _generate_key and a 32-byte key via _generate_key. Even though the underlying Blowfish algorithm only consumes the first 16 bytes of the key. The actual encryption is performed by the _encrypt() function, which processes the file content in place.

For each encrypted file, the malware records the generated key, IV, and full file path into a linked list using _append(). This data is later serialized into a file named enc_files.gc, presumably to enable decryption in exchange for ransom.

Fig 12: Encryption function

Finally, the original plaintext file is securely overwritten and deleted using _shred() to prevent recovery.

Fig 13: Execution Flow (from main function)

Key generation

The ransomware relies on the standard C library function rand() for key and IV generation, rather than employing a cryptographically secure pseudorandom number generator (CSPRNG).

This is a known weakness: rand() produces entirely predictable output once the seed is known, and in this sample, no call to srand() appears anywhere in the code. As a result, the RNG initializes with its default seed, typically 1 on most systems, causing every execution of the malware to generate identical keys and IVs.

Fig 14: Encryption key generation

A further flaw to the existing weakness (rand()), the generated material is not raw binary but instead drawn from a restricted 78-character set:

abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789,.-#@$%&(){};'?!

Each byte is thus limited to one of 78 possible values (0x4E in hexadecimal), far short of the full 256-value range (0x00–0xFF) expected in proper cryptographic keys. This huge drop in randomness makes guessing the key easy, even without knowing the starting seed, and totally breaks the Blowfish encryption.

Secure deletion

The _shred() function securely wipes the original plaintext file by first retrieving its size with stat(). It then opens the file in write-only mode (O_WRONLY) and allocates a 4 KB buffer filled entirely with zeros.

The function proceeds to overwrite the entire file in 4 KB chunks, ensuring all original data is destroyed. Once overwriting is complete, it closes the file descriptor and finally deletes the file using remove().

Fig 15: File deletion function

Key storage

The _save_into_file_encrypted_list() function writes all encryption keys and initialization vectors to a file named enc_files.gc on the victim's Desktop. Each entry follows a simple plaintext format:

KEY:IV:ENCRYPTED_FILE_PATH

An example entry might appear as:

- aB3dE5fG7hI9jK1l:mN2oP4qR6sT8uV0w:/Users/victim/Documents/report.docx.GNNCRY

- xY9zA1bC3dE5fG7h:iJ1kL3mN5oP7qR9s:/Users/victim/Pictures/photo.jpg.GNNCRY

- tU8vW0xY2zA4bC6d:eF8gH0iJ2kL4mN6o:/Users/victim/Desktop/presentation.pptx.GNNCRY

Here, the 32-byte key comes first, followed by the 16-byte IV, and then the full path to the encrypted file (ending in .GNNCRY). The original plaintext path is not stored, only the encrypted version.

Fig 16: Key storage function

The ransomware saves all the decryption keys and IVs in plain text inside enc_files.gc. If you still have this file, you can recover every encrypted file for free, no ransom needed.

Knowing this as an open-source ransomware PoC this behavior is expected, but I can't say the same for other samples which may be tampered with. This further shows a clear sign that this is either a test version or beginner-level malware, not the work of threat actors.

Dynamic analysis

Testing the sample to confirm if it works, I tried running it on a macOS system (Tahoe), and you get the usual warning which shows that the program is not verified and may harm the machine.

Fig 17: Warning from macOS while running sample

We ignore the message and allow the file to run, but I was surprised to see the process terminating immediately with another warning since the file fails verification.

Fig 18: Warning from macOS while running sample

As a third attempt we explicitly give the sample permission to run, but Apple once again kills the process, this time with a 'trace trap' message. Given that I got 52108 killed, macOS security is actively killing the process. This is either:

  • Gatekeeper: Quarantine attribute blocking
  • XProtect: macOS malware detection
  • System Integrity Protection (SIP): Blocking certain operations

I quickly check for quarantine flags using xattr and find the quarantine flag which I remove.

Fig 19: Checking file attribute using xattr
Fig 20: Removing quarantine flag from file attribute

Unfortunately, this approach also fails, requiring us to disable additional protections to run the sample. This is beneficial for macOS, as these safeguards effectively prevent regular users from executing the file without modifying system settings.

Fig 21: File execution error

Conclusion

The sample depends on the Homebrew package openssl@3 (specifically the library at homebrew/opt/openssl@3/lib/libcrypto.3.dylib) to perform encryption and will fail entirely if this dependency is missing.

In summary, GonnaCry is not a live threat targeting macOS users in the wild, it is clearly a test proof-of-concept. Its behavior is consistent and observable:

It performs mass file reads across common user directories, followed by sequential encryption (read originalwrite encrypteddelete original). Encrypted files are renamed with the .GNNCRY extension, and the original files are zero-overwritten before deletion. Finally, it creates two key files on the Desktop: enc_files.gc (containing plaintext keys and IVs) and your_encrypted_files.txt (the ransom note).

These traits confirm its experimental nature.

IOC

SHA256
4379a2f58d3088233e7f051d882a9ce934960488aaaba640c26785645bb711c2

Yara

rule GNNCRY_Ransomware_macOS {
    meta:
        description = "Detects GNNCRY macOS ransomware"
        author = "Tonmoy Jitu"
        date = "2025-11-15"
        malware_family = "GNNCRY"

    strings:
        // Ransom note text
        $ransom_note = "Sup brother, all your files below have been encrypted, cheers!" ascii wide

        // File artifacts
        $extension = ".GNNCRY" ascii wide
        $key_file = "enc_files.gc" ascii wide
        $ransom_file = "your_encrypted_files.txt" ascii wide

        // Extension list (unique to this malware)
        $ext_list1 = "doc docx xls xlsx ppt pptx pst ost msg eml vsd vsdx" ascii
        $ext_list2 = "vdi vmdk vmx gpg aes ARC PAQ bz2 tbk bak tar tgz" ascii

        // Linux paths on macOS (suspicious)
        $linux_path1 = ".local/share/Trash/" ascii
        $linux_path2 = "/media/" ascii

        // Portuguese debug strings
        $debug_pt1 = "nao abriu" ascii
        $debug_pt2 = "abriu" ascii
        $debug_pt3 = "Arquivos ainda n" ascii

        // Blowfish encryption
        $crypto1 = "EVP_bf_cbc" ascii
        $crypto2 = "EVP_CipherInit_ex" ascii
        $crypto3 = "EVP_CipherUpdate" ascii
        $crypto4 = "EVP_CipherFinal_ex" ascii

        // Key generation charset
        $keygen_charset = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789,.-#@$%&(){};'?!" ascii

    condition:
        // Must be Mach-O executable
        uint32(0) == 0xfeedfacf or uint32(0) == 0xcefaedfe or
        uint32(0) == 0xfeedface or uint32(0) == 0xcffaedfe

        and filesize < 5MB

        and (
            ($ransom_note and $extension and $key_file) or
            (2 of ($ext_list*) and 2 of ($crypto*)) or
            (1 of ($linux_path*) and 1 of ($debug_pt*) and 2 of ($crypto*)) or
            ($keygen_charset and 3 of ($crypto*) and $extension)
        )
}

Reference

GitHub - tarcisio-marinho/GonnaCry: A Linux Ransomware
A Linux Ransomware. Contribute to tarcisio-marinho/GonnaCry development by creating an account on GitHub.