From e7d7b21daa7e4fb2b56f91cbeb28b56a4c74a797 Mon Sep 17 00:00:00 2001 From: JingMatrix Date: Wed, 28 Jan 2026 14:06:38 +0100 Subject: [PATCH] Fix ARM ptrace compatibility and improve remote call safety (#94) - Implement fallbacks to `PTRACE_GETREGS` and `PTRACE_SETREGS` for 32-bit ARM (`__arm__`). Some kernels return `EIO` or `EINVAL` when attempting to access `NT_PRSTATUS` via `PTRACE_GETREGSET`/`PTRACE_SETREGSET`. - Update `transfer_fd_to_remote` to use `libc_return_addr` instead of `0` as the return address during the `recvmsg` split-call. This ensures the remote process stops predictably at a known non-executable location rather than relying on a potentially unsafe jump to `0x0`. - Clarify comments regarding i386 argument passing in `utils.cpp`. Correctly note that a linear `write_proc` starting at the new SP matches the `cdecl` Right-to-Left memory layout (since stacks grow downwards while memory writes move upwards), removing the suggestion that arguments needed reversing. --- ...kotlin-compiler-7816880656539169480.salive | 0 app/src/main/cpp/inject/main.cpp | 4 +-- app/src/main/cpp/inject/utils.cpp | 29 ++++++++++++------- 3 files changed, 20 insertions(+), 13 deletions(-) delete mode 100644 .kotlin/sessions/kotlin-compiler-7816880656539169480.salive diff --git a/.kotlin/sessions/kotlin-compiler-7816880656539169480.salive b/.kotlin/sessions/kotlin-compiler-7816880656539169480.salive deleted file mode 100644 index e69de29..0000000 diff --git a/app/src/main/cpp/inject/main.cpp b/app/src/main/cpp/inject/main.cpp index 4042f0d..3ffc75c 100644 --- a/app/src/main/cpp/inject/main.cpp +++ b/app/src/main/cpp/inject/main.cpp @@ -374,7 +374,7 @@ static std::optional transfer_fd_to_remote(int pid, const char *lib_path, s // 12. Initiate the remote recvmsg call. This will block the remote process. args = {static_cast(remote_fd), remote_hdr, MSG_WAITALL}; - if (!remote_pre_call(pid, regs, reinterpret_cast(funcs.recvmsg_addr), 0, args)) { + if (!remote_pre_call(pid, regs, reinterpret_cast(funcs.recvmsg_addr), libc_return_addr, args)) { LOGE("Failed to initiate remote recvmsg call."); close_remote(remote_fd); return std::nullopt; @@ -414,7 +414,7 @@ static std::optional transfer_fd_to_remote(int pid, const char *lib_path, s // 15. Complete the remote recvmsg call. This will retrieve the return value. auto recvmsg_result = - static_cast(remote_post_call(pid, regs, 0)); // No specific expected return address for recvmsg + static_cast(remote_post_call(pid, regs, libc_return_addr)); if (recvmsg_result == -1) { errno = get_remote_errno(); PLOGE("Remote recvmsg call failed."); diff --git a/app/src/main/cpp/inject/utils.cpp b/app/src/main/cpp/inject/utils.cpp index 3282ca8..bda8a26 100644 --- a/app/src/main/cpp/inject/utils.cpp +++ b/app/src/main/cpp/inject/utils.cpp @@ -263,7 +263,14 @@ bool get_regs(int pid, struct user_regs_struct ®s) { struct iovec reg_iov = {.iov_base = ®s, .iov_len = sizeof(struct user_regs_struct)}; if (ptrace(PTRACE_GETREGSET, pid, NT_PRSTATUS, ®_iov) == -1) { PLOGE("Failed to get register set for PID %d.", pid); +#if defined(__arm__) + if (ptrace(PTRACE_GETREGS, pid, 0, ®s) == -1) { + PLOGE("Fallback to PTRACE_GETREGS failed."); + return false; + } +#else return false; +#endif } #else # error "Unsupported architecture for register access in get_regs." @@ -296,7 +303,14 @@ bool set_regs(int pid, struct user_regs_struct ®s) { struct iovec reg_iov = {.iov_base = ®s, .iov_len = sizeof(struct user_regs_struct)}; if (ptrace(PTRACE_SETREGSET, pid, NT_PRSTATUS, ®_iov) == -1) { PLOGE("Failed to set register set for PID %d.", pid); +#if defined(__arm__) + if (ptrace(PTRACE_SETREGS, pid, 0, ®s) == -1) { + PLOGE("Fallback to PTRACE_SETREGS failed."); + return false; + } +#else return false; +#endif } #else # error "Unsupported architecture for register access in set_regs." @@ -588,17 +602,10 @@ bool remote_pre_call(int pid, struct user_regs_struct ®s, uintptr_t func_addr size_t stack_args_size = args.size() * sizeof(uintptr_t); align_stack(regs, stack_args_size); - // Push all arguments onto the stack (order is important if ABI is right-to-left push). - // The current implementation writes args.data() directly, - // assuming it's already in the correct order for push. - // For cdecl, arguments are pushed right-to-left. - // A vector `args = {A, B, C}` means A is arg1, B is arg2 etc. - // So, `C` should be pushed first, then `B`, then `A`. - // `write_proc` copies linearly. - // This implies `args` should be pre-reversed for cdecl. - // For simplicity, we assume the remote function is compatible with how it's pushed, - // or that it's variadic where order doesn't matter for first args. - // A robust i386 implementation would need to push args in reverse order. + // i386 cdecl expects arguments pushed Right-to-Left (stack grows down). + // Since `write_proc` writes to increasing addresses (up), a linear write + // starting at the new SP places the first argument at the lowest address. + // This matches the ABI memory layout without needing to reverse the vector. if (write_proc(pid, static_cast(regs.REG_SP), args.data(), stack_args_size) != static_cast(stack_args_size)) { LOGE("Failed to push arguments for i386 remote call.");