burpow
第09节-用户态内核态和系统调用

第09节-用户态内核态和系统调用

第 9 节:用户态、内核态和系统调用

所属课程:操作系统自学路线:面向网络安全、逆向工程与漏洞分析
所属周次:第 5 周
课程主题:系统调用
本节目标:理解用户态、内核态和系统调用的基本概念,掌握普通程序为什么必须通过操作系统访问文件、网络、进程等资源,并能用 strace 观察 Linux 系统调用。


1. 本节课你要学会什么

学完这一节,你应该能回答下面几个问题:

  1. 什么是用户态?什么是内核态?
  2. 为什么普通程序不能直接访问硬件?
  3. CPU 特权级大致是什么?
  4. 系统调用是什么?
  5. API、库函数、系统调用有什么区别?
  6. printfwrite 有什么关系?
  7. Linux 下常见系统调用有哪些?
  8. Windows 下 Win32 API、Native API、系统调用大致是什么关系?
  9. 如何用 strace 观察系统调用?
  10. 为什么系统调用对逆向工程、漏洞分析、恶意代码分析很重要?

本节课的主线是:

1
用户程序 -> 库函数/API -> 系统调用 -> 内核态 -> 操作系统访问资源 -> 返回用户态

前面我们学习了进程如何创建,这节课开始理解进程如何请求操作系统做事。


2. 为什么需要用户态和内核态

操作系统要保护系统资源。

如果普通程序可以随便操作硬件,会出现严重问题。

例如普通程序如果可以直接:

  • 修改硬盘任意扇区
  • 读取其他进程内存
  • 控制网卡发送任意数据
  • 修改页表
  • 禁用中断
  • 改写内核代码

那么一个 bug 或恶意程序就可能破坏整个系统。

所以现代操作系统会区分不同权限级别。

最重要的两个概念是:

1
2
用户态:普通应用程序运行的低权限状态
内核态:操作系统内核运行的高权限状态

3. 用户态是什么

用户态英文常写作:

1
User Mode

普通应用程序通常运行在用户态。

例如:

  • 浏览器
  • 编辑器
  • 游戏
  • 命令行程序
  • Python 程序
  • 你自己写的 C 程序

用户态程序权限受限。

它不能直接:

  • 访问任意物理内存
  • 执行特权指令
  • 直接操作硬件设备
  • 修改内核数据结构
  • 任意读写其他进程内存

用户态程序想做这些事情,必须请求内核帮忙。

这个请求通道就是系统调用。


4. 内核态是什么

内核态英文常写作:

1
Kernel Mode

操作系统内核运行在内核态。

内核态拥有更高权限,可以:

  • 管理进程
  • 管理内存
  • 访问硬件
  • 操作文件系统
  • 处理网络协议栈
  • 管理权限
  • 调度 CPU
  • 加载驱动
  • 响应中断和异常

内核态代码非常关键。

如果内核崩溃,通常会影响整个系统。

例如:

  • Linux kernel panic
  • Windows 蓝屏 BSOD

所以内核代码必须非常谨慎。


5. CPU 特权级的初步概念

CPU 本身也支持权限等级。

在 x86/x86-64 架构中,常见说法是 Ring。

1
2
Ring 0:最高权限,通常运行内核
Ring 3:低权限,通常运行用户程序

还有 Ring 1、Ring 2,但现代主流操作系统通常主要使用 Ring 0 和 Ring 3。

简化理解:

层级 常见用途 权限
Ring 0 操作系统内核、驱动
Ring 3 普通用户程序

用户态到内核态不是普通函数调用。

它需要通过特定机制进行受控切换。

系统调用就是这种受控入口之一。


6. 为什么不能直接访问硬件

假设普通程序可以直接写磁盘。

那么一个错误程序可能执行:

1
把磁盘前 1MB 全部写 0

后果可能是系统无法启动。

假设普通程序可以直接访问其他进程内存。

