第08节-进程创建的Linux-Windows对照
第 8 节:进程创建的 Linux / Windows 对照
所属课程:操作系统自学路线:面向网络安全、逆向工程与漏洞分析
所属周次:第 4 周
课程主题:进程模型
本节目标:理解 Linux 和 Windows 创建进程的不同方式,掌握fork、execve、wait、CreateProcess的基本思想,并能从安全和逆向角度观察进程创建行为。
1. 本节课你要学会什么
学完这一节,你应该能回答下面几个问题:
- Linux 中为什么常说进程创建是
fork + exec? fork和execve有什么区别?wait/waitpid为什么重要?- Windows 中
CreateProcess大致做了什么? - 为什么 Windows 没有传统 Unix 风格的
fork? - 父进程创建子进程时,哪些资源可能被继承?
- 命令行参数和环境变量如何影响子进程?
- 如何用
strace观察 Linux 进程创建? - 如何用 Process Monitor / Process Explorer 观察 Windows 进程创建?
- 恶意代码为什么经常创建子进程?
本节课的主线是:
1 | |
上一节我们学习了“进程是什么”,这一节重点看“进程是怎么被创建出来的”。
2. 为什么要单独学习进程创建
进程创建是操作系统中非常核心的机制。
普通用户每天都在使用它:
- 打开浏览器
- 启动终端
- 运行命令
- 双击 exe
- 执行脚本
- 打开编辑器
安全分析中也经常关注它。
例如:
1 | |
这类进程链可以反映攻击行为。
逆向工程中,如果一个程序真正的逻辑在子进程中,那么只分析父进程可能会漏掉关键行为。
漏洞分析中,子进程继承的权限、文件描述符、句柄、环境变量也可能影响漏洞后果。
3. Linux 进程创建的经典模型
Linux / Unix 系统中,创建并运行新程序常见模式是:
1 | |
大致意思:
- 父进程调用
fork创建子进程 - 子进程调用
execve把自己替换成新程序 - 父进程调用
wait等待子进程结束并回收状态
例如 shell 执行:
1 | |
大致过程是:
1 | |
4. fork 做了什么
上一节已经讲过,fork 创建当前进程的一个子进程。
特点:
- 子进程从
fork返回的位置继续执行 - 父子进程代码相同
- 父子进程内存内容初始看起来相同
- 父子进程 PID 不同
fork返回值不同
fork 之后:
1 | |
这就是为什么 fork 后通常要判断返回值:
1 | |
5. execve 做了什么
execve 的作用不是创建新进程。
它的作用是:
用一个新的程序映像替换当前进程的程序映像。
也就是说,调用 execve 后:
- 当前进程 PID 通常不变
- 当前进程的代码、数据、堆、栈会被新程序替换
- 新程序从入口点开始执行
- 原来的程序逻辑不再继续执行
例如:
1 | |
如果执行成功,当前进程会变成 /bin/ls。
execve 成功时不会返回。
只有失败时才返回 -1。
这是一个很重要的点:
1 | |
6. fork 和 execve 的区别
| 对比 | fork | execve |
|---|---|---|
| 是否创建新进程 | 是 | 否 |
| PID 是否变化 | 子进程有新 PID | 通常不变 |
| 程序映像是否替换 | 否 | 是 |
| 返回特点 | 父子进程都返回 | 成功时不返回 |
| 常见用途 | 创建子进程 | 在当前进程中加载新程序 |
可以这样记:
1 | |
所以 shell 运行外部命令时,经常是:
1 | |
7. 第一个 fork + exec 程序
创建 fork_exec_demo.c:
1 | |
编译运行:
1 | |
你会看到:
- 父进程打印子进程 PID
- 子进程执行
/bin/ls -l - 父进程等待子进程结束
- 父进程打印结束信息
注意:
1 | |
只有在 execve 失败时才会执行。
8. execve 的参数
execve 原型大致是:
1 | |
三个参数:
| 参数 | 含义 |
|---|---|
| pathname | 要执行的程序路径 |
| argv | 命令行参数数组 |
| envp | 环境变量数组 |
例如:
1 | |
对应命令类似:
1 | |
注意:
argv[0] 通常是程序名,但它本质上也是调用者传入的字符串。
9. 环境变量是什么
环境变量是传递给进程的一组键值对。
例如 Linux 下:
1 | |
常见环境变量:
1 | |
程序可以读取环境变量。
例如:
1 | |
环境变量在安全中很重要。
原因:
- 它可能影响程序查找命令
- 它可能影响动态库加载
- 它可能被子进程继承
- 某些 setuid 程序需要谨慎处理环境变量
10. wait 和 waitpid
父进程通常需要等待子进程结束。
常用函数:
1 | |
作用:
- 等待子进程退出
- 获取子进程退出状态
- 回收子进程资源
- 避免僵尸进程
示例:
1 | |
如果父进程不关心子进程退出状态,子进程结束后可能暂时变成僵尸进程。
11. shell 执行命令时发生了什么
当你在 shell 中输入:
1 | |
大致发生:
1 | |
所以你在终端里运行命令,本质上一直在触发进程创建和程序替换。
12. 用 strace 观察 shell 命令
可以用:
1 | |
参数解释:
| 参数 | 含义 |
|---|---|
-f |
跟踪子进程 |
-e trace=process |
只关注进程相关系统调用 |
bash -c 'ls -l' |
让 bash 执行命令 |
你可能看到:
1 | |
不同 Linux 版本可能显示 clone、clone3 或 vfork 等。
重点不是死记具体名字,而是理解:
1 | |
13. system() 和进程创建
C 语言中常见函数:
1 | |
它会让系统 shell 执行命令。
大致等价于:
1 | |
这意味着它背后也会创建进程。
system() 在安全中很敏感。
如果命令字符串包含用户可控输入,可能导致命令注入。
例如危险写法:
1 | |
如果 user_input 是:
1 | |
就可能产生非预期命令执行。
学习本课程时只在本地安全环境中理解原理,不做未授权攻击。
14. Windows 进程创建模型
Windows 中创建新进程通常使用:
1 | |
和 Linux 的 fork + exec 不同,Windows 的 CreateProcess 一次完成:
1 | |
也就是说,Windows 没有传统 Unix 风格的:
1 | |
常见流程是:
1 | |
15. Windows CreateProcess 基本参数
CreateProcess 参数很多,初学者不用死记。
大致要知道它可以指定:
- 应用程序路径
- 命令行参数
- 安全属性
- 是否继承句柄
- 创建标志
- 环境变量
- 当前目录
- 启动窗口信息
- 返回进程和线程句柄
常见结构体:
1 | |
PROCESS_INFORMATION 中通常包含:
- 新进程句柄
- 主线程句柄
- 新进程 PID
- 主线程 TID
Windows API 比 Linux fork 看起来复杂很多。
原因之一是 Windows 创建进程时需要一次性指定更多信息。
16. Windows 为什么没有传统 fork
传统 Unix fork 会复制当前进程上下文。
Windows 的进程模型和 Unix 设计不同。
Windows 更倾向于:
1 | |
这和它的对象模型、句柄、安全属性、子系统设计有关。
对入门来说,你只需要记住:
| 系统 | 常见创建方式 |
|---|---|
| Linux / Unix | fork 后 execve |
| Windows | CreateProcess |
这会影响你逆向和行为分析时对 API 的理解。
17. 父子进程会继承什么
创建子进程时,某些资源可能被继承。
Linux fork 后,子进程通常继承:
- 打开的文件描述符
- 当前工作目录
- 环境变量
- 用户和组 ID
- 信号处理设置
- 资源限制
- 部分进程属性
Windows CreateProcess 中,如果允许句柄继承,子进程可能继承某些句柄。
这在安全中很重要。
例如:
- 子进程继承了敏感文件描述符
- 子进程继承了网络 socket
- 子进程继承了高权限 Token 或句柄
- 子进程继承了危险环境变量
这些都可能影响安全边界。
18. 文件描述符继承示例
Linux 中,fork 后子进程会继承父进程打开的文件描述符。
示例:
1 | |
编译运行:
1 | |
你会看到父子进程都能写入同一个文件。
这说明子进程继承了文件描述符。
19. 命令行参数的重要性
很多程序行为由命令行参数决定。
例如:
1 | |
安全分析中,命令行非常重要。
因为同一个程序,不同参数代表完全不同的行为。
例如:
1 | |
本身不一定可疑。
但如果命令行包含:
1 | |
就值得进一步分析。
Windows Process Explorer 和 Sysmon 都常用于记录命令行。
Linux 可以用:
1 | |
20. 当前工作目录的重要性
进程有当前工作目录。
例如:
1 | |
程序使用相对路径时,会基于当前工作目录解析。
例如:
1 | |
它不是固定打开某个绝对路径,而是打开:
1 | |
这对安全分析有影响。
例如 DLL 搜索路径、配置文件加载、相对路径执行,都可能和当前目录有关。
Linux 查看某进程当前目录:
1 | |
Windows 可用 Process Explorer 查看进程属性。
21. Linux 实验:观察 fork + exec + wait
创建 fork_exec_wait_demo.c:
1 | |
编译运行:
1 | |
观察:
- 父进程 PID
- 子进程 PID
- 子进程
execve后输出 - 父进程是否等待子进程结束
22. Linux 实验:用 strace 观察进程相关系统调用
执行:
1 | |
重点观察:
execveclone/fork/vforkwait4exit_group
你可能看到类似:
1 | |
解释:
1 | |
23. Linux 实验:查看命令行和环境变量
启动一个长时间运行的进程:
1 | |
另开终端:
1 | |
查看命令行:
1 | |
注意:
cmdline 中参数之间可能用 \0 分隔,显示时不一定换行。
查看环境变量:
1 | |
观察:
- 命令行参数是什么
- 环境变量里有什么
PATH、HOME、PWD等是否存在
24. Windows 实验:Process Explorer 观察 CreateProcess
在 Windows 下:
- 打开 Process Explorer
- 打开
cmd.exe - 在 cmd 中执行:
1 | |
观察:
- notepad 的父进程是否是 cmd.exe
- notepad 的 PID
- notepad 的命令行
- notepad 加载的 DLL
- notepad 的当前用户
再从资源管理器双击打开 notepad。
对比:
- 父进程是否变化
- 命令行是否变化
- 当前目录是否变化
25. Windows 实验:Process Monitor 观察进程创建
使用 Process Monitor:
- 打开 Process Monitor
- 清空当前事件
- 设置过滤器:
1 | |
- 启动一个程序,例如 notepad
- 观察 Process Create 事件
重点看:
- Parent PID
- Command line
- Image Path
- Current Directory
- User
Process Monitor 能帮助你看到进程创建时的上下文。
恶意代码分析中,这些字段非常重要。
26. 从逆向工程角度看进程创建
逆向一个程序时,如果它创建子进程,你要关注:
- 调用了哪个进程创建 API
- 子进程路径是什么
- 命令行参数是什么
- 是否隐藏窗口
- 是否继承句柄
- 是否等待子进程
- 是否重定向标准输入输出
- 是否通过 shell 执行命令
Linux 常见函数或系统调用:
1 | |
Windows 常见 API:
1 | |
这些 API 是逆向分析中的重要行为线索。
27. 从漏洞分析角度看进程创建
进程创建也可能引入漏洞。
常见问题包括:
- 命令注入
- 路径劫持
- 环境变量污染
- 文件描述符或句柄泄露
- 子进程继承高权限资源
- 相对路径执行错误程序
- 参数拼接不安全
例如危险模式:
1 | |
如果 user_input 没有严格处理,就可能导致命令注入。
更安全的思路通常是:
- 避免 shell 拼接
- 使用参数数组传参
- 使用绝对路径
- 限制环境变量
- 关闭不必要的继承句柄或文件描述符
- 最小权限运行
28. 从恶意代码分析角度看进程创建
恶意代码经常利用进程创建实现行为链。
常见例子:
1 | |
1 | |
1 | |
1 | |
分析时重点看:
- 父进程是谁
- 子进程是谁
- 命令行是什么
- 程序路径在哪里
- 是否在临时目录
- 是否隐藏窗口
- 是否短时间内创建多个进程
- 是否调用系统解释器或管理工具
常见 Windows 被滥用程序包括:
1 | |
这些程序本身是合法工具,但在异常上下文中可能可疑。
29. 进程创建分析的基本问题清单
当你看到一个程序创建了子进程,可以问:
- 谁创建了它?
- 被创建的程序路径是什么?
- 命令行参数是什么?
- 当前工作目录是什么?
- 运行用户是谁?
- 是否继承了句柄或文件描述符?
- 是否隐藏窗口?
- 父进程是否等待它退出?
- 子进程是否继续创建其他进程?
- 这个行为是否符合正常业务逻辑?
这些问题适用于:
- 逆向分析
- 恶意代码分析
- 应急响应
- 日志分析
- 漏洞影响评估
30. 本节重点总结
你需要记住这些核心结论:
- Linux 中运行新程序常见模型是
fork + exec + wait。 fork创建子进程,父子进程从fork后继续执行。execve不创建新进程,而是用新程序替换当前进程映像。execve成功时不会返回。wait/waitpid用于等待和回收子进程。- Windows 通常用
CreateProcess一次性创建新进程并加载程序。 - Linux 和 Windows 的进程创建模型不同,逆向时要按平台理解 API。
- 子进程可能继承环境变量、文件描述符、句柄、当前目录等资源。
- 命令行参数和当前工作目录对安全分析非常重要。
- 恶意代码经常通过创建子进程执行命令、隐藏行为或形成攻击链。
31. 本节课后作业
作业 1:fork + exec + wait 实验
写并运行 fork_exec_wait_demo.c。
提交内容:
1 | |
作业 2:strace 观察进程创建
执行:
1 | |
提交内容:
1 | |
作业 3:文件描述符继承实验
写并运行 fd_inherit_demo.c。
提交内容:
1 | |
作业 4:命令行和环境变量观察
启动:
1 | |
查看:
1 | |
提交内容:
1 | |
作业 5:Windows 进程创建观察
如果你有 Windows 环境:
- 用 cmd 启动 notepad
- 用资源管理器启动 notepad
- 用 Process Explorer 对比父进程
- 用 Process Monitor 过滤 Process Create
提交内容:
1 | |
32. 自测题
题 1
Linux 中 fork 和 execve 的区别是什么?
题 2
为什么 execve 成功时不会返回?
题 3
wait / waitpid 的作用是什么?
题 4
Windows 中常用哪个 API 创建进程?
题 5
为什么 Linux 常说创建并运行新程序是 fork + exec?
题 6
子进程可能继承父进程的哪些资源?
题 7
为什么命令行参数对恶意代码分析很重要?
题 8
为什么 system() 使用不当可能有安全风险?
题 9
用 strace -f -e trace=process 可以观察什么?
33. 自测题参考答案
答 1
fork 创建一个新的子进程,父子进程从 fork 后继续执行;execve 不创建新进程,而是用指定程序替换当前进程的程序映像。
答 2
因为 execve 成功后,当前进程的代码、数据、栈等被新程序替换,原程序后续代码已经不存在于新的执行流中,所以不会返回。只有失败时才返回。
答 3
wait / waitpid 用于等待子进程退出、获取退出状态并回收子进程资源,避免僵尸进程。
答 4
Windows 中常用 CreateProcess 创建新进程。
答 5
因为 Unix/Linux 通常先用 fork 复制当前进程创建子进程,再在子进程中用 execve 加载要运行的新程序。
答 6
子进程可能继承打开的文件描述符或句柄、环境变量、当前工作目录、权限信息、资源限制等。具体取决于操作系统和创建参数。
答 7
因为同一个程序配合不同命令行参数可能产生完全不同的行为。恶意代码常通过命令行调用系统工具、脚本解释器或隐藏参数执行可疑操作。
答 8
因为 system() 会通过 shell 执行命令。如果命令字符串拼接了未严格处理的用户输入,可能导致命令注入。
答 9
它可以跟踪进程相关系统调用,包括程序启动的 execve、创建子进程的 clone/fork/vfork、等待子进程的 wait4 等,并且 -f 会跟踪子进程。
34. 下一节预告
下一节课会讲:
1 | |
你会学习:
- CPU 特权级
- 用户态和内核态的区别
- 为什么普通程序不能直接访问硬件
- 系统调用是什么
- API 和 syscall 的区别
- Linux syscall
- Windows Win32 API / Native API / NTAPI 的层次