Linux内核中如何添加新功能隐藏进程地址空间内存不被窃取

61次阅读
没有评论

共计 4557 个字符,预计需要花费 12 分钟才能阅读完成。

这篇文章将为大家详细讲解有关 Linux 内核中如何添加新功能隐藏进程地址空间内存不被窃取,文章内容质量较高,因此丸趣 TV 小编分享给大家做个参考,希望大家阅读完这篇文章后对相关知识有一定的了解。

首先看怎样能获取其它进程地址空间的内存,答案是 ptrace 毫无疑问了, 其它比如使用 crash 工具,利用系统漏洞,插入模块等邪门方法不在本篇讨论范围之内。

上例子:test.c

 #define handle_error(msg) \ do { perror(msg); exit(EXIT_FAILURE); } while (0) int main(void) { char *p; char const str[] =  Jeff Xie\n  p = malloc(sizeof(str)); if (!p) handle_error(malloc  printf( p:0x%llx\n , p); memcpy(p, str, sizeof(str)); printf(str:%s\n , p); sleep(10000); return 0; }

地址: https://github.com/x-lugoo/hide-memory

上面例子 test.c 中只是非常单纯的 malloc 了一块区域(堆区),然后保存了一个字符串.

terminal 1:

#gcc test.c #./a.out p:0xd3d260 str:Jeff Xie

terminal 2:

#ps -C a.out PID TTY TIME CMD 19145 pts/4 00:00:00 a.out #cat /proc/19145/maps 00400000-00401000 r-xp /home/jeff/a.out 00600000-00601000 r--p /home/jeff/a.out 00601000-00602000 rw-p /home/jeff/a.out 00d3d000-00d5e000 rw-p [heap]

可以看到 0xd3d260 在 heap 区域范围内,使用 readmem 就可以简单粗暴的读出了进程 19145(a.out)的 0xd3d260   向后十个字节的内容.

terminal 2:

#readmem 19145 0xd3d260 10 Jeff Xie

程序 readmem 使用 ptrace 功能实现,代码见:

https://github.com/x-lugoo/hide-memory/tree/main/ptrace

如果进程 19145 保存的不是一个普通的字符串,而是某位皇帝留下的千年宝藏的地址,或者里面的信息关系到整个公司的命脉,如果被 nice 值不高的人获取了,后果可想而知。

最近有人 (前辈) 在 linux 内核社区提交了一个 patch,解决了这个问题,我把整个 patch 简化了一些。

原始 patch:

https://lore.kernel.org/linux-fsdevel/20201203062949.5484-1-rppt@kernel.org/T/#t

被我简化后:

https://github.com/x-lugoo/hide-memory/blob/main/hidemem/0001-hidemem-Initialization-version.patc

此 patch 实现的原理:

新增一个系统调用 memfd_hide, 当用户使用这个系统调用时,会返回一个 fd,   进而使用 mmap(…fd…),map 一段内存,此段内存将是安全的,其它人不能通过 ptrace 获取。

--- a/arch/x86/entry/syscalls/syscall_64.tbl +++ b/arch/x86/entry/syscalls/syscall_64.tbl @@ -362,6 +362,7 @@ 438 common pidfd_getfd sys_pidfd_getfd 439 common faccessat2 sys_faccessat2 440 common process_madvise sys_process_madvise +441 common memfd_hide sys_memfd_hide SYSCALL_DEFINE1(memfd_hide, unsigned long, flags) { struct file *file; int fd, err; fd = get_unused_fd_flags(flags   O_CLOEXEC); file = hidemem_file_create(flags); fd_install(fd, file); return fd; }

当用户调用 441 号系统调用时, 系统会返回一个 fd, 例如用户层这样调用:

#define __NR_memfd_hide 441 static int memfd_secret(unsigned long flags) { return syscall(__NR_memfd_hide, flags); } fd = memfd_secret(0);

fd_install 做了以下操作,把 fd 和当前进程关联起来.

struct fdtable *fdt; struct task_struct { ... struct files_struct *files; } fdt = current- files-  fdt- fd[fd] = file;

hidemem_file_create 最终是返回了一个 struct file,   但是做的一个很重要的动作是初始化一系列回调函数, 让用户调用 mmap 和 memcpy 时,在发生 page  fault 时进行合适的动作,比如调用 alloc_page(gfp)申请一块内存.

fd = memfd_secret(0); p = mmap(NULL, 4096, prot, mode, fd, 0); memcpy(p, str, sizeof(str));

追随以下绿色标记 可以很好理清函数调用关系:

static struct file *hidemem_file_create(unsigned long flags) { struct file *file = ERR_PTR(-ENOMEM); struct inode *inode; inode = alloc_anon_inode(hidemem_mnt- mnt_sb); file = alloc_file_pseudo(inode, hidemem_mnt,  hidemem , O_RDWR,  hidemem_fops); inode- i_mapping- a_ops =  hidemem_aops; static const struct file_operations hidemem_fops = { .release = hidemem_release, .mmap = hidemem_mmap, }; static int hidemem_mmap(struct file *file, struct vm_area_struct *vma) { vma- vm_ops =  hidemem_vm_ops; vma- vm_flags |= VM_LOCKED; } static const struct vm_operations_struct hidemem_vm_ops = { .fault = hidemem_fault, }; static vm_fault_t hidemem_fault(struct vm_fault *vmf) { struct address_space *mapping = vmf- vma- vm_file- f_mapping; vm_fault_t ret = 0; struct page *page; int err; page = find_get_page(mapping, offset); if (!page) { page = hidemem_alloc_page(vmf- gfp_mask); err = add_to_page_cache(page, mapping, offset, vmf- gfp_mask); } vmf- page = page; }
static struct page *hidemem_alloc_page(gfp_t gfp) { return alloc_page(gfp); }

回到怎样隐藏进程空间的问题上:

当其它进程使用 ptrace 功能获取指定进程地址空间内容时,会调用到 check_vma_flags(),   此时加上一个条件判断,如果此段 vma(/proc/pid/maps 中的每一列地址范围属于一个 vma)属于 hidemem, 直接返回错误.

 static int check_vma_flags(struct vm_area_struct *vma, unsigned long gup_flags) { vm_flags_t vm_flags = vma- vm_flags; @@ -923,6 +925,9 @@ static int check_vma_flags(struct vm_area_struct *vma, unsigned long gup_flags) if (gup_flags   FOLL_ANON   !vma_is_anonymous(vma)) return -EFAULT; + if (vma_is_hidemem(vma)) + return -EFAULT; + if (write) { if (!(vm_flags   VM_WRITE)) { if (!(gup_flags   FOLL_FORCE))
static const struct vm_operations_struct hidemem_vm_ops = { .fault = hidemem_fault, }; bool vma_is_hidemem(struct vm_area_struct *vma) { return vma- vm_ops ==  hidemem_vm_ops; }

加上 vma_is_hidemem(vma)判断之后,此时如果使用 readmem 利用 ptrace 获取指定进程内存段的时候,会直接报错,以达到隐藏 vma 背后 page 内容的目的。

以上 patch 和测试代码都在:

https://github.com/x-lugoo/hide-memory

原始 patch:

https://lore.kernel.org/linux-fsdevel/20201203062949.5484-1-rppt@kernel.org/T/#t

关于 Linux 内核中如何添加新功能隐藏进程地址空间内存不被窃取就分享到这里了,希望以上内容可以对大家有一定的帮助,可以学到更多知识。如果觉得文章不错,可以把它分享出去让更多的人看到。

正文完
 
丸趣
版权声明:本站原创文章,由 丸趣 2023-08-25发表,共计4557字。
转载说明:除特殊说明外本站除技术相关以外文章皆由网络搜集发布,转载请注明出处。
评论(没有评论)