macOS JIT Memory
好的,我现在需要帮用户总结一篇文章的内容,控制在100字以内。用户已经提供了文章的标题和内容,看起来是关于macOS的强化运行时(Hardened Runtime)及其对恶意软件执行的影响。 首先,我需要理解文章的主要内容。文章讨论了macOS的安全机制,特别是Hardened Runtime如何防止未签名代码的执行。接着,作者探讨了恶意软件如何绕过这些保护措施,包括使用有效签名、脚本语言以及利用Hardened Runtime的例外情况。 然后,文章详细介绍了几种恶意软件的实现范例,如Mach-O可执行文件、反射加载库和位置无关代码(PIC)。还讨论了应用程序如何通过特定权限(entitlements)禁用Hardened Runtime功能,从而允许恶意软件执行。 接下来,作者分析了JIT(即时编译)内存的行为,并展示了如何在允许JIT权限的应用程序中执行shellcode。最后,列举了一些常见应用中存在这些权限的情况,并提到了工具和技术的发展。 现在,我需要将这些要点浓缩到100字以内。重点应放在macOS的安全机制、恶意软件的绕过方法以及JIT权限的影响上。 可能会这样组织:macOS通过强化运行时防止未签名代码执行,但恶意软件可利用有效签名、脚本或例外权限绕过限制。JIT内存和特定应用权限使shellcode执行成为可能。 检查字数是否在限制内,并确保信息准确且连贯。 </think> macOS通过强化运行时防止未签名代码执行,但恶意软件可利用有效签名、脚本或例外权限绕过限制。JIT内存和特定应用权限使shellcode执行成为可能。 2026-2-19 13:38:46 Author: www.outflank.nl(查看原文) 阅读量:9 收藏

The macOS Hardened Runtime prevents execution of unsigned code. Unsigned executables will not run, regardless of compilation settings. Processes cannot load unsigned shared libraries into apps with the Hardened Runtime. Nearly every app found on a modern system enables the Hardened Runtime, and Apple silicon processors enable memory protection for all apps. So, how does malware execute within such constraints?

  1. Valid signatures – There are a few examples of criminals signing their malware, but Apple can revoke certificates, and has done so quickly, even over the weekend.
  2. Script-based malware – Malware written in languages like Python and JavaScript for Automation (JXA) does not require a signature, though initial access delivery may be constrained.
  3. Hardened Runtime exceptions – Many legitimate applications permit unsigned memory or libraries using entitlements to disable Hardened Runtime features. Such exceptions may permit dylib sideloading or shellcode execution.

Before discussing these exceptions, I will catalog three malware implementation paradigms.

  1. Mach-O executable or library – A very common format for macOS malware, as seen in various open-source C2 implants. Mach-O files have the fewest in-memory restrictions in this context, but limit the ability of an operator to quickly obfuscate or wrap the payload in various execution containers.
  2. Reflectively loaded library – While more common on Windows, Mach-O reflective loaders are gaining popularity. Malware in this category typically consists of a shared library (.dylib) and a position-independent “loader” which maps the library into memory and executes a predefined export.
  3. Position-independent code (PIC) – Though I haven’t seen a PIC macOS implant, it is certainly possible. Instead of a shared library with a position-independent loader, the malware itself is PIC. In the context of this post, PIC implants have effectively the same constraints as a reflectively loaded library.

Applications can disable relevant Hardened Runtime capabilities using the following entitlements:

Only one of these entitlements, disable-executable-page-protection, is typically sufficient on its own to execute shellcode. The combination of allow-dyld-environment-variables and disable-library-validation allows an operator to load an arbitrary dylib, but is insufficient for shellcode execution. The allow-unsigned-executable-memory and allow-jit entitlements permit shellcode execution, but we cannot execute shellcode without some method of injecting a loader, such as VBA macros.

Executing shellcode in a process with the allow-unsigned-executable-memory entitlement is straightforward and very similar to shellcode execution on Windows. Applications with the allow-jit entitlement, however, require a bit more nuance. In this post, I aim to demystify macOS JIT protections and demonstrate a reflective loader that works under multiple scenarios.

JIT Memory Behavior

The Hardened Runtime prevents writable+executable memory. We can allocate memory with read-write permissions, and then change it to read-execute later, though code execution in such memory still requires a valid signature. However, the allow-jit entitlement permits allocation of RWX memory by passing the MAP_JIT flag to mmap(). Without allow-jit, allow-unsigned-executable-memory, or disable-executable-page-protection, calls to mmap() with MAP_JIT will fail. Any of these entitlements (not just allow-jit) will allow the use of JIT memory. Code in memory allocated with MAP_JIT can be executed without a code signature.

To better understand the behavior of JIT memory, I published a GitHub repo with a few proof-of-concept programs at https://github.com/outflanknl/macos-jit.

According to Apple’s documentation, apps with the allow-jit entitlement “can only create one memory region with the MAP_JIT flag set,” though this was not true in my testing on macOS Tahoe 26.2. The “multiple-regions” PoC allocates two separate memory regions with the MAP_JIT flag, copies different data to each, ensures the addresses do not overlap, and validates that they each behave the same way.


