Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

usage on macOS arm64 #53

Open
redthing1 opened this issue Apr 5, 2024 · 7 comments
Open

usage on macOS arm64 #53

redthing1 opened this issue Apr 5, 2024 · 7 comments

Comments

@redthing1
Copy link

redthing1 commented Apr 5, 2024

Hello,

I am interested in getting this fully working on arm64 macOS.

I saw the issues with it mentioned earlier here:

I am building it on my machine with the following procedure:

CC=gcc-13 CXX=g++-13 cmake -G Ninja -DFUNCHOOK_DISASM=capstone -DCMAKE_BUILD_TYPE=Release -DFUNCHOOK_BUILD_TESTS=0 ..
ninja

Out of the box, it fails with this error:

/code/funchook/src/prehook-arm64-gas.S:4:2: error: unknown directive
 .type funchook_hook_caller_asm, %function
 ^

I was indeed able to build the main library, but I had to make some changes to the assembly:

diff --git a/src/prehook-arm64-gas.S b/src/prehook-arm64-gas.S
index de39edb..dbdb717 100644
--- a/src/prehook-arm64-gas.S
+++ b/src/prehook-arm64-gas.S
@@ -1,8 +1,8 @@
        .arch armv8-a
        .text
-       .globl  funchook_hook_caller_asm
-       .type   funchook_hook_caller_asm, %function
-funchook_hook_caller_asm:
+       .globl  _funchook_hook_caller_asm
+       ; .type _funchook_hook_caller_asm, %function
+_funchook_hook_caller_asm:
        .cfi_startproc
        // save frame pointer (x29) and link register (x30).
        stp x29, x30, [sp, -0xe0]!
@@ -46,7 +46,7 @@ funchook_hook_caller_asm:
        // 2nd arg: frame pointer
        mov x1, x29
        // call funchook_hook_caller
-       bl  funchook_hook_caller
+       bl  _funchook_hook_caller
        mov x9, x0
        // restore registers
        ldp x0, x1, [sp, 0x10]

This is needed to get it to link for macOS.
It seems like the supported syntax for macOS arm64 assembler is different from standard arm64 GAS syntax, so we may have to add an additional file with these changes?

I believe it should be possible to get build with tests working too, I will try that.

@redthing1
Copy link
Author

Tests also build on macOS, with the following assembly syntax fixes:

diff --git a/test/libfunchook_test_aarch64_gas.S b/test/libfunchook_test_aarch64_gas.S
index 2c0025b..6a0d9b8 100644
--- a/test/libfunchook_test_aarch64_gas.S
+++ b/test/libfunchook_test_aarch64_gas.S
@@ -7,39 +7,39 @@
 test_data:
 	.8byte 0x1020304050607080, 0x0102030405060708
 
-call_get_val_in_dll:
-	.global	call_get_val_in_dll
-	.type	call_get_val_in_dll, %function
+_call_get_val_in_dll:
+	.global	_call_get_val_in_dll
+	; .type	call_get_val_in_dll, %function
 	stp	x29, x30, [sp, -16]!
-	bl	get_val_in_dll
+	bl	_get_val_in_dll
 	ldp	x29, x30, [sp], 16
 	ret
 
-jump_get_val_in_dll:
-	.global	jump_get_val_in_dll
-	.type	jump_get_val_in_dll, %function
-	b	get_val_in_dll
+_jump_get_val_in_dll:
+	.global	_jump_get_val_in_dll
+	; .type	jump_get_val_in_dll, %function
+	b	_get_val_in_dll
 
-arm64_test_adr:
-	.global	arm64_test_adr
-	.type	arm64_test_adr, %function
+_arm64_test_adr:
+	.global	_arm64_test_adr
+	; .type	arm64_test_adr, %function
 	adr x9, test_data
 	ldr x9, [x9]
 	add x0, x0, x9
 	ret
 
-arm64_test_beq:
-	.global	arm64_test_beq
-	.type	arm64_test_beq, %function
+_arm64_test_beq:
+	.global	_arm64_test_beq
+	; .type	arm64_test_beq, %function
 	adds x0, x0, 1
 	beq 1f
 	sub x0, x0, 2
 1:
 	ret
 
-arm64_test_bne:
-	.global	arm64_test_bne
-	.type	arm64_test_bne, %function
+_arm64_test_bne:
+	.global	_arm64_test_bne
+	; .type	arm64_test_bne, %function
 	adds x0, x0, 1
 	bne 1f
 	sub x0, x0, 2
@@ -47,57 +47,57 @@ arm64_test_bne:
 	ret
 
 
