diff --git a/01/README.md b/01/README.md index 0663c4f..7cd0a18 100644 --- a/01/README.md +++ b/01/README.md @@ -162,7 +162,7 @@ Yama会在**security_ptrace_access_check**这个层次介入,但具体的介 ### /proc/\/mem -**/proc/\/mem**是Linux在Vfs中创建的一个重要的控制节点,其在Linux源码的**linux/fs/proc/base.c**文件中实现。 +**/proc/\/mem**是Linux在Vfs中创建的一个重要的控制节点,读写该文件相当于读写进程的内存,其在Linux源码的**linux/fs/proc/base.c**文件中实现。 ```c static const struct file_operations proc_mem_operations = { @@ -268,6 +268,145 @@ static bool may_access_mm(struct mm_struct *mm, struct task_struct *task, unsign ### process_vm_readv/writev +**process_vm_readv/writev** 是两个独立的系统调用,类似于Windows中的**Read/WriteProcessMemory**。 +需要指出的是,**Behavior Nets: Context-Aware Behavior Modeling for Code Injection-Based Windows Malware** 的 +作者在2.2节中所说的 **NtReadVirtualMemory**、**NtWriteVirtualMemory**实际上是**Read/WriteProcessMemory**在更底层的链接库中的形式。 + +在**kernel32.dll**中,**Read/WriteProcessMemory** API被定义,并且在内部实现中引用了 **ntdll**, +**NtReadVirtualMemory**、**NtWriteVirtualMemory** 在 **ntdll** 中被实现,这里也是系统调用的实际上下文交换发生的地方。 + +那再进一步呢?内核中发生了什么?这就难说了,毕竟Windows是闭源的。尽管可以通过逆向工程的方式窥见一二,但这不是现在的主题。 + +与之不同的是,我们完全可以追踪到 **process_vm_readv/writev** 在内核层面做了哪些事,一个不错的切入点是系统调用表。 + +> /arch/x86/entry/syscalls/syscall_64.tbl: +> +> 310 64 process_vm_readv sys_process_vm_readv +> +> 311 64 process_vm_writev sys_process_vm_writev + +可以明确的看到,310和311号系统调用对应 **process_vm_readv/writev**, +而在 **linux/include/linux/syscalls.h** 中,可以找到这两个系统调用的原型: + +```c +asmlinkage long sys_process_vm_readv(pid_t pid, + const struct iovec __user *lvec, + unsigned long liovcnt, + const struct iovec __user *rvec, + unsigned long riovcnt, + unsigned long flags); +asmlinkage long sys_process_vm_writev(pid_t pid, + const struct iovec __user *lvec, + unsigned long liovcnt, + const struct iovec __user *rvec, + unsigned long riovcnt, + unsigned long flags); +``` + +进一步地,这两个系统调用的实现可以在 **linux/mm/process_vm_access.c** 中被找到: + +```c +SYSCALL_DEFINE6(process_vm_readv, pid_t, pid, const struct iovec __user *, lvec, + unsigned long, liovcnt, const struct iovec __user *, rvec, + unsigned long, riovcnt, unsigned long, flags) +{ + return process_vm_rw(pid, lvec, liovcnt, rvec, riovcnt, flags, 0); +} + +SYSCALL_DEFINE6(process_vm_writev, pid_t, pid, + const struct iovec __user *, lvec, + unsigned long, liovcnt, const struct iovec __user *, rvec, + unsigned long, riovcnt, unsigned long, flags) +{ + return process_vm_rw(pid, lvec, liovcnt, rvec, riovcnt, flags, 1); +} +``` + +可以很明确的看到,实际上 **process_vm_readv** 和 **process_vm_writev** 的实现指向了一个相同的内核函数 **process_vm_rw**, +只不过 **process_vm_writev** 传入了一个为1的写标志。 + +**process_vm_rw** 的定义如下: + +```c +static ssize_t process_vm_rw(pid_t pid, + const struct iovec __user *lvec, + unsigned long liovcnt, + const struct iovec __user *rvec, + unsigned long riovcnt, + unsigned long flags, int vm_write) +{ + struct iovec iovstack_l[UIO_FASTIOV]; + struct iovec iovstack_r[UIO_FASTIOV]; + struct iovec *iov_l = iovstack_l; + struct iovec *iov_r; + struct iov_iter iter; + ssize_t rc; + int dir = vm_write ? ITER_SOURCE : ITER_DEST; + + if (flags != 0) + return -EINVAL; + + /* Check iovecs */ + rc = import_iovec(dir, lvec, liovcnt, UIO_FASTIOV, &iov_l, &iter); + if (rc < 0) + return rc; + if (!iov_iter_count(&iter)) + goto free_iov_l; + iov_r = iovec_from_user(rvec, riovcnt, UIO_FASTIOV, iovstack_r, + in_compat_syscall()); + if (IS_ERR(iov_r)) { + rc = PTR_ERR(iov_r); + goto free_iov_l; + } + rc = process_vm_rw_core(pid, &iter, iov_r, riovcnt, flags, vm_write); // <- 看这里看这里 + if (iov_r != iovstack_r) + kfree(iov_r); +free_iov_l: + kfree(iov_l); + return rc; +} +``` + +从定义中可以看出,**process_vm_rw** 将处理逻辑推后到了 **process_vm_rw_core** 函数。 +而对于这个函数,我们的分析可以掐头去尾: + +```c +static ssize_t process_vm_rw_core(pid_t pid, struct iov_iter *iter, + const struct iovec *rvec, + unsigned long riovcnt, + unsigned long flags, int vm_write) +{ + // ... 前省略 + + /* Get process information */ + task = find_get_task_by_vpid(pid); + if (!task) { + rc = -ESRCH; + goto free_proc_pages; + } + + mm = mm_access(task, PTRACE_MODE_ATTACH_REALCREDS); // <- 这里是关键 + if (IS_ERR(mm)) { + rc = PTR_ERR(mm); + /* + * Explicitly map EACCES to EPERM as EPERM is a more + * appropriate error code for process_vw_readv/writev + */ + if (rc == -EACCES) + rc = -EPERM; + goto put_task_struct; + } + + // ... 后省略 +} +``` + +源代码中最关键的一点是,**mm_access**最终被用来做鉴权,而我们已经证明过, +**mm_access** 最终所依赖的是 **ptrace_may_access** +调用链为 **mm_access -> may_access_mm -> ptrace_may_access**。 + +故而,我们已经可以得出 **process_vm_readv/writev** 同样受Yama的影响的结论了。 + ### /dev/mem ### 例外的情况