那么它可以读取:

  • 浏览器密码
  • SSH 私钥
  • 聊天记录
  • 加密钱包数据

假设普通程序可以直接修改页表。

那么进程隔离就失效。

所以操作系统要求:

1
普通程序不能直接访问敏感资源,必须通过内核提供的接口。

7. 系统调用是什么

系统调用英文是:

1
System Call

简称:

1
syscall

它是用户态程序请求内核服务的接口。

例如用户程序想读文件:

1
2
3
4
5
用户程序不能自己直接读硬盘
它调用 read 系统调用
内核检查权限
内核访问文件系统和设备
内核把结果返回给用户程序

常见系统调用包括:

类别 Linux 常见系统调用
进程 forkexecvewait4exit
文件 openatreadwriteclose
内存 mmapmunmapbrkmprotect
网络 socketconnectsendtorecvfrom
权限 setuidsetgidchmodchown
信号 rt_sigactionkill
时间 nanosleepclock_gettime

8. 系统调用不是普通函数调用

普通函数调用发生在同一权限级别内。

例如:

1
2
foo();
printf("hello\n");

这通常是用户态内部的函数调用。

系统调用不同。

它需要从用户态切换到内核态。

大致过程:

1
2
3
4
5
6
7
8
1. 用户程序准备系统调用号和参数
2. 执行特殊指令进入内核
3. CPU 切换到内核态
4. 内核根据系统调用号找到对应处理函数
5. 内核检查参数和权限
6. 内核完成请求
7. 返回结果
8. CPU 切回用户态

在 x86-64 Linux 中,常见进入内核的指令是:

1
syscall

早期或其他架构中还可能见到:

1
2
int 0x80
sysenter

9. 系统调用号是什么

内核中有很多系统调用。

用户程序需要告诉内核:

1
我要调用哪一个系统调用

这通常通过系统调用号实现。

在 Linux x86-64 中,系统调用号通常放在:

1
rax

参数通常放在:

1
rdi, rsi, rdx, r10, r8, r9

注意这里第 4 个参数是 r10,不是普通函数调用中的 rcx

例如 Linux x86-64 中:

1
write 系统调用号通常是 1

write(fd, buf, count) 大致需要:

1
2
3
4
5
rax = 1        系统调用号 write
rdi = fd 文件描述符
rsi = buf 缓冲区地址
rdx = count 字节数
syscall

你现在不需要背所有系统调用号,只要知道系统调用通过编号进入内核分发。


10. API、库函数、系统调用的区别

这三个词很容易混淆。

10.1 库函数

库函数是库提供的普通函数。

例如 C 标准库中的:

1
2
3
4
printf
fopen
malloc
strcpy

它们通常运行在用户态。

有些库函数内部会调用系统调用,有些不会。

例如:

  • strlen 一般只是用户态计算字符串长度
  • printf 最终可能调用 write
  • malloc 可能调用 brkmmap

10.2 API

API 是应用程序编程接口。

它是更广泛的概念。

Windows 中常说 Win32 API,例如:

1
2
3
4
5
6
CreateFileW
ReadFile
WriteFile
CreateProcessW
VirtualAlloc
CreateThread

Linux 中也可以说 POSIX API 或 libc API,例如:

1
2
3
4
5
6
open
read
write
fork
execve
pthread_create

API 不一定等于系统调用。

API 可能是对系统调用的封装。


10.3 系统调用

系统调用是进入内核的真正接口。

例如 Linux 中:

1
2
3
4
5
6
7
read
write
openat
execve
mmap
socket
connect

它们最终由内核处理。

总结:

1
2
库函数/API:用户态接口,给程序员使用
系统调用:用户态进入内核态的受控入口

11. printf 背后发生了什么

看这段代码:

1
2
3
4
5
6
#include <stdio.h>

int main() {
printf("hello syscall\n");
return 0;
}

从 C 语言视角看,只是调用 printf

