操作系统课设笔记
因为实习,没法一次性把课设做完,就干脆些把笔记写成博文方便自己查阅吧,持续更新,直至完成。
这课设的设计基本上是依赖于一本书的,大概是怕我们遇到太多的坑吧。。而OS上的坑又不是那么容易解决的,总之以下操作不带有普遍性。
课设平台:
- fedora 7
- linux-2.6.21 source code
- i386
实验1 编译内核¶
纯粹照着书搞。。
实验过程¶
以下操作假设用户为root,目录自行决定。
% tar -zxvf linux-2.6.21.tar.gz
% cd linux-2.6.21
% make mrproper # 清楚掉之前编译产生的垃圾
% make oldconfig # 如果自己有配置文件的话,直接 copy 进来命名为 .config 即可
% vim Makefile
修改EXTARVERSION =
,改为EXTRAVERSION = .7-custom-version
。
这样的话你生成的内核版本号就为2.6.21.7-custom-version(前面有几个参数是指定 2,6,21 的,这里没给出),继续:
% make all
% make modules_install
% make install
这样的话新的内核就已经更新到了/boot下,并且grub的配置文件(/etc/grub.conf)也已经被更新了。
有兴趣研究的同学可以继续去查看terminal给出的那个sh命令之后的那个文件,进而会跟到另外一个文件里面,我只是粗略看了下没有深究,就不多说了。
最后一步reboot,就可以看到在grub的选项中看到你的内核了(.7-custom-version结尾的那个)。
实验2 系统调用¶
这里只是添加一个最简单的系统调用,无参数,无实际操作!加复杂的系统调用的时候可能会遇到各种坑,在做往后几个实验的时候再去解决吧。
实验过程¶
假设要添加的系统调用名为goclis,则内核对应的实现函数名一般为sys_goclis
,现在开始实现。目录和用户假设同实验1。
% cd linux-2.6.21
% vim arch/i386/kernel/syscall_table.S
往系统调用表的末尾添加要新增的系统调用:
.long sys_tee /* 315 */
.long sys_vmsplice
# 省略几个。。
.long sys_goclis /* 320 */
添加头文件goclis.h(这里其实非必要):
% vim include/linux/goclis.h
正如我所说,这个系统调用非常简单,所以。。头文件除了宏保护就是空:
// goclis.h
#ifndef _LINUX_GOCLIS_H
#define _LINUX_GOCLIS_H
#endif
添加实现文件goclis.c,在里面要加入我们指定的系统调用的实现,宏asmlinkage
表示该函数的参数通过栈传递:
% vim kernel/goclis.c
// goclis.c
#include <linux/linkage.h>
#include <linux/goclis.h>
asmlinkage int sys_goclis()
{
return 320; // 这个是随意的
}
实现很简单的系统调用,继续,为该系统调用添加支持:
vim kernel/Makefile
找到如下内容:
obj-y = sched.o fork.o exec_domain.o \
...
加入goclis.o:
obj-y = goclis.o sched.o ...
这样才能保证goclis.c在内核编译链接的时候是可见的。但其实这个调用的实现也不一定要写在goclis.c中,完全可以放在一个已经存在的.c中即可,只要保证这个.c对应的.o能被编译链接。
接着为这个系统调用定义系统调用,我们之前那个320只是个注释:
% vim include/asm-i386/unistd.h
是i386不是ia64,因为我们是32位的系统。接着找到:
#define __NR_epoll_wait 319
#ifdef __KERNEL__
#define NR_syscalls 320
修改为:
#define __NR_epoll_wait 319
#define __NR_goclis 320
#ifdef __KERNEL__
#define NR_syscalls 321
很显然吧,用__NR_goclis
来命名我们的系统调用,然后把它定义为320,这样就真的 320了。。
最后,还得把你的这个系统调用加入到统一的系统调用头文件中:
% vim include/linux/syscalls.h
加入头文件:
#include <linux/goclis.h>
加入声明:
asmlinkage int sys_goclis();
OK,重新编译内核然后reboot,就ok了!
关于编译内核,因为之前已经编译过产生了很多的.o 了,节省时间的话,直接make就可以接着编译了,否则重头编译超级浪费时间。。
重启后测试下我们的系统调用是否生效,注意得重启为我们创的这个内核!
// test.c
#include <sys/syscall.h>
#include <unistd.h>
int main()
{
int ret = syscall(320);
printf("%d\n", ret);
return 0;
}
编译后运行:
% gcc test.c
% ./a.out
320
ok~,打印出 320 了。
实验3 进程隐藏¶
实验的本质目的应该是修改一系列与/proc目录相关的方法,从而实现“隐藏”的要求。
思路分析¶
ls /proc及ps aux引起的系统调用层次如下:
% ls /proc (or ps aux)
=> proc_root_readdir
=> proc_readdir
=> proc_pid_readdir
=> proc_base_fill_cache
=> proc_pid_fill_cache
其中,proc_base_fill_cache
和proc_pid_fill_cache
会将进程标识写入用户缓冲区中。因此,实验的关键就在于要过滤掉那些隐藏的进程,不让它们调用这些方法,这个通过修改其上层的方法proc_pid_readdir
可以做到。
其他的功能的实现方式其实类似于ls /proc隐藏进程的实现:大致就是用户通过系统调用,修改了相应的task_struct
内部的值,然后修改proc文件系统的一些方法,使得它们根据task_struct
中的值的不同,产生不同的行为即可。
现在来大致分析下实验需要实现什么:
- 添加两个系统调用:
asmlinkage int sys_hide(pid_t pid, int on)
和asmlinkage int sys_hide_user_process(uid_t uid, char *binname)
。 - 修改task_struct的结构,添加支持隐藏的信息。
- 修改proc文件系统的一些方法:如
proc_pid_readdir
方法。 - 修改proc文件系统的某些模块:这个其实可以替换成自己实现一个模块,但是修改系统现成的可以省挺多事的。往模块中加入一些全局变量来实现自己的功能,如hidden_flag。
实验过程¶
添加系统调用,过程类似于实验2。
修改系统调用表:
% vim arch/i386/kernel/syscall_table.S
// syscall_table.S
.long sys_hide
.long sys_hide_user_processes
增加系统调用实现:
% vim kernel/hide.c
// hide.c
#include <linux/linkage.h>
#include <linux/sched.h>
#include <asm/current.h>
/*
* set the attribute hide of task with pid,
* according to the value of on.
*
* if on value is invalid (except for 1 and 0), return 0 to indicate.
* if current user is not root, return 1 to indicate.
* if pid is not exist, return 2 to indicate.
* return 3 to indicate success.
*/
asmlinkage int sys_hide(pid_t pid, int on)
{
if (current->uid != 0) {
return 1; // current user is not root
}
struct task_struct *p;
p = find_task_by_pid(pid);
if (!pid) {
return 2; // pid is invalid
}
if (on == 1 || on == 0) {
p->hide = on;
return 3; // success
} else {
return 0; // on is invalid
}
}
% vim kernel/hide_user_processes.c
// hide_user_processes.c
#include <linux/linkage.h>
#include <linux/sched.h>
#include <asm/current.h>
/*
* hide processes with uid, accroing to binname, hide one or all.
*
* hide all processes when binname is NULL, otherwise, hide the process
* which binary name is equal to binname.
*
* if current user is not root, return 0 to indicate.
* return 1 to indicate success.
*/
asmlinkage int sys_hide_user_processes(uid_t uid, char *binname)
{
if (current->uid != 0) {
return 0;
}
struct task_struct *p;
if (binname == NULL) {
for_each_process(p) {
if (p->uid == uid) {
p->hide = 1;
}
}
} else {
for_each_process(p) {
if (p->uid == uid && strcmp(binname, p->comm) == 0) {
p->hide = 1;
}
}
}
return 1;
}
这里简要描述下实现,都很简单,实现的开始都检查了当前的进程的uid是否为0,即是否为root,因为这些系统调用只能root用户使用。
接着,hide.c
通过方法查找到对应pid的task_struct
指针,修改其内部的hide的值,这个hide是为了实验添加的,后面讲。
而hide_user_processes.c
则通过for_each_process
遍历所有的task_struct
,将符合条件的修改hide进行隐藏。这里使用到的p->comm
代表的是该task_struct
的二进制映像名称。
继续完善系统调用的添加,修改kernel/Makefile
,unistd.h
,syscalls.h
:
% vim kernel/Makefile
// Makefile
obj-y = hide.o hide_user_processes.o goclis.o ...
% vim include/asm-i386/unistd.h
// unistd.h
#define __NR_hide 321
#define __NR_hide_user_processes 322
#define NR_syscalls 323
% vim include/linux/syscalls.h
// syscalls.h
asmlinkage int sys_hide(pid_t pid, int on);
asmlinkage int sys_hide_user_processes(uid_t uid, char *binname);
注意,要保持NR_syscalls
最大!系统调用添加完毕,下面修改task_struct
,添加hide属性:
% vim include/linux/sched.h
// sched.h
...
struct task_struct {
...
int hide; // 1 to hide
};
既然为task_struct
添加了属性,就得为该属性进行初始化,而它的初始化发生在fork的时候,所以要进行相应修改:
% vim kernel/fork.c
// fork.c
...
long do_fork(...)
{
...
if (!IS_ERR(p)) {
p->hide = 0;
...
}
...
}
...
接下来,实现一下/proc/hidden
和/proc/hidden_process
的功能,显然,可以通过添加一个模块来实现它,但是,麻烦的是那样我们还得手动加载,不如修改现有模块来的方便,所以,下面通过修改fs/proc/proc_misc.c
这个模块来实现功能:
% vim fs/proc/proc_misc.c
// proc_misc.c
...
#define BUFSIZE 1024
static char global_buffer[BUFSIZE];
int hidden_flag = 0; // 1 enable hide function
struct proc_dir_entry *hidden_file, *hidden_process_file;
static int proc_read_hidden(char *page, char **start, off_t off, int count,
int *cof, void *data)
{
int len;
len = sprintf(page, "%d", hidden_flag);
return len;
}
static int proc_write_hidden(struct file *file, const char *buffer,
unsigned long count, void *data)
{
int len;
if (count > BUFSIZE - 1) {
len = BUFSIZE - 1;
} else {
len = count;
}
if (copy_from_user(global_buffer, buffer, len)) {
return -EFAULT;
}
global_buffer[len] = '\0';
hidden_flag = global_buffer[0] - '0';
return len;
}
static int proc_read_hidden_process(char *page, char **start, off_t off, int count,
int *cof, void *data)
{
int len;
if (hidden_flag == 0) {
// no hide process
len = sprintf(page, "No hide process.\n");
return len;
}
struct task_struct *p;
int increment_length;
char process_seq_buf[BUFSIZE];
len = 0;
for_each_process(p) {
if (p->hide == 1) {
increment_length = sprintf(process_seq_buf + len, "%d ", p->pid);
len += increment_length;
}
}
if (len > 0) {
process_seq_buf[--len] = '\0'; // remove the last blank space
} else {
process_seq_buf[0] = '\0';
}
len = sprintf(page, "%s", process_seq_buf);
return len;
}
void __init proc_misc_init(void)
{
hidden_file = create_proc_entry("hidden", 0644, NULL);
hidden_file->read_proc = proc_read_hidden;
hidden_file->write_proc = proc_write_hidden;
hidden_process_file = create_proc_read_entry("hidden_process",
0444, NULL, proc_read_hidden_process, NULL);
...
}
...
简单地解释一下,global_buffer
是为了供copy_from_user
将用户态的数据拷贝到内核态时使用,而全局变量hidden_flag
是/proc/hidden
中的内容的备份,用于供其他方法如proc_pid_readdir
使用,后面会描述。proc_read_hidden
和proc_write_hidden
分别为/proc/hidden
的读写回调方法,/proc/hidden_process
是个只读文件,为其指定读回调方法proc_read_hidden_process
,具体参见内核模块的内容。
按照之前的思路分析,接下来该修改一些proc文件系统的方法来实现隐藏的功能,主要是修改以下几个方法:
proc_pid_readdir
:让其过滤掉隐藏的task_struct
,不返回给用户缓冲区。proc_pid_lookup
:让其过滤掉隐藏的task_struct
,使得open不能解析类似/proc/pid/xxx
的路径名。
% vim fs/proc/base.c
// base.c
...
extern int hidden_flag;
struct dentry *proc_pid_lookup(struct inode *dir, struct dentry * dentry, struct nameidata *nd)
{
...
rcu_read_lock();
task = find_task_by_pid(tgid);
if (task) {
if (hidden_flag == 1 && task->hide == 1) {
goto out;
}
get_task_struct(task);
}
rcu_read_unlock();
if (!task)
goto out;
result = proc_pid_instantiate(dir, dentry, task, NULL);
put_task_struct(task);
out:
return result;
}
...
int proc_pid_readdir(struct file * filp, void * dirent, filldir_t filldir)
{
...
for (task = next_tgid(tgid);
task;
put_task_struct(task), task = next_tgid(tgid + 1)) {
tgid = task->pid;
if (hidden_flag == 0 || task->hide != 1) {
flip->f_pos = tgid + TGID_OFFSET;
if (proc_pid_fill_cache(filp, dirent, filldir, task, tgid) < 0) {
put_task_struct(task);
goto out;
}
} else {
// task should be hidden
}
}
...
}
基本完成了,下面添加两个基础的程序用于测试:
% vim hide.c
#include <sys/syscall.h>
#include <stdio.h>
#include <stdlib.h>
int main(int argc, char **argv)
{
if (argc != 3) {
printf("You should offer pid and on\n");
return 0;
}
int pid = atoi(argv[1]);
int on = atoi(argv[2]);
int ret = syscall(321, pid, on);
if (ret == -1) {
printf("System call error.\n");
} else if (ret == 0) {
printf("The value of on is invalid.\n");
} else if (ret == 1) {
printf("Current user is not root.\n");
} else if (ret == 2) {
printf("The pid is invalid.\n");
} else if (ret == 3) {
printf("Hide success.\n");
}
return 0;
}
% vim hide_all.c
#include <sys/syscall.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int main(int argc, char **argv)
{
if (argc != 3) {
printf("You should offer uid and binname.\n");
return 0;
}
int uid = atoi(argv[1]);
char *binname = argv[2];
if (uid == -1) {
// hide current user
uid = getuid();
}
if (strcmp(binname, "NULL") == 0) {
binname = NULL;
}
int ret = syscall(322, uid, binname);
if (ret == -1) {
printf("System call error.\n");
} else if (ret == 0) {
printf("Current user is not root.\n");
} else if (ret == 1) {
printf("Hide success.\n");
}
return 0;
}
基于这两个程序进行测试:
% cat /proc/hidden # 0
% cat /proc/hidden_process # No hide process.
% gcc -o hide hide.c
% gcc -o hide_all hide_all.c
% ps aux # all process
% ./hide 1 1 # hide pid 1
% ps aux # no hide process because of hidden_flag == 0
% echo "1" > /proc/hidden
% ps aux # pid 1 is hided.
% ls /proc # can not find directory 1
% cat /proc/hidden_process # 1
% ./hide 3 1 # hide pid 3
% ps aux # pid 1 and 3 is hided.
% cat /proc/hidden_process # 1 3
% ./hide 1 0 # show pid 1
% ps aux # pid 3 is hided.
% cat /proc/hidden_process # 3
% echo "0" > /proc/hidden
% ps aux # no hide process because of hidden_flag == 0
% cat /proc/hidden_process # No hide process.
% echo "1" > /proc/hidden
% ./hide_all -1 NULL # hide all root processes
% ps aux
% cat /proc/hidden_process # 1 2 3 ....
% ./hide 1 0 # show pid 1
% ps aux
% cat /proc/hidden_process # 2 3...
% ./hide_all -1 init # hide root process with binname 'init'
% ps aux
% cat /proc/hidden_process # 1 2 3 ....
实验4 实现Shell¶
用户态编程,使用lex进行前端的词法分析,然后结合系统调用即可实现。
总结¶
@_@坑还真不少,再次强调不具有普遍性,相同的步骤走下来,不对,可能根本走不到最后就出bug了。