;)
What I want to ask is why modifying the .text section in the mach-o executable file (ARM64) will cause runtime crashes?
I use some executable file editor (eg: MachOView) to do this。
before modifying, the .text section is read + execute:
after modifying, the .text section is read + writable + execute:
Can you see that in the ARM64 format mach-o executable file, I added a writable attribute to the .text section, but the program crashes immediately at runtime.
Debugging with lldb, error occurred at the main entry point, prompting EXC_ BAD_ ACCESS:
% lldb arm64
(lldb) target create "arm64"
Current executable set to '/Users/hopy/src/asm/arm64' (arm64).
(lldb) b main
Breakpoint 1: where = arm64`main, address = 0x0000000100003f10
(lldb) r
Process 2663 launched: '/Users/hopy/src/asm/arm64' (arm64)
Process 2663 stopped
* thread #1, queue = 'com.apple.main-thread', stop reason = EXC_BAD_ACCESS (code=2, address=0x100003f10)
frame #0: 0x0000000100003f10 arm64`main
arm64`main:
-> 0x100003f10 <+0>: sub sp, sp, #0x20
0x100003f14 <+4>: stp x29, x30, [sp, #0x10]
0x100003f18 <+8>: add x29, sp, #0x10
0x100003f1c <+12>: ldr x1, #0x8c ; my_data + 8
Target 0: (arm64) stopped.
Do I need to modify other parts of the mach-o file? If so, what further modifications are needed? Thank you very much! ;)
If the .text section cannot be made writable through editing tools, how can I make it writable and executable during compilation or linking (I have asm source code)?
Update:
The reason why I want to modify .text to be writable is that the executable file generated by the following code will call fixupPage64() when the system loads to fix my_data’s address(eg: 0x3fa8 to 100003fa8):
.text
.globl _main
.p2align 2
_main:
func_constructor
ldr x2,=my_data
func_destructor
ret
.p2align 3
my_data: .quad 0x12345678aabbccdd
Due to the fact that .text is not writable, it can cause the executable file to be killed, prompting an error of 'bus error'
So how to solve the problem of ldr=Label_Address causing executable files to crash during runtime?
Thanks!
You broke the code signature. If you only have the implicit signature that was inserted by the linker, you can re-sign the binary with:
codesign -s - [your binary]
But your change won't actually make a difference. On arm64 macOS, if a memory region has execute permission, then write permission will be removed from it. You cannot have rwx permissions on arm64 macOS. Even if you mmap()
with MAP_JIT
, you need to toggle between r-x
and rw-
with pthread_jit_write_protect_np()
.1
If you want to patch code pages that aren't mapped with MAP_JIT
, you need to:
rw-
.r-x
.However, you cannot do this with any code page that you're currently running from, for obvious reasons (and you cannot flip JIT pages to rw-
either if you're running from them).
So in this case, you need to first remap the page somewhere else, then do the steps above, then remap it back over the original page, so that the change there is atomic.
#include <stdio.h>
#include <mach/mach.h>
#include <mach/mach_vm.h>
#include <libkern/OSCacheControl.h>
unsigned int somefunc(void);
__asm__
(
".p2align 2\n"
".globl _somefunc\n"
"_somefunc:\n"
" mov w0, 0x123\n"
" ret\n"
);
int main(void)
{
printf("Start\n");
printf("somefunc: 0x%x\n", somefunc());
// 1. Remap the page somewhere else
mach_vm_address_t addr = (mach_vm_address_t)&somefunc;
mach_vm_address_t remap;
vm_prot_t cur, max;
kern_return_t ret = mach_vm_remap(mach_task_self(), &remap, 0x8, 0, VM_FLAGS_ANYWHERE | VM_FLAGS_RETURN_DATA_ADDR, mach_task_self(), addr, FALSE, &cur, &max, VM_INHERIT_NONE);
printf("mach_vm_remap: %s\n", mach_error_string(ret));
// 2. Reprotect the page to rw- (needs VM_PROT_COPY because the max protection is currently r-x)
ret = mach_vm_protect(mach_task_self(), remap, 0x8, FALSE, VM_PROT_READ | VM_PROT_WRITE | VM_PROT_COPY);
printf("mach_vm_protect: %s\n", mach_error_string(ret));
// 3. Write the changes
((uint32_t*)remap)[0] = 0x52800820; // mov w0, 0x41
((uint32_t*)remap)[1] = 0xd65f03c0; // ret
// 4. Flush the data cache
sys_dcache_flush((void*)addr, 0x8);
// 5. Reprotect the page to r-x
ret = mach_vm_protect(mach_task_self(), remap, 0x8, FALSE, VM_PROT_READ | VM_PROT_EXECUTE);
printf("mach_vm_protect: %s\n", mach_error_string(ret));
// 6. Invalidate the instruction cache
sys_icache_invalidate((void*)addr, 0x8);
// 7. Remap the page back over the original
ret = mach_vm_remap(mach_task_self(), &addr, 0x8, 0, VM_FLAGS_OVERWRITE | VM_FLAGS_RETURN_DATA_ADDR, mach_task_self(), remap, FALSE, &cur, &max, VM_INHERIT_NONE);
printf("mach_vm_remap: %s\n", mach_error_string(ret));
printf("somefunc: 0x%x\n", somefunc());
printf("End\n");
return 0;
}
1 There is an exception to this: arm64 macOS in virtual machines powered by Virtualization.framework. Those don't get Apple Silicon ISA extensions and thus do have rwx
JIT regions.