Skip to content
update: cuckoo malware evolves
Blog Threat Intelligence Update: Cu...

Update: Cuckoo Malware Evolves

Since our initial report about the Cuckoo malware, there have been some updates to its functionality and infection vector that we wanted to let the Apple security community know about. 

In a recent blog post, Alden Schmidt from Huntress reported that the malware authors had changed tactics for tricking users into downloading and installing Cuckoo, from hiding it in random shovelware to using a landing page that mimics the download site Homebrew. Not long after that post went live, security researchers at Intego noticed that a new version of Cuckoo was now being hosted, with some new functionality. 

New Password Capture Methods

As Alden described in their blog post, the fake Homebrew website tells the user to copy and paste a command, which then downloads a script—the same workflow you use on the actual Homebrew site. When the install.sh script runs in Terminal, the user is then prompted for their password. This password is then base64 encoded and passed to the Cuckoo binary as argv[1]

/tmp/brew_agent \"$password\"

When we first analyzed the original Cuckoo sample, it was unclear how argv[1] was used. Now it’s clearer.

There is a check in the _start() function for argc

100005b2c    char* password
100005b2c if (argc s< 2)
100005b40 password = nullptr
100005b2c        else
100005b30            password = *(argv[] + 8)

If argc is less than 2, the variable—which we renamed to password—is assigned a null. Otherwise, the password variable is set to the dereferenced value of argv[] + 8. The second element of the argv array is stored 8 bytes from argv[0]; this captures the base64-encoded password that was passed in by the install.sh script when running the Cuckoo malware. 

We noticed that this variant does not prompt the user for a password via osascript, as we observed in the first variant. We learned that this occurs if a password was passed in via argv[1] as explained above. Let’s look at this inside of a function we called savePassword():

10000518c    uint8_t* savePassword(int64_t curlIP, char* password)
<...>
1000052a8 else
1000052d0 if (password == 0)
1000052f4 result = osaScript_Password(userName)
1000052d0 else
1000052e8            result = base64Decode(password, passwordLength: _strlen(password), nullptr: nullptr)
1000052f8       result_1 = result
1000052fc        if (result != 0)
100005308            _strcpy(&userPassword, result_1)
<...>

This function takes two arguments: the C2 URL and the password (either a null or the base64-encoded password value). There is a check to see if the password variable is equal to 0. If it is, the osascript we explained in our original post is executed to capture the user password. If this variable is not 0, then the value is passed to a base64 decoding function along with the length. The result is then saved to an address using the strcpy() function and written to the pw.dat file. 

This indicates that the malware authors designed this malware to leverage different password-capture methods. 

Virtual Machine Detection

Most strings in the Cuckoo malware are XOR-encoded, as we’ve explained before. However, we noticed that three newly added strings in the __cstring section are not encoded:

10001bcc1    char const data_10001bcc1[0xb] = "virtualbox", 0
10001bccc  char const data_10001bccc[0x7] = "vmware", 0
10001bcd3  char const data_10001bcd3[0xa] = "parallels", 0

These strings match known virtual machine applications and could be used by the malware to see if it is being executed in a virtualized environment. Looking for cross-references to these strings in the binary, we found that they are used in one function, which we will call VMCheck()

The XOR key (ZqrKwBeyoHPiBWB7tUuVpI7vh) is different in this binary; it’s used to decode the strings used by this function:

ioreg -l | grep -e Manufacturer -e 'Vendor Name'

Once the command string is decoded, it is passed to the strncpy() function and finally to the function that calls popen(), which then returns the output of the command: 

100018b50    char* popenSTDout = popenCMD(&ioreg -l | grep -e Manufacturer -e 'Vendor Name', 1)
100018b54    int64_t result
100018b54    if (popenSTDout == 0)
100018ba4        result = 0
100018b54    else
100018b64        int64_t isVirtualBox = _strcasestr(popenSTDout, “virtualbox”)
100018b68        int64_t isVMware
100018b68        int64_t isParallels
100018b68        if (isVirtualBox == 0)
100018b78            isVMware = _strcasestr(popenSTDout, "vmware")
100018b7c            if (isVMware == 0)
100018b8c                isParallels = _strcasestr(popenSTDout, "parallels")
100018b90                if (isParallels == 0)
100018bd4                    result = 0
100018b90        if (isVirtualBox != 0 || isVMware != 0 || isParallels != 0)
100018b94            result = 1
100018b9c        _free(popenSTDout)
100018bbc    if (*___stack_chk_guard != x8)
100018bdc    ___stack_chk_fail()
100018bdc        noreturn
100018bd0    return result

Once the ioreg command is executed by popen(), the output is used to query for strings matching either virtualbox, vmware, or parallels with a series of if statements. The function strcasestr() is used to check for these strings while also ignoring case; it returns a 0 if a match is not found and points to the first character of the first occurrence of the match if it is. 

Usually, logic like this is used to detect a virtual machine and then terminate the application once a match is found; this variant is doing something different. Instead, it returns a 1 (true) if either of these virtual machine strings is found in the ioreg command output or a 0 (false) if not. 

Interestingly, this function is not associated with any cross-references, so it does not yet seem to be used, even though the capability is there. This is similar to the screenshot capability we discussed in our first blog post, which also did not have any cross-references to it. 

Our Conclusions

The password capture method and the newly added virtual-machine detection function were not the only updates found in newer versions of Cuckoo. The malware is also now using 85[.]217[.]222[.]185 for C2 communication—a change from the original. 

These quick updates to Cuckoo show that it’s in continued active development and will probably continue to evolve moving forward. Also, the move to targeting Homebrew as a means of distribution potentially shows that the malware authors see macOS power users as a more rewarding target, with a better chance of infecting users in corporate environments. 

Updated IOC’s

Domains/IP 

  • homebrew[.]cx 
  • https://homebrew[.]cx/brewinstaller
  • 85[.]217[.]222[.]185
  • 5[.]255[.]107[.]149

Files

BrewUpdater

  • 513bb09807c9c343fccf7df30f687ea490125745e5ae02177c92efeb514e4b30 (Mach-0)

Install.sh 

  • 478d3cb27b4ddd2cf2f132f3b84c1f445d5ec803cc65385b372dbfa6db78fbb3 (script)
  • 1ea41635116b43afd1e50ed9dec1534699fb1958bd777971dd0fb7bc0ed104ec (script)
  • f608301ebb09ecdc9840c84f758f5e60cb6f7ab4d34d2f2d468af624eb800e50  (script)

About Kandji

Kandji is the Apple device management and security platform that empowers secure and productive global work. With Kandji, Apple devices transform themselves into enterprise-ready endpoints, with all the right apps, settings, and security systems in place. Through advanced automation and thoughtful experiences, we’re bringing much-needed harmony to the way IT, InfoSec, and Apple device users work today and tomorrow.