[doc] doc 01-2

This commit is contained in:
acite
2025-10-10 02:31:32 +08:00
parent 00a9369ea4
commit ffb4c13c72

View File

@@ -162,7 +162,7 @@ Yama会在**security_ptrace_access_check**这个层次介入,但具体的介
### /proc/\<pid\>/mem
**/proc/\<pid\>/mem**是Linux在Vfs中创建的一个重要的控制节点其在Linux源码的**linux/fs/proc/base.c**文件中实现。
**/proc/\<pid\>/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
### 例外的情况