Ptrace pokedata内存注入时出现Input/output error问题求助
问题分析与解决
错误根源
你遇到的ptrace pokedata: Input/output error核心原因是:你在注入器进程内调用mmap分配的内存地址,属于注入器自身的地址空间,目标进程的地址空间中该地址并未被映射,是完全无效的地址。每个进程的虚拟地址空间完全独立,直接用这个地址操作目标进程内存必然失败。
正确的内存注入逻辑
要完成内存注入,必须在目标进程的地址空间内分配可执行内存,再将payload写入该地址,最后修改目标进程的指令指针使其执行payload。
修正后的注入器代码示例
#include <stdio.h> #include <stdlib.h> #include <sys/ptrace.h> #include <sys/wait.h> #include <sys/mman.h> #include <string.h> #include <errno.h> #include <sys/user.h> #include <stdarg.h> // 32位兼容payload:输出"Hello world!"后退出 unsigned char g_payload[] = "\x31\xC0\x31\xDB\x31\xD2\xB0\x04\xB3\x01\x68\x72\x6C\x64\x21\x68\x6F\x20\x57\x6F\x68\x48\x65\x6C\x6C\x89\xE1\xB2\x0C\xCD\x80\x31\xC0\xB0\x01\xCD\x80"; void ft_error(const char *msg) { perror(msg); exit(EXIT_FAILURE); } void info(const char *fmt, ...) { va_list args; va_start(args, fmt); fprintf(stderr, "[*] "); vfprintf(stderr, fmt, args); fprintf(stderr, "\n"); va_end(args); } int main(int argc, char **argv) { pid_t target_pid; struct user_regs_struct regs; void *remote_addr; if (argc != 2) { fprintf(stderr, "Usage: %s <target_pid>\n", argv[0]); exit(EXIT_FAILURE); } target_pid = atoi(argv[1]); // 附加到目标进程 if (ptrace(PTRACE_ATTACH, target_pid, NULL, NULL) < 0) ft_error("ptrace attach"); waitpid(target_pid, NULL, 0); info("Attached to process (%d)", target_pid); // 获取目标进程当前寄存器状态 if (ptrace(PTRACE_GETREGS, target_pid, NULL, ®s) < 0) ft_error("ptrace getregs"); // 在目标进程栈上写入mmap的调用参数(x86_64架构) unsigned long mmap_args[] = { 0, // addr: NULL sizeof(g_payload), // length: payload大小 PROT_READ | PROT_WRITE | PROT_EXEC, // prot: 读写执行权限 MAP_ANONYMOUS | MAP_PRIVATE, // flags: 匿名私有映射 -1, // fd: 无效文件描述符 0 // offset: 0 }; void *stack_addr = (void *)(regs.rsp - sizeof(mmap_args)); // 将参数写入目标进程栈 for (int i = 0; i < sizeof(mmap_args)/sizeof(unsigned long); i++) { if (ptrace(PTRACE_POKEDATA, target_pid, stack_addr + i*sizeof(unsigned long), mmap_args[i]) < 0) ft_error("ptrace pokedata (stack args)"); } // 修改寄存器,让目标进程执行mmap regs.rip = (unsigned long)mmap; // 指令指针指向mmap函数 regs.rsp = (unsigned long)stack_addr; // 栈指针指向参数起始位置 // x86_64调用约定:前6个参数对应rdi/rsi/rdx/r10/r8/r9 regs.rdi = mmap_args[0]; regs.rsi = mmap_args[1]; regs.rdx = mmap_args[2]; regs.r10 = mmap_args[3]; // 注意mmap的第四个参数用r10而非rcx regs.r8 = mmap_args[4]; regs.r9 = mmap_args[5]; if (ptrace(PTRACE_SETREGS, target_pid, NULL, ®s) < 0) ft_error("ptrace setregs (mmap)"); // 执行mmap并等待进程暂停 if (ptrace(PTRACE_CONT, target_pid, NULL, NULL) < 0) ft_error("ptrace continue (mmap)"); waitpid(target_pid, NULL, 0); // 获取mmap返回的内存地址(存在rax寄存器中) if (ptrace(PTRACE_GETREGS, target_pid, NULL, ®s) < 0) ft_error("ptrace getregs (mmap result)"); remote_addr = (void *)regs.rax; if (remote_addr == MAP_FAILED) ft_error("mmap in target process failed"); info("Allocated %ld bytes in target process at %p", sizeof(g_payload), remote_addr); // 将payload写入目标进程的分配内存 for (int i = 0; i < sizeof(g_payload); i += sizeof(long)) { unsigned long data = 0; memcpy(&data, g_payload + i, sizeof(long)); if (ptrace(PTRACE_POKEDATA, target_pid, remote_addr + i, data) < 0) ft_error("ptrace pokedata (payload)"); } info("Payload loaded into process"); // 保存原指令指针,执行完payload后可恢复(可选) unsigned long original_rip = regs.rip; // 修改指令指针指向payload起始地址 regs.rip = (unsigned long)remote_addr; if (ptrace(PTRACE_SETREGS, target_pid, NULL, ®s) < 0) ft_error("ptrace setregs (payload)"); info("Entry point set to payload"); // 执行payload if (ptrace(PTRACE_CONT, target_pid, NULL, NULL) < 0) ft_error("ptrace continue (payload)"); waitpid(target_pid, NULL, 0); // 恢复原指令指针,让目标进程回到原循环(可选) regs.rip = original_rip; if (ptrace(PTRACE_SETREGS, target_pid, NULL, ®s) < 0) ft_error("ptrace setregs (restore)"); // 分离目标进程 if (ptrace(PTRACE_DETACH, target_pid, NULL, NULL) < 0) ft_error("ptrace detach"); info("Detached from process"); exit(EXIT_SUCCESS); }
关键注意事项
- 地址空间独立性:永远不要混淆注入器和目标进程的地址空间,所有操作必须针对目标进程的有效地址。
- 架构适配:上述代码针对x86_64架构,若为32位系统,需调整寄存器(如用eax/ebx等)和调用约定(栈传参)。
- 内存权限:分配内存时必须添加
PROT_EXEC权限,否则目标进程执行payload会触发权限错误。 - ptrace同步:每次调用
PTRACE_CONT后,必须用waitpid等待目标进程暂停,确保后续操作的正确性。
内容的提问来源于stack exchange,提问作者PacoFrost
相关产品推荐
相关产品推荐

