当前位置: 首页 > 科技观察

Linux内核新增隐藏进程地址空间内存不被窃取

时间:2023-03-16 23:44:57 科技观察

文章,转载请联系邂逅Linux公众号。首先,让我们看看如何获??取其他进程地址空间的内存。答案是ptrace。这个毋庸置疑。使用崩溃工具、利用系统漏洞和插入模块等其他方法不在本文讨论范围之内。上面的例子:test.c#definehandle_error(msg)\do{perror(msg);exit(EXIT_FAILURE);}while(0)intmain(void){char*p;charconststr[]="JeffXie\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);return0;}地址:https://github.com/x-lugoo/hide-memory上面的例子test.c只是一个很简单的malloc区(堆区),然后保存一个string.terminal1:#gcctest.c#./a.outp:0xd3d260str:JeffXieterminal2:#ps-Ca.outPIDTTYTIMECMD19145pts/400:00:00a.out#cat/proc/19145/maps00400000-00401000r-xp/home/jeff/a.out00600000-00601000r--p/home/jeff/a.out00601000-00602000rw-p/home/jeff/a.out00d3d000-00d5e000rw-p[heap]260内可以看到heap区的范围,用readmem简单粗暴的读取进程19145(a.out)的0xd3d260后面十个字节的内容。终端2:#readmem191450xd3d26010JeffXie程序readmem是使用ptrace函数实现的,看代码:https://github.com/x-lugoo/hide-memory/tree/main/ptrace如果19145进程保存的不是普通字符串,而是某位皇帝留下的千年宝物的地址,或者说里面的信息关系……整个公司的命脉,如果被nice值不高的人收购了,后果可想而知。最近有人(前辈)在linux内核社区提交了补丁来解决这个问题,我对整个补丁做了一些简化。原始补丁: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本补丁实现原理:新增一个系统调用memfd_hide,当用户使用这个系统调用时,会返回一个fd,然后使用mmap(...fd...),映射一段内存,这段内存会是安全的,其他人无法通过ptrace得到。---a/arch/x86/entry/syscalls/syscall_64.tbl+++b/arch/x86/entry/syscalls/syscall_64.tbl@@-362,6+362,7@@438commonpidfd_getfdsys_pidfd_getfd439commonfaccessat2sys_faccessat2440commonprocess_madvisesys_process_madvise+441commonmemfd_hidesys_memfd_hideSYSCALL_DEFINE1(memfd_hide,unsignedlong,flags){structfile*file;intfd,err;fd=get_unused_fd_flags(flags&O_CLOEXEC);file=hidemem_file_create(flags);fd_install(fd,file);returnfd;}当用户调用第441次系统调用时,系统会返回以fd为例,用户层调用是这样的:#define__NR_memfd_hide441staticintmemfd_secret(unsignedlongflags){returnsyscall(__NR_memfd_hide,flags);}fd=memfd_secret(0);fd_install做了如下操作将fd关联到当前进程{.structfdtable*fdt;struct.sk..structfiles_struct*files;}fdt=current->files->fdt;fdt->fd[fd]=file;hidemem_file_create最后返回一个structfile,但是很重要的一个动作是初始化一系列回调函数,让用户采取适当的e调用mmap和memcpy时发生pagefault时的动作,比如调用alloc_page(gfp)申请一块内存。fd=memfd_secret(0);p=mmap(NULL,4096,prot,mode,fd,0);memcpy(p,str,sizeof(str));后面的绿色标记很好地清理了函数调用关系:staticstructfile*hidemem_file_create(unsignedlongflags){structfile*file=ERR_PTR(-ENOMEM);structnode*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;staticconststructfile_operationshidemem_fops={.release=hidemem_release,.mmap=hidemem_mmap,};staticinthidemem_mmap(structfile*file,structvm_area_struct*vma){vma->vm_ops=&hidemem_vm_ops;vma->vm_flags|;}staticconststructvm_operations_structhidemem_vm_ops={.fault=hidemem_fault,};staticvm_fault_thidemem_fault(structvm_fault*vmf){structaddress_space*mapping=vmf->vma->vm_file->f_mapping;vm_fault_tret=0;structpage*page;interr;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;}staticstructpage*hidemem_alloc_page(gfp_tgfp){回报rnalloc_page(gfp);}回到如何隐藏进程空间的问题:当其他进程使用ptrace函数获取指定进程地址空间的内容时,会调用check_vma_flags(),并在此添加条件判断此时,如果这段vma(/proc/pid/maps中的每一列地址范围属于一个vma)属于hidemem,则直接返回错误。staticintcheck_vma_flags(structvm_area_struct*vma,unsignedlonggup_flags){vm_flags_tvm_flags=vma->vm_flags;@@-923,6+925,9@@staticintcheck_vma_flags(structvm_area_struct*vma,unsignedlonggup_flags)if(gup_flags&FOLL_ANON&&!vma_is_anonymous(vma))return-EFAULT;+如果(vma_is_hidemem(vma))+return-EFAULT;+if(write){if(!(VMm_WflagTEs)&RIs{(!(gup_flags&FOLL_FORCE))staticconststructvm_operations_structhidemem_vm_ops={.fault=hidemem_fault,};boolvma_is_hidemem(structvm_area_struct*vma){->vm_ops==&hidemem_vm_ops;}加上vma_istramem_hidemem(如果使用pma使用)来判断(此时使用vma_istra_hidemem)来判断进程内存被分段时,会直接报错,达到隐藏页面内容的目的vma后面,以上补丁和测试代码都在:https://github.com/x-lugoo/hide-memory原补丁:https://lore.kernel.org/linux-fsdevel/20201203062949.5484-1-rppt@kernel.org/T/#t