@RunZhi
2016-09-14T00:13:42.000000Z
字数 2156
阅读 2115
操作系统实验报告
在Linux终端运行2个自己写的进程A和B
A的运行逻辑为:执行一个无限循环,在屏幕上输出一特定不变的字符
B的运行逻辑为:通过某种方式,侵入A进程的运行空间,修改A进程输出字符的内容
1.一个进程想要侵入其它的进程,毫无疑问,这个进程必须有root权限。
2.B想要侵入A的进程空间,那么,显然B要先能够访问A的进程空间
3.必须要知道所要修改内容的地址,否则无从更改。
综上三点,通过查阅资料,作出以下的解决方案:
1.一个su命令搞定
2.使用ptrace调用来实现进程内存的读写访问(其实这个调用还有巨多功能)。
3.使用nm命令查看obj文件各段内容的地址(偏移地址)
首先,编写如下代码
//p1.cpp
#include<stdio.h>
#include<unistd.h>
char c = 'p';
int main(int argc, char *argv[])
{
while(true)
{
sleep(1);
printf("%c\n",c);
}
return 0;
}
这个程序所要做的事很显然:每隔一秒输出一次字符'p'.
编译,链接:
# g++ -o p1 p1.cpp
此时,通过如下命令查看全局变量c偏移地址:
# nm ./p1
得到如下输出:
0000000000601040 B __bss_start
000000000060103c D c
0000000000601040 b completed.6337
0000000000601038 D __data_start
0000000000601038 W data_start
....
可知,c的偏移地址为0x000000000060103c
现再编写如下程序:
//p2.cpp
#include <sys/ptrace.h> // for ptrace
#include <sys/wait.h> //for wait
#include <stdlib.h>
#include <stdio.h>
int main(int argc, char *argv[])
{
char c = 't'; //把输出修改为't'
int ret = 0;
int status = 0;;
long data = 0;
long addr = 0x000000000060103c; //p1的变量c的偏移地址
pid_t apid = atoi(argv[1]); //从参数中获得pid
ptrace(PTRACE_ATTACH, apid, 0, 0); //把此进程attach上进程号为apid的进程,从而对该进程进行进程内存访问
wait(&status);
ptrace(PTRACE_POKEDATA, apid, addr, c); //对addr的偏移地址内容修改为c
ptrace(PTRACE_CONT, apid, 0, 0);
ptrace(PTRACE_DETACH, apid, NULL, NULL);
return 0;
}
编译,链接:
g++ -o p2 p2.cpp
1.在程序p1.cpp中,printf里千万不要忽略'\n'字符,因为如果没有它,printf输出的内容会缓存到内存里而不会输出到终端上.
2.本程序在x86_64下可以运行,但是其它系统没做过测试!
运行的时候,需要开两个终端,一个运行p1,另一个要先root然后再运行p2
运行p1的终端进入到相应目录后,输入:
./p1 &
此时终端会显示p1的进程号,然后再不断输出字符'p'.假设我们看到的进程号是%pid%
在运行p2的终端运行root后,同样进入相应目录,输入:
./p2 %pid%
然后就可以看到,p1的那个终端输出的字符变化为't'了。
为什么进程B可以破坏进程之间的隔离性,从而修改进程A的运行数据?
为什么需要进程通信?进程通信和进程隔离性之间如何做到较好的平衡?
很显然,其实这样的程序并没有多大意思,它通过了nm命令对要修改的内容进行定位。实际上,我们要修改的数据很可能并非是全局变量,还可能是临时变量!如此的话,nm命令就没法用了。那么如何进行内存数据的定位呢?
本人曾遇到过一个叫金山游侠的外挂。它的原理是多次在目标进程的整个进程空间内搜索目标数据。比如说我现在的血是100,我想要修改血量,可以先搜100,得到一大堆地址,然后故意扣2点血变成98,然后搜索98,一次推类,直至搜到只剩下一个地址,那么这个地址很有可能就是储存血量数据的地址。
这样的话,搜索进程中的目标数据就可以使用这样的方法L修改进程对目标进程进行attach之后,多次搜索其内存空间,然后定位地址。当然这都还未实现,暂时不知道这样做是否可行。