-
-
[原创]窥探Proot原理
-
发表于: 2025-3-5 21:07 2865
-
一、环境准备与源码获取
引导视频(大佬的视频):
https://www.bilibili.com/video/BV1GJ4m1L74L/?vd_source=75367b241cddc050d313c64bd570cf39
编译教程:
https://blog.csdn.net/Denny_Chen_/article/details/137055642
源码地址:
https://github.com/proot-me/proot
理解基础理论很重要!
ptrace 的基本功能
跟踪进程行为:允许一个进程(跟踪者,tracer)观察和控制另一个进程(被跟踪者,tracee)的执行。
读写内存和寄存器:可以修改被跟踪进程的内存和寄存器状态。
拦截信号和系统调用:在系统调用(syscall)或信号(如
SIGTRAP
)触发时暂停被跟踪进程,供跟踪者处理。
常用的 ptrace 请求
通过 ptrace(request, pid, addr, data)
发送请求,常见 request
参数包括:
PTRACE_TRACEME
被跟踪进程主动声明自己被父进程跟踪(常用于子进程)。PTRACE_ATTACH
跟踪者附加到正在运行的进程(需权限)。PTRACE_DETACH
解除跟踪,恢复被跟踪进程执行。PTRACE_PEEKTEXT
/PTRACE_POKETEXT
读/写被跟踪进程的内存。PTRACE_GETREGS
/PTRACE_SETREGS
读/写被跟踪进程的寄存器(如eax
,ebx
等)。PTRACE_SYSCALL
使被跟踪进程在下一个系统调用入口和退出时暂停(用于拦截系统调用)。PTRACE_CONT
恢复被跟踪进程的执行。
系统调用拦截
拦截系统调用:通过
PTRACE_SYSCALL
,跟踪者可以在被跟踪进程执行系统调用前(入口)和系统调用后(退出)获取控制权。修改参数/返回值:
在入口阶段:可修改系统调用的参数(通过寄存器,如
eax
存储系统调用号,ebx
,ecx
等存储参数)。在退出阶段:可修改系统调用的返回值(如模拟成功或失败)。
开发涉及到的关键API都是直接参考官方文档:
https://man7.org/linux/man-pages/man2/ptrace.2.html
基础理论 到这了 ! 我们来进行实践:
Demo 打印主进程 系统openat调用 打印参数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 | #include <string> #include <unistd.h> #include <android/log.h> #include <linux/ptrace.h> #include <sys/ptrace.h> #include <sys/wait.h> #include <sys/reg.h> #include <signal.h> #include <sys/types.h> #include <sys/wait.h> #include <unistd.h> #include <sys/prctl.h> #include <fcntl.h> #include <linux/filter.h> #include <linux/seccomp.h> #include <linux/elf.h> #include "Syscall_arm64.h" #define LOG_TAG "NDK_LOG" #define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG, LOG_TAG, __VA_ARGS__) #define LOGE(...) __android_log_print(ANDROID_LOG_ERROR, LOG_TAG, __VA_ARGS__) void test(); void ptrace_attach_pid( int pid); void install_seccomp_filter(){ struct sock_filter filter[] = { BPF_STMT(BPF_LD | BPF_W | BPF_ABS, (offsetof( struct seccomp_data, nr))), BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, __NR_openat, 0, 1), BPF_STMT(BPF_RET | BPF_K, SECCOMP_RET_TRACE), BPF_STMT(BPF_RET | BPF_K, SECCOMP_RET_ALLOW), }; struct sock_fprog prog = { .len = (unsigned short ) ( sizeof (filter) / sizeof (filter[0])), .filter = filter, }; if (prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0) == -1) { LOGD( "prctl(PR_SET_NO_NEW_PRIVS)" ); } if (prctl(PR_SET_SECCOMP, SECCOMP_MODE_FILTER, &prog) == -1) { LOGD( "when setting seccomp filter" ); } } void test() { LOGD( "test begin ..." ); int mainPid = getpid(); int childPid = fork(); switch (childPid) { case -1: LOGE( "fork fail" ); break ; case 0: LOGD( "子进程 逻辑 %d" , childPid); LOGD( "mainPid=%d" , mainPid); ptrace_attach_pid(mainPid); break ; default : LOGD( "主进程 逻辑 %d" , mainPid); install_seccomp_filter(); kill(getpid(), SIGSTOP); kill(getpid(), SIGCONT); LOGD( "waitpid" ); break ; } LOGD( "test end ..." ); } void ptrace_attach_pid( int pid) { int status; if (ptrace(PTRACE_ATTACH,pid,0,0) == -1){ LOGE( "ptrace attach fail" ); } //设置 ptrace 选项 const unsigned long default_ptrace_options = ( PTRACE_O_TRACESYSGOOD | //当被跟踪的进程产生一个系统调用时,会发送 SIGTRAP 信号,并且 siginfo 结构中的 si_code 会被设置为 SYS_SECCOMP。 PTRACE_O_TRACEFORK | //允许跟踪进程的创建和克隆事件 PTRACE_O_TRACEVFORK | //允许跟踪进程的创建和克隆事件 PTRACE_O_TRACEVFORKDONE | //允许跟踪进程的创建和克隆事件 PTRACE_O_TRACECLONE | //允许跟踪进程的创建和克隆事件 PTRACE_O_TRACEEXEC | //允许跟踪进程的创建和克隆事件 PTRACE_O_TRACEEXIT ); //当被跟踪的进程退出时,会触发跟踪事件 int state; state = ptrace(PTRACE_SETOPTIONS, pid, 0, default_ptrace_options | PTRACE_O_TRACESECCOMP); //这个选项特别重要,它允许跟踪 seccomp 过滤器触发的事件。当被跟踪的进程因为 seccomp 规则而触发一个 SIGSYS 信号时,会发送一个 SIGTRAP 信号,并且 siginfo 结构中的 si_code 会被设置为 SYS_SECCOMP。 if (state == -1){ LOGE( "PTRACE_SETOPTIONS failed" ); } waitpid(pid, &status, 0); if ( WIFSTOPPED(status) && WSTOPSIG(status) == SIGSTOP){ while (1){ struct user_regs_struct regs; struct iovec io; io.iov_base = ®s; io.iov_len = sizeof (regs); ptrace(PTRACE_CONT, pid, 0, 0); waitpid(pid, &status, 0); ptrace(PTRACE_GETREGSET, pid, ( void *)NT_PRSTATUS, &io); if (status >> 8 == (SIGTRAP | (PTRACE_EVENT_SECCOMP << 8)) ){ LOGD( "seccomp svc 中断调用号 : %lx" ,regs.regs[8]); LOGD( "seccomp svc 寄存器0内容: %lx" ,regs.regs[0]); LOGD( "seccomp svc 寄存器1内容: %lx" ,regs.regs[1]); LOGD( "seccomp svc 寄存器2内容: %lx" ,regs.regs[2]); LOGD( "seccomp svc 路径内容: %s" ,regs.regs[1]); } if (WIFEXITED(status)){ break ; } } } } |
成功的在安卓实现了 使用ptrace 对 主进程的openat的系统函数进行监控
开始从源码分析:
首先还是从main开始分析:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 | /* 主函数:程序入口点 * @argc 参数数量 * @argv 参数值数组 * @return 程序退出状态 */ int main( int argc, char * const argv[]) { Tracee *tracee; // 被跟踪进程的上下文对象 int status; // 操作状态返回值 /* 配置内存分配器 - 启用内存泄漏报告 */ talloc_enable_leak_report(); /* TALLOC 2.0+ 版本需要将日志输出到标准错误 */ #if defined(TALLOC_VERSION_MAJOR) && TALLOC_VERSION_MAJOR >= 2 talloc_set_log_stderr(); #endif /* 预创建第一个被跟踪进程(初始pid设为0) */ tracee = get_tracee(NULL, 0, true ); if (tracee == NULL) goto error; tracee->pid = getpid(); // 设置实际进程ID /* 解析配置参数 */ status = parse_config(tracee, argc, argv); if (status < 0) goto error; /* 启动被跟踪进程 */ status = launch_process(tracee, &argv[status]); if (status < 0) { print_execve_help(tracee, tracee->exe, status); // 执行失败时显示帮助 goto error; } /* 进入事件循环跟踪进程及其子进程 */ exit (event_loop()); // 事件循环返回时退出程序 /* 错误处理模块 */ error: TALLOC_FREE(tracee); // 释放跟踪对象内存 /* 根据错误状态退出 */ if (exit_failure) { fprintf (stderr, "致命错误:请查看 `%s --help`.\n" , basename(argv[0])); exit (EXIT_FAILURE); } else exit (EXIT_SUCCESS); } |
在这个代码中一来就出现不认识的结构体 Tracee
那么我们先了解这个结构体
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 | typedef struct tracee { /********************************************************************** * 私有资源 (Private resources) * **********************************************************************/ /* 所有被跟踪进程(tracee)列表的链接。 */ LIST_ENTRY(tracee) link; /* 进程标识符。 */ pid_t pid; /* 当前是否正在运行? */ bool running; /* 该被跟踪进程的父进程,若不存在则为NULL。 */ struct tracee *parent; /* 是否为“克隆”进程(即与其创建者共享同一个父进程)。 */ bool clone; /* ptrace模拟支持(跟踪器端)。 */ struct { size_t nb_ptracees; // 被ptrace跟踪的进程数量 LIST_HEAD(zombies, tracee) zombies; // 僵尸进程列表 struct direct_ptracees *direct_ptracees; // 直接跟踪的进程 pid_t wait_pid; // 等待的进程ID word_t wait_options; // 等待选项 /* 等待状态: */ enum { DOESNT_WAIT = 0, // 不等待 WAITS_IN_KERNEL, // 在内核中等待 WAITS_IN_PROOT // 在PRoot中等待 } waits_in; } as_ptracer; /* ptrace模拟支持(被跟踪进程端)。 */ struct { struct tracee *ptracer; // 对应的跟踪器 struct { #define STRUCT_EVENT struct { int value; bool pending; } STRUCT_EVENT proot; // PRoot相关事件 STRUCT_EVENT ptracer; // 跟踪器相关事件 } event4; bool tracing_started; // 是否已启动跟踪 bool ignore_loader_syscalls; // 是否忽略加载器系统调用 bool ignore_syscalls; // 是否忽略所有系统调用 word_t options; // ptrace选项 bool is_zombie; // 是否为僵尸进程 } as_ptracee; /* 当前状态: * 0: 进入系统调用(syscall入口) * 1: 系统调用正常退出(无错误) * -errno: 系统调用错误退出(错误号为errno) */ int status; #define IS_IN_SYSENTER(tracee) ((tracee)->status == 0) // 是否在系统调用入口 #define IS_IN_SYSEXIT(tracee) (!IS_IN_SYSENTER(tracee)) // 是否在系统调用退出 #define IS_IN_SYSEXIT2(tracee, sysnum) (IS_IN_SYSEXIT(tracee) \ &&***INAL) == sysnum) // 是否在指定系统调用的退出阶段 /* 该被跟踪进程的重启方式。 */ enum __ptrace_request restart_how; /* 被跟踪进程的通用寄存器值(多版本存储)。 */ struct user_regs_struct _regs[NB_REG_VERSION]; bool _regs_were_changed; // 寄存器是否被修改 bool restore_original_regs; // 是否恢复原始寄存器值 /* SIGSTOP信号的特殊处理状态。 */ enum { SIGSTOP_IGNORED = 0, // 忽略SIGSTOP(当父进程已知时) SIGSTOP_ALLOWED, // 允许SIGSTOP(当父进程已知时) SIGSTOP_PENDING, // 阻塞SIGSTOP直到父进程未知 } sigstop; /* 用于临时动态内存分配的上下文。 */ TALLOC_CTX *ctx; /* 用于存储生命周期内动态内存分配的上下文(进程释放时自动回收)。 */ TALLOC_CTX *life_context; /* 注:可将"ctx"重命名为"event_span","life_context"重命名为"life_span"。 */ /* 在绑定路径初始化时指定最终组件的类型(由bind_path()定义,build_glue()使用)。 */ mode_t glue_type; /* 在子重配置期间,新配置相对于该被跟踪进程的文件系统命名空间。@paths保存其$PATH环境变量以模拟execvp(3)行为。 */ struct { struct tracee *tracee; // 关联的被跟踪进程 const char *paths; // 环境变量PATH值 } reconf; /* 由PRoot在实际系统调用后插入的未请求的系统调用链。 */ struct { struct chained_syscalls *syscalls; // 链式系统调用 bool force_final_result; // 是否强制最终结果 word_t final_result; // 强制设置的最终结果 } chain; /* 在execve系统调用入口生成,并在退出时使用的加载信息。 */ struct load_info *load_info; /* 加载进程的当前状态。 */ struct { enum { LOADING_STEP_NONE = 0, // 未在加载 LOADING_STEP_OPEN, // 打开文件阶段 LOADING_STEP_MMAP, // 内存映射阶段 LOADING_STEP_CLOSE // 关闭文件阶段 } step; struct load_info *info; // 加载信息指针 size_t index; // 当前步骤索引 } loading; /********************************************************************** * 私有但可继承的资源 * **********************************************************************/ /* 详细级别。 */ int verbose; /* 该被跟踪进程的seccomp加速状态。 */ enum { DISABLED = 0, DISABLING, ENABLED } seccomp; /* 确保在seccomp下始终触发系统调用退出阶段。 */ bool sysexit_pending; /********************************************************************** * 共享或私有资源(取决于CLONE_FS/VM标志) * **********************************************************************/ /* 文件系统命名空间相关信息。 */ FileSystemNameSpace *fs; /* 虚拟堆(通过常规内存映射模拟)。 */ Heap *heap; /********************************************************************** * 共享资源(直到该进程调用execve()) * **********************************************************************/ /* 可执行文件路径(类似/proc/self/exe)。 */ char *exe; /********************************************************************** * 共享或私有资源(取决于配置或重配置) * **********************************************************************/ /* 模拟器(QEMU)命令行参数。 */ char **qemu; /* 宿主机与客户机根文件系统之间的粘合路径。 */ const char *glue; /* 为此跟踪对象启用的扩展列表。*/ struct extensions *extensions; /********************************************************************** * 共享但只读的资源 * **********************************************************************/ /* 在混合模式下,宿主机LD_LIBRARY_PATH会在"客户机->宿主机"转换期间被保存, * 以便在"宿主机->客户机"转换时恢复(仅当宿主机的LD_LIBRARY_PATH未发生变化时)。*/ const char *host_ldso_paths; const char *guest_ldso_paths; /* 用于诊断目的 */ const char *tool_name; } Tracee; |
观察上面的结构体 ,我用ai做了翻译,很清楚的知道 Tracee 这个结构体就是被观测的进程。
我们不关注它的内存管理,我们只关心 它主要的运行逻辑。
好,那么接下来就是创建的一个 Tracee get_tracee(NULL, 0, true)这个方法
看看:
在tracee.h 存在该方法的定义:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 | /* 根据给定的进程ID查找或创建对应的Tracee结构体 */ Tracee *get_tracee( const Tracee *current_tracee, pid_t pid, bool create) { Tracee *tracee; /* 如果当前正在追踪的进程就是要找的进程 * 则直接返回当前Tracee对象,避免重置内存收集器。 * 因为调用者可能持有子分配数据的指针 */ if (current_tracee != NULL && current_tracee->pid == pid) return (Tracee *)current_tracee; /* 遍历tracees链表寻找匹配PID的Tracee对象 */ LIST_FOREACH(tracee, &tracees, link) { if (tracee->pid == pid) { /* 释放旧的内存上下文并创建新的内存分配器 * 使用talloc内存管理库进行内存管理 */ TALLOC_FREE(tracee->ctx); tracee->ctx = talloc_new(tracee); return tracee; } } /* 如果没有找到且允许创建,则新建Tracee对象 * 否则返回NULL */ return (create ? new_tracee(pid) : NULL); } |
ok,不做深入,进行下一步 parse_config这个方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 | /** 根据存储在@argv[]中的命令行参数配置@tracee。该函数返回@argv[]中要启动的命令的索引,若发生错误则返回-1。 */ static int parse_config(Tracee *tracee, size_t argc, char * const argv[]) { option_handler_t handler = NULL; // 当前选项的处理函数 const Option *options; // CLI选项列表 const Cli *cli = NULL; // 当前使用的CLI工具(CARE或PRoot) size_t argc_offset; // 命令在argv中的起始位置 size_t i, j, k; // 循环计数器 int status; // 操作状态码 /* 检查是否为自解压CARE归档 */ if (get_care_cli != NULL) { // 尝试从"/proc/self/exe"提取归档(当前可执行文件是否为CARE自解压包?) status = extract_archive_from_file( "/proc/self/exe" ); if (status == 0) { // 成功提取则退出 exit_failure = 0; // 标记正常退出 return -1; } /* 检查工具名是否为"care"(如"care-3.4") */ if (strncasecmp(basename(argv[0]), "care" , strlen ( "care" )) == 0) cli = get_care_cli(tracee->ctx); // 获取CARE的CLI配置 } /* 默认使用PRoot CLI */ if (cli == NULL) cli = get_proot_cli(tracee->ctx); // 获取PRoot的CLI配置 tracee->tool_name = cli->name; // 设置工具名称(如"proot") /* 参数不足时打印用法 */ if (argc == 1) { print_usage(tracee, cli, false ); // 显示帮助信息 return -1; } /* 遍历所有参数进行解析 */ for (i = 1; i < argc; i++) { const char *arg = argv[i]; 复制 /* 处理短选项的值(如 -o value,此时handler已指向处理函数) */ if (handler != NULL) { status = handler(tracee, cli, arg); // 调用之前设置的处理器 if (status < 0) return -1; // 错误则退出 handler = NULL; // 重置处理器 continue ; } /* 遇到非选项参数(如命令),停止解析 */ if (arg[0] != '-' ) break ; /* 遍历所有支持的选项 */ options = cli->options; // 获取当前CLI的选项列表 for (j = 0; options[j]. class != NULL; j++) { // 遍历每个选项类别 const Option *option = &options[j]; /* 检查所有选项别名(如 -v 和 --verbose) */ for (k = 0; ; k++) { // 遍历选项的多个别名 const Argument *argument = &option->arguments[k]; size_t length; /* 无更多别名时跳出循环 */ if (!argument->name) break ; length = strlen (argument->name); if ( strncmp (arg, argument->name, length) != 0) continue ; // 不匹配则跳过 /* 处理选项与值的分隔符(如 -I/usr 或 -I /usr) */ if ( strlen (arg) > length && arg[length] != argument->separator) { print_error_separator(tracee, argument); // 分隔符错误(如 -I=usr) return -1; } /* 无值的选项(如 --help) */ if (!argument->value) { status = option->handler(tracee, cli, NULL); // 调用处理函数 if (status < 0) return -1; goto known_option; // 跳转到后续处理 } /* 合并值的选项(如 -I/usr,分隔符为'/') */ if (argument->separator == arg[length]) { status = option->handler(tracee, cli, &arg[length + 1]); // 提取值 if (status < 0) return -1; goto known_option; } /* 分隔符必须为空格的情况(如 -I /usr) */ if (argument->separator != ' ' ) { print_error_separator(tracee, argument); return -1; } /* 短选项需要后续参数作为值(如 -o value) */ handler = option->handler; // 设置处理器,等待下一个参数 goto known_option; } } /* 未知选项处理 */ note(tracee, ERROR, USER, "unknown option '%s'." , arg); return -1; known_option: /* 检查是否缺少选项值(如 -o 后无参数) */ if (handler != NULL && i == argc - 1) { note(tracee, ERROR, USER, "missing value for option '%s'." , arg); return -1; } } argc_offset = i; // 记录命令起始位置(如 argv[3] 是命令名) /* 通过钩子函数进行初始化阶段配置 / #define HOOK_CONFIG(callback) do { if (cli->callback != NULL) { status = cli->callback(tracee, cli, argc, argv, i); if (status < 0) return -1; i = status; / 可能调整参数索引 */ } } while (0) HOOK_CONFIG(pre_initialize_bindings); // 绑定初始化前处理 /* 解析用户绑定的路径(如 -b /host:/guest) */ status = initialize_bindings(tracee); if (status < 0) return -1; HOOK_CONFIG(post_initialize_bindings); // 绑定初始化后处理 HOOK_CONFIG(pre_initialize_cwd); // 工作目录初始化前处理 /* 设置当前工作目录 */ status = initialize_cwd(tracee); if (status < 0) return -1; HOOK_CONFIG(post_initialize_cwd); // 工作目录初始化后处理 HOOK_CONFIG(pre_initialize_exe); // 可执行文件初始化前处理 /* 解析目标可执行文件路径 */ status = initialize_exe(tracee, argv[argc_offset]); if (status < 0) return -1; HOOK_CONFIG(post_initialize_exe); // 可执行文件初始化后处理 #undef HOOK_CONFIG print_config(tracee, &argv[argc_offset]); // 打印最终配置信息 return argc_offset; // 返回命令的argv起始索引(如 argv[3]) } |
这里根据参数进行初始化,也就是对proot 后面输入的参数 进行 对应的初始化
后面启动进程:
launch_process
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 | /** * 启动 @tracee->exe 并使用给定的 @argv[] 参数。此函数在发生错误时返回 -errno,否则返回 0。 */ int launch_process(Tracee *tracee, char * const argv[]) { char * const default_argv[] = { "-" , NULL }; long status; pid_t pid; /* Warn about open file descriptors. They won't be * translated until they are closed. */ if (tracee->verbose > 0) list_open_fd(tracee); pid = fork(); switch (pid) { case -1: note(tracee, ERROR, SYSTEM, "fork()" ); return - errno ; case 0: /* child */ /* Declare myself as ptraceable before executing the * requested program. */ status = ptrace(PTRACE_TRACEME, 0, NULL, NULL); if (status < 0) { note(tracee, ERROR, SYSTEM, "ptrace(TRACEME)" ); return - errno ; } /* Synchronize with the tracer's event loop. Without * this trick the tracer only sees the "return" from * the next execve(2) so PRoot wouldn't handle the * interpreter/runner. I also verified that strace * does the same thing. */ kill(getpid(), SIGSTOP); /* Improve performance by using seccomp mode 2, unless * this support is explicitly disabled. */ if ( getenv ( "PROOT_NO_SECCOMP" ) == NULL) ( void ) enable_syscall_filtering(tracee); /* Now process is ptraced, so the current rootfs is already the * guest rootfs. Note: Valgrind can't handle execve(2) on * "foreign" binaries (ENOEXEC) but can handle execvp(3) on such * binaries. */ execvp(tracee->exe, argv[0] != NULL ? argv : default_argv); return - errno ; default : /* parent */ /* We know the pid of the first tracee now. */ tracee->pid = pid; return 0; } /* Never reached. */ return -ENOSYS; } |
这里创建了 一个子进程 并且 开启了seccomp
我们看看 :enable_syscall_filtering
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 | /** * 通知内核仅跟踪由PRoot及其扩展处理的系统调用。 * 该过滤器将对给定的@tracee及其所有未来子进程生效。 * 如果发生错误,该函数返回-errno,否则返回0。 */ int enable_syscall_filtering( const Tracee *tracee) { FilteredSysnum *filtered_sysnums = NULL; // 被过滤的系统调用号列表 Extension *extension; // 扩展模块指针 int status; // 操作状态码 // 断言确保tracee及其上下文不为空 assert (tracee != NULL && tracee->ctx != NULL); /* 将PRoot需要的系统调用号添加到过滤列表。 * TODO: 仅在需要路径转换时添加 */ status = merge_filtered_sysnums(tracee->ctx, &filtered_sysnums, proot_sysnums); if (status < 0) return status; /* 将扩展模块需要的系统调用号合并到过滤列表 */ if (tracee->extensions != NULL) { // 遍历所有扩展模块 LIST_FOREACH(extension, tracee->extensions, link) { if (extension->filtered_sysnums == NULL) continue ; // 合并当前扩展的过滤列表 status = merge_filtered_sysnums(tracee->ctx, &filtered_sysnums, extension->filtered_sysnums); if (status < 0) return status; } } // 设置seccomp过滤器 status = set_seccomp_filters(filtered_sysnums); if (status < 0) return status; return 0; // 成功返回0 } |
这里就是设置 设置seccomp过滤器 的地方
完成后就开始 主进程就开始循环处理了
event_loop()
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 | /** * 等待并处理来自所有被跟踪进程(tracees)的事件。本函数返回最后一个终止程序的退出状态。 */ int event_loop() { struct sigaction signal_action; long status; int signum; /* 退出时杀死所有被跟踪进程 */ status = atexit (kill_all_tracees); if (status != 0) note(NULL, WARNING, INTERNAL, "atexit() 失败" ); /* 信号处理函数被调用时会阻塞所有信号。 * 使用 SIGINFO 标识进程信号来源,RESTART 标识无缝重启 waitpid(2) */ bzero(&signal_action, sizeof (signal_action)); signal_action.sa_flags = SA_SIGINFO | SA_RESTART; status = sigfillset(&signal_action.sa_mask); if (status < 0) note(NULL, WARNING, SYSTEM, "sigfillset() 错误" ); /* 遍历所有可能的信号并配置处理方式 */ for (signum = 0; signum < SIGRTMAX; signum++) { switch (signum) { case SIGQUIT: // 终止请求信号 case SIGILL: // 非法指令 case SIGABRT: // 异常中止 case SIGFPE: // 算术错误 case SIGSEGV: // 段错误 /* 在异常终止信号时杀死所有被跟踪进程,确保无残留 */ signal_action.sa_sigaction = kill_all_tracees2; break ; case SIGUSR1: // 用户自定义信号1 case SIGUSR2: // 用户自定义信号2 /* 调试用:在 stderr 打印完整 talloc 内存层级 */ signal_action.sa_sigaction = print_talloc_hierarchy; break ; case SIGCHLD: // 子进程状态变化 case SIGCONT: // 继续执行 case SIGSTOP: // 停止进程 case SIGTSTP: // 终端停止请求 case SIGTTIN: // 后台读终端 case SIGTTOU: // 后台写终端 /* 保留与终端和作业控制相关信号的默认行为 */ continue ; default : /* 忽略其他信号(如终止信号 SIGINT/^C) */ signal_action.sa_sigaction = ( void *)SIG_IGN; break ; } /* 注册信号处理函数 */ status = sigaction(signum, &signal_action, NULL); if (status < 0 && errno != EINVAL) note(NULL, WARNING, SYSTEM, "sigaction(%d) 错误" , signum); } /* 主事件循环 */ while (1) { int tracee_status; // 被跟踪进程状态 Tracee *tracee; // 被跟踪进程对象 int signal ; // 接收到的信号 pid_t pid; // 进程ID /* 等待任意被跟踪进程的状态变化 */ pid = waitpid(-1, &tracee_status, __WALL); if (pid < 0) { if ( errno != ECHILD) { // 无子进程时正常退出 note(NULL, ERROR, SYSTEM, "waitpid() 错误" ); return EXIT_FAILURE; } break ; // 所有子进程已退出,结束循环 } /* 获取对应被跟踪进程的信息 */ tracee = get_tracee(NULL, pid, true ); assert (tracee != NULL); tracee->running = false ; // 标记为停止状态 /* 通知扩展模块处理新状态 */ status = notify_extensions(tracee, NEW_STATUS, tracee_status, 0); if (status != 0) continue ; /* 处理来自 ptrace 的事件 */ if (tracee->as_ptracee.ptracer != NULL) { bool keep_stopped = handle_ptracee_event(tracee, tracee_status); if (keep_stopped) continue ; // 需要保持停止状态,不重启进程 } /* 处理事件并获取需传递的信号 */ signal = handle_tracee_event(tracee, tracee_status); /* 重启被跟踪进程(可能附带信号) */ ( void ) restart_tracee(tracee, signal ); } return last_exit_status; // 返回最后一个退出状态码 } |
我们跟进 handle_ptracee_event 看看它是如何处理ptrace的调用的?
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 | /** * 对于给定的被跟踪进程 @ptracee,如果其跟踪者(ptracer)正在等待当前 @event, * 则将事件传递给跟踪者;否则将 @ptracee 置为“等待跟踪者”状态。 * 返回值表示是否应保持 @ptracee 的停止状态。 */ bool handle_ptracee_event(Tracee *ptracee, int event) { bool handled_by_proot_first = false ; // 标记事件是否需要由PRoot优先处理 Tracee *ptracer = PTRACEE.ptracer; // 获取跟踪者(父进程) bool keep_stopped; // 返回值:是否保持停止状态 assert (ptracer != NULL); // 确保跟踪者存在 /* 保存原始事件信息,供PRoot后续处理 */ PTRACEE.event4.proot.value = event; // 存储事件值 PTRACEE.event4.proot.pending = true ; // 标记事件待处理 /* 默认情况下,保持ptracee停止,直到跟踪者恢复它 */ keep_stopped = true ; /* 处理因信号停止的事件(WIFSTOPPED) */ if (WIFSTOPPED(event)) { switch ((event & 0xfff00) >> 8) { // 提取高位信号类型 case SIGTRAP | 0x80: // 系统调用退出事件(带syscall-good标志) if (PTRACEE.ignore_syscalls || PTRACEE.ignore_loader_syscalls) return false ; // 若忽略系统调用,直接返回不保持停止 if ((PTRACEE.options & PTRACE_O_TRACESYSGOOD) == 0) // 未启用TRACESYSGOOD event &= ~(0x80 << 8); // 清除高位标志 handled_by_proot_first = IS_IN_SYSEXIT(ptracee); // 系统调用退出阶段由PRoot处理 break ; // 宏定义:处理特定跟踪事件(FORK/VFORK等) #define PTRACE_EVENT_VFORKDONE PTRACE_EVENT_VFORK_DONE // 兼容性定义 #define CASE_FILTER_EVENT(name) \ case SIGTRAP | PTRACE_EVENT_ ##name << 8: \ if ((PTRACEE.options & PTRACE_O_TRACE ##name) == 0) \ // 未启用对应跟踪选项 return false ; \ PTRACEE.tracing_started = true ; \ // 标记跟踪已开始 handled_by_proot_first = true ; \ // PRoot优先处理 break ; CASE_FILTER_EVENT(FORK); // 处理进程fork事件 CASE_FILTER_EVENT(VFORK); // 处理vfork事件 CASE_FILTER_EVENT(VFORKDONE); // 处理vfork完成事件 CASE_FILTER_EVENT(CLONE); // 处理clone事件 CASE_FILTER_EVENT(EXIT); // 处理进程退出事件 CASE_FILTER_EVENT(EXEC); // 处理exec事件 /* 以下代码不可达,触发断言 */ assert (0); // 不支持的事件类型(如seccomp) case SIGTRAP | PTRACE_EVENT_SECCOMP2 << 8: case SIGTRAP | PTRACE_EVENT_SECCOMP << 8: return false ; // 直接返回不处理 default : // 其他信号停止事件 PTRACEE.tracing_started = true ; // 标记跟踪开始 break ; } } /* 处理进程退出或信号终止事件 */ else if (WIFEXITED(event) || WIFSIGNALED(event)) { PTRACEE.tracing_started = true ; // 标记跟踪已开始 keep_stopped = false ; // 进程已终止,无需保持停止 } /* 若跟踪尚未开始(如TRACEME后首次事件),直接返回 */ if (!PTRACEE.tracing_started) return false ; /* PRoot优先处理事件(如系统调用退出) */ if (handled_by_proot_first) { int signal = handle_tracee_event(ptracee, PTRACEE.event4.proot.value); PTRACEE.event4.proot.value = signal ; // 更新处理后的信号值 assert ( signal == 0); // 当前逻辑下信号应为0(如sysexit无需传递信号) } /* 保存处理后的事件信息,供跟踪者使用 */ PTRACEE.event4.ptracer.value = event; // 记录最终事件值 PTRACEE.event4.ptracer.pending = true ; // 标记事件待跟踪者处理 /* 异步通知跟踪者(发送SIGCHLD模拟内核行为) */ kill(ptracer->pid, SIGCHLD); /* 若跟踪者正在等待此ptracee的事件 */ if ((PTRACER.wait_pid == -1 || PTRACER.wait_pid == ptracee->pid) && EXPECTED_WAIT_CLONE(PTRACER.wait_options, ptracee)) { bool restarted; int status = update_wait_status(ptracer, ptracee); // 更新等待状态 poke_reg(ptracer, SYSARG_RESULT, (word_t) status); // 修改寄存器返回值 ( void ) push_regs(ptracer); // 写回寄存器缓存 /* 重启跟踪者进程 */ PTRACER.wait_pid = 0; restarted = restart_tracee(ptracer, 0); // 尝试恢复跟踪者执行 if (!restarted) // 重启失败则不再保持停止 keep_stopped = false ; } return keep_stopped; // 返回是否保持ptracee停止 } // 关键逻辑说明: // 1. 事件分层处理: // - 原始事件(如系统调用退出)先由PRoot处理(如模拟执行后清理) // - 处理后的事件转发给跟踪者(如GDB),模拟内核的ptrace事件传递 // // 2. 状态同步机制: // - 通过SIGCHLD通知跟踪者有事件待处理 // - 若跟踪者正在waitpid,直接更新其寄存器状态并唤醒,减少延迟 // // 3. 停止状态决策: // - 进程终止(EXITED/SIGNALED)时必须返回false,防止僵尸进程滞留 // - 默认保持停止,直到跟踪者显式调用PTRACE_CONT // // 潜在问题: // 1. 事件掩码计算: // - `(event & 0xfff00) >> 8` 假设高位存储事件类型,需确保与内核实现一致 // - 若PTRACE_EVENT_* 的编码方式变化,可能导致错误过滤 // // 2. 异步竞争条件: // - kill()发送SIGCHLD后,跟踪者可能尚未进入waitpid,导致信号丢失 // - 需依赖内核的ptrace语义保证事件不会丢失 // // 3. 宏展开风险: // - CASE_FILTER_EVENT(EXEC) 展开后依赖PTRACE_O_TRACEEXEC选项的存在 // - 若PTrace实现不兼容某些选项,需添加条件编译 |
然后就是对 tracee的处理handle_tracee_event
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 | /** * 处理被跟踪进程(tracee)的当前事件(@tracee_status)。该函数返回用于恢复该进程执行的"计算"信号。 */ int handle_tracee_event(Tracee *tracee, int tracee_status) { static bool seccomp_detected = false ; // 静态标志位,检测是否启用seccomp pid_t pid = tracee->pid; // 获取被跟踪进程的PID long status; // 系统调用状态 int signal ; // 要发送的信号 /* 如果重启方式未被显式设置(例如在单步调试的ptrace模拟中), 则自动设置默认值 */ if (tracee->restart_how == 0) { /* 当启用seccomp时,所有事件在非停止模式下重启,但后续可能需要修改。 检查"sysexit_pending"确保不会因其他事件(如execve退出的PTRACE_EVENT_EXEC) 清除用于seccomp退出阶段的PTRACE_SYSCALL */ if (tracee->seccomp == ENABLED && !tracee->sysexit_pending) tracee->restart_how = PTRACE_CONT; // 继续执行 else tracee->restart_how = PTRACE_SYSCALL; // 在下一个系统调用时停止 } /* 默认不是信号触发的停止 */ signal = 0; // 处理进程退出状态 if (WIFEXITED(tracee_status)) { last_exit_status = WEXITSTATUS(tracee_status); VERBOSE(tracee, 1, "进程 %d:已退出,状态码 %d" , pid, last_exit_status); } // 处理信号终止 else if (WIFSIGNALED(tracee_status)) { check_architecture(tracee); VERBOSE(tracee, ( int ) (last_exit_status != -1), "进程 %d:被信号 %d 终止" , pid, WTERMSIG(tracee_status)); } // 处理停止状态 else if (WIFSTOPPED(tracee_status)) { /* 不使用WSTOPSIG()提取信号,因为它会清除PTRACE_EVENT_*标志位 */ signal = (tracee_status & 0xfff00) >> 8; // 解码信号 switch ( signal ) { static bool deliver_sigtrap = false ; // 静态标志控制SIGTRAP传递 case SIGTRAP: { // 断点/单步异常 // 默认ptrace监控选项 const unsigned long default_ptrace_options = ( PTRACE_O_TRACESYSGOOD | // 系统调用时发送SIGTRAP|0x80 PTRACE_O_TRACEFORK | // 跟踪fork PTRACE_O_TRACEVFORK | // 跟踪vfork PTRACE_O_TRACEVFORKDONE | // vfork完成跟踪 PTRACE_O_TRACEEXEC | // 跟踪exec PTRACE_O_TRACECLONE | // 跟踪clone PTRACE_O_TRACEEXIT); // 跟踪进程退出 /* 区分不同事件类型,自动为新进程设置相同跟踪选项 只有第一个纯SIGTRAP与跟踪循环相关,其他SIGTRAP携带 TRACE*FORK/CLONE/EXEC的跟踪信息 */ if (deliver_sigtrap) break ; // 直接传递该信号 deliver_sigtrap = true ; /* 尝试启用seccomp模式2... */ status = ptrace(PTRACE_SETOPTIONS, tracee->pid, NULL, default_ptrace_options | PTRACE_O_TRACESECCOMP); if (status < 0) { /* ...否则仅使用默认选项 */ status = ptrace(PTRACE_SETOPTIONS, tracee->pid, NULL, default_ptrace_options); if (status < 0) { note(tracee, ERROR, SYSTEM, "ptrace(PTRACE_SETOPTIONS失败)" ); exit (EXIT_FAILURE); } } } /* 继续处理 */ case SIGTRAP | 0x80: // 带系统调用标志的SIGTRAP signal = 0; /* 当tracee在系统调用进入阶段被释放,但内核仍报告退出阶段时, 丢弃这个无效的tracee/事件 */ if (tracee->exe == NULL) { tracee->restart_how = PTRACE_CONT; return 0; } // 根据seccomp状态处理系统调用 switch (tracee->seccomp) { case ENABLED: // seccomp启用状态 if (IS_IN_SYSENTER(tracee)) { // 系统调用进入阶段 tracee->restart_how = PTRACE_SYSCALL; // 捕获退出阶段 tracee->sysexit_pending = true ; // 标记退出阶段待处理 } else { // 系统调用退出阶段 tracee->restart_how = PTRACE_CONT; // 直接继续执行 tracee->sysexit_pending = false ; // 清除退出标志 } /* 继续处理 */ case DISABLED: // seccomp禁用状态 translate_syscall(tracee); // 转换系统调用 /* 当前系统调用已禁用seccomp */ if (tracee->seccomp == DISABLING) { tracee->restart_how = PTRACE_SYSCALL; tracee->seccomp = DISABLED; } break ; case DISABLING: // seccomp正在禁用 /* 前一个系统调用已禁用seccomp, 但其进入阶段已处理完成 */ tracee->seccomp = DISABLED; if (IS_IN_SYSENTER(tracee)) tracee->status = 1; break ; } break ; // 处理seccomp事件(模式2或原始模式) case SIGTRAP | PTRACE_EVENT_SECCOMP2 << 8: case SIGTRAP | PTRACE_EVENT_SECCOMP << 8: { unsigned long flags = 0; signal = 0; if (!seccomp_detected) { VERBOSE(tracee, 1, "已启用ptrace加速(seccomp模式2)" ); tracee->seccomp = ENABLED; seccomp_detected = true ; } /* 如果该tracee已显式禁用seccomp,使用普通ptrace流程 */ if (tracee->seccomp != ENABLED) break ; status = ptrace(PTRACE_GETEVENTMSG, tracee->pid, NULL, &flags); if (status < 0) break ; /* 需要处理系统调用退出阶段时,使用普通ptrace流程 */ if ((flags & FILTER_SYSEXIT) != 0) { tracee->restart_how = PTRACE_SYSCALL; break ; } /* 否则立即处理系统调用进入阶段 */ tracee->restart_how = PTRACE_CONT; translate_syscall(tracee); /* 如果该调用禁用了seccomp,切换回普通流程以确保处理退出阶段 */ if (tracee->seccomp == DISABLING) /* 设置跟踪对象的系统调用重启方式为PTRACE_SYSCALL(在进入和退出时都停止) */ tracee->restart_how = PTRACE_SYSCALL; /* 标记该跟踪对象的seccomp状态为已禁用 */ tracee->seccomp = DISABLED; break ; // 退出当前switch分支 case DISABLING: // 处理seccomp正在禁用中的状态 /* * 前一个系统调用已禁用seccomp, * 但其sysenter阶段(系统调用入口)已被处理。 */ tracee->seccomp = DISABLED; // 更新状态为完全禁用 /* 如果当前处于sysenter阶段,设置状态标志为1 */ if (IS_IN_SYSENTER(tracee)) tracee->status = 1; break ; break ; // 退出外层switch /* 处理SECCOMP相关ptrace事件 */ case SIGTRAP | PTRACE_EVENT_SECCOMP2 << 8: case SIGTRAP | PTRACE_EVENT_SECCOMP << 8: { unsigned long flags = 0; signal = 0; // 重置信号,表示不传递信号给被跟踪进程 /* 首次检测到seccomp时的初始化 */ if (!seccomp_detected) { VERBOSE(tracee, 1, "启用ptrace加速(seccomp模式2)" ); tracee->seccomp = ENABLED; // 启用seccomp跟踪 seccomp_detected = true ; // 设置全局检测标志 } /* 如果该跟踪对象未启用seccomp,走普通ptrace流程 */ if (tracee->seccomp != ENABLED) break ; /* 获取事件消息中的过滤器标志 */ status = ptrace(PTRACE_GETEVENTMSG, tracee->pid, NULL, &flags); if (status < 0) break ; /* 当需要处理sysexit(系统调用退出)时,使用常规流程 */ if ((flags & FILTER_SYSEXIT) != 0) { tracee->restart_how = PTRACE_SYSCALL; // 捕获进入和退出 break ; } /* 否则立即处理sysenter阶段 */ tracee->restart_how = PTRACE_CONT; // 继续执行直到下一个事件 translate_syscall(tracee); // 处理系统调用参数/模拟 /* 如果该syscall禁用了seccomp,切回常规路径以确保处理sysexit */ if (tracee->seccomp == DISABLING) tracee->restart_how = PTRACE_SYSCALL; break ; } /* 处理vfork事件 */ case SIGTRAP | PTRACE_EVENT_VFORK << 8: signal = 0; ( void ) new_child(tracee, CLONE_VFORK); // 创建vfork子进程跟踪对象 break ; /* 处理fork/clone事件 */ case SIGTRAP | PTRACE_EVENT_FORK << 8: case SIGTRAP | PTRACE_EVENT_CLONE << 8: signal = 0; ( void ) new_child(tracee, 0); // 创建普通子进程跟踪对象 break ; /* 处理其他事件(不执行特殊操作) */ case SIGTRAP | PTRACE_EVENT_VFORK_DONE << 8: case SIGTRAP | PTRACE_EVENT_EXEC << 8: case SIGTRAP | PTRACE_EVENT_EXIT << 8: signal = 0; // 仅清除信号 break ; /* 处理SIGSTOP信号 */ case SIGSTOP: /* 当进程镜像未设置时,挂起跟踪直到收到fork/clone通知 */ if (tracee->exe == NULL) { tracee->sigstop = SIGSTOP_PENDING; // 设置挂起状态 signal = -1; // 阻止信号传递 } /* 对每个跟踪对象,首个SIGSTOP仅用于通知跟踪器 */ if (tracee->sigstop == SIGSTOP_IGNORED) { tracee->sigstop = SIGSTOP_ALLOWED; // 标记为已处理 signal = 0; // 允许后续传递 } break ; default : /* 其他信号直接传递给被跟踪进程 */ break ; } |
接着就是系统调用的处理了!
translate_syscall
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 | /** * 系统调用翻译核心函数 - 处理系统调用进入/退出阶段的寄存器操作 * @param tracee 被跟踪进程的上下文信息 */ void translate_syscall(Tracee *tracee) { const bool is_enter_stage = IS_IN_SYSENTER(tracee); // 判断当前阶段:系统调用入口 int status; assert (tracee->exe != NULL); // 确保已加载目标可执行文件 /* 获取当前寄存器状态 */ status = fetch_regs(tracee); if (status < 0) return ; // 获取失败直接返回 if (is_enter_stage) { /* ==== 系统调用入口阶段处理 ==== */ /* 标记本阶段结束时不需要恢复原始寄存器 */ tracee->restore_original_regs = false ; print_current_regs(tracee, 3, "sysenter start" ); // 调试输出:三级详细度的寄存器状态 /* 仅处理真实用户请求的系统调用(非PRoot内部链式调用) */ if (tracee->chain.syscalls == NULL) { save_current_regs(tracee, ORIGINAL); // 保存原始寄存器快照 status = translate_syscall_enter(tracee); // 执行系统调用入口翻译 save_current_regs(tracee, MODIFIED); // 保存修改后的寄存器状态 } else { /* 链式调用处理:通知扩展模块 */ status = notify_extensions(tracee, SYSCALL_CHAINED_ENTER, 0, 0); tracee->restart_how = PTRACE_SYSCALL; // 设置ptrace为完整跟踪模式 } /* 错误处理逻辑 */ if (status < 0) { set_sysnum(tracee, PR_void); // 将系统调用号设为无效值 poke_reg(tracee, SYSARG_RESULT, (word_t) status); // 将错误码写入结果寄存器 tracee->status = status; // 记录错误状态 } else tracee->status = 1; // 标记正常状态 /* 特殊场景处理:当使用PTRACE_CONT直接继续时恢复栈指针 */ if (tracee->restart_how == PTRACE_CONT) { tracee->status = 0; poke_reg(tracee, STACK_POINTER, peek_reg(tracee, ORIGINAL, STACK_POINTER)); // 还原原始栈指针 } } else { /* ==== 系统调用退出阶段处理 ==== */ /* 默认在退出阶段结束时恢复原始寄存器 */ tracee->restore_original_regs = true ; print_current_regs(tracee, 5, "sysexit start" ); // 调试输出:五级详细度 /* 仅处理真实系统调用的退出 */ if (tracee->chain.syscalls == NULL) translate_syscall_exit(tracee); // 执行退出阶段翻译 else ( void ) notify_extensions(tracee, SYSCALL_CHAINED_EXIT, 0, 0); tracee->status = 0; // 重置状态 /* 链式调用处理:执行下一个链式系统调用 */ if (tracee->chain.syscalls != NULL) chain_next_syscall(tracee); // 加载下一个系统调用参数 } /* 将修改后的寄存器写回被跟踪进程 */ ( void ) push_regs(tracee); /* 阶段结束调试输出 */ if (is_enter_stage) print_current_regs(tracee, 5, "sysenter end" ); else print_current_regs(tracee, 4, "sysexit end" ); } |
这里面就有处理 syscall 的函数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 | /** * Translate the input arguments of the current @tracee's syscall in the * @tracee->pid process area. This function sets @tracee->status to * -errno if an error occured from the tracee's point-of-view (EFAULT * for instance), otherwise 0. */ int translate_syscall_enter(Tracee *tracee) { int flags; int dirfd; int olddirfd; int newdirfd; int status; int status2; char path[PATH_MAX]; char oldpath[PATH_MAX]; char newpath[PATH_MAX]; word_t syscall_number; bool special = false ; status = notify_extensions(tracee, SYSCALL_ENTER_START, 0, 0); if (status < 0) goto end; if (status > 0) return 0; /* Translate input arguments. */ syscall_number = get_sysnum(tracee, ORIGINAL); switch (syscall_number) { default : /* Nothing to do. */ status = 0; break ; case PR_execve: status = translate_execve_enter(tracee); break ; case PR_ptrace: status = translate_ptrace_enter(tracee); break ; case PR_wait4: case PR_waitpid: status = translate_wait_enter(tracee); break ; case PR_brk: status = translate_brk_enter(tracee); break ; case PR_getcwd: set_sysnum(tracee, PR_void); status = 0; break ; case PR_fchdir: case PR_chdir: { struct stat statl; char *tmp; /* The ending "." ensures an error will be reported if * path does not exist or if it is not a directory. */ if (syscall_number == PR_chdir) { status = get_sysarg_path(tracee, path, SYSARG_1); if (status < 0) break ; status = join_paths(2, oldpath, path, "." ); if (status < 0) break ; dirfd = AT_FDCWD; } else { strcpy (oldpath, "." ); dirfd = peek_reg(tracee, CURRENT, SYSARG_1); } status = translate_path(tracee, path, dirfd, oldpath, true ); if (status < 0) break ; status = lstat(path, &statl); if (status < 0) break ; /* Check this directory is accessible. */ if ((statl.st_mode & S_IXUSR) == 0) return -EACCES; /* Sadly this method doesn't detranslate statefully, * this means that there's an ambiguity when several * bindings are from the same host path: * * $ proot -m /tmp:/a -m /tmp:/b fchdir_getcwd /a * /b * * $ proot -m /tmp:/b -m /tmp:/a fchdir_getcwd /a * /a * * A solution would be to follow each file descriptor * just like it is done for cwd. */ status = detranslate_path(tracee, path, NULL); if (status < 0) break ; /* Remove the trailing "/" or "/.". */ chop_finality(path); tmp = talloc_strdup(tracee->fs, path); if (tmp == NULL) { status = -ENOMEM; break ; } TALLOC_FREE(tracee->fs->cwd); tracee->fs->cwd = tmp; talloc_set_name_const(tracee->fs->cwd, "$cwd" ); set_sysnum(tracee, PR_void); status = 0; break ; } case PR_bind: case PR_connect: { word_t address; word_t size; address = peek_reg(tracee, CURRENT, SYSARG_2); size = peek_reg(tracee, CURRENT, SYSARG_3); status = translate_socketcall_enter(tracee, &address, size); if (status <= 0) break ; poke_reg(tracee, SYSARG_2, address); poke_reg(tracee, SYSARG_3, sizeof ( struct sockaddr_un)); status = 0; break ; } #define SYSARG_ADDR(n) (args_addr + ((n) - 1) * sizeof_word(tracee)) #define PEEK_WORD(addr, forced_errno) \ peek_word(tracee, addr); \ if ( errno != 0) { \ status = forced_errno ?: - errno ; \ break ; \ } #define POKE_WORD(addr, value) \ poke_word(tracee, addr, value); \ if ( errno != 0) { \ status = - errno ; \ break ; \ } case PR_accept: case PR_accept4: /* Nothing special to do if no sockaddr was specified. */ if (peek_reg(tracee, ORIGINAL, SYSARG_2) == 0) { status = 0; break ; } special = true ; /* Fall through. */ case PR_getsockname: case PR_getpeername:{ int size; /* Remember: PEEK_WORD puts -errno in status and breaks if an * error occured. */ size = ( int ) PEEK_WORD(peek_reg(tracee, ORIGINAL, SYSARG_3), special ? -EINVAL : 0); /* The "size" argument is both used as an input parameter * (max. size) and as an output parameter (actual size). The * exit stage needs to know the max. size to not overwrite * anything, that's why it is copied in the 6th argument * (unused) before the kernel updates it. */ poke_reg(tracee, SYSARG_6, size); status = 0; break ; } case PR_socketcall: { word_t args_addr; word_t sock_addr_saved; word_t sock_addr; word_t size_addr; word_t size; args_addr = peek_reg(tracee, CURRENT, SYSARG_2); switch (peek_reg(tracee, CURRENT, SYSARG_1)) { case SYS_BIND: case SYS_CONNECT: /* Handle these cases below. */ status = 1; break ; case SYS_ACCEPT: case SYS_ACCEPT4: /* Nothing special to do if no sockaddr was specified. */ sock_addr = PEEK_WORD(SYSARG_ADDR(2), 0); if (sock_addr == 0) { status = 0; break ; } special = true ; /* Fall through. */ case SYS_GETSOCKNAME: case SYS_GETPEERNAME: /* Remember: PEEK_WORD puts -errno in status and breaks * if an error occured. */ size_addr = PEEK_WORD(SYSARG_ADDR(3), 0); size = ( int ) PEEK_WORD(size_addr, special ? -EINVAL : 0); /* See case PR_accept for explanation. */ poke_reg(tracee, SYSARG_6, size); status = 0; break ; default : status = 0; break ; } /* An error occured or there's nothing else to do. */ if (status <= 0) break ; /* Remember: PEEK_WORD puts -errno in status and breaks if an * error occured. */ sock_addr = PEEK_WORD(SYSARG_ADDR(2), 0); size = PEEK_WORD(SYSARG_ADDR(3), 0); sock_addr_saved = sock_addr; status = translate_socketcall_enter(tracee, &sock_addr, size); if (status <= 0) break ; /* These parameters are used/restored at the exit stage. */ poke_reg(tracee, SYSARG_5, sock_addr_saved); poke_reg(tracee, SYSARG_6, size); /* Remember: POKE_WORD puts -errno in status and breaks if an * error occured. */ POKE_WORD(SYSARG_ADDR(2), sock_addr); POKE_WORD(SYSARG_ADDR(3), sizeof ( struct sockaddr_un)); status = 0; break ; } #undef SYSARG_ADDR #undef PEEK_WORD #undef POKE_WORD case PR_access: case PR_acct: case PR_chmod: case PR_chown: case PR_chown32: case PR_chroot: case PR_getxattr: case PR_listxattr: case PR_mknod: case PR_oldstat: case PR_creat: case PR_removexattr: case PR_setxattr: case PR_stat: case PR_stat64: case PR_statfs: case PR_statfs64: case PR_swapoff: case PR_swapon: case PR_truncate: case PR_truncate64: case PR_umount: case PR_umount2: case PR_uselib: case PR_utime: case PR_utimes: status = translate_sysarg(tracee, SYSARG_1, REGULAR); break ; case PR_open: flags = peek_reg(tracee, CURRENT, SYSARG_2); if ( ((flags & O_NOFOLLOW) != 0) || ((flags & O_EXCL) != 0 && (flags & O_CREAT) != 0)) status = translate_sysarg(tracee, SYSARG_1, SYMLINK); else status = translate_sysarg(tracee, SYSARG_1, REGULAR); break ; case PR_fchownat: case PR_fstatat64: case PR_newfstatat: case PR_utimensat: case PR_name_to_handle_at: dirfd = peek_reg(tracee, CURRENT, SYSARG_1); status = get_sysarg_path(tracee, path, SYSARG_2); if (status < 0) break ; flags = ( syscall_number == PR_fchownat || syscall_number == PR_name_to_handle_at) ? peek_reg(tracee, CURRENT, SYSARG_5) : peek_reg(tracee, CURRENT, SYSARG_4); if ((flags & AT_SYMLINK_NOFOLLOW) != 0) status = translate_path2(tracee, dirfd, path, SYSARG_2, SYMLINK); else status = translate_path2(tracee, dirfd, path, SYSARG_2, REGULAR); break ; case PR_fchmodat: case PR_faccessat: case PR_futimesat: case PR_mknodat: dirfd = peek_reg(tracee, CURRENT, SYSARG_1); status = get_sysarg_path(tracee, path, SYSARG_2); if (status < 0) break ; status = translate_path2(tracee, dirfd, path, SYSARG_2, REGULAR); break ; case PR_inotify_add_watch: flags = peek_reg(tracee, CURRENT, SYSARG_3); if ((flags & IN_DONT_FOLLOW) != 0) status = translate_sysarg(tracee, SYSARG_2, SYMLINK); else status = translate_sysarg(tracee, SYSARG_2, REGULAR); break ; case PR_readlink: case PR_lchown: case PR_lchown32: case PR_lgetxattr: case PR_llistxattr: case PR_lremovexattr: case PR_lsetxattr: case PR_lstat: case PR_lstat64: case PR_oldlstat: case PR_unlink: case PR_rmdir: case PR_mkdir: status = translate_sysarg(tracee, SYSARG_1, SYMLINK); break ; case PR_pivot_root: status = translate_sysarg(tracee, SYSARG_1, REGULAR); if (status < 0) break ; status = translate_sysarg(tracee, SYSARG_2, REGULAR); break ; case PR_linkat: olddirfd = peek_reg(tracee, CURRENT, SYSARG_1); newdirfd = peek_reg(tracee, CURRENT, SYSARG_3); flags = peek_reg(tracee, CURRENT, SYSARG_5); status = get_sysarg_path(tracee, oldpath, SYSARG_2); if (status < 0) break ; status = get_sysarg_path(tracee, newpath, SYSARG_4); if (status < 0) break ; if ((flags & AT_SYMLINK_FOLLOW) != 0) status = translate_path2(tracee, olddirfd, oldpath, SYSARG_2, REGULAR); else status = translate_path2(tracee, olddirfd, oldpath, SYSARG_2, SYMLINK); if (status < 0) break ; status = translate_path2(tracee, newdirfd, newpath, SYSARG_4, SYMLINK); break ; case PR_mount: status = get_sysarg_path(tracee, path, SYSARG_1); if (status < 0) break ; /* The following check covers only 90% of the cases. */ if (path[0] == '/' || path[0] == '.' ) { status = translate_path2(tracee, AT_FDCWD, path, SYSARG_1, REGULAR); if (status < 0) break ; } status = translate_sysarg(tracee, SYSARG_2, REGULAR); break ; case PR_openat: dirfd = peek_reg(tracee, CURRENT, SYSARG_1); flags = peek_reg(tracee, CURRENT, SYSARG_3); status = get_sysarg_path(tracee, path, SYSARG_2); if (status < 0) break ; if ( ((flags & O_NOFOLLOW) != 0) || ((flags & O_EXCL) != 0 && (flags & O_CREAT) != 0)) status = translate_path2(tracee, dirfd, path, SYSARG_2, SYMLINK); else status = translate_path2(tracee, dirfd, path, SYSARG_2, REGULAR); break ; case PR_readlinkat: case PR_unlinkat: case PR_mkdirat: dirfd = peek_reg(tracee, CURRENT, SYSARG_1); status = get_sysarg_path(tracee, path, SYSARG_2); if (status < 0) break ; status = translate_path2(tracee, dirfd, path, SYSARG_2, SYMLINK); break ; case PR_link: case PR_rename: status = translate_sysarg(tracee, SYSARG_1, SYMLINK); if (status < 0) break ; status = translate_sysarg(tracee, SYSARG_2, SYMLINK); break ; case PR_renameat: olddirfd = peek_reg(tracee, CURRENT, SYSARG_1); newdirfd = peek_reg(tracee, CURRENT, SYSARG_3); status = get_sysarg_path(tracee, oldpath, SYSARG_2); if (status < 0) break ; status = get_sysarg_path(tracee, newpath, SYSARG_4); if (status < 0) break ; status = translate_path2(tracee, olddirfd, oldpath, SYSARG_2, SYMLINK); if (status < 0) break ; status = translate_path2(tracee, newdirfd, newpath, SYSARG_4, SYMLINK); break ; case PR_symlink: status = translate_sysarg(tracee, SYSARG_2, SYMLINK); break ; case PR_symlinkat: newdirfd = peek_reg(tracee, CURRENT, SYSARG_2); status = get_sysarg_path(tracee, newpath, SYSARG_3); if (status < 0) break ; status = translate_path2(tracee, newdirfd, newpath, SYSARG_3, SYMLINK); break ; } end: status2 = notify_extensions(tracee, SYSCALL_ENTER_END, status, 0); if (status2 < 0) status = status2; return status; } |
这里就对 syscall的函数进行的修改
到这里 就基本搞清楚 proot的实现流程 与我们实现的小 demo 是基本差不多的(假的)
先进行小小的整理一下:
分主进程 与 子进程, proot 对子进程进行 attach 主进程来对 信号进行处理 以及仿真
设置 seccomp 对 ptrace的速度进行优化
那我们接下来 对 proot进行移植到安卓上!
移植proot 到安卓
这里使用的环境: Uabntu 22 android Studio Clion
参考实现的文章:https://blog.csdn.net/Denny_Chen_/article/details/137055642
我首先按照教程 ,在clion上实现 然后照猫画虎 移植到 android
cmakelist:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 | # For more information about using CMake with Android Studio, read the # documentation: https://d.android.com/studio/projects/add-native-code.html. # For more examples on how to use CMake, see https://github.com/android/ndk-samples. # Sets the minimum CMake version required for this project. cmake_minimum_required(VERSION 3.22 . 1 ) # Declares the project name. The project name can be accessed via ${ PROJECT_NAME}, # Since this is the top level CMakeLists.txt, the project name is also accessible # with ${CMAKE_PROJECT_NAME} (both CMake variables are in-sync within the top level # build script scope). project( "proot_demo" ) enable_language(C ASM) #设置支持的语言,C 和 ASM(内联汇编) #add_definitions(-DHAVE_SECCOMP_FILTER) add_definitions( - D_GNU_SOURCE) include_directories( proot / src / proot / src / cli proot / src / execve proot / src / extension # proot/src/root/extension/care proot / src / extension / portmap proot / src / extension / python proot / src / loader proot / src / path proot / src / ptrace proot / src / syscall proot / src / tracee proot / src / lib / uthash / include / uthash / src # talloc include talloc / talloc / lib / replace / # talloc/lib/replace/test/ talloc / bin / default / ) # Creates and names a library, sets it as either STATIC # or SHARED, and provides the relative paths to its source code. # You can define multiple libraries, and CMake builds them for you. # Gradle automatically packages shared libraries with your APK. # # In this top level CMakeLists.txt, ${CMAKE_PROJECT_NAME} is used to define # the target library name; in the sub-module's CMakeLists.txt, ${PROJECT_NAME} # is preferred for the same purpose. # # In order to load a library into your app from Java/Kotlin, you must call # System.loadLibrary() and pass the name of the library defined here; # for GameActivity/NativeActivity derived applications, the same library name must be # used in the AndroidManifest.xml file. add_library(${CMAKE_PROJECT_NAME} SHARED # List C/C++ source files with relative paths to this CMakeLists.txt. proot / src / cli / cli.c proot / src / cli / proot.c proot / src / cli / note.c proot / src / execve / enter.c proot / src / execve / exit.c proot / src / execve / shebang.c proot / src / execve / elf.c proot / src / execve / ldso.c proot / src / execve / auxv.c proot / src / execve / aoxp.c proot / src / path / binding.c proot / src / path / glue.c proot / src / path / canon.c proot / src / path / path.c proot / src / path / proc.c proot / src / path / temp.c proot / src / syscall / seccomp.c proot / src / syscall / syscall.c proot / src / syscall / chain.c proot / src / syscall / enter.c proot / src / syscall / exit.c proot / src / syscall / sysnum.c proot / src / syscall / socket.c proot / src / syscall / heap.c proot / src / syscall / rlimit.c proot / src / tracee / tracee.c proot / src / tracee / mem.c proot / src / tracee / reg.c proot / src / tracee / event.c proot / src / ptrace / ptrace.c proot / src / ptrace / user.c proot / src / ptrace / wait.c proot / src / extension / extension.c proot / src / extension / kompat / kompat.c proot / src / extension / fake_id0 / fake_id0.c proot / src / extension / link2symlink / link2symlink.c proot / src / extension / portmap / portmap.c proot / src / extension / portmap / map .c proot / src / loader / loader.c talloc / lib / replace / cwrap.c talloc / lib / replace / replace.c talloc / talloc.c native - lib.cpp) # Specifies libraries CMake should link to your target library. You # can link libraries from various origins, such as libraries defined in this # build script, prebuilt third-party libraries, or Android system libraries. target_link_libraries(${CMAKE_PROJECT_NAME} # List libraries link to the target library archive android log) add_subdirectory(libarchive) |
依赖文件 就按照 官网的把源代码下载下来。
后面我的github 会上传源码 这里就完成了proot的移植了(这里我在linux 上 编译arm64成功)
https://github.com/ywl20020421/-AndroidReverse/tree/main/Proot%E7%BC%96%E8%AF%91
proot 继续探究
对于子进程干了那些事?
声明可跟踪性:通过 ptrace(PTRACE_TRACEME) 将当前进程标记为可被跟踪。
同步状态:通过 SIGSTOP 暂停自身,确保 tracer 能够捕获初始状态。
性能优化:启用 seccomp 模式(如果未禁用)。
执行目标程序:调用 execvp 替换当前进程为目标程序。
我们主要的侧重点 应该放在 event_loop()这个循环里面干的事!
这个event_loop() 到底干了什么:
- 初始化信号处理
1 | 把退出的进程 干掉! |
- 配置信号处理函数
1 | 每次都要proot需要的信号 配置上 - - - - 万一被覆盖了呢? 确保程序在接收到特定信号时能够正确处理,防止进程失控或资源泄漏。 |
- 主事件循环
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | 释放已终止的进程:调用 free_terminated_tracees() 安全地释放已终止的进程资源。 等待下一个进程停止:使用 waitpid( - 1 , &tracee_status, __WALL) 等待任意子进程的状态变化。__WALL 标志确保捕获所有子进程的事件。 获取进程信息:通过 get_tracee(NULL, pid, true) 获取当前停止的进程信息,并确保其存在。 日志记录:记录详细的调试信息,帮助追踪事件处理过程。 通知扩展:调用 notify_extensions 通知扩展模块新状态,允许扩展模块进行自定义处理。 处理 ptrace 事件:如果进程正在被 ptrace 跟踪,调用 handle_ptracee_event 处理特定的 ptrace 事件。 处理通用事件:调用 handle_tracee_event 处理通用的进程事件,并确定如何重启进程。 重启进程:根据计算出的信号值调用 restart_tracee 重新启动进程。 |
在主事件循环 中,就设计到 处理 进程的syscall调用,以及对ptrace的仿真。
下面对他进行 模块化分析 方便后续满足自己项目的需要
Proot如何进行ptrace仿真?
设想一个ptrace函数触发
那么会出现在 event_loop() 的这个部分进行判断
1 2 3 4 5 6 7 8 | if (tracee->as_ptracee.ptracer != NULL) { bool keep_stopped = handle_ptracee_event(tracee, tracee_status); if (keep_stopped) continue ; } signal = handle_tracee_event(tracee, tracee_status); ( void ) restart_tracee(tracee, signal ); |
会判断这里是不是有tracee->as_ptracee.ptracer 存在
那么一开始 在前面 我也没看到对这个设置的代码 猜测 在系统调用的处理handle_tracee_event
首先会调用 translate_syscall_enter 这个函数
确实存在对ptrace的处理
1 2 3 | case PR_ptrace: status = translate_ptrace_enter(tracee); break ; |
后面的处理
1 2 3 4 5 6 | int translate_ptrace_enter(Tracee *tracee) { /* The ptrace syscall have to be emulated since it can't be nested. */ set_sysnum(tracee, PR_void); return 0; } |
处理就是将ptrace的 中断调用号变成 0 ? 大概就是设置成无效吧
当然 这是处理前
还有函数调用后呢
translate_syscall_exit(Tracee *tracee) 呐,这个就是了
1 2 3 | case PR_ptrace: status = translate_ptrace_exit(tracee); break ; |
跟进translate_ptrace_exit
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 | int translate_ptrace_exit(Tracee *tracee) { word_t request, pid, address, data, result; Tracee *ptracee, *ptracer; int forced_signal = -1; int signal ; int status; /* Read ptrace parameters. */ request = peek_reg(tracee, ORIGINAL, SYSARG_1); pid = peek_reg(tracee, ORIGINAL, SYSARG_2); address = peek_reg(tracee, ORIGINAL, SYSARG_3); data = peek_reg(tracee, ORIGINAL, SYSARG_4); /* Propagate signedness for this special value. */ if (is_32on64_mode(tracee) && pid == 0xFFFFFFFF) pid = (word_t) -1; /* The TRACEME request is the only one used by a tracee. */ if (request == PTRACE_TRACEME) { ptracer = tracee->parent; ptracee = tracee; /* The emulated ptrace in PRoot has the same * limitation as the real ptrace in the Linux kernel: * only one tracer per process. */ if (PTRACEE.ptracer != NULL || ptracee == ptracer) return -EPERM; attach_to_ptracer(ptracee, ptracer); /* Detect when the ptracer has gone to wait before the * ptracee did the ptrace(ATTACHME) request. */ if (PTRACER.waits_in == WAITS_IN_KERNEL) { status = kill(ptracer->pid, SIGSTOP); if (status < 0) note(tracee, WARNING, INTERNAL, "can't wake ptracer %d" , ptracer->pid); else { ptracer->sigstop = SIGSTOP_IGNORED; PTRACER.waits_in = WAITS_IN_PROOT; } } /* Disable seccomp acceleration for this tracee and * all its children since we can't assume what are the * syscalls its tracer is interested with. */ if (tracee->seccomp == ENABLED) tracee->seccomp = DISABLING; return 0; } /* The ATTACH, SEIZE, and INTERRUPT requests are the only ones * where the ptracee is in an unknown state. */ if (request == PTRACE_ATTACH) { ptracer = tracee; ptracee = get_tracee(ptracer, pid, false ); if (ptracee == NULL) return -ESRCH; /* The emulated ptrace in PRoot has the same * limitation as the real ptrace in the Linux kernel: * only one tracer per process. */ if (PTRACEE.ptracer != NULL || ptracee == ptracer) return -EPERM; attach_to_ptracer(ptracee, ptracer); /* The tracee is sent a SIGSTOP, but will not * necessarily have stopped by the completion of this * call. * * -- man 2 ptrace. */ kill(pid, SIGSTOP); return 0; } /* Here, the tracee is a ptracer. Also, the requested ptracee * has to be in the "stopped for ptracer" state. */ ptracer = tracee; ptracee = get_stopped_ptracee(ptracer, pid, false , __WALL); if (ptracee == NULL) { static bool warned = false ; /* Ensure we didn't get there only because inheritance * mechanism has missed this one. */ ptracee = get_tracee(tracee, pid, false ); if (ptracee != NULL && ptracee->exe == NULL && !warned) { warned = true ; note(ptracer, WARNING, INTERNAL, "ptrace request to an unexpected ptracee" ); } return -ESRCH; } /* Sanity checks. */ if ( PTRACEE.is_zombie || PTRACEE.ptracer != ptracer || pid == (word_t) -1) return -ESRCH; switch (request) { case PTRACE_SYSCALL: PTRACEE.ignore_syscalls = false ; forced_signal = ( int ) data; status = 0; break ; /* Restart the ptracee. */ case PTRACE_CONT: PTRACEE.ignore_syscalls = true ; forced_signal = ( int ) data; status = 0; break ; /* Restart the ptracee. */ case PTRACE_SINGLESTEP: ptracee->restart_how = PTRACE_SINGLESTEP; forced_signal = ( int ) data; status = 0; break ; /* Restart the ptracee. */ case PTRACE_SINGLEBLOCK: ptracee->restart_how = PTRACE_SINGLEBLOCK; forced_signal = ( int ) data; status = 0; break ; /* Restart the ptracee. */ case PTRACE_DETACH: detach_from_ptracer(ptracee); status = 0; break ; /* Restart the ptracee. */ case PTRACE_KILL: status = ptrace(request, pid, NULL, NULL); break ; /* Restart the ptracee. */ case PTRACE_SETOPTIONS: if (data & PTRACE_O_TRACESECCOMP) { /* We don't really support forwarding seccomp traps */ note(ptracer, WARNING, INTERNAL, "ptrace option PTRACE_O_TRACESECCOMP " "not supported yet" ); return -EINVAL; } PTRACEE.options = data; return 0; /* Don't restart the ptracee. */ case PTRACE_GETEVENTMSG: { status = ptrace(request, pid, NULL, &result); if (status < 0) return - errno ; poke_word(ptracer, data, result); if ( errno != 0) return - errno ; return 0; /* Don't restart the ptracee. */ } case PTRACE_PEEKUSER: if (is_32on64_mode(ptracer)) { address = convert_user_offset(address); if (address == (word_t) -1) return -EIO; } /* Fall through. */ case PTRACE_PEEKTEXT: case PTRACE_PEEKDATA: errno = 0; result = (word_t) ptrace(request, pid, address, NULL); if ( errno != 0) return - errno ; poke_word(ptracer, data, result); if ( errno != 0) return - errno ; return 0; /* Don't restart the ptracee. */ case PTRACE_POKEUSER: if (is_32on64_mode(ptracer)) { address = convert_user_offset(address); if (address == (word_t) -1) return -EIO; } status = ptrace(request, pid, address, data); if (status < 0) return - errno ; return 0; /* Don't restart the ptracee. */ case PTRACE_POKETEXT: case PTRACE_POKEDATA: if (is_32on64_mode(ptracer)) { word_t tmp; errno = 0; tmp = (word_t) ptrace(PTRACE_PEEKDATA, ptracee->pid, address, NULL); if ( errno != 0) return - errno ; data |= (tmp & 0xFFFFFFFF00000000ULL); } status = ptrace(request, pid, address, data); if (status < 0) return - errno ; return 0; /* Don't restart the ptracee. */ case PTRACE_GETSIGINFO: { siginfo_t siginfo; status = ptrace(request, pid, NULL, &siginfo); if (status < 0) return - errno ; status = write_data(ptracer, data, &siginfo, sizeof (siginfo)); if (status < 0) return status; return 0; /* Don't restart the ptracee. */ } case PTRACE_SETSIGINFO: { siginfo_t siginfo; status = read_data(ptracer, &siginfo, data, sizeof (siginfo)); if (status < 0) return status; status = ptrace(request, pid, NULL, &siginfo); if (status < 0) return - errno ; return 0; /* Don't restart the ptracee. */ } case PTRACE_GETREGS: { size_t size; union { struct user_regs_struct regs; uint32_t regs32[USER32_NB_REGS]; } buffer; status = ptrace(request, pid, NULL, &buffer); if (status < 0) return - errno ; if (is_32on64_mode(tracee)) { struct user_regs_struct regs64; memcpy (®s64, &buffer.regs, sizeof ( struct user_regs_struct)); convert_user_regs_struct( false , (uint64_t *) ®s64, buffer.regs32); size = sizeof (buffer.regs32); } else size = sizeof (buffer.regs); status = write_data(ptracer, data, &buffer, size); if (status < 0) return status; return 0; /* Don't restart the ptracee. */ } case PTRACE_SETREGS: { size_t size; union { struct user_regs_struct regs; uint32_t regs32[USER32_NB_REGS]; } buffer; size = (is_32on64_mode(ptracer) ? sizeof (buffer.regs32) : sizeof (buffer.regs)); status = read_data(ptracer, &buffer, data, size); if (status < 0) return status; if (is_32on64_mode(ptracer)) { uint32_t regs32[USER32_NB_REGS]; memcpy (regs32, buffer.regs32, sizeof (regs32)); convert_user_regs_struct( true , (uint64_t *) &buffer.regs, regs32); } status = ptrace(request, pid, NULL, &buffer); if (status < 0) return - errno ; return 0; /* Don't restart the ptracee. */ } case PTRACE_GETFPREGS: { size_t size; union { struct user_fpregs_struct fpregs; uint32_t fpregs32[USER32_NB_FPREGS]; } buffer; status = ptrace(request, pid, NULL, &buffer); if (status < 0) return - errno ; if (is_32on64_mode(tracee)) { #if 0 /* TODO */ struct user_fpregs_struct fpregs64; memcpy (&fpregs64, &buffer.fpregs, sizeof ( struct user_fpregs_struct)); convert_user_fpregs_struct( false , (uint64_t *) &fpregs64, buffer.fpregs32); #else static bool warned = false ; if (!warned) note(ptracer, WARNING, INTERNAL, "ptrace 32-bit request '%s' not supported on 64-bit yet" , stringify_ptrace(request)); warned = true ; bzero(&buffer, sizeof (buffer)); #endif size = sizeof (buffer.fpregs32); } else size = sizeof (buffer.fpregs); status = write_data(ptracer, data, &buffer, size); if (status < 0) return status; return 0; /* Don't restart the ptracee. */ } case PTRACE_SETFPREGS: { size_t size; union { struct user_fpregs_struct fpregs; uint32_t fpregs32[USER32_NB_FPREGS]; } buffer; size = (is_32on64_mode(ptracer) ? sizeof (buffer.fpregs32) : sizeof (buffer.fpregs)); status = read_data(ptracer, &buffer, data, size); if (status < 0) return status; if (is_32on64_mode(ptracer)) { #if 0 /* TODO */ uint32_t fpregs32[USER32_NB_FPREGS]; memcpy (fpregs32, buffer.fpregs32, sizeof (fpregs32)); convert_user_fpregs_struct( true , (uint64_t *) &buffer.fpregs, fpregs32); #else static bool warned = false ; if (!warned) note(ptracer, WARNING, INTERNAL, "ptrace 32-bit request '%s' not supported on 64-bit yet" , stringify_ptrace(request)); warned = true ; return -ENOTSUP; #endif } status = ptrace(request, pid, NULL, &buffer); if (status < 0) return - errno ; return 0; /* Don't restart the ptracee. */ } #if defined(ARCH_X86_64) || defined(ARCH_X86) case PTRACE_GET_THREAD_AREA: { struct user_desc user_desc; status = ptrace(request, pid, address, &user_desc); if (status < 0) return - errno ; status = write_data(ptracer, data, &user_desc, sizeof (user_desc)); if (status < 0) return status; return 0; /* Don't restart the ptracee. */ } case PTRACE_SET_THREAD_AREA: { struct user_desc user_desc; status = read_data(ptracer, &user_desc, data, sizeof (user_desc)); if (status < 0) return status; status = ptrace(request, pid, address, &user_desc); if (status < 0) return - errno ; return 0; /* Don't restart the ptracee. */ } #endif case PTRACE_GETREGSET: { struct iovec local_iovec; word_t remote_iovec_base; word_t remote_iovec_len; remote_iovec_base = peek_word(ptracer, data); if ( errno != 0) return - errno ; remote_iovec_len = peek_word(ptracer, data + sizeof_word(ptracer)); if ( errno != 0) return - errno ; /* Sanity check. */ assert ( sizeof (local_iovec.iov_len) == sizeof (word_t)); local_iovec.iov_len = remote_iovec_len; local_iovec.iov_base = talloc_zero_size(ptracer->ctx, remote_iovec_len); if (local_iovec.iov_base == NULL) return -ENOMEM; status = ptrace(PTRACE_GETREGSET, pid, address, &local_iovec); if (status < 0) return status; remote_iovec_len = local_iovec.iov_len = MIN(remote_iovec_len, local_iovec.iov_len); /* Update remote vector content. */ status = writev_data(ptracer, remote_iovec_base, &local_iovec, 1); if (status < 0) return status; /* Update remote vector length. */ poke_word(ptracer, data + sizeof_word(ptracer), remote_iovec_len); if ( errno != 0) return - errno ; return 0; /* Don't restart the ptracee. */ } case PTRACE_SETREGSET: { struct iovec local_iovec; word_t remote_iovec_base; word_t remote_iovec_len; remote_iovec_base = peek_word(ptracer, data); if ( errno != 0) return - errno ; remote_iovec_len = peek_word(ptracer, data + sizeof_word(ptracer)); if ( errno != 0) return - errno ; /* Sanity check. */ assert ( sizeof (local_iovec.iov_len) == sizeof (word_t)); local_iovec.iov_len = remote_iovec_len; local_iovec.iov_base = talloc_zero_size(ptracer->ctx, remote_iovec_len); if (local_iovec.iov_base == NULL) return -ENOMEM; /* Copy remote content into the local vector. */ status = read_data(ptracer, local_iovec.iov_base, remote_iovec_base, local_iovec.iov_len); if (status < 0) return status; status = ptrace(PTRACE_SETREGSET, pid, address, &local_iovec); if (status < 0) return status; return 0; /* Don't restart the ptracee. */ } case PTRACE_GETVFPREGS: case PTRACE_GETFPXREGS: { static bool warned = false ; if (!warned) note(ptracer, WARNING, INTERNAL, "ptrace request '%s' not supported yet" , stringify_ptrace(request)); warned = true ; return -ENOTSUP; } #if defined(ARCH_X86_64) case PTRACE_ARCH_PRCTL: switch (data) { case ARCH_GET_GS: case ARCH_GET_FS: status = ptrace(request, pid, &result, data); if (status < 0) return - errno ; poke_word(ptracer, address, result); if ( errno != 0) return - errno ; break ; case ARCH_SET_GS: case ARCH_SET_FS: { static bool warned = false ; if (!warned) note(ptracer, WARNING, INTERNAL, "ptrace request '%s' ARCH_SET_{G,F}S not supported yet" , stringify_ptrace(request)); return -ENOTSUP; } default : return -ENOTSUP; } return 0; /* Don't restart the ptracee. */ #endif case PTRACE_SET_SYSCALL: status = ptrace(request, pid, address, data); if (status < 0) return - errno ; return 0; /* Don't restart the ptracee. */ default : note(ptracer, WARNING, INTERNAL, "ptrace request '%s' not supported yet" , stringify_ptrace(request)); return -ENOTSUP; } /* Now, the initial tracee's event can be handled. */ signal = PTRACEE.event4.proot.pending ? handle_tracee_event(ptracee, PTRACEE.event4.proot.value) : PTRACEE.event4.proot.value; /* The restarting signal from the ptracer overrides the * restarting signal from PRoot. */ if (forced_signal != -1) signal = forced_signal; ( void ) restart_tracee(ptracee, signal ); return status; } |
这段代码就非常长了 ,显然就是在模拟ptrace了
这里确实与之前 猜测的一样
1 | attach_to_ptracer(ptracee, ptracer); |
这个代码 就设置 上了as_ptracee.ptracer 就对上了 event_loop
1 2 3 4 5 | if (tracee->as_ptracee.ptracer != NULL) { bool keep_stopped = handle_ptracee_event(tracee, tracee_status); if (keep_stopped) continue ; } |
这里就可以 进入 handle_ptracee_event
这个函数也是处理ptrace的事件 只不过 是proot仿真的 仅对我们跟踪的函数有效
到这里 proot 对ptrace 的仿真就到这里了
说白了 自己维护一个假的ptrace 给 子进程用 。
结束
我打算将proot 能用的部分拆分下来 供自己实现项目 奈何功力不足 但是柳暗花明又一村,我发现我之前看不懂的abyss项目的 demo代码 就是 魔改的proot ,天助我也。
https://github.com/iofomo/abyss
发现差不多的 在循环部分 处理好tracee 进行 attach 其他地方差不多的
到这里就是 我对proot理论部分的学习了 接下来 开始 自己 的 svc hook框架搭建
上面的代码分析仅仅是我个人的观点,难免会有疏漏之处。望大佬勿喷,但欢迎指正。
学习的文章:
https://bbs.kanxue.com/thread-275511.htm
https://bbs.kanxue.com/thread-273160.htm
https://www.cnblogs.com/bunner/p/14870587.html
https://www.cnblogs.com/bunner/p/14874439.html
https://github.com/google/sandboxed-api/tree/main/sandboxed_api/sandbox2
https://www.cnblogs.com/mysky007/p/11047943.html
https://blog.lleavesg.top/article/Android-Seccomp#f04cfd06ea374e2e9336eec450afad32
赞赏
|
|
---|---|
|
你搞错了,,,, 你发的那个proot地址是linux用的,不是安卓上用的,https://github.com/termux/proot
这个才是安卓在termux用的,linux的在android上用很多syscall无法拦截,需要兼容的地方非常多..... |
|
原来如此,好滴好滴,路漫漫其修远兮,多谢珍惜 解惑 ![]() |
|
不错噢, 有时间再学习学习。
|
|
你好,可以私聊吗
|
![]() |
- [原创]窥探Proot原理 2866
- [原创]安卓签名校验-探讨 23823
- [原创]某加固so层脱壳 23052
- [原创] NP签名校验分析 23591
- [原创]手搓函数抽取加固(上) 3635