-arm64_test_cbnz:
-	.global	arm64_test_cbnz
-	.type	arm64_test_cbnz, %function
+_arm64_test_cbnz:
+	.global	_arm64_test_cbnz
+	; .type	arm64_test_cbnz, %function
 	cbnz x0, 1f
 	add x0, x0, 2
 1:
 	sub x0, x0, 1
 	ret
 
-arm64_test_cbz:
-	.global	arm64_test_cbz
-	.type	arm64_test_cbz, %function
+_arm64_test_cbz:
+	.global	_arm64_test_cbz
+	; .type	arm64_test_cbz, %function
 	cbz x0, 1f
 	add x0, x0, 2
 1:
 	sub x0, x0, 1
 	ret
 
-arm64_test_ldr_w:
-	.global	arm64_test_ldr_w
-	.type	arm64_test_ldr_w, %function
+_arm64_test_ldr_w:
+	.global	_arm64_test_ldr_w
+	; .type	arm64_test_ldr_w, %function
 	ldr w9, test_data
 	add x0, x0, x9
 	ret
 
-arm64_test_ldr_x:
-	.global	arm64_test_ldr_x
-	.type	arm64_test_ldr_x, %function
+_arm64_test_ldr_x:
+	.global	_arm64_test_ldr_x
+	; .type	arm64_test_ldr_x, %function
 	ldr x9, test_data
 	add x0, x0, x9
 	ret
 
-arm64_test_ldr_s:
-	.global	arm64_test_ldr_s
-	.type	arm64_test_ldr_s, %function
+_arm64_test_ldr_s:
+	.global	_arm64_test_ldr_s
+	; .type	arm64_test_ldr_s, %function
 	ldr s16, test_data
 	mov w9, v16.s[0]
 	add x0, x0, x9
 	ret
 
-arm64_test_ldr_d:
-	.global	arm64_test_ldr_d
-	.type	arm64_test_ldr_d, %function
+_arm64_test_ldr_d:
+	.global	_arm64_test_ldr_d
+	; .type	arm64_test_ldr_d, %function
 	ldr d16, test_data
 	mov x9, v16.d[0]
 	add x0, x0, x9
 	ret
 
-arm64_test_ldr_q:
-	.global	arm64_test_ldr_q
-	.type	arm64_test_ldr_q, %function
+_arm64_test_ldr_q:
+	.global	_arm64_test_ldr_q
+	; .type	arm64_test_ldr_q, %function
 	ldr q16, test_data
 	mov x9, v16.d[0]
 	add x0, x0, x9
@@ -105,33 +105,33 @@ arm64_test_ldr_q:
 	add x0, x0, x9
 	ret
 
-arm64_test_prfm:
-	.global	arm64_test_prfm
-	.type	arm64_test_prfm, %function
+_arm64_test_prfm:
+	.global	_arm64_test_prfm
+	; .type	arm64_test_prfm, %function
 	prfm pldl1keep, test_data
 	ldr x9, test_data
 	add x0, x0, x9
 	ret
 
-arm64_test_ldrsw:
-	.global	arm64_test_ldrsw
-	.type	arm64_test_ldrsw, %function
+_arm64_test_ldrsw:
+	.global	_arm64_test_ldrsw
+	; .type	arm64_test_ldrsw, %function
 	ldrsw x9, 1f
 	add x0, x0, x9
 	ret
 
-arm64_test_tbnz:
-	.global	arm64_test_tbnz
-	.type	arm64_test_tbnz, %function
+_arm64_test_tbnz:
+	.global	_arm64_test_tbnz
+	; .type	arm64_test_tbnz, %function
 	tbnz x0, 32, 1f
 	add x0, x0, 2
 1:
 	sub x0, x0, 1
 	ret
 
-arm64_test_tbz:
-	.global	arm64_test_tbz
-	.type	arm64_test_tbz, %function
+_arm64_test_tbz:
+	.global	_arm64_test_tbz
+	; .type	arm64_test_tbz, %function
 	tbz x0, 32, 1f
 	add x0, x0, 2
 1:

However, as was noted before, the test does not seem to work. I will see if I can get it working:

❯ ./test/funchook_test_shared
[1] test_funchook_int: get_val_in_exe
ERROR: failed to install hook get_val_in_exe (Failed to unprotect memory 0x102764000 (size=16384, prot=read,write,exec) <- 0x102765230 (size=8, error=Permission denied))
[2] test_funchook_int: get_val_in_dll
[3] test_funchook_int: call_get_val_in_dll
[4] test_funchook_int: jump_get_val_in_dll
[5] test_funchook_uint64: arm64_test_adr
[6] test_funchook_uint64: arm64_test_beq
[7] test_funchook_uint64: arm64_test_bne
[8] test_funchook_uint64: arm64_test_cbnz
[9] test_funchook_uint64: arm64_test_cbz
[10] test_funchook_uint64: arm64_test_ldr_w
[11] test_funchook_uint64: arm64_test_ldr_x
[12] test_funchook_uint64: arm64_test_ldrsw
[13] test_funchook_uint64: arm64_test_ldr_s
[14] test_funchook_uint64: arm64_test_ldr_d
[15] test_funchook_uint64: arm64_test_ldr_q
[16] test_funchook_uint64: arm64_test_prfm
[17] test_funchook_uint64: arm64_test_tbnz
[18] test_funchook_uint64: arm64_test_tbz
[19] test_hook_open_and_fopen
ERROR: failed to install open and fopen hooks. (Failed to unprotect memory 0x180ecc000 (size=16384, prot=read,write) <- 0x180ececa4 (size=8, error=Permission denied))
[20] test_hook_many_funcs
........................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................
ERROR: failed to install hooks (Failed to unprotect memory 0x102770000 (size=16384, prot=read,write) <- 0x102770dd0 (size=8, error=Permission denied))
[21] test_prehook: func_info
[22] test_prehook: long_args
[23] test_prehook: double_args
[24] test_prehook: mixed_args
[25] test_prehook: fastcall_args
[26] test_cpp: thiscall
[27] test_cpp: exception in prehook
3 of 27 tests are failed.
ERROR

@redthing1 redthing1 changed the title building on arm64 macOS without tests usage on macOS arm64 Apr 5, 2024
@redthing1
Copy link
Author

I tried the scripts from here to add the debug entitlement, but that did not seem to make a difference. Perhaps there's another entitlement, I need to look into it more.

@redthing1
Copy link
Author

@kubo this commit adds support for building for apple silicon out of the box: redthing1@7d304b8

I will send it as a PR here too.

@redthing1
Copy link
Author

The reason it does not work right now, is due to these changes in macOS for Apple Silicon:
https://developer.apple.com/documentation/apple-silicon/porting-just-in-time-compilers-to-apple-silicon
eclipse-openj9/openj9#11164 (comment)
https://news.ycombinator.com/item?id=29714587
https://developer.apple.com/forums/thread/650931

Apple Silicon macOS enforces W^X.
It seems that what is not possible:

  • protecting ANY region as rwx
  • protecting the currently executing region as rw- (because it's already executing, the kernel refuses to let us set it as rw-)

One workaround I have found, is that you can protect the main executable as rw- and edit its code, if you are currently executing in another library.

kubo added a commit that referenced this issue Apr 6, 2024
@kubo
Copy link
Owner

kubo commented Apr 6, 2024

One workaround I have found, is that you can protect the main executable as rw-

It has been done already.

Call mprotect with PROT_READ | PROT_WRITE | PROT_EXEC(rwx) at first.

funchook/src/os_unix.c

Lines 357 to 365 in a9fc560

static int prot = PROT_READ | PROT_WRITE | PROT_EXEC;
char errbuf[128];
size_t saddr = ROUND_DOWN((size_t)start, page_size);
int rv;
mstate->addr = (void*)saddr;
mstate->size = len + (size_t)start - saddr;
mstate->size = ROUND_UP(mstate->size, page_size);
rv = mprotect(mstate->addr, mstate->size, prot);

When it fails by EACCES(Permission denied), call mprotect with PROT_READ | PROT_WRITE(rw-).

funchook/src/os_unix.c

Lines 371 to 372 in a9fc560

if (rv == -1 && errno == EACCES && (prot & PROT_EXEC)) {
rv = mprotect(mstate->addr, mstate->size, PROT_READ | PROT_WRITE);

@redthing1
Copy link
Author

Interestingly, I sometimes observe behavior where calling mprotect alone fails, but calling mach_vm_protect followed by mprotect succeeds. I am still not quite sure as to the cause and am still trying to reproduce it.
Additionally I am observing that after changing perms to rw-, the program segfaults immediately after a function is called that jumps to this non executable memory.

@redthing1
Copy link
Author

redthing1 commented Apr 6, 2024

So it looks like it is never possible to unprotect the region of memory containing open/fopen, almost certainly due to SIP. When dumping the region info using mach_vm_region, I see that those functions are located in a ~1.4 GB region of address space, which presumably is a region for SIP-protected standard library code.
However, other than directly trying to hook core libraries, I find funchook to work generally.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants