内核态 用户态
- 当前程序执行在什么态,由
CS
的最低两位来表示:0是内核态,3是用户态 - 内核态可以访问任何数据,用户态不能访问内核数据
- 对于指令跳转也一样
DPL
用来描述目标段
CPL
用来描述当前段
- 这样设置可以防止外部可以查看一些私密信息
实现一个whoami系统调用
- 用户程序
main
是当前段
CPL是3 - 内核中函数
whoami
是目标段
DPL是0 - 因为DPL < CPL
- 所以无法实现跳转,也就无法调用这个函数
硬件提供了主动进入内核的方法
- 使用终端指令
int
,可以将CS
中的CPL
改为0 - 系统调用的核心:
用户程序中包含一段包含int指令的代码
操作系统写终端处理,获取想调程序的编号
操作系统根据编号执行相应的代码 - 过程:
开始CPL = 3
(main),DPL = 0
(whoami)
设置系统调用号eax = 72
调用指令int 0x80;
使CPL = 3
,DPL = 3
,可以使调用whoami()
成功,一旦穿过接口,那么CPL = 0
在_system_call
中通过移动查表调用了sys_whoami
这时候再调用100就可以了
Linux系统调用的实现细节
- 应用程序调用
printf(...)
- 库函数要调用
write(...)
- 所以需要使用库函数
_syscall3()
,将printf
里面的参数转化成write
需要的格式,#include <unistd.h> // 3的意思是有3个参数,意思是只要有3个参数的都可以调用这个套路 _syscall3(int, write, int, fd, const, char *buf, off_t, count)
- 下面这段核心代码只有一句
int 0x80
"=a"(__res)
这个是输出""(__NR_##name)
这个是输入__NR_write
是系统调用号
- 将
__NR_##name
(__NR_#write
)放入到eax
中,将参数a,b,c放入到ebx
,ecx
,edx
- 然后执行
int 0x80
,执行完之后,把eax
置给res
,最后return res,就返回了系统调用号。
int 0x80中断的处理
- 下面代码的意思就是初始化表中
0x80
(中断处理号)所对应的中断处理函数,如果之后再遇到0x80
,就直接调用这个函数就可以了 - 经过下面的设置之后,
DPL
就等于3了,而CPL
也是3,所以可以实现跳转 - 同时设置了新的CS和IP,让CS的最后两位为00,就可以到内核中去执行相关的函数调用了
中断处理程序:system_call
_sys_call_table
是那个表eax
就是系统调用号- 乘以4的目的是每个系统调用占4个字节