OS:FC6
内核版本:2.6.18
目的:新增一个系统调用,将当前进程的UID和EUID都设置成0(超级用户)
步骤:
1 内核源码树位置:SRC=/usr/src/redhat/BUILD/kernel2.6.18/linux-2.6.18.i386
2 增加新系统调用号,修改头文件
$SRC/include/arm-i386/unistd.h
。
。
。
#define __NR_splice 313
#define __NR_sync_file_range 314
#define __NR_tee 315
#define __NR_vmsplice 316
#define __NR_move_pages 317
#define __NR_mysyscall 318 /*新增加的一行,其中318是系统调用号,
根据系统不同自己定义与已经有的系统
调用号不相同即可*/
3 修改系统调用表。修改文件
$SRC/arch/i386/kernel/syscall-table.s(注意,以前版本的内核应该修改和前面文件同
级目录下的entry.s)
ENTRY(sys_call_table)
.long sys_restart_syscall /* 0 - old "setup()" system call, used
for restarting */
.long sys_exit
.long sys_fork
.long sys_read
.long sys_write
.long sys_open /* 5 */
.long sys_close
.long sys_waitpid
.long sys_creat
.long sys_link
.long sys_unlink /* 10 */
.
.
.
.long sys_mysyscall /*新增加一行*/
4 定义新系统调用的具体内容,一般可以直接在$SRC/kernel/sys.c中添加,
当然也可以加入与其功能紧密联系的代码中去。这里我加在$SRC/kernel/sys.c中,
在该文件末尾加入:
asmlinkage int sys_mysyscall(void)
{
current->uid = current->euid = 0;
return 0;
}
5 通常我们在用户层使用系统调用都是通过C库支持来使用的,也就是说C库中对系统调用
进行了一次封装
,可以通过查看glibc的源码可以看到具体封装过程。由于glibc十分庞大,如果我们自己
新定义对上面新增
系统调用的C库函数的话,编译时间太长,所以我想直接利用LINUX提供的宏_syscallN来完
成。
其中N是系统调用的参数个数。举例说明该宏的用法:
对于系统调用open()的定义本来是:
long open(const char* filename,int flags,int mode)
如果不靠c库支持使用该系统调用方法如下:
#define __NR_open 5 /*5是open的系统调用号*/
_syscall3(long,open,const char* filename,flags,mode)
有了这些定义后下面对open可以直接调用了
(一般书上都会讲到这种方法),但是在实际操作过程中,会发现在2.6.X中,已经取消了
宏_syscallN的定义,所以
你在编译的时候是通不过的。当然2.6取消了这些宏定义,我们可以自己在版本的代码中找
出这些宏定义加上即
在2.4.18内核的源代码中,可以在其内核源码树下include/asm-i386/unistd.h中找到这些
宏定义:
#define __syscall_return(type, res) \
do { \
if ((unsigned long)(res) >= (unsigned long)(-125)) { \
errno = -(res); \
res = -1; \
} \
return (type) (res); \
} while (0)
#define _syscall0(type,name) \
type name(void) \
{ \
long __res; \
__asm__ volatile ("int $0x80" \
: "=a" (__res) \
: "0" (__NR_##name)); \
__syscall_return(type,__res); \
}
#define _syscall1(type,name,type1,arg1) \
type name(type1 arg1) \
{ \
long __res; \
__asm__ volatile ("int $0x80" \
: "=a" (__res) \
: "0" (__NR_##name),"b" ((long)(arg1))); \
__syscall_return(type,__res); \
}
#define _syscall2(type,name,type1,arg1,type2,arg2) \
type name(type1 arg1,type2 arg2) \
{ \
long __res; \
__asm__ volatile ("int $0x80" \
: "=a" (__res) \
: "0" (__NR_##name),"b" ((long)(arg1)),"c" ((long)(arg2))); \
__syscall_return(type,__res); \
}
。
。
。
将这些代码copy到你自己的$SRC/include/asm-i386/unistd.h(我在实际中去掉了所有宏
中
包含变量errno的一行,因为在我们的代码中没有这个变量的定义,即使你在其中加入
extern int errno;
在编译没问题,链接也会有问题的,试试就知道了,如果非要使用errno,那就自己专研吧
,呵呵!)
6 所有准备工作完成,现在就是编译新内核了(可能有人会提到将这个系统调用以模块方
式加载就不用重新
编译内核了,想法是好的,但是考虑到安全问题,不知道是从什么版本开始,已经取消了
对系统调用表
sys_call_table的导出,所以你在模块中是没法访问系统调用表的,当然也可以通过其他
途径来完成这个任务
,不过那是hacker们的办法了,这里不加讨论)
在$SRC中
make menuconfig
make
make install
即可,在2.6中已经对编译内核的方法简化了许多,不需要以前对依赖关系的检查了,新的
Makefile帮助完成这些工作了。
在make install中已经帮你完成了
(1)将新内核镜像bzImage拷贝到/boot/vmlinuz-verison
(2)构建新的initrd-version.img文件
(3)创建新的内核符号表System.map(这个文件不是内核启动必须的,一般调试用)
(4)修改你的grub或者lilo启动文件
所以你只需要reboot就可以看到你新编译的内核了。至于内核的配置和vmlinuz和initrd2
个文件各自的用处,大家可以上网查询,这里
不详细介绍了。如果你的新内核无法启动,多半是initrd出了问题(简单的说下initrd文
件包含了你的内核在加载文件系统前的一些驱动程序
比如硬盘驱动,ext3文件系统的驱动等等,如果这些有问题,内核是无法启动的,网上说
可以把这些一起编译到内核vmlinuz中而不需要initrd,
大家可以去试试)
7 现在新内核启动起来了,我们写一个用户测试程序mysyscall.c使用我们新的系统调用吧
。
#include
#include
#include
#include
#include
#define __NR_mysyscall 318
_syscall0(int,mysyscall)
int main()
{
printf("before, my euid is: %d!\n",geteuid());
if(open("/etc/shadow",O_RDONLY)<0)
printf("open failed!\n");
mysyscall();
printf("after, my euid is %d!\n",geteuid());
if(open("/etc/shadow",O_RDONLY)>0)
printf("open succeed!\n");
return 0;
}
在普通用户下运行该程序
before, my euid is: 500!
open failed!
after, my euid is 0!
open succeed!
发现居然可以访问本来只有root能访问的shadown文件了 o(∩_∩)o!