By Steven Du, Gabrielle Mabutas, and Luis Magisa
Right as July of this year began, we noticed an emerging malware dubbed by most as ThiefQuest (also known as EvilQuest), a threat that targets macOS devices, encrypts files, and installs keyloggers in affected systems. It has been found in pirated versions of macOS shared on popular torrent sites. Developments on the malware have been reported by MalwareBytes, BleepingComputer and security researchers Dinesh Devadoss, Phil Stokes, Patrick Wardle, and Thomas Reed.
The aforementioned reports state the assumption that the malware’s ransomware activity is not its main attack method; rather, it is a pre-emptive move to disguise its other capabilities such as file exfiltration, Command and Control (C&C) communication, and keylogging. This assumption is also supported by our recent discoveries.
Given that both the previously mentioned researchers and the updated report from Objective-See have conducted an in-depth look into the malware, in this blog post we will discuss our own discoveries such as the differences between the old and new versions of the malware, including unusual observations in VirusTotal. More importantly, we’d like to add to the current information provided by published reports that prove our belief that ThiefQuest is an example of highly capable malware that should be kept under close monitoring.
New ThiefQuest variants
Besides the old ThiefQuest variant that has been reported by various researchers, we also discovered some improved variants with stronger capabilities and other changes compared with earlier iterations of the malware. For instance, these new variants seem to emerge only days after the detection of older variants. Notably, previously encountered ransomware behavior, such as file encryption and ransom note dropping, have been removed.
These new updates are not called by the main code of the malware, and through further investigation, we discovered that the authors have implemented a new routine for computing and calling the new functions’ addresses. Other versions of these new variants have even obfuscated the function names to make malware tracing more difficult.
Figure 1. Code snippet showing the function eisl_apply_function() that is used to call the new updates in the main code.
The following are the new functions, some of which will be discussed:
- fb_uniconf_* (other related functions)
Payload reading and attaching
Figure 2. Code snippet showing payload reading and attachment
The extract_payload() function loads the embedded (and encoded) payload data from the specified file, where the offset and length of its data are saved at the end of the file. After reading the data, it calls eib_secure_decode to decode the payload data.
The attach_payload() function is the opposite to extract_payload(). It reads payload data from a specified source file, encodes them, and saves the encoded data to a specified target file.
Bundle compression and decompression
Figure 3. Code snippet showing bundle compression and decompression
The compress_bundle() function encodes the contents of each file in a bundle and saves them to a specified file. On the other hand, the decompress_bundle() function is the opposite of compresss_bundle(). It loads and decodes bundle files from a specified file.
C&C IP generation
Figure 4. Code snippet showing C&C and IP generation
The ei_rfind_cnc() function uses the current time as a seed for random number initialization in a 1000-counter loop. It calls ei_getip() to generate an IP address with the generated random number and tries to connect to it via http_request(). If it can be reached, it will then be used as the C&C server address.
Improved anti-analysis techniques
In the function is_virtual_mchn(), condition checks including getting the MAC address, CPU count, and physical memory of the machine, have been increased.
Figure 5. Code snippet showing condition checks
Figure 6. Code snippet showing analysis checks
In its string decryption function eip_str(), anti-analysis checks have also been added. One of these checks is eisl_debugging_um(), a new function that calls task_get_exception_ports() to check if the current process is being debugged. However, it seems that it does not fully work yet since the functions always return 0.
Figure 7. Code snippet showing the checking of debugging for the current process
We also found several new functions that are used for anti-analysis; however, a few of these functions are still empty. We suspect that these will be populated soon:
The function _react_updatesettings() has been added as well. This is used for getting updated settings from the C&C server.
Ability to run image and sound files
Meanwhile, run_audio and run_image are new functions that are meant to save a target file into a hidden .m4a sound file or .jpg image file respectively. These functions would then be run through a hidden opened terminal. The malware simply calls “open.filename.m4a” or “open.filename.jpg” to play it with default applications associated with either, such as Music.app or Preview.app.
With these two functions, threat actors behind ThiefQuest may be preparing for new features of the malware. Possibly, the group is planning for ThiefQuest to have a similar concept to the previous version that uses text-to-speech to read its dropped ransom note.
The next image shows the disassembly of the run_audio function. It displays the filename that the target will be saved as and the encrypted strings (decrypted as an AppleScript command for launching a hidden terminal) for running them.
Figure 8. Code snippet showing the disassembly of run_audio function
More security tools terminated
Aside from the tweet by user @Myrtus0x0, which states that ObjectiveSee’s KnockKnock solution has been added to the list of security tools running in the system to check and terminate, we also discovered that a few other security vendors have made it to this updated list:
- Little Snitch
Figure 10. Strings of security tools in their encrypted and decrypted form
Changes in file name and server
The dropped file name, persistence item PLIST file name, and connected server’s subdomain name of both previous and new variants have also been changed.
|Item||Previous variant||New variants|
|Primary Executable file path||/Library/AppQuest/com.apple.questd||/Library/PrivateSync/com.apple.abtpd|
|Persistent item plist file path||/Library/LaunchDaemons/com.apple.questd.plist||/Library/LaunchDaemons/com.apple.abtpd.plist|
Analyzing samples from VirusTotal
Data from VirusTotal submissions for the first versions of the malware shows that ThiefQuest had already been lurking since early to mid-June. These older samples don’t exhibit as many features as newer ones; additionally, we did notice some gradual changes in them that demonstrate the malware author’s efforts to continuously improve ThiefQuest.
One notable characteristic of the early versions is their lack of ransomware capability. In fact, ThiefQuest was initially a backdoor with the capability to modify the victim’s hosts file(/private/etc/hosts). In one of these earlier samples, such as effeeeadfdc3caf523635fcb86581a807f719fa5e322872854499, we observed it adding entries for certain domains to redirect to the C&C server. The following are some entries for hosts file modification:
The file path of the submission to VirusTotal contains /Users/user1 and country code RU (referring to Russia). Another submission name, with the country code BG (referring to Bulgaria), also contains the notable com.apple.questd.
Figures 11-12. Screenshots of VirusTotal submissions on an early version of the malware
The first ransomware version
In the beginning, we identified an older variant that wasn’t as comprehensive as the samples analyzed by other reports. This variant had no viral infector routines, and certain C&C tasks had no functioning code yet. However, it did demonstrate ransomware behavior.
Figure 13. Code snippet showing infector variants containing the ei_loader_main() function in the main code that is responsible for infecting files in the victim machines.
Figure 14. Code snippet showing the first ransomware variant in the main code that does not contain the infector function call.
Figure 15. Code snippet showing the first ransomware variant with the C&C task _react_keys() returning a null value.
Observations from infected samples
Given the viral infector routine of later samples, we checked VirusTotal Intelligence using the similar-to condition and found the results of many samples.
From June 29 to July 3, there were more than 30,000 similar new samples submitted to VirusTotal. Most of them come from the same API submission with country code ZZ, which means that the country where the submission originated from is unknown.
Figure 16. Screenshot of a VirusTotal submission on a newer version of the malware
The folder /Users/user1 is the same one used in older samples, which indicates that these samples are from the same machine where the older samples came from. In an estimated five-minute period, the same file path “/Users/user1/Library/Google/GoogleSoftwareUpdate/GoogleSoftware.bundle/Contents/Helpers/crashpad_handler” was submitted two times and file size increased from 16.27MB to 32.09MB. On our testing machine, the size of the file is only 499,264 bytes, or less than 500KB.
Figure 17. Screenshot of other VirusTotal submissions on a newer version of the malware
After analyzing the sample with 32.09 MB file size, here are some findings:
a) When searched, the unique string “/toidievitceffe/libpersist/rennur.c” appears for 366 times. This means that the file is infected repeatedly by the sample for at least 366 times.
b) Dumping the last Mach-O file at the end of the file resulted in confirming that the file is the crashpad_handler.exe. This is the same file that exists in our clean machine.
c) There were some incomplete copies of the sample in the file, which might have been caused by improper handling of multiple infection instances that were being run at the same time or other potential issues.
d) The malware allows multiple instances of malicious code to be run at the same time in one system.
e) Infected samples can reinfect themselves.
f) Although it avoids infecting Mach-O files that are inside app bundles, the malware still infects files in ~/Library/Google/GoogleSoftwareUpdate/GoogleSoftwareUpdate.bundle/, which is similar to an app bundle folder.
Generally, these processes are not usually included by experienced malware authors when they produce a file infector malware.
Generations of ThiefQuest
We recorded the preceding changes based on the infector samples we sourced. The following is the outline of the malware’s evolution:
Date First Seen
||June 4, 2020|
|effeeeadfdc3caf523635fcb86581a807f719fa5e322872854499f5270bc0eba||June 19, 2020|
||July 2, 2020|
||June 26, 2020|
||July 3, 2020|
|d18daea336889f5d7c8bd16a4d6358ddb315766fa21751db7d41f0839081aee2||July 3, 2020|
|851dfdbffd250523c5c7ff07b29778a04ebd44400b12f23d18a6ee5a3fcfbedc||July 4, 2020|
|06974e23a3bf303f75c754156f36f57b960f0df79a38407dfdef9a1c55bf8bff||July 5, 2020|
|7292004b57562223fed4ee122a956a8db38349c95d4dd8853b1ebc60ef7508b1||July 5, 2020|
|c5a77de3f55cacc3dc412e2325637ca7a2c36b1f4d75324be8833465fd1383d3||July 6, 2020|
|e69e9dc0d343165aa0f5df942d1b48ddd0337c8a79dcdf40f3c3b490d6e96a78||July 6, 2020|
|41036e1b78a122e57f2125526d673ffe3358d7323fc577703662740b3e651dcc||July 6, 2020|
|92ad2b0220f6903fb5fa48ce411af44a60c06031fee3aa682bd28f3f3fde1eda||July 9, 2020|
|bcdb0ca7c51e9de4cf6c5c346fd28a4ed28e692319177c8a94c86dc676ee8e48||July 11, 2020|
The malware also downloads certain files, such as Python dependencies and two particular files, p.gif and pct.gif. BleepingComputer has uploaded the raw text of p.gif, revealing how heavily-obfuscated it is.
Figure 18. Code snippet of p.gif
Based on this, we assume that the malware authors used nested Lambda function abused from a tool to make the script difficult to read. In researching further on the structure of this nested Lambda obfuscation style, we came across a tool developed by Chelsea Voss and a team that converts any Python2 script into a single line via Lambda functions.
The authors used python string obfuscation for the strings. For example, line 3 has the string “‘r%squ%ssts”, while line 28 used the pattern “’r%squ%ssts” %(‘e’,’e’)” . By manually deobfuscating this, the real string is revealed to be “requests”. Another similar string at line 10 reads as “’__b%s%slt%s%ss__’ % (‘u’, ‘i’, ‘i’, ‘n’)”, which is then revealed as “__builtins__”
Using this logic, we also deobfuscated the file on our end, confirming that p.gif is only for installing dependencies.
Figure 19. Code snippet showing requests
While Bleeping Computer obtained an earlier version of pct.gif, during our investigation we observed that the malware author updated pct.gif to exhibit the same nested Lambda obfuscation as well.
Figure 20. Screenshot of the pct.gif obtained from our recent sourcing
A more recent version of pct.gif that we uncovered also reveals that a string decryption routine is in place due to the presence of unreadable, encrypted strings. We will update this space once the code has been deciphered.
Figure 21. Screenshot of the more recent version of pct.gif obtained from our recent sourcing
Additional input for the core features
While much of our investigation of the core components for this malware (especially for its older versions) matches the findings of Objective-See’s Patrick Wardle, we would like to highlight additional information that may prove useful for those who might want a deep dive into the workings of ThiefQuest.
We would like to point out that the malware’s encryption logic branches depending on the size of the target file.
In the core encryption function carve_target(), there are calls to three different branches:
- The first branch targets files with sizes of less than 2MB.
- The second branch targets files with sizes between 2MB and 30MB.
- The third branch targets files with sizes greater than 32MB.
While all the parameters for the three callings are the same, there are certain differences in the second and third branches. For example, the malware limits the number of files to encrypt to 3,000, and if by the second branch it already encrypts 3,000 files, the third branch will then be skipped.
What we find odd in its logic, however, is that if the second branch already encrypts 2,900 samples, the counter for the third branch still starts from 0.
Figure 22. Code snippet for the file encryption process
Mach-O File Infection
The function append_ei() is where the routine performs the actual infection. It also adds the original/host file size and magic number at end of infected file as seen in the following figure:
The function pack_trailer(), on the other hand, is used to prepare the trailer data such as the file size of the host file for the infection.
Figure 24. Code snippet of pack_trailer()
Figure 25. Comparison of the original file(left) and infected file (right)
Comparing the original file and infected file in Figure 20 shows the data added. The original malware sample is appended on top of the infected Mach-O file.
The malware was found to exhibit a few differences in behavior depending on whether it is the original malicious sample or an infected file. The following differences have been observed between these two types of samples:
- Some anti-analysis check procedures were not executed on infected samples (__is_debugging, _prevent_trace,_ kill_unwanted).
- The routine in infected samples that drop the original/host code as a hidden file might deceive the user into thinking that the infected executed file was not affected while the malware performs malicious routines in the background.
- When an infected sample is executed, the dropped file .<filename>1 is not removed after the execution.
Observable in the following code snippets is the disassembly that shows the following processes: calling the unpack_trailer, extracting it from the same directory with ‘1’ suffix, and saving it as a hidden file. It also shows the infected sample and the hidden dropped file (assumed normal file).
Figures 26-28. Code snippets showing the calling of unpack_trailer
Figure 29. Code snippet showing the infected sample and the hidden dropped file (assumed normal file)
The main() function of the binary first installs an autorun/persistence mechanism using the functions persist_executable_frombundle(), ei_persistence_main, and install_daemon() to ensure that the malware is running on startup.
Figure 30. Decompiled code from the main() function showing several encrypted strings used for persistence
Figure 31. Decompiled code in ei_persistence_main() to install persistence
Running through this part of the code reveals that the malware installs a launch agent (~Library/LaunchAgents) and launches daemon (Library/LaunchDaemons) as com.apple.questd.plist; this targets another copy of the malware binary ~/Library/AppQuest/com.apple.questd, if certain conditions are met.
Figure 32. Content of the installed LaunchAgent with its target com.apple.questd. The symbol ~ indicates the current logged in user folder of the machine.
The function lfsc_dirlist() is called by the main exfiltration function ei_forensic_thread() where it concatenates all files under the /Users folder into one long string. A check for this string’s length is first performed. If the string is longer than 10,240 characters, it separates the string into 10,240 character-sized blocks which are sent one by one to the server.
After sending, it sleeps for 10 seconds to prevent making the network load too high. This 10-second sleep routine is also observed each time the malware sends exfiltrated data to the server.
Figure 33. Code snippet showing lfsc_dirlist()
C&C communication routines
As reports about the malware only mention the presence and a few features of the C&C server, we would like to share more information on this, especially on these functions:
Figure 34. Code snippet showing C&C functions
One of the functions of the C&C server is the _react_exec(). When the malware receives the _react_exec command from the attacker, it will attempt to decode the data and load or run this from the memory.
Figure 35. Code snippet showing _react_exec command
When unsuccessful, it will write the file into a .xookc hidden file and run it with elevated privileges through AppleScript.
Figure 36. Writing the file into a .xookc hidden file
Figure 37. Running the file with elevated privileges through AppleScript
For the _react_save command, the sample decodes the data received from the server into a file, through the function eib_decode(). This file will be saved as the filename that is also included in the encoded data received from the server.
Figure 38. Disassembly of the _react_save() function calling the eib_decode() function
Inside eib_decode() is the final function called eib_unpack_i, which is used for setting the decoded file onto the memory, then for saving as a file.
Figure 39. Code snippet of eib_unpack_i function
_react_ping is a command used to decrypt a string received from the server. Once successfully decrypted, the sample sends a message to the server, possibly indicating that it is working and ready to receive more commands from the server.
Figure 40. Disassembly of _react_ping() showing the encrypted string “Hi there” used for checking.
The binary waits for a response from the C&C server. Depending on the command received, it can initiate a keylogger through _react_keys().
First, it collects user information, such as the user ID of the ransomware binary called and environment path of the HOME variable, and then creates a thread for eilf_rglk_watch_routine() that contains the keylogger function.
Figure 41. Code snippet showing _react_keys()
In this thread, the routine uses the CGEventTapCreate() function to log and print keystrokes, where one of its parameters, process_event(), is the callback function for converting keystroke into strings to print.
Figure 42. Code snippet showing CGEventTapCreate()
The kconvert() function formats keystroke into strings. All possible button presses are found, including volume up/down/mute and function keys. However, the logged keystrokes are only printed through the console.
Figures 43-45. Code snippets showing kconvert()
Given the questionable keylogger printing and the null functions, we believe that the malware still lacks capabilities for C&C-related tasks. Perhaps the malware author will improve this part, as well as its file encryption and infection routines in later variants.
MITRE TTP Matrix
Based on the information we obtained from investigating both the previous and newer versions, here is the malware’s coverage using MITRE’s Tools, Techniques, and Procedures (TTPs). Entries in orange indicate observed and implemented behavior, while entries in yellow indicate identified, but not fully implemented, code for behavior.
Figures 46-47. Screenshot of TTP Matrix using the updated MITRE ATT&CK Navigator
As some variants of ThiefQuest exhibit ransomware capabilities, it is interesting to note that compared with some platforms, fewer ransomware detections are found to affect macOS. The emergence of ThiefQuest might mean that cybercriminals are finding more ways to target macOS with such attacks, or there is a higher interest in targeting the OS in general – or perhaps even both.
There is a misconception that Mac software is secure from malware; however, cybercriminals seek to target software that is used by a large number of people, and macOS is not exempt from such a basis of consideration. For example, besides ransomware, there have been other types of attacks against macOS. Last year, the most detected of these was Shlayer, a trojan that spreads adware and unwanted applications.
Newer variants of ThiefQuest with more capabilities are released within days. Having observed this, we can assume that the threat actors behind the malware still have many plans to improve it. Potentially, they could be preparing to make it an even more vicious threat. In any case, it is certain that these threat actors act fast, whatever their plans. Security researchers should be reminded of this and strive to keep up with the malware’s progress by continuously detecting and blocking whatever ThiefQuest variants cybercriminals come up with.
The threat actors discussed here constantly and quickly update this malware; therefore, security teams and users alike should remain vigilant for any curveballs that this malware could throw at them. To do so, the following actions are recommended:
- Only download applications from trusted sources such as official application stores or download centers.
- In emails, never download attachments or click links from untrusted sources.
- Patch and update software to ensure that vulnerabilities are protected.
The following solutions are also recommended to detect and block threats before they can infiltrate the system:
- Trend Micro™ XDR– gathers and correlates data across multiple vectors – such as email, endpoints, servers, cloud workloads, and networks – to better analyze and detect threats.
- Trend Micro Apex One™ – employs advanced endpoint detection and response (EDR) to provide actionable insights, expanded investigative capabilities, and centralized visibility across the network.
Indicators of compromise
Trend Micro pattern detection
|hxxp://andrewka6[.]pythonanywhere[.]com/ret[.]txt||Blocked and categorized as C&C server|