region_a: 0x1004b8000
pad     : 0x1004bc000 (0x100000 bytes)
region_b: 0x1005bc000

WP WRITE(0)
  region_a off=0x100 write=OK exec=FAULT => WRITE(0)
  region_b off=0x200 write=OK exec=FAULT => WRITE(0)

WP EXEC(1)
  region_a off=0x100 write=FAULT exec=OK => EXEC(1)
  region_b off=0x200 write=FAULT exec=OK => EXEC(1)

region_a leaf: 0x1004b8000 - 0x1004bc000
region_b leaf: 0x1005bc000 - 0x1005c0000

Apple mentions another key detail in their documentation: “a thread cannot write to a memory region and execute instructions in that region at the same time”. Indeed, whether memory is writable or executable is thread-specific. The “different-threads” PoC uses two separate threads with opposite JIT states to demonstrate this behavior.


MAP_JIT region: 0x100228000

Phase 0: main=WRITE(0) child=EXEC(1)
  child    off=0x200 write=FAULT exec=OK => EXEC(1)
  main     off=0x100 write=OK exec=FAULT => WRITE(0)

Phase 1: main=EXEC(1) child=WRITE(0)
  child    off=0x200 write=OK exec=FAULT => WRITE(0)
  main     off=0x100 write=FAULT exec=OK => EXEC(1)

Each memory region with the MAP_JIT flag can have different permissions, even for the same thread. As shown in the “chained-alloc” PoC, we can allocate JIT memory, jump to it, and then repeat this process any number of times. This specific behavior confirms what I stated earlier: reflectively loaded libraries and fully PIC malware are effectively the same in this context.


Root page: 0x1040c8000
Payload size: 324 bytes
Per-hop trace (4 recorded):
  hop 1: src=0x1040c8000 dst=0x1040cc000 mprotect_rc=0
  hop 2: src=0x1040cc000 dst=0x1040d0000 mprotect_rc=0
  hop 3: src=0x1040d0000 dst=0x1040d4000 mprotect_rc=0
  hop 4: src=0x1040d4000 dst=0x1040d8000 mprotect_rc=0
Requested hops: 4
Assembly-reported depth: 4
Chain result: PASS

Shellcode Execution in JIT Memory

The allow-jit entitlement may be more secure than allow-unsigned-executable-memory for some scenarios, but I will establish their equivalence for a typical shellcode loader with an example that works in either scenario.

There are at least two working implementations, one more robust than the other. Both require changes to the shellcode loader and reflective loader. The first method requires the following steps:

  1. Allocate RWX memory with the MAP_JIT flag using mmap().
  2. Execute pthread_jit_write_protect_np(0).
  3. Copy shellcode to the allocated memory.
  4. Execute pthread_jit_write_protect_np(1).
  5. Jump to the shellcode or create a new thread with pthread_create().

This method is affected by another entitlement, jit-write-allowlist, that can further harden JIT memory by preventing use of the pthread_jit_write_protect_np() function. I could not find any applications with this entitlement, though. As shown below, the “target” example program executes a simple shellcode while the “target-allowlist” example prevents execution using the jit-write-allowlist entitlement.

Alternatively, the following implementation works in spite of the jit-write-allowlist entitlement.

  1. Allocate RW memory with the MAP_JIT flag using mmap().
  2. Copy shellcode to the allocated memory.
  3. Update the memory to RX with mprotect().
  4. Jump to the shellcode or create a new thread with pthread_create().

This technique executes a simple shellcode in both example programs:

Since macOS permits multiple JIT memory regions, one can implement either technique in both the shellcode loader and the reflective loader.

Use Cases

Several common applications have the allow-jit entitlement and will execute shellcode using dylib sideloading, VBA macros, or some other method:

  • Firefox
  • GoTo
  • Obsidian
  • OpenAI Codex
  • Microsoft Excel, PowerPoint, and Word
  • PyCharm
  • Spotify
  • VLC
  • VSCode (and forks like Antigravity, Cursor, etc.)

I updated Outflank C2 and our public Mach-O reflective loader to support apps with either allow-jit or allow-unsigned-executable-memory. I also submitted a pull request to the Sliver macOS reflective loader. This PR makes it possible to execute Sliver shellcode in Microsoft Word using a VBA macro:

Conclusion

Within the context of shellcode execution, the allow-jit and allow-unsigned-executable-memory entitlements are effectively the same, even with the jit-write-allowlist entitlement. Shellcode execution on macOS may be more constrained than on Windows and Linux, but it is certainly possible given the variety of exceptions.

Outflank continually expands the tools and techniques available in Outflank Security Tooling (OST), a broad set of evasive tools that allow users to safely and easily perform complex tasks. We aim to expand the options available to red team operators through development of tools like our macOS C2 implant and research into OS and EDR internals. Consider scheduling an expert-led demo to learn more about the diverse offerings in OST.


文章来源: https://www.outflank.nl/blog/2026/02/19/macos-jit-memory/
如有侵权请联系:admin#unsafe.sh