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.
This commit is contained in:
JingMatrix
2026-01-28 14:08:28 +01:00
parent c29bc35a36
commit e7d7b21daa
3 changed files with 20 additions and 13 deletions
+2 -2
View File
@@ -374,7 +374,7 @@ static std::optional<int> transfer_fd_to_remote(int pid, const char *lib_path, s
// 12. Initiate the remote recvmsg call. This will block the remote process. // 12. Initiate the remote recvmsg call. This will block the remote process.
args = {static_cast<uintptr_t>(remote_fd), remote_hdr, MSG_WAITALL}; args = {static_cast<uintptr_t>(remote_fd), remote_hdr, MSG_WAITALL};
if (!remote_pre_call(pid, regs, reinterpret_cast<uintptr_t>(funcs.recvmsg_addr), 0, args)) { if (!remote_pre_call(pid, regs, reinterpret_cast<uintptr_t>(funcs.recvmsg_addr), libc_return_addr, args)) {
LOGE("Failed to initiate remote recvmsg call."); LOGE("Failed to initiate remote recvmsg call.");
close_remote(remote_fd); close_remote(remote_fd);
return std::nullopt; return std::nullopt;
@@ -414,7 +414,7 @@ static std::optional<int> transfer_fd_to_remote(int pid, const char *lib_path, s
// 15. Complete the remote recvmsg call. This will retrieve the return value. // 15. Complete the remote recvmsg call. This will retrieve the return value.
auto recvmsg_result = auto recvmsg_result =
static_cast<ssize_t>(remote_post_call(pid, regs, 0)); // No specific expected return address for recvmsg static_cast<ssize_t>(remote_post_call(pid, regs, libc_return_addr));
if (recvmsg_result == -1) { if (recvmsg_result == -1) {
errno = get_remote_errno(); errno = get_remote_errno();
PLOGE("Remote recvmsg call failed."); PLOGE("Remote recvmsg call failed.");
+18 -11
View File
@@ -263,7 +263,14 @@ bool get_regs(int pid, struct user_regs_struct &regs) {
struct iovec reg_iov = {.iov_base = &regs, .iov_len = sizeof(struct user_regs_struct)}; struct iovec reg_iov = {.iov_base = &regs, .iov_len = sizeof(struct user_regs_struct)};
if (ptrace(PTRACE_GETREGSET, pid, NT_PRSTATUS, &reg_iov) == -1) { if (ptrace(PTRACE_GETREGSET, pid, NT_PRSTATUS, &reg_iov) == -1) {
PLOGE("Failed to get register set for PID %d.", pid); PLOGE("Failed to get register set for PID %d.", pid);
#if defined(__arm__)
if (ptrace(PTRACE_GETREGS, pid, 0, &regs) == -1) {
PLOGE("Fallback to PTRACE_GETREGS failed.");
return false;
}
#else
return false; return false;
#endif
} }
#else #else
# error "Unsupported architecture for register access in get_regs." # error "Unsupported architecture for register access in get_regs."
@@ -296,7 +303,14 @@ bool set_regs(int pid, struct user_regs_struct &regs) {
struct iovec reg_iov = {.iov_base = &regs, .iov_len = sizeof(struct user_regs_struct)}; struct iovec reg_iov = {.iov_base = &regs, .iov_len = sizeof(struct user_regs_struct)};
if (ptrace(PTRACE_SETREGSET, pid, NT_PRSTATUS, &reg_iov) == -1) { if (ptrace(PTRACE_SETREGSET, pid, NT_PRSTATUS, &reg_iov) == -1) {
PLOGE("Failed to set register set for PID %d.", pid); PLOGE("Failed to set register set for PID %d.", pid);
#if defined(__arm__)
if (ptrace(PTRACE_SETREGS, pid, 0, &regs) == -1) {
PLOGE("Fallback to PTRACE_SETREGS failed.");
return false;
}
#else
return false; return false;
#endif
} }
#else #else
# error "Unsupported architecture for register access in set_regs." # error "Unsupported architecture for register access in set_regs."
@@ -588,17 +602,10 @@ bool remote_pre_call(int pid, struct user_regs_struct &regs, uintptr_t func_addr
size_t stack_args_size = args.size() * sizeof(uintptr_t); size_t stack_args_size = args.size() * sizeof(uintptr_t);
align_stack(regs, stack_args_size); align_stack(regs, stack_args_size);
// Push all arguments onto the stack (order is important if ABI is right-to-left push). // i386 cdecl expects arguments pushed Right-to-Left (stack grows down).
// The current implementation writes args.data() directly, // Since `write_proc` writes to increasing addresses (up), a linear write
// assuming it's already in the correct order for push. // starting at the new SP places the first argument at the lowest address.
// For cdecl, arguments are pushed right-to-left. // This matches the ABI memory layout without needing to reverse the vector.
// 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.
if (write_proc(pid, static_cast<uintptr_t>(regs.REG_SP), args.data(), stack_args_size) != if (write_proc(pid, static_cast<uintptr_t>(regs.REG_SP), args.data(), stack_args_size) !=
static_cast<ssize_t>(stack_args_size)) { static_cast<ssize_t>(stack_args_size)) {
LOGE("Failed to push arguments for i386 remote call."); LOGE("Failed to push arguments for i386 remote call.");