macOS code injection for fun and no profit (2024)
This detailed technical guide explores macOS code injection using Mach APIs, driven by the author's desire for a Live++ equivalent on Apple's platform. It meticulously walks through attaching to processes, manipulating memory, and injecting new functions with trampolines. The article is a popular read for those interested in low-level macOS development and understanding how tools like debuggers or hot-reloading systems function.
The Lowdown
The author, inspired by the C/C++ hot-reloading capabilities of Live++ on Windows, embarked on a vacation project to understand the mechanics of code injection on macOS. Since Live++ lacks a macOS version, this exploration serves as a practical, step-by-step guide to achieve similar low-level process manipulation for 'fun and no profit', demonstrating core principles used by debuggers and other system tools.
- Setup and Entitlements: The guide begins with setting up a CMake project and configuring macOS entitlements via
codesignto grant the injection program debugger-like capabilities. - Process Attachment: It details how to attach to a running process using its PID and the Mach API
task_for_pid(), which provides a task control port for further interaction. The example cleverly uses adata.txtfile to share runtime addresses and PID, bypassing ASLR complexities. - Process Control: Instructions are provided for suspending and resuming a target process's threads using
task_suspend()andtask_resume()to ensure atomic memory operations. - Memory Manipulation: The core functionalities of reading and writing to a remote process's memory are explained with
vm_read_overwrite()andvm_write(), demonstrated by altering an integer variable in the target program. - Code Injection via Trampolines: The most advanced section covers injecting an entirely new function. This involves allocating executable memory in the target process using
vm_allocate()andvm_protect(), then writing the new function's machine code. The key technique, trampolines, is introduced to redirect calls from an existing function (foo()) to the newly injected one (bar()). - Architecture-Specific Trampolines: The guide provides assembly details for both x86_64 and ARM64 architectures for creating these jump instructions, noting the use of
fpatchable-function-entrycompiler flags to reserve space for the trampoline by adding NOPs.
While not production-ready, this detailed walkthrough provides a solid foundation for understanding the intricate processes behind code injection and hot-reloading. It demystifies how debuggers, live-coding solutions, and even certain malware/antivirus programs interact with and modify running applications at a fundamental level on macOS.