MacOS Red Teaming 211: Dylib Hijacking
This time I'm looking at an awesome persistence technique for macOS, dylib hijacking. Like the last post, this is a very well explored technique so I'm going to point to a lot of existing research to make this a shorter guide. I also like this as a persistence technique because I feel like it is less common and harder to detect than the standard launch agent or launch daemon persistence techniques. Dylibs are a type of shared libraries on macOS used by the linker and mach-o binaries. A basic feature of dynamic libraries and dynamic linking is the ability to load libraries from different locations, which is the core principal we will be abusing with this technique. Because this is an older technique there are a lot of writeups and tooling for it already, but the original technique for macOS dylib hijacking was first written about by Patrick Wardle in his white paper for Virus Bulletin. He later talked about it at DEFCON (the pdf is very useful) and broke down the technique in excellent fashion, first laying out the dynamic linking process on macOS, then identifying two general hijacking points:
Essentially two things allow a dylib to be hijacked. The first is when an application loads a dylib using the LC_LOAD_WEAK_DYLIB load command, it will check a certain path and continue functioning even if the dylib is not found there. These are often used as a fault tolerant or dev friendly way of designing an application. The second is @rpath dylibs, or runpath dylibs, which use dynamic pathing so an application doesn't need to use absolute paths when specifying where a supporting framework may live, relative to an application. These can also be detected based on the load commands as you will most likely see an LC_RPATH command, which tells it which dynamic paths to check. We can exploit both of these scenarios by placing our hijacked dylib in that target location and proxying the symbol calls to the legitimate dylib.
Patrick releases a ton of great tools and one of my favorite tools to search for dylib hijacks is Wardle's DHS or Dylib Hijack Scanner, although we will use a few of his other scripts along the way as well. I mostly like DHS because of the GUI and it can be nice to save the output and diff them over time. But like I said at the beginning of this post, this is a pretty well explored technique at this point, so there is a lot of existing tooling if you don't want to use DHS for any reason. There is the original python script Patrick Wardle wrote, which we will use later. There are also some more python supporting tools here, and a version written in JXA. You will likely want a command line option if you plan to run this on a victim, although I recommend collecting their installed applications and searching for the vuln on a local research machine. This technique has also been incorporated as a post exploitation module in the empire framework for awhile now, if your looking for a fully fledged attacker framework with this capability. But I couldn't complete this post without finding a vulnerable example and practicing this persistence technique myself. In my example below I'm largely going off of Csaba's writeup (theevilbit) because I found it really clear and concise. I ended up finding a dylib hijack point in one of the macOS features of Zoom, but I don't really consider this a vulnerability of Zoom as it requires root permissions and it's a feature of dynamic linking. Still let's exploit it for persistence.
I found it in this helper app of zoom called Airhost which uses airplay to screenshare on the local network. When it does this it references the openssl libcrypto dylib via an @rpath, making it vulnerable to dylib hijacking. Granted, you need root permissions to write to this location, so there is no exploitation of privileges, just a sneaky way to persist for attackers. We can craft the template dylib like so, starting with compiling EvilBit's PoC code, and targeting openssl's libcrypto for the symbol re-exporting, which is what proxies our function calls for us:
gcc -dynamiclib hijacker.c -o libcrypto.1.0.1.dylib -Wl,-reexplort_library,"/usr/local/Cellar/openssl/1.0.2r/lib/libcrypto.1.0.0.dylib"
Next we run Patrick Wardle's CreateHijacker.py script on our new hijacker dylib and the dylib we are proxying. This will fix the @rpath versions in our hijacker dylib and uses install_name_tool to update the linker with our new library paths:
python createHijacker.py libcrypto.1.0.1.dylib "/usr/local/Cellar/openssl/1.0.2r/lib/libcrypto.1.0.0.dylib"
This will create our newly re-exported libcrypto.1.0.0.dylib in the same directory as our libcrypto.1.0.1.dylib, all thats left is moving this newly created hijack dylib to the target hijack path:
sudo mv libcrypto.1.0.0.dylib /Applications/zoom.us.app/Contents/Frameworks/airhost.app/Contents/Frameworks/
After placing it in the proper location let's run some quick tests. We can validate the dylib hijack worked by first calling the airhost executable from the command line, and we should see our PoC appear on the command line. After that, we can trigger it via the main zoom application by starting or clicking a video call, then clicking "Share" and selecting the Airplay option on macOS:
This will cause your backdoored functionality to run when the dylib is loaded, before passing the functionality on to to the real libcrypto dylib. It's a pretty slick persistence technique, although I need to keep searching for better applications to persist in. This specific feature isn't the best for persistence, as I will likely want something that is used more often. That said, these dylib hijacking locations are fairly common, so I recommend searching for some yourself to practice the technique. If you want to take this technique even further, such as fully automating the hijacking or sussing out and shimming private functions (symbol fishing), check out the following talk by Jimi Sebree below:
Essentially two things allow a dylib to be hijacked. The first is when an application loads a dylib using the LC_LOAD_WEAK_DYLIB load command, it will check a certain path and continue functioning even if the dylib is not found there. These are often used as a fault tolerant or dev friendly way of designing an application. The second is @rpath dylibs, or runpath dylibs, which use dynamic pathing so an application doesn't need to use absolute paths when specifying where a supporting framework may live, relative to an application. These can also be detected based on the load commands as you will most likely see an LC_RPATH command, which tells it which dynamic paths to check. We can exploit both of these scenarios by placing our hijacked dylib in that target location and proxying the symbol calls to the legitimate dylib.
Patrick releases a ton of great tools and one of my favorite tools to search for dylib hijacks is Wardle's DHS or Dylib Hijack Scanner, although we will use a few of his other scripts along the way as well. I mostly like DHS because of the GUI and it can be nice to save the output and diff them over time. But like I said at the beginning of this post, this is a pretty well explored technique at this point, so there is a lot of existing tooling if you don't want to use DHS for any reason. There is the original python script Patrick Wardle wrote, which we will use later. There are also some more python supporting tools here, and a version written in JXA. You will likely want a command line option if you plan to run this on a victim, although I recommend collecting their installed applications and searching for the vuln on a local research machine. This technique has also been incorporated as a post exploitation module in the empire framework for awhile now, if your looking for a fully fledged attacker framework with this capability. But I couldn't complete this post without finding a vulnerable example and practicing this persistence technique myself. In my example below I'm largely going off of Csaba's writeup (theevilbit) because I found it really clear and concise. I ended up finding a dylib hijack point in one of the macOS features of Zoom, but I don't really consider this a vulnerability of Zoom as it requires root permissions and it's a feature of dynamic linking. Still let's exploit it for persistence.
I found it in this helper app of zoom called Airhost which uses airplay to screenshare on the local network. When it does this it references the openssl libcrypto dylib via an @rpath, making it vulnerable to dylib hijacking. Granted, you need root permissions to write to this location, so there is no exploitation of privileges, just a sneaky way to persist for attackers. We can craft the template dylib like so, starting with compiling EvilBit's PoC code, and targeting openssl's libcrypto for the symbol re-exporting, which is what proxies our function calls for us:
gcc -dynamiclib hijacker.c -o libcrypto.1.0.1.dylib -Wl,-reexplort_library,"/usr/local/Cellar/openssl/1.0.2r/lib/libcrypto.1.0.0.dylib"
Next we run Patrick Wardle's CreateHijacker.py script on our new hijacker dylib and the dylib we are proxying. This will fix the @rpath versions in our hijacker dylib and uses install_name_tool to update the linker with our new library paths:
python createHijacker.py libcrypto.1.0.1.dylib "/usr/local/Cellar/openssl/1.0.2r/lib/libcrypto.1.0.0.dylib"
This will create our newly re-exported libcrypto.1.0.0.dylib in the same directory as our libcrypto.1.0.1.dylib, all thats left is moving this newly created hijack dylib to the target hijack path:
sudo mv libcrypto.1.0.0.dylib /Applications/zoom.us.app/Contents/Frameworks/airhost.app/Contents/Frameworks/
After placing it in the proper location let's run some quick tests. We can validate the dylib hijack worked by first calling the airhost executable from the command line, and we should see our PoC appear on the command line. After that, we can trigger it via the main zoom application by starting or clicking a video call, then clicking "Share" and selecting the Airplay option on macOS:
This will cause your backdoored functionality to run when the dylib is loaded, before passing the functionality on to to the real libcrypto dylib. It's a pretty slick persistence technique, although I need to keep searching for better applications to persist in. This specific feature isn't the best for persistence, as I will likely want something that is used more often. That said, these dylib hijacking locations are fairly common, so I recommend searching for some yourself to practice the technique. If you want to take this technique even further, such as fully automating the hijacking or sussing out and shimming private functions (symbol fishing), check out the following talk by Jimi Sebree below: