千家信息网

Linux内核私闯进程地址空间并修改进程内存的示例分析

发表于:2026-11-01 作者:千家信息网编辑
千家信息网最后更新 2026年11月01日,这篇文章主要介绍Linux内核私闯进程地址空间并修改进程内存的示例分析,文中介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们一定要看完!进程地址空间的隔离 是现代操作系统的一个显著特征。这也是区别
千家信息网最后更新 2026年11月01日Linux内核私闯进程地址空间并修改进程内存的示例分析

这篇文章主要介绍Linux内核私闯进程地址空间并修改进程内存的示例分析,文中介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们一定要看完!

进程地址空间的隔离 是现代操作系统的一个显著特征。这也是区别于 "古代"操作系统 的显著特征。

进程地址空间隔离意味着进程P1无法以随意的方式访问进程P2的内存,除非这块内存被声明是共享的。

这非常容易理解,我举个例子。

我们知道,在原始野人社会,是没有家庭的观念的,所有的资源都是部落内共享的,所有的野人都可以以任意的方式在任意时间和任何其他野人交互。类似Dos这样的操作系统就是这样的,内存地址空间并没有隔离。进程可以随意访问其它进程的内存。

后来有了家庭的观念,家庭的资源被隔离,人们便不能私闯民宅了,人们无法以随意的方式进入别人的家用别人的东西,除非这是主人允许的。操作系统进入现代模式后,进程也有了类似家庭的概念。

但家庭的概念是虚拟的,人们只是遵守约定而不去破坏别人的家庭。房子作为一个物理基础设施,保护着家庭。在操作系统中,家庭类似于虚拟地址空间,而房子就是页表。

邻居不能闯入你的房子,但警察可以,政府公务人员以合理的理由也可以。所谓的特权管理机构只要理由充分,就可以进入普通人家的房子,touch这家人的东西。对于操作系统而言,这就是内核可以做的事,内核可以访问任意进程的地址空间。

当然了,内核并不会无故私闯民宅,就像警察不会随意闯入别人家里一样。

但是,你可以让内核故意这么做,做点无赖的事情。

我们来试一下,先看一个程序:

// test.c// gcc test.c -o test#include #include #include #include #include int main(){  char* addr = mmap(NULL, 1024, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);  strcpy(addr, "Zhejiang wenzhou pixie shi");  printf("addr: %lu  pid:%d\n", addr, getpid());  printf("before:%s \n", addr); getchar();  printf("after:%s\n", addr);  return 0;}

这个程序的输出非常简单,before和after都会输出 "Zhejiang wenzhou pixie shi",但是我们想把这句话给改了,怎么办呢?显然,test进程如果自己不改它,那就没辙…但是可以让内核强制改啊,让内核私闯民宅就是了。

接下来我写一个内核模块:

// test.c// make -C /lib/modules/`uname -r`/build SUBDIRS=`pwd` modules#include #include #include static int pid = 1;module_param(pid, int, 0644);static unsigned long addr = 0;module_param(addr, long, 0644);// 根据一个进程的虚拟地址找到它的页表,相当于找到这家人的房子地址,然后闯入!static pte_t* get_pte(struct task_struct *task, unsigned long address){ pgd_t* pgd; pud_t* pud; pmd_t* pmd; pte_t* pte; struct mm_struct *mm = task->mm; pgd = pgd_offset(mm, address); if(pgd_none(*pgd) || pgd_bad(*pgd)) return NULL; pud = pud_offset(pgd, address); if(pud_none(*pud) || pud_bad(*pud)) return NULL; pmd = pmd_offset(pud, address); if(pmd_none(*pmd) || pmd_bad(*pmd)) return NULL; pte = pte_offset_kernel(pmd, address); if(pte_none(*pte)) return NULL; return pte;}static int test_init(void){ struct task_struct *task; pte_t* pte; struct page* page; // 找到这家人 task = pid_task(find_pid_ns(pid, &init_pid_ns), PIDTYPE_PID); // 找到这家人住在哪里 if(!(pte = get_pte(task, addr))) return -1; page = pte_page(*pte); // 强行闯入 addr = page_address(page); // sdajgdoiewhgikwnsviwgvwgvw strcpy(addr, (char *)"rain flooding water will not get fat!"); // 事了拂衣去,深藏功与名 return 0;}static void test_exit(void){}module_init(test_init);module_exit(test_exit);MODULE_LICENSE("GPL");

来来来,我们来试一下:

[root@10 page_replace]# ./testaddr: 140338535763968  pid:9912before:Zhejiang wenzhou pixie shi

