# 一。系统调用

系统调用也是一个函数,但是系统调用运行在内核态,用户自定义的函数在用户态。而我们想要调用在内核态的指令(如关闭 / 打开中断,I/O 操作等),就需要利用系统调用作为接口,让用户进入内核态。

系统调用是 Linux 内核提供的一段代码(也可以理解为函数)用来实现特定的功能,**32 位程序(x86 CPU)利用 int 0x80 ** 来进行系统调用,64 位程序 (X64 CPU) 提供调用 syscall 来进行系统调用。Linux 内核提供用户空间程序与内核空间进行交互的接口(接口让用户态程序能受限访问硬件设备,比如申请系统资源,操作设备读写,创建新进程等),用户空间发起请求,内核空间负责进行执行,两者之间就需要接口作为桥梁,用户可以通过这种方式来进行系统调用,但是用户是受到限制的,不能直接执行内核代码,也不能随意进行修改系统,必须通过特定方式才能进行才能进入内核,也需要一定的权限才能使用接口。

上述提到的用户空间与内核空间之间的桥梁就是系统调用 (syscall,system call), 其作为中间层用来连接用户态和内核态,这样做加强了一定的安全性,并且我们不需要关系内核是如和完成指令的直接调用需要的接口即可。当用户空间向系统空间发起系统调用时,Linux 系统便会进行软中断,进入内核态执行相应的操作。

不同的系统调用执行的命令有着不同的系统调用号(32 位程序与 64 位程序也不尽相同)

# 二.32 位程序系统调用

# 1. 在 32 位系统(x86 CPU)中,Linux 通过了 int 0x80 中断来进入系统调用,

void system_call()
{
    ...
    // 变量 eax 代表 eax 寄存器的值
    syscall = sys_call_table[eax];
    eax = syscall();
    ...
}

sys_call_table 变量是一个数组(eax 寄存器的值作为其下标),数组的每一个元素代表着一个系统调用的入口(这让我想起了操作系统实验 x_x), 其 C 代码如下

long sys_call_table[] = {
   sys_ni_syscall,
   sys_exit,
   sys_fork,
   sys_read,
   sys_write,
   sys_open,
   sys_close,
   ...
};

# 用户调用系统调用时,通过向 eax 寄存器写入对应命令的系统调用号,这个号就是 sys_call_table 数组的下标,system_call 过程获取 eax 寄存器的值,然后通过 eax 寄存器的值找到要调用的系统入口并调用,系统调用完成后会把返回值保存到 eax 寄存器中

# 用户进行系统调用时,在 eax 寄存器写入对应的系统调用编号,而用户态和内核态使用的栈不同,系统调用是用户态调用然后进入系统调用后会转变成内核态,要经历用户态与内核态的转化,所以不能直接使用用户空间的栈来传递参数(32 位系统用户态内利用栈来传参,64 位仍然需要寄存器来传参)。Linux 使用寄存器来传递参数,其顺序如下:

  • 第 1 个参数放置在 ebx 寄存器。
  • 第 2 个参数放置在 ecx 寄存器。
  • 第 3 个参数放置在 edx 寄存器。
  • 第 4 个参数放置在 esi 寄存器。
  • 第 5 个参数放置在 edi 寄存器。
  • 第 6 个参数放置在 ebp 寄存器。

# Linux 进入中断处理时,就会将这些寄存器的值保存到内核栈中,这样系统调用就能通过内核栈来获取参数。

x86 架构系统调用漏洞利用参考: https://bbs.kanxue.com/thread-248682.htm

参考文章: Linux 下 syscall 系统调用原理及实现

# 三.64 位系统系统调用

# 1.64 位(x64 架构)系统中,Linux 通过 syscall 指令来进入系统调用,其他原理与 x86 架构相似,这里偷懒不再解释,不过 64 位系统与 32 位系统寄存器不同,这里传参的寄存器也不一样

# 传参方式:将系统调用号存入 rax 寄存器中,然后从左到右依次将参数传入 rdi、rsi、rdx 寄存器中:

  • 第 1 个参数放置在 rdi 寄存器。
  • 第 2 个参数放置在 rsi 寄存器。
  • 第 3 个参数放置在 rdx 寄存器。
  • 第 4 个参数放置在 rcx 寄存器。
  • 第 5 个参数放置在 r8 寄存器。
  • 第 6 个参数放置在 r9 寄存器。

# 系统调用完成后,把返回值保存到 rax 寄存器中

# 四.32 位系统与 64 位系统 对比系统调用不同

# 1. 系统调用号不同

# 32 位

#ifndef _ASM_X86_UNISTD_32_H
#define _ASM_X86_UNISTD_32_H 1
#define __NR_restart_syscall 0
#define __NR_exit 1
#define __NR_fork 2
#define __NR_read 3
#define __NR_write 4
#define __NR_open 5
#define __NR_close 6
#define __NR_waitpid 7
#define __NR_creat 8
#define __NR_link 9
#define __NR_unlink 10
#define __NR_execve 11
#define __NR_chdir 12
#define __NR_time 13
#define __NR_mknod 14
#define __NR_chmod 15
#define __NR_lchown 16
#define __NR_break 17
#define __NR_oldstat 18
#define __NR_lseek 19
#define __NR_getpid 20
#define __NR_mount 21
#define __NR_umount 22
#define __NR_setuid 23
#define __NR_getuid 24
#define __NR_stime 25
#define __NR_ptrace 26
#define __NR_alarm 27
#define __NR_oldfstat 28
#define __NR_pause 29
#define __NR_utime 30
#define __NR_stty 31
#define __NR_gtty 32
#define __NR_access 33
#define __NR_nice 34
#define __NR_ftime 35
#define __NR_sync 36
#define __NR_kill 37
#define __NR_rename 38
#define __NR_mkdir 39
#define __NR_rmdir 40