但底层可能是:

1
2
3
4
5
6
main
-> printf
-> libc 缓冲处理
-> write 系统调用
-> 内核
-> 终端设备

注意:

printf 不一定每次立刻调用 write

因为 C 标准库可能有缓冲。

例如输出到终端、文件、管道时,缓冲策略可能不同。

但最终要把内容输出到终端或文件,通常需要系统调用。


12. write 是更接近系统调用的接口

看这个程序:

1
2
3
4
5
6
#include <unistd.h>

int main() {
write(1, "hello syscall\n", 14);
return 0;
}

这里调用的是:

1
write

write 是 POSIX API,同时在 Linux 下通常是系统调用封装。

参数:

1
ssize_t write(int fd, const void *buf, size_t count);

含义:

参数 含义
fd 文件描述符
buf 要写出的缓冲区地址
count 写出多少字节

其中:

1
fd = 1 表示标准输出

常见标准文件描述符:

fd 含义
0 标准输入 stdin
1 标准输出 stdout
2 标准错误 stderr

13. 用 strace 观察系统调用

strace 可以跟踪 Linux 程序执行过程中的系统调用。

创建 write_demo.c

1
2
3
4
5
6
#include <unistd.h>

int main() {
write(1, "hello syscall\n", 14);
return 0;
}

编译:

1
gcc write_demo.c -o write_demo

运行:

1
./write_demo

跟踪:

1
strace ./write_demo

你会看到很多输出。

重点找:

1
write(1, "hello syscall\n", 14) = 14

意思是:

1
2
3
4
5
调用 write 系统调用
fd 是 1
写入字符串 hello syscall\n
请求写 14 字节
返回 14,表示成功写入 14 字节

14. 为什么 strace 输出很多内容

你可能会疑惑:

1
我的程序只写了一行,为什么 strace 输出这么多?

原因是程序启动和退出也需要很多系统调用。

例如:

  • 加载动态库
  • 设置内存映射
  • 初始化运行时
  • 查询系统信息
  • 写输出
  • 退出进程

你可能看到:

1
2
3
4
5
6
7
8
execve
brk
mmap
openat
newfstatat
close
write
exit_group

这些不一定是你手写代码直接调用的,但运行时和动态链接器可能会触发。

这也说明:

1
一个程序运行时的系统行为比源代码表面看起来更丰富。

15. 过滤 strace 输出

如果只想看某类系统调用,可以过滤。

只看写操作:

1
strace -e trace=write ./write_demo

只看文件相关:

1
strace -e trace=file ./write_demo

只看进程相关:

1
strace -e trace=process ./write_demo

跟踪子进程:

1
strace -f ./program

显示时间:

1
strace -tt ./program

这些选项在后面分析进程行为、文件行为、网络行为时很常用。


16. read 系统调用示例

创建 read_demo.c

1
2
3
4
5
6
7
8
9
10
#include <unistd.h>

int main() {
char buf[32];
ssize_t n = read(0, buf, sizeof(buf));
if (n > 0) {
write(1, buf, n);
}
return 0;
}

编译运行:

1
2
gcc read_demo.c -o read_demo
./read_demo

输入一行文字,它会原样输出。

strace

1
strace -e trace=read,write ./read_demo

观察:

1
2
read(0, ..., 32)
write(1, ..., n)

这说明:

1
程序从标准输入读取,再写到标准输出。

17. 文件读写系统调用示例

创建 file_syscall_demo.c

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>

int main() {
int fd = open("syscall_demo.txt", O_CREAT | O_WRONLY | O_TRUNC, 0644);
if (fd < 0) {
perror("open");
return 1;
}

write(fd, "hello file\n", 11);
close(fd);
return 0;
}

编译运行:

1
2
gcc file_syscall_demo.c -o file_syscall_demo
strace -e trace=file,write,close ./file_syscall_demo

你可能看到:

1
2
3
openat(..., "syscall_demo.txt", O_WRONLY|O_CREAT|O_TRUNC, 0644) = 3
write(3, "hello file\n", 11) = 11
close(3) = 0

注意:

你写的是 open,但 strace 可能显示 openat

这是因为现代 libc 可能用 openat 系统调用实现 open

这再次说明:

1
API 名称和最终系统调用名称不一定完全一样。

18. 系统调用返回值和错误码

系统调用通常会返回结果。

例如:

1
write(...) = 14

表示成功写入 14 字节。

如果失败,可能返回:

1
-1

并设置错误码 errno

例如打开不存在文件失败:

1
openat(..., "no_such_file", O_RDONLY) = -1 ENOENT (No such file or directory)

常见错误:

错误 含义
ENOENT 文件或目录不存在
EACCES 权限不足
EPERM 操作不允许
EBADF 文件描述符无效
EFAULT 地址无效
EINVAL 参数无效

错误码对漏洞分析也很有用。

例如某个系统调用返回 EACCES,说明权限检查失败。


19. 系统调用和权限检查

系统调用进入内核后,内核通常会做权限检查。

例如打开文件时,内核会检查:

  • 文件是否存在
  • 当前用户是否有权限
  • 路径是否可访问
  • 文件系统是否允许写入
  • 是否违反安全策略

所以用户程序即使调用:

1
open("/etc/shadow", O_RDONLY)

如果权限不足,内核也会拒绝。

这说明:

1
系统调用不是绕过权限的后门,而是内核实施权限控制的入口。

很多漏洞的关键,就是内核或高权限程序在处理系统调用/API 参数时出现错误。


20. 用户态到内核态的数据传递

系统调用经常需要用户程序传指针给内核。

例如:

1
write(1, buf, count);

这里 buf 是用户态地址。

内核要从这个地址读取数据。

内核不能盲目信任用户传来的指针。

因为用户可能传:

  • 空指针
  • 非法地址
  • 没有权限访问的地址
  • 指向被并发修改的内存
  • 指向内核地址的伪造指针

所以内核必须谨慎检查用户态指针。

这在内核漏洞中非常重要。

很多驱动漏洞的根源就是:

1
内核错误地信任了用户态传入的数据或指针。

21. Windows 中的 API 层次

Windows 下常见调用层次可以简化为:

1
2
3
4
5
应用程序
-> Win32 API
-> Native API / NTAPI
-> ntdll.dll 中的 syscall stub
-> Windows 内核

例如应用程序调用:

1
CreateFileW

它可能经过:

1
2
3
4
5
kernel32.dll / KernelBase.dll
-> ntdll.dll
-> NtCreateFile
-> syscall
-> Windows Kernel

简化理解:

层次 示例
Win32 API CreateFileWReadFileWriteFile
Native API NtCreateFileNtReadFileNtWriteFile
系统调用入口 ntdll 中的 syscall stub
内核处理 Windows 内核函数

Windows 逆向和恶意代码分析中,经常会看到这些层次。


22. Windows API 和系统调用不是一回事

很多初学者会把 Windows API 和系统调用混为一谈。

例如:

1
CreateFileW 是不是系统调用?

严格来说,CreateFileW 是 Win32 API。

它是用户态 DLL 提供的函数。

它底层可能调用 Native API,最终进入内核系统调用。

所以:

1
CreateFileW 不是最底层系统调用,但它是应用程序常用的文件 API。

恶意代码分析中,经常以 API 行为描述程序:

1
2
3
调用 CreateFileW 创建文件
调用 WriteFile 写入内容
调用 CreateProcessW 创建子进程

这足够表达行为,不一定每次都要追到 syscall 层。


23. 为什么恶意代码关注 API 和系统调用

恶意代码要做事,也必须请求系统资源。

例如:

  • 写文件
  • 修改注册表
  • 创建进程
  • 连接网络
  • 分配内存
  • 修改内存权限
  • 创建线程
  • 读取敏感信息

这些行为通常会通过 API 或系统调用体现出来。

安全软件、沙箱、EDR 常常监控:

  • API 调用序列
  • 系统调用序列
  • 参数
  • 调用上下文
  • 进程树
  • 文件路径
  • 网络目标

例如行为链:

1
CreateFileW -> WriteFile -> CreateProcessW

可能表示:

1
写出一个文件,然后执行它

24. 直接系统调用的基本概念

有些程序可能不通过常规 API,而是直接执行系统调用。

在 Windows 恶意代码分析中,有时会提到:

1
Direct Syscall

它的目的可能是绕过某些用户态 API Hook。

但对入门阶段来说,你只需要知道:

1
2
普通 API 调用和最终系统调用之间可能隔着多层用户态库。
有些程序会试图绕过高层 API,直接进入更底层接口。

本课程强调防御和分析视角。

你需要理解这种行为为什么可疑,以及如何从动态行为和内核层日志中分析它。


25. 系统调用和逆向工程

逆向时,系统调用/API 是理解程序行为的重要线索。

如果你看到一个 Linux 程序调用:

1
2
3
4
openat
read
write
close

它可能在读写文件。

看到:

1
2
3
4
socket
connect
sendto
recvfrom

它可能有网络行为。

看到:

1
2
3
fork
execve
wait4

它可能创建子进程。

看到:

1
2
mmap
mprotect

它可能在管理内存权限。

系统调用比普通计算指令更能体现程序和操作系统的交互。


26. 系统调用和漏洞分析

漏洞分析中,系统调用很重要。

原因包括:

  1. 系统调用是用户态进入内核态的边界
  2. 内核必须解析用户传入的参数
  3. 权限检查发生在系统调用路径上
  4. 文件、网络、进程、内存操作都通过系统调用体现
  5. 沙箱逃逸、内核漏洞、驱动漏洞都和边界处理有关

例如:

1
用户态传入一个指针 -> 内核复制数据 -> 检查不充分 -> 内核内存破坏

这就是很多内核/驱动漏洞的基本思路。

在用户态漏洞中,系统调用也能帮助判断程序行为。

例如崩溃前是否:

  • 打开了特定文件
  • 连接了网络
  • mmap 了可执行内存
  • 创建了子进程

27. 系统调用和沙箱分析

沙箱常通过系统调用或 API 记录程序行为。

例如报告中可能写:

1
2
3
4
Process created file: C:\Users\Public\test.exe
Process connected to 1.2.3.4:443
Process created child process: powershell.exe
Process modified registry key: Run

这些行为底层都对应系统调用或 API。

所以如果你懂系统调用,就更容易看懂沙箱报告。

你不会只看到“创建文件”四个字,而会想到:

1
2
3
4
程序调用了文件相关 API
内核检查路径和权限
文件系统完成创建
沙箱记录了这次行为

28. Linux 实验:printfwrite 对比

创建 printf_demo.c

1
2
3
4
5
6
#include <stdio.h>

int main() {
printf("hello printf\n");
return 0;
}

创建 write_demo.c

1
2
3
4
5
6
#include <unistd.h>

int main() {
write(1, "hello write\n", 12);
return 0;
}

编译:

1
2
gcc printf_demo.c -o printf_demo
gcc write_demo.c -o write_demo

分别观察:

1
2
strace -e trace=write ./printf_demo
strace -e trace=write ./write_demo

思考:

  • 两个程序是否都出现 write
  • printf 是库函数还是系统调用?
  • write 更接近哪一层?

29. Linux 实验:观察文件系统调用

创建 open_read_demo.c

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>

int main() {
char buf[64];
int fd = open("/etc/hostname", O_RDONLY);
if (fd < 0) {
perror("open");
return 1;
}

ssize_t n = read(fd, buf, sizeof(buf));
if (n > 0) {
write(1, buf, n);
}

close(fd);
return 0;
}

编译并跟踪:

1
2
gcc open_read_demo.c -o open_read_demo
strace -e trace=file,read,write,close ./open_read_demo

观察:

  • 打开了哪个文件
  • 返回的文件描述符是多少
  • 读取了多少字节
  • 写到了哪个 fd
  • 是否成功关闭文件

30. Linux 实验:观察错误码

创建 error_demo.c

1
2
3
4
5
6
7
8
9
10
11
12
13
#include <fcntl.h>
#include <stdio.h>
#include <unistd.h>

int main() {
int fd = open("/no/such/file", O_RDONLY);
if (fd < 0) {
perror("open");
return 1;
}
close(fd);
return 0;
}

编译并跟踪:

1
2
gcc error_demo.c -o error_demo
strace -e trace=file ./error_demo

你可能看到:

1
openat(..., "/no/such/file", O_RDONLY) = -1 ENOENT (No such file or directory)

这说明系统调用失败,原因是文件不存在。


31. Windows 实验:Process Monitor 观察 API 行为

在 Windows 下:

  1. 打开 Process Monitor
  2. 清空事件
  3. 设置过滤器:
1
Process Name is notepad.exe
  1. 启动 notepad
  2. 保存一个文本文件
  3. 观察事件

重点看:

  • CreateFile
  • WriteFile
  • CloseFile
  • QueryInformationFile

这些是 Process Monitor 以 Windows 行为形式展示的文件操作。

它们不一定直接等于系统调用名,但能反映程序与操作系统交互。


32. Windows 实验:x64dbg 观察 API 调用

用 x64dbg 打开一个简单 Windows 程序。

可以尝试在导入函数上下断点,例如:

1
2
3
4
CreateFileW
WriteFile
ReadFile
MessageBoxA

观察:

  • 调用前参数寄存器 rcxrdxr8r9
  • 调用后 rax 返回值
  • 栈上是否有额外参数
  • API 调用后程序行为

Windows x64 前四个参数通常在:

1
rcx, rdx, r8, r9

这和前面汇编课程中讲的调用约定对应起来。


33. 本节重点总结

你需要记住这些核心结论:

  1. 用户态是普通程序运行的低权限状态。
  2. 内核态是操作系统内核运行的高权限状态。
  3. 普通程序不能直接访问硬件和关键系统资源。
  4. 系统调用是用户态请求内核服务的受控入口。
  5. 系统调用会导致用户态到内核态的切换。
  6. API、库函数和系统调用不是完全一样的概念。
  7. printf 是库函数,最终可能通过 write 系统调用输出。
  8. strace 可以观察 Linux 程序的系统调用。
  9. Windows 中常见层次是 Win32 API -> Native API -> syscall -> 内核。
  10. 系统调用/API 是逆向工程、漏洞分析和恶意代码行为分析的重要线索。

34. 本节课后作业

作业 1:观察 write 系统调用

write_demo.c

1
2
3
4
5
6
#include <unistd.h>

int main() {
write(1, "hello syscall\n", 14);
return 0;
}

执行:

1
2
3
gcc write_demo.c -o write_demo
strace ./write_demo
strace -e trace=write ./write_demo

提交内容:

1
2
3
4
5
1. 源代码
2. strace 中 write 的完整输出
3. fd=1 表示什么
4. write 返回值是多少
5. 为什么 write 需要进入内核

作业 2:对比 printfwrite

printf_demo.cwrite_demo.c

执行:

1
2
strace -e trace=write ./printf_demo
strace -e trace=write ./write_demo

提交内容:

1
2
3
4
1. 两个程序是否都触发 write
2. printf 和 write 分别属于哪一层
3. 为什么库函数可能最终调用系统调用
4. printf 是否一定立刻触发 write,为什么

作业 3:观察文件读写系统调用

open_read_demo.c

执行:

1
strace -e trace=file,read,write,close ./open_read_demo

提交内容:

1
2
3
4
5
1. 打开的文件路径
2. open/openat 返回的 fd
3. read 读取了多少字节
4. write 写到了哪个 fd
5. close 是否成功

作业 4:观察系统调用错误码

error_demo.c,打开一个不存在的文件。

执行:

1
strace -e trace=file ./error_demo

提交内容:

1
2
3
4
5
1. 失败的系统调用
2. 返回值
3. 错误码
4. 错误码含义
5. perror 输出内容

作业 5:Windows API 行为观察

如果你有 Windows 环境:

  1. 用 Process Monitor 观察 notepad 保存文件
  2. 过滤 notepad.exe
  3. 记录 CreateFile / WriteFile / CloseFile 事件
  4. 用 x64dbg 观察一个简单程序的 API 调用

提交内容:

1
2
3
4
1. 观察到的文件路径
2. 观察到的文件操作
3. API 调用前后参数或返回值
4. Windows API 和系统调用为什么不能简单等同

35. 自测题

题 1

用户态和内核态有什么区别?

题 2

为什么普通程序不能直接访问硬件?

题 3

系统调用是什么?

题 4

printfwritesyscall 三者有什么层次关系?

题 5

Linux 下 strace 用来做什么?

题 6

为什么 openstrace 中可能显示为 openat

题 7

Windows 下 Win32 API 和 Native API 大致是什么关系?

题 8

为什么系统调用对恶意代码分析重要?

题 9

为什么内核不能盲目信任用户态传入的指针?


36. 自测题参考答案

答 1

用户态是普通应用程序运行的低权限状态,不能直接访问硬件和内核资源;内核态是操作系统内核运行的高权限状态,可以管理硬件、内存、进程、文件系统和网络等资源。

答 2

为了稳定性、安全性和权限控制。如果普通程序能直接访问硬件或任意内存,一个 bug 或恶意程序就可能破坏系统、泄露数据或绕过权限隔离。

答 3

系统调用是用户态程序请求内核服务的受控接口。程序通过系统调用进入内核态,由内核完成文件、进程、内存、网络等操作并返回结果。

答 4

printf 是 C 标准库函数,运行在用户态;它最终可能调用 write 这类更接近系统调用的接口;真正进入内核时会通过系统调用机制完成用户态到内核态的切换。

答 5

strace 用于跟踪 Linux 程序执行过程中的系统调用,显示系统调用名称、参数、返回值和错误码。

答 6

因为 open 是用户态 API,现代 libc 可能用 openat 系统调用实现它。所以源代码调用 open,底层系统调用可能显示为 openat

答 7

Win32 API 是应用程序常用的高层 Windows API;Native API / NTAPI 是更底层的用户态接口,通常位于 ntdll.dll,最终通过 syscall stub 进入 Windows 内核。

答 8

因为恶意代码要进行文件、网络、进程、内存、注册表等操作,通常都会通过 API 或系统调用体现出来。监控这些调用及其参数可以帮助判断程序行为。

答 9

因为用户态指针可能为空、非法、不可访问、指向恶意构造的数据,或在检查后被修改。内核如果盲目信任这些指针,可能导致崩溃、信息泄露或权限提升漏洞。


37. 下一节预告

下一节课会讲:

1
从逆向角度看 API 和 syscall

你会学习:

  • 库函数、API、系统调用之间的调用链
  • Linux libc 与 syscall wrapper
  • Windows kernel32.dll、KernelBase.dll、ntdll.dll 的关系
  • 为什么恶意代码行为常被描述为 API 序列
  • 如何用 ltracestrace、Process Monitor、x64dbg 对照分析程序行为
隐藏
换装
本文作者:burpow
本文链接:https://youthfulnesszxx.github.io/2026/05/28/第09节-用户态内核态和系统调用/
版权声明:本文采用 CC BY-NC-SA 3.0 CN 协议进行许可