[关闭]
@RunZhi 2016-09-13T16:13:42.000000Z 字数 2156 阅读 2043

进程内存修改小实例

操作系统实验报告

任务目标

在Linux终端运行2个自己写的进程A和B
A的运行逻辑为:执行一个无限循环,在屏幕上输出一特定不变的字符
B的运行逻辑为:通过某种方式,侵入A进程的运行空间,修改A进程输出字符的内容

任务过程

动手前的思考

1.一个进程想要侵入其它的进程,毫无疑问,这个进程必须有root权限
2.B想要侵入A的进程空间,那么,显然B要先能够访问A的进程空间
3.必须要知道所要修改内容的地址,否则无从更改。

综上三点,通过查阅资料,作出以下的解决方案:
1.一个su命令搞定
2.使用ptrace调用来实现进程内存的读写访问(其实这个调用还有巨多功能)。
3.使用nm命令查看obj文件各段内容的地址(偏移地址)

实现

首先,编写如下代码

  1. //p1.cpp
  2. #include<stdio.h>
  3. #include<unistd.h>
  4. char c = 'p';
  5. int main(int argc, char *argv[])
  6. {
  7. while(true)
  8. {
  9. sleep(1);
  10. printf("%c\n",c);
  11. }
  12. return 0;
  13. }

这个程序所要做的事很显然:每隔一秒输出一次字符'p'.
编译,链接:

  1. # g++ -o p1 p1.cpp

此时,通过如下命令查看全局变量c偏移地址:

  1. # nm ./p1

得到如下输出:

  1. 0000000000601040 B __bss_start
  2. 000000000060103c D c
  3. 0000000000601040 b completed.6337
  4. 0000000000601038 D __data_start
  5. 0000000000601038 W data_start
  6. ....

可知,c的偏移地址为0x000000000060103c

现再编写如下程序:

  1. //p2.cpp
  2. #include <sys/ptrace.h> // for ptrace
  3. #include <sys/wait.h> //for wait
  4. #include <stdlib.h>
  5. #include <stdio.h>
  6. int main(int argc, char *argv[])
  7. {
  8. char c = 't'; //把输出修改为't'
  9. int ret = 0;
  10. int status = 0;;
  11. long data = 0;
  12. long addr = 0x000000000060103c; //p1的变量c的偏移地址
  13. pid_t apid = atoi(argv[1]); //从参数中获得pid
  14. ptrace(PTRACE_ATTACH, apid, 0, 0); //把此进程attach上进程号为apid的进程,从而对该进程进行进程内存访问
  15. wait(&status);
  16. ptrace(PTRACE_POKEDATA, apid, addr, c); //对addr的偏移地址内容修改为c
  17. ptrace(PTRACE_CONT, apid, 0, 0);
  18. ptrace(PTRACE_DETACH, apid, NULL, NULL);
  19. return 0;
  20. }

编译,链接:

  1. g++ -o p2 p2.cpp

注意事项

1.在程序p1.cpp中,printf里千万不要忽略'\n'字符,因为如果没有它,printf输出的内容会缓存到内存里而不会输出到终端上.

2.本程序在x86_64下可以运行,但是其它系统没做过测试!

实验结果

运行的时候,需要开两个终端,一个运行p1,另一个要先root然后再运行p2
运行p1的终端进入到相应目录后,输入:

  1. ./p1 &

此时终端会显示p1的进程号,然后再不断输出字符'p'.假设我们看到的进程号是%pid%

在运行p2的终端运行root后,同样进入相应目录,输入:

  1. ./p2 %pid%

然后就可以看到,p1的那个终端输出的字符变化为't'了。

实验小结

  1. 完成了修改进程所输出字符
  2. 很遗憾,如果把char c = 'p'放到代码段里(main里),就难以寻找c的位置。所以本人选择把c作为全局变量方便寻找。难以寻找的原因在于,linux中,代码段中的变量的基址是储存在rbp(x86_64)中的,而来来回回的函数调用使得rbp值不断变化,无法寻找。

思考

为什么进程B可以破坏进程之间的隔离性,从而修改进程A的运行数据?
为什么需要进程通信?进程通信和进程隔离性之间如何做到较好的平衡?

  1. 进程间的运行数据并非完全隔离,进程之间也存在数据的交换。比如某个父进程需要实时的读取子进程的数据以确定子进程的运行状态。
  2. 通信和隔离应做到:在隔离基础上进行通信,因为安全性是第一位的。然后在适地放宽通信的限制。

更多思考

很显然,其实这样的程序并没有多大意思,它通过了nm命令对要修改的内容进行定位。实际上,我们要修改的数据很可能并非是全局变量,还可能是临时变量!如此的话,nm命令就没法用了。那么如何进行内存数据的定位呢?

本人曾遇到过一个叫金山游侠的外挂。它的原理是多次在目标进程的整个进程空间内搜索目标数据。比如说我现在的血是100,我想要修改血量,可以先搜100,得到一大堆地址,然后故意扣2点血变成98,然后搜索98,一次推类,直至搜到只剩下一个地址,那么这个地址很有可能就是储存血量数据的地址。

这样的话,搜索进程中的目标数据就可以使用这样的方法L修改进程对目标进程进行attach之后,多次搜索其内存空间,然后定位地址。当然这都还未实现,暂时不知道这样做是否可行。

参考资料

linux man ptrace
ptrace tricks

添加新批注
在作者公开此批注前,只有你和作者可见。
回复批注