此时,我们加载内核模块test.ko

[root@10 test]# insmod test.ko pid=9912 addr=140338535763968[root@10 test]#

在test进程拍入回车:

[root@10 page_replace]# ./testaddr: 140338535763968  pid:9912before:Zhejiang wenzhou pixie shiafter:rain flooding water will not get fat![root@10 page_replace]#

显然,"浙江温州皮鞋湿"被改成了"下雨进水不会胖"。

仔细看上面那个内核模块的 get_pte 函数,这个函数要想写对,你必须对你想蹂躏的进程所在的机器的MMU有一定的了解,比如是32位系统还是64位系统,是3级页表还是4级页表或者5级?这…

Linux的可玩性在于你可以自己动手,又可以让人代劳。比如,获取一个进程的虚拟地址的页表项指示的物理页面,就可以直接得到。

有这样的API吗?有啊,别忘了一切皆文件,恰好在proc文件系统中,就有这么一个文件:

/proc/$pid/pagemap

读取这个文件,得到的就是进程虚拟地址的页表项,下图截自内核Doc:

Documentation/vm/pagemap.txt

虚拟地址空间是每进程的,而物理地址空间则是所有进程共享的。换句话说,物理地址是全局的。

现在,根据Documentation/vm/pagemap.txt的解释,写一个程序,获取任意进程任意虚拟地址的全局物理地址:

// getphys.c// gcc getphys -o getphys#include #include #include int main(int argc, char **argv){ int fd; int pid; unsigned long pte; unsigned long addr; unsigned long phy_addr; char procbuf[64] = {0}; pid = atoi(argv[1]); addr = atol(argv[2]); sprintf(procbuf, "/proc/%d/pagemap", pid); fd = open(procbuf, O_RDONLY); size_t offset = (addr/4096) * sizeof(unsigned long); lseek(fd, offset, SEEK_SET); read(fd, &pte, sizeof(unsigned long)); phy_addr = (pte & ((((unsigned long)1) << 55) - 1))*4096 + addr%4096; printf("phy addr:%lu\n", phy_addr); return 0;}

随后,我们修改内核模块:

#include static unsigned long addr = 0;module_param(addr, long, 0644);static int test_init(void){ strcpy(phys_to_virt(addr), (char *)"rain flooding water will not get fat!"); return 0;}static void test_exit(void){}module_init(test_init);module_exit(test_exit);MODULE_LICENSE("GPL");

先运行test,然后根据test的输出作为getphys的输入,再根据getphys的输出作为内核模块test.ko的输入,就成了。还记得吗?这不就是管道连接多个程序的风格吗?

输入一个物理地址,然后把它改了,仅此而已。通过虚拟地址获取页表的操作已经由用户态的pagemap文件的读取并解析代劳了。

以上是"Linux内核私闯进程地址空间并修改进程内存的示例分析"这篇文章的所有内容,感谢各位的阅读!希望分享的内容对大家有帮助,更多相关知识,欢迎关注行业资讯频道!

进程 地址 内核 空间 系统 家庭 内存 操作系统 就是 物理 房子 文件 模块 程序 输出 隔离 人们 方式 民宅 输入 数据库的安全要保护哪些东西 数据库安全各自的含义是什么 生产安全数据库录入 数据库的安全性及管理 数据库安全策略包含哪些 海淀数据库安全审计系统 建立农村房屋安全信息数据库 易用的数据库客户端支持安全管理 连接数据库失败ssl安全错误 数据库的锁怎样保障安全 精品咖啡豆 深圳市人人互联网科技有限公司 哈尔滨大商圈互联网科技有限公司 互联网科技公司华为 科技服务与互联网 掌上梦互联网科技 互联网前沿科技 重庆科技学院互联网 济南和联互联网科技有限公司 中化能源互联网科技招聘 互联网金融科技展 徐州市互联网科技有限公司 工业互联网展湾科技股份有限公司 云南省自由互联网科技有限公司 互联网科技平台产品运营 互联网科技新闻 互联网科技手抄报图片 互联网科技博主张洲 第三届互联网大会安防黑科技 恒生互联网科技趋势 云南咖啡 北京大学互联网科技怎么样 苏州铭阳互联网科技有限公司 互联网企业科技类企业如何做账 上海创道互联网络科技有限公司 互联网风格科技公司名称 宁波跨境商互联网科技有限公司 互联网时代科技型公司的机会 互联网金融运营模式中的金融科技 山东亿川互联网科技有限责任公司
0