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.
See Kandji in Action
Experience Apple device management and security that actually gives you back your time.
See Kandji in Action
Experience Apple device management and security that actually gives you back your time.