2. 系统调用基础:系统调用的概念、用户态与内核态切换、常见系统调用分类
系统调用(System Call)是用户空间程序请求内核服务的唯一合法入口。在 Linux 安全审计中,每一次系统调用都可能成为攻击者留下的足迹。理解系统调用的本质、切换机制与分类,是进行安全事件溯源的前提。
2.1 系统调用的概念
系统调用是操作系统内核提供给用户空间程序的一组标准接口,用于执行特权操作(如读写文件、创建进程、网络通信)。用户程序不能直接访问内核空间,必须通过系统调用陷入内核,由内核代表用户完成操作。
关键特征:
- 原子性:系统调用在内核态执行,不会被用户态信号中断。
- 特权分离:用户态无法直接操作硬件或内核数据结构。
- 审计锚点:所有安全审计工具(如 auditd、sysdig)均基于系统调用拦截。
| 对比维度 | 系统调用 | 库函数(如 glibc) | Shell 命令 |
|---|---|---|---|
| 执行层级 | 内核态 | 用户态(可能封装系统调用) | 用户态(调用库函数) |
| 直接审计 | 是(auditd 直接捕获) | 否(需跟踪到内部系统调用) | 否(需解析进程行为) |
| 性能开销 | 较高(上下文切换) | 较低(无切换) | 高(进程创建) |
| 安全溯源价值 | 最高(原始行为记录) | 中等(需反推) | 低(已高度抽象) |
安全提示: 攻击者即使使用混淆后的二进制文件或加密 shell,最终仍会触发系统调用。因此,系统调用审计是溯源攻击链的“不可绕过”层。
2.2 用户态与内核态切换
CPU 通过特权级别(Ring 0 ~ Ring 3)隔离用户态与内核态。Linux 使用 Ring 3 运行用户程序,Ring 0 运行内核。系统调用触发时,CPU 必须完成一次完整的上下文切换。
2.2.1 切换流程
- 触发陷阱:用户程序执行
syscall(x86-64)或int 0x80(x86)指令。 - 保存上下文:CPU 自动将用户态寄存器(RIP、CS、RSP、SS、EFLAGS)压入内核栈。
- 切换栈:从用户栈切换到内核栈(每个进程对应一个内核栈)。
- 查找系统调用表:根据系统调用号(如
__NR_read = 0)索引sys_call_table。 - 执行内核函数:调用对应处理函数(如
sys_read)。 - 返回用户态:恢复上下文,执行
sysret或iret指令。
安全关注点: 系统调用切换是 CPU 级事件,无法被用户态代码隐藏。即使攻击者使用 rootkit 修改了系统调用表,切换过程本身仍会被硬件记录(如 Intel PT),这是高级溯源的突破口。
2.3 常见系统调用分类
Linux 提供约 300~400 个系统调用(版本不同有差异)。从安全审计角度,可将其分为三大核心类别:文件操作、进程控制、网络通信。每一类都对应典型的攻击行为模式。
2.3.1 文件系统调用
文件系统调用是审计日志中出现频率最高的类别。攻击者读取配置文件、写入恶意脚本、修改权限等行为均通过此类调用完成。
| 系统调用 | 功能 | 典型攻击场景 | 审计关键字段 |
|---|---|---|---|
open / openat |
打开或创建文件 | 读取 /etc/shadow、写入 webshell |
文件名、标志位(O_RDONLY/O_WRONLY) |
read / write |
读写文件内容 | 窃取数据库文件、写入后门 | 文件描述符、字节数 |
chmod / fchmod |
修改文件权限 | 将恶意脚本设为可执行(+x) | 新权限模式(如 0755) |
unlink / rename |
删除或重命名文件 | 清除日志、替换系统二进制文件 | 旧路径、新路径 |
mmap |
内存映射文件 | 加载恶意共享库、绕过文件扫描 | 映射地址、保护标志 |
2.3.2 进程系统调用
进程控制类系统调用直接反映攻击者的横向移动、权限提升、持久化等行为。
| 系统调用 | 功能 | 典型攻击场景 | 审计关键字段 |
|---|---|---|---|
fork / clone |
创建子进程/线程 | 挖矿程序批量派生进程 | 子进程 PID、标志位 |
execve / execveat |
执行新程序 | 反弹 shell、运行恶意二进制 | 程序路径、参数列表 |
kill / tkill |
发送信号 | 终止安全进程(如 kill -9 agent) | 目标 PID、信号编号 |
ptrace |
进程跟踪/调试 | 注入代码、内存修改 | 被跟踪进程 PID、请求类型 |
setuid / setgid |
设置用户/组 ID | 提权至 root | 新 UID/GID |
2.3.3 网络系统调用
网络系统调用是远程攻击、数据外泄、C2 通信的直接证据。
| 系统调用 | 功能 | 典型攻击场景 | 审计关键字段 |
|---|---|---|---|
socket |
创建套接字 | 建立反向连接、监听端口 | 协议族(AF_INET)、类型(SOCK_STREAM) |
connect |
发起连接 | 反弹 shell 连接 C2 服务器 | 目标 IP、端口 |
bind / listen |
绑定端口并监听 | 植入后门监听 4444 端口 | 本地端口、地址 |
sendto / recvfrom |
发送/接收数据 | 外泄敏感数据、接收指令 | 数据长度、对端地址 |
setsockopt |
设置套接字选项 | 设置超时、复用地址(SO_REUSEADDR) | 选项名、选项值 |
2.4 系统调用号与审计日志格式
每个系统调用在 sys_call_table 中对应唯一的系统调用号。审计日志(如 auditd 的 ausearch 输出)会记录系统调用号及其参数。
# 查看 x86-64 系统调用号(部分)
# /usr/include/asm/unistd_64.h
#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
审计日志示例(auditd):
type=SYSCALL msg=audit(1700000000.123:456):
arch=c000003e syscall=2 # __NR_open
success=yes exit=3
a0=7ffe12345678 a1=0 a2=1b6 a3=0
items=1 ppid=1234 pid=5678
auid=1000 uid=0 gid=0
comm="bash" exe="/usr/bin/bash"
key="file_monitor"
关键字段解析:syscall=2 表示 open 系统调用;a0 为文件路径指针;success=yes 表示调用成功;uid=0 表示以 root 身份执行。
2.5 系统调用与安全审计的关联
系统调用是安全审计的“原子事件”。每一次攻击行为,无论多么隐蔽,最终都会表现为一系列系统调用的组合。理解这种映射关系,是构建溯源规则的基础。
| 攻击阶段 | 典型系统调用序列 | 审计检测策略 |
|---|---|---|
| 信息收集 | open(/etc/passwd) → read → close |
监控敏感文件读取 |
| 权限提升 | setuid(0) → execve(/bin/sh) |
检测非授权 UID 切换 |
| 持久化 | open(/etc/cron.d/malware) → write → chmod |
监控 cron 目录写入 |
| 横向移动 | socket(AF_INET) → connect(10.0.0.5:22) → fork → execve(ssh) |
检测异常 SSH 连接 |
| 数据外泄 | open(/var/lib/mysql/data.sql) → read → socket → sendto(8.8.8.8:53) |
检测大流量 DNS 查询 |
实践建议: 在搭建审计规则时,不要只监控单个系统调用,而应关注“系统调用序列模式”。例如,
open + write + chmod 的组合比单独的 open 更具威胁指示性。