# 64 位系统

#ifndef _ASM_X86_UNISTD_64_H
#define _ASM_X86_UNISTD_64_H 1
#define __NR_read 0
#define __NR_write 1
#define __NR_open 2
#define __NR_close 3
#define __NR_stat 4
#define __NR_fstat 5
#define __NR_lstat 6
#define __NR_poll 7
#define __NR_lseek 8
#define __NR_mmap 9
#define __NR_mprotect 10
#define __NR_munmap 11
#define __NR_brk 12
#define __NR_rt_sigaction 13
#define __NR_rt_sigprocmask 14
#define __NR_rt_sigreturn 15
#define __NR_ioctl 16
#define __NR_pread64 17
#define __NR_pwrite64 18
#define __NR_readv 19
#define __NR_writev 20
#define __NR_access 21
#define __NR_pipe 22
#define __NR_select 23
#define __NR_sched_yield 24
#define __NR_mremap 25
#define __NR_msync 26
#define __NR_mincore 27
#define __NR_madvise 28
#define __NR_shmget 29
#define __NR_shmat 30
#define __NR_shmctl 31
#define __NR_dup 32
#define __NR_dup2 33
#define __NR_pause 34
#define __NR_nanosleep 35
#define __NR_getitimer 36
#define __NR_alarm 37
#define __NR_setitimer 38
#define __NR_getpid 39
#define __NR_sendfile 40
#define __NR_socket 41
#define __NR_connect 42
#define __NR_accept 43
#define __NR_sendto 44
#define __NR_recvfrom 45
#define __NR_sendmsg 46
#define __NR_recvmsg 47
#define __NR_shutdown 48
#define __NR_bind 49
#define __NR_listen 50
#define __NR_getsockname 51
#define __NR_getpeername 52
#define __NR_socketpair 53
#define __NR_setsockopt 54
#define __NR_getsockopt 55
#define __NR_clone 56
#define __NR_fork 57
#define __NR_vfork 58
#define __NR_execve 59
#define __NR_exit 60

完整系统调用号:http://t.csdn.cn/drsyy

# 2. 寄存器传参不同 (上面已经说明过了)

# 3. 进行系统调用方式不同

32 位系统通过 ** int 0x80 ** 中断进入系统调用

64 位系统通过 ** syscall ** 命令进入系统调用

http://t.csdn.cn/Jz6Hm

# 五、open、write、read、close 的系统调用

# 1. 文件描述符

每一个进程都有一个与之相关的文件描述符,它们是一些小值整数,我们可以通过这些文件描述符来访问打开的文件

一般地,一个程序开始运行时,会自动打开 3 个文件描述符:

  • 0——–标准输入 ———-stdin
  • 1——–标准输出 ———-stdout
  • 2——–标准错误 ———-stderr

# 2.write 系统调用

c
#include <unistd.h>
size_t write(int flides, const void *buf, size_t nbytes);

write 系统调用,是把缓存区 buf 中的前 nbytes 字节写入到与文件描述符 flides有关 的文件中,write 系统调用返回的是实际写入到文件中的 字节数

例如:

c
write(1,"aaa",3) #标准输出流(1),将aaa输出到屏幕上

# 3.read 系统调用

c
#include <unistd.h>
size_t read(int flides, void *buf, size_t nbytes);

read 系统调用,是从与文件描述符 flides 相关联的文件中读取前 nbytes 字节的内容,并且写入到数据区 buf 中。read 系统调用返回的是实际读入的 字节数

# 4.open 系统调用

两种系统调用方式

# 第一种

c
#include <fcntl.h>
#include <sys/types.h>
#include <sys/stat.h>
int open(const *path, int oflags);

将准备打开的文件或是设备的名字作为参数 path 传给函数,oflags 用来指定文件访问模式。open 系统调用成功返回一个新的文件描述符,失败返回 - 1。

其中,oflags 是由必需文件访问模式和可选模式一起构成的 (通过按位或 “|”):

必需部分:

  • O_RDONLY------ 以只读方式打开
  • O_WRONLY------ 以只写方式打开
  • O_RDWR -------- 以读写方式打开

例如:

c
int f=open("file.c",O_RDONLY);// 以只读方式打开文件

# 第二种

c
#include <fcntl.h>
#include <sys/types.h>
#include <sys/stat.h>
int open(const *path, int oflags, mode_t mode);

在第一种调用方式上,加上了第三个参数 mode,主要是搭配 O_CREAT 使用,同样地,这个参数规定了属主、同组和其他人对文件的文件操作权限。

# 5.close 系统调用

c
#include <unistd.h>
int close(int flides);

终止文件描述符 flides 与其对应的文件间的联系,文件描述符被释放,可重新使用。