![]() |
|
Memory Injection on macOS - Printable Version +- Sinisterly (https://sinister.li) +-- Forum: Hacking (https://sinister.li/Forum-Hacking) +--- Forum: Tutorials (https://sinister.li/Forum-Tutorials) +--- Thread: Memory Injection on macOS (/Thread-Memory-Injection-on-macOS) |
Memory Injection on macOS - Equinox - 06-19-2021 bueno. Been a long time since I've written any sort of tutorial, but lately I've been toying with memory injection, particularly on macOS. Now there may be a number of reasons as to why you would want to do some sort of memory injection, and for my purposes, its so I can write a program that is started as a direct child to Dock.app, so I can take control of the macOS window server. Your reasons may vary, but without further ado... On systems running macOS, you can boil down memory injection into about 4 steps. First and foremost, allocating memory in a target task's memory region. From there, writing payload data to said memory, then setting access control for the region, and lastly creating and running a thread which will call and execute your payload. From macOS 10.4 (Mac OS X as it was called then) and onward, Apple introduced a new virtual memory API. The Mach VM API, for our purposes, is going to have nearly identical function signatures, save for the mach_ prefix in method names. To keep things relatively organized, I prefer to use a structure that contains all of my virtual memory related data. In addition to this, we will have a payload that is written in assembly. The assembly will be stored in a string we call payload. Because of the architecture specific assembly, and the architecture agnostic nature of this thread, I won't be giving an actual payload, so keep in mind that our payload here is notional. Code: typdef struct {
mach_vm_address_t addr;
size_t size;
vm_prot_t prot;
} vm_region_t;
char *payload = "...";Code: vm_region_t shellcode = {
.addr = 0,
.size = sizeof(payload),
.prot = VM_PROT_READ | VM_PROT_EXECUTE
};The access control flags are bitwise OR'd together to create a value which will later be passed to our vm_protect calls. If you need R/W on your memory region, VM_PROT_DEFAULT is equivalent to VM_PROT_READ and VM_PROT_WRITE. As a pre-memory injection step, we'll need to get our task so that we can actually inject a payload into an address space. Code: task_t task;
task_for_pid(mach_task_self(), atoi(argv[1]), &task);
mach_vm_allocate(task, &shellcode.addr, shellcode.size, VM_FLAGS_ANYWHERE);With our memory allocated in a given tasks' memory space, we'll go ahead and write our payload. Our payload, again, is notional for this thread. In actuality, it will be assembly that we've written and compiled, and in the string stored as the actual machine code. Code: mach_vm_write(task, shellcode.addr, payload, sizeof(payload));And our last step is a call that will set our access control. If you don't set access control for our virtual memory region, you won't be able to execute it. Code: mach_vm_protect(task, shellcode.addr, shellcode.size, 0, shellcode.prot);Now you've got a basic idea of the calls to inject your code into a task's memory region, you'll need to execute it. This is where your mileage may vary. You may want to spawn a thread which will execute some code you've written, or maybe you'll just want to execute the code as is. That's about it for this thread. Very short, just a few function calls. As my last bit of information, to spawn a thread, you'll probably want to use the thread_create_running method, as it creates optimized threads and spawns them immediately. For M1 you'll need to use the arm_thread_state64_t structure to store information regarding your thread. The __pc and __sp members are your program counter and stack pointers respectively. Specifically for the M1, you'll need to call ptrauth_sign_unauthenticated, and assign that to your program counter. The reason being is because the M1 is actually arm64e, which signs pointers to prevent unauthenticated pointers to be used. Also make sure that you target arm64e when compiling, otherwise you will more than likely get a protection failure error from thread_create_running. For x86 CPUs you'll use the x86_thread_state64_t structure. __rip and __rsp are your instruction pointers and stack pointers respectively. Now you're ready to do some memory injection. The rest is pretty much up to you, as this was only the ground work for doing it. Have fun. RE: Memory Injection on macOS - sunjester - 09-08-2021 I'm going to say thank you even though I have no clue about anything MAC related, it's just nice to see some quality looking posts RE: Memory Injection on macOS - mothered - 09-09-2021 Very well formatted and elaborated. A brilliant tutorial to say the least. RE: Memory Injection on macOS - anhtuan2912 - 09-14-2021 Thank you bro, this is a great tutorial
RE: Memory Injection on macOS - Equinox - 10-07-2021 I know it's been a fair four months since I originally posted this, and also about a month since the last reply, but I did want to update some details that I've found. First and foremost, your compiled binary will fail to run if you target arm64e without the preview ABI on macOS. Apple is a little weird, but oddly clever in that they've programmed and built kernel space programs to target arm64e and user space programs to target arm64. The difference is that arm64e, an extension of arm64, does pointer authentication. Anyways, the reason this is relevant is if you try to do memory injection into a program that runs in kernel space but you've targeted arm64 without pointer authentication for your built binary, you'll be disappointed to find that it does not run. You can't inject into an arm64e task using an arm64 process. That being said, the resolution to this specific problem is to build your binary to target arm64e. BUUUUT if you are using any compiler as of the day I'm posting this reply, to include Apple's own fork of clang, you will find that no matter the program you build to target arm64e, it will fail to execute (note; it will not fail to build. Only execution will fail). If you use your MacBook with stock parameters and such, that is. As of right now, if you wish to build binaries that target arm64e, you need to enable the arm64e preview ABI. This can be done by changing the firmware variable for boot arguments. Code: sudo nvram boot-args="-arm64e_preview_abi"Some information regarding the ABI and system integrity protection can be found here: https://developer.apple.com/documentation/driverkit/debugging_and_testing_system_extensions Anyways, and the last bit of very important information is that if your target machine has system integrity protection enabled then you will not be able to inject into given tasks. At least not kernel space tasks. So this becomes a much more valuable tool for writing programs to modify system behavior, and less for exploiting someone. Unless you could in theory disable their SIP. That being said though, I have yet to test whether or not regular arm64 tasks in the userspace are protected by SIP in this manner. Apple is very particular in saying that SIP (https://developer.apple.com/documentation/security/disabling_and_enabling_system_integrity_protection) does protect the entire system, but it goes on to say that notarized applications and programs are automatically authorized. So in theory, if you codesign a binary with a signature that the system has in its keychain already, then SIP lets it bypass the runtime checks. Just thinking out loud though. And it's all in theory. Haven't really tested it myself, maybe I'll make another update post some time. RE: Memory Injection on macOS - CoolA1d - 11-16-2021 Very good post, thank you for the useful information. This is my first post on the forum so sorry if I'm breaking any rules by posting a month and ten days after the last post. I'm attempting memory injection (more specifically, dylib injection) on the M1 chip, and I'm running into some issues and wanted to see if you guys had any ideas for me. For some background information, the target application that I would like to inject my dylib into is x86_64 and running through rosetta 2. Therefore, I wrote my dylib which I will be injecting into the target as an x86_64 dylib. My injector is also x86_64 for consistency. I don't know if I made this clear, but I'm basically attempting to do what would be on Windows machines termed "manual mapping"; basically dlopen without all the overhead bullshit and from another process. Anyways, I set my program counter to the dylib's entry point and stack pointer to the stack I want in arm_thread_state64_t, and then I call thread_create_running with ARM_THREAD_STATE64 as the second argument. Since my injector is written in x86_64, I just defined the structs myself based on apple's source. I get the following error after running the thread: assertion failed [thread_context != nullptr]: got an exception for an unknown thread (ExceptionServer.cpp:803 handle_exception) Any ideas? I'm assuming something is wrong with how I set up my thread. Or maybe it's not correct to spawn an ARM thread on x86_64 code, I'm not too sure, kind of new to mac's in general. RE: Memory Injection on macOS - Equinox - 11-17-2021 (11-16-2021, 08:20 AM)CoolA1d Wrote: Very good post, thank you for the useful information. This is my first post on the forum so sorry if I'm breaking any rules by posting a month and ten days after the last post. Since you're trying to inject an x86 64 process, I'd say that your best bet is to not use arm threads for it. Rosetta is probably emulated the thread states that x86 does as well. In this case, you'll need to use an x86_thread_state64_t instead of arm_thread_state64_t structure. Implementation will mostly be the same idea: set your pointer counter and stack pointer as appropriate. That's my best idea, I've never tried to inject into an application that's being ran through the emulation layer. Moreover, I'd say you probably are going to have to deal with the hurdle of rossetta2 being sandboxed. I don't know that this is the case, but if each running thread gets its own memory space, it may just be easier to inject the dylib using a preload environment variable (e.g.DYLD_INSERT_LIBRARIES) rather than memory injection. Hope it helps. RE: Memory Injection on macOS - CoolA1d - 11-17-2021 (11-17-2021, 10:53 AM)Equinox Wrote:(11-16-2021, 08:20 AM)CoolA1d Wrote: Very good post, thank you for the useful information. This is my first post on the forum so sorry if I'm breaking any rules by posting a month and ten days after the last post. Thank you, I actually ended up using DYLD_INSERT_LIBRARIES. However I would like to get manual mapping working. I tried using x86_64 threads however thread_create_running is giving a os/kern error - actually as I am writing this I just realized this could be due to SIP? I will try to disable it and return back with info. Edit: Disabled SIP, still getting the following error: (os/kern) invalid argument when calling thread_create_running w/ arg x86_THREAD_STATE64 |