funchook
funchook copied to clipboard
usage on macOS arm64
Hello,
I am interested in getting this fully working on arm64 macOS.
I saw the issues with it mentioned earlier here:
- https://github.com/kubo/funchook/issues/40
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.
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
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.
@kubo this commit adds support for building for apple silicon out of the box: https://github.com/redthing1/rt1_funchook/commit/7d304b8244a0427fdc0a9ac7d2c8eed2c865a2be
I will send it as a PR here too.
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 https://github.com/eclipse-openj9/openj9/issues/11164#issuecomment-730093344 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 asrw-)
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.
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.
https://github.com/kubo/funchook/blob/a9fc560a620abc64729a08a821a8fb18fcb92496/src/os_unix.c#L357-L365
When it fails by EACCES(Permission denied), call mprotect with PROT_READ | PROT_WRITE(rw-).
https://github.com/kubo/funchook/blob/a9fc560a620abc64729a08a821a8fb18fcb92496/src/os_unix.c#L371-L372
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.
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.