burpow
第08节-进程创建的Linux-Windows对照

第08节-进程创建的Linux-Windows对照

第 8 节:进程创建的 Linux / Windows 对照

所属课程:操作系统自学路线:面向网络安全、逆向工程与漏洞分析
所属周次:第 4 周
课程主题:进程模型
本节目标:理解 Linux 和 Windows 创建进程的不同方式,掌握 forkexecvewaitCreateProcess 的基本思想,并能从安全和逆向角度观察进程创建行为。


1. 本节课你要学会什么

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

  1. Linux 中为什么常说进程创建是 fork + exec
  2. forkexecve 有什么区别?
  3. wait / waitpid 为什么重要?
  4. Windows 中 CreateProcess 大致做了什么?
  5. 为什么 Windows 没有传统 Unix 风格的 fork
  6. 父进程创建子进程时,哪些资源可能被继承?
  7. 命令行参数和环境变量如何影响子进程?
  8. 如何用 strace 观察 Linux 进程创建?
  9. 如何用 Process Monitor / Process Explorer 观察 Windows 进程创建?
  10. 恶意代码为什么经常创建子进程?

本节课的主线是:

1
父进程 -> 创建子进程 -> 加载新程序 -> 传递参数/环境 -> 子进程运行 -> 父进程等待/回收

上一节我们学习了“进程是什么”,这一节重点看“进程是怎么被创建出来的”。


2. 为什么要单独学习进程创建

进程创建是操作系统中非常核心的机制。

普通用户每天都在使用它:

  • 打开浏览器
  • 启动终端
  • 运行命令
  • 双击 exe
  • 执行脚本
  • 打开编辑器

安全分析中也经常关注它。

例如:

1
2
3
word.exe -> powershell.exe
powershell.exe -> cmd.exe
cmd.exe -> suspicious.exe

这类进程链可以反映攻击行为。

逆向工程中,如果一个程序真正的逻辑在子进程中,那么只分析父进程可能会漏掉关键行为。

漏洞分析中,子进程继承的权限、文件描述符、句柄、环境变量也可能影响漏洞后果。


3. Linux 进程创建的经典模型

Linux / Unix 系统中,创建并运行新程序常见模式是:

1
fork -> exec -> wait

大致意思:

  1. 父进程调用 fork 创建子进程
  2. 子进程调用 execve 把自己替换成新程序
  3. 父进程调用 wait 等待子进程结束并回收状态

例如 shell 执行:

1
ls -l

大致过程是:

1
2
3
4
5
bash 调用 fork 创建子进程
子进程调用 execve 加载 /bin/ls
/bin/ls 运行并输出结果
父进程 bash 调用 wait 等待 ls 结束
bash 继续显示命令提示符

4. fork 做了什么

上一节已经讲过,fork 创建当前进程的一个子进程。

特点:

  • 子进程从 fork 返回的位置继续执行
  • 父子进程代码相同
  • 父子进程内存内容初始看起来相同
  • 父子进程 PID 不同
  • fork 返回值不同

fork 之后:

1
2
父进程继续执行
子进程也继续执行

这就是为什么 fork 后通常要判断返回值:

1
2
3
4
5
6
7
8
9
pid_t pid = fork();

if (pid == 0) {
// 子进程逻辑
} else if (pid > 0) {
// 父进程逻辑
} else {
// 错误处理
}

5. execve 做了什么

execve 的作用不是创建新进程。

它的作用是:

用一个新的程序映像替换当前进程的程序映像。

也就是说,调用 execve 后:

  • 当前进程 PID 通常不变
  • 当前进程的代码、数据、堆、栈会被新程序替换
  • 新程序从入口点开始执行
  • 原来的程序逻辑不再继续执行

例如:

1
execve("/bin/ls", argv, envp);

如果执行成功,当前进程会变成 /bin/ls

execve 成功时不会返回。

只有失败时才返回 -1

这是一个很重要的点:

1
execve 成功后,原程序后面的代码不会继续执行。

6. forkexecve 的区别

对比 fork execve
是否创建新进程
PID 是否变化 子进程有新 PID 通常不变
程序映像是否替换
返回特点 父子进程都返回 成功时不返回
常见用途 创建子进程 在当前进程中加载新程序

可以这样记:

1
2
fork 负责“复制出一个进程”
execve 负责“把这个进程变成另一个程序”

所以 shell 运行外部命令时,经常是:

1
2
3
fork 出子进程
子进程 execve 新程序
父进程 wait 子进程

7. 第一个 fork + exec 程序

创建 fork_exec_demo.c

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
#include <stdio.h>
#include <unistd.h>
#include <sys/wait.h>

int main() {
pid_t pid = fork();

if (pid == 0) {
char *argv[] = {"ls", "-l", NULL};
printf("child: before exec, pid=%d\n", getpid());
execve("/bin/ls", argv, NULL);
perror("execve");
return 1;
} else if (pid > 0) {
printf("parent: child pid=%d\n", pid);
wait(NULL);
printf("parent: child finished\n");
} else {
perror("fork");
return 1;
}

return 0;
}

编译运行:

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

你会看到:

  1. 父进程打印子进程 PID
  2. 子进程执行 /bin/ls -l
  3. 父进程等待子进程结束
  4. 父进程打印结束信息

注意:

1
perror("execve");

只有在 execve 失败时才会执行。


8. execve 的参数

execve 原型大致是:

1
int execve(const char *pathname, char *const argv[], char *const envp[]);

三个参数:

参数 含义
pathname 要执行的程序路径
argv 命令行参数数组
envp 环境变量数组

例如:

1
2
char *argv[] = {"ls", "-l", "/tmp", NULL};
execve("/bin/ls", argv, NULL);

对应命令类似:

1
ls -l /tmp

注意:

argv[0] 通常是程序名,但它本质上也是调用者传入的字符串。


9. 环境变量是什么

环境变量是传递给进程的一组键值对。

例如 Linux 下:

1
env

常见环境变量:

1
2
3
4
5
6
7
PATH
HOME
USER
SHELL
LANG
PWD
LD_LIBRARY_PATH

程序可以读取环境变量。

例如:

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

int main() {
char *home = getenv("HOME");
printf("HOME=%s\n", home);
return 0;
}

环境变量在安全中很重要。

原因:

  • 它可能影响程序查找命令
  • 它可能影响动态库加载
  • 它可能被子进程继承
  • 某些 setuid 程序需要谨慎处理环境变量

10. waitwaitpid

父进程通常需要等待子进程结束。

常用函数:

1
2
wait(NULL);
waitpid(pid, &status, 0);

作用:

  1. 等待子进程退出
  2. 获取子进程退出状态
  3. 回收子进程资源
  4. 避免僵尸进程

示例:

1
2
3
4
5
6
int status;
waitpid(pid, &status, 0);

if (WIFEXITED(status)) {
printf("exit code=%d\n", WEXITSTATUS(status));
}

如果父进程不关心子进程退出状态,子进程结束后可能暂时变成僵尸进程。


11. shell 执行命令时发生了什么

当你在 shell 中输入:

1
cat /etc/hostname

大致发生:

1
2
3
4
5
6
7
8
9
1. bash 读取用户输入
2. bash 解析命令和参数
3. bash 查找 cat 的路径,例如 /usr/bin/cat
4. bash 调用 fork 创建子进程
5. 子进程调用 execve('/usr/bin/cat', ...)
6. cat 运行,读取 /etc/hostname
7. cat 输出内容后退出
8. bash wait 子进程
9. bash 显示下一次提示符

所以你在终端里运行命令,本质上一直在触发进程创建和程序替换。


12. 用 strace 观察 shell 命令

可以用:

1
strace -f -e trace=process bash -c 'ls -l'

参数解释:

参数 含义
-f 跟踪子进程
-e trace=process 只关注进程相关系统调用
bash -c 'ls -l' 让 bash 执行命令

你可能看到:

1
2
3
clone(...)
execve("/usr/bin/ls", ["ls", "-l"], ...)
wait4(...)

不同 Linux 版本可能显示 cloneclone3vfork 等。

重点不是死记具体名字,而是理解:

1
创建执行实体 -> 加载新程序 -> 父进程等待

13. system() 和进程创建

C 语言中常见函数:

1
system("ls -l");

它会让系统 shell 执行命令。

大致等价于:

1
/bin/sh -c "ls -l"

这意味着它背后也会创建进程。

system() 在安全中很敏感。

如果命令字符串包含用户可控输入,可能导致命令注入。

例如危险写法:

1
2
3
char cmd[256];
sprintf(cmd, "ping %s", user_input);
system(cmd);

如果 user_input 是:

1
127.0.0.1; rm -rf /tmp/test

就可能产生非预期命令执行。

学习本课程时只在本地安全环境中理解原理,不做未授权攻击。


14. Windows 进程创建模型

Windows 中创建新进程通常使用:

1
CreateProcess

和 Linux 的 fork + exec 不同,Windows 的 CreateProcess 一次完成:

1
创建新进程 + 加载目标程序

也就是说,Windows 没有传统 Unix 风格的:

1
先复制当前进程,再把子进程替换成新程序

常见流程是:

1
2
3
4
5
父进程调用 CreateProcess
Windows 内核创建新进程对象和主线程
加载 exe 和 DLL
设置命令行、环境、当前目录
新进程从入口点开始执行

15. Windows CreateProcess 基本参数

CreateProcess 参数很多,初学者不用死记。

大致要知道它可以指定:

  • 应用程序路径
  • 命令行参数
  • 安全属性
  • 是否继承句柄
  • 创建标志
  • 环境变量
  • 当前目录
  • 启动窗口信息
  • 返回进程和线程句柄

常见结构体:

1
2
STARTUPINFO
PROCESS_INFORMATION

PROCESS_INFORMATION 中通常包含:

  • 新进程句柄
  • 主线程句柄
  • 新进程 PID
  • 主线程 TID

Windows API 比 Linux fork 看起来复杂很多。

原因之一是 Windows 创建进程时需要一次性指定更多信息。


16. Windows 为什么没有传统 fork

传统 Unix fork 会复制当前进程上下文。

Windows 的进程模型和 Unix 设计不同。

Windows 更倾向于:

1
直接创建一个指定可执行文件的新进程

这和它的对象模型、句柄、安全属性、子系统设计有关。

对入门来说,你只需要记住:

系统 常见创建方式
Linux / Unix forkexecve
Windows CreateProcess

这会影响你逆向和行为分析时对 API 的理解。


17. 父子进程会继承什么

创建子进程时,某些资源可能被继承。

Linux fork 后,子进程通常继承:

  • 打开的文件描述符
  • 当前工作目录
  • 环境变量
  • 用户和组 ID
  • 信号处理设置
  • 资源限制
  • 部分进程属性

Windows CreateProcess 中,如果允许句柄继承,子进程可能继承某些句柄。

这在安全中很重要。

例如:

  • 子进程继承了敏感文件描述符
  • 子进程继承了网络 socket
  • 子进程继承了高权限 Token 或句柄
  • 子进程继承了危险环境变量

这些都可能影响安全边界。


18. 文件描述符继承示例

Linux 中,fork 后子进程会继承父进程打开的文件描述符。

示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/wait.h>

int main() {
int fd = open("inherit_demo.txt", O_CREAT | O_WRONLY | O_TRUNC, 0644);

if (fd < 0) {
perror("open");
return 1;
}

pid_t pid = fork();

if (pid == 0) {
write(fd, "child write\n", 12);
return 0;
} else if (pid > 0) {
write(fd, "parent write\n", 13);
wait(NULL);
} else {
perror("fork");
return 1;
}

close(fd);
return 0;
}

编译运行:

1
2
3
gcc fd_inherit_demo.c -o fd_inherit_demo
./fd_inherit_demo
cat inherit_demo.txt

你会看到父子进程都能写入同一个文件。

这说明子进程继承了文件描述符。


19. 命令行参数的重要性

很多程序行为由命令行参数决定。

例如:

1
2
3
python3 script.py input.txt
curl http://example.com
powershell.exe -ExecutionPolicy Bypass -File script.ps1

安全分析中,命令行非常重要。

因为同一个程序,不同参数代表完全不同的行为。

例如:

1
powershell.exe

本身不一定可疑。

但如果命令行包含:

1
2
3
-EncodedCommand
-ExecutionPolicy Bypass
-WindowStyle Hidden

就值得进一步分析。

Windows Process Explorer 和 Sysmon 都常用于记录命令行。

Linux 可以用:

1
2
ps -eo pid,ppid,args
cat /proc/<pid>/cmdline

20. 当前工作目录的重要性

进程有当前工作目录。

例如:

1
pwd

程序使用相对路径时,会基于当前工作目录解析。

例如:

1
fopen("config.txt", "r");

它不是固定打开某个绝对路径,而是打开:

1
当前工作目录/config.txt

这对安全分析有影响。

例如 DLL 搜索路径、配置文件加载、相对路径执行,都可能和当前目录有关。

Linux 查看某进程当前目录:

1
readlink /proc/<pid>/cwd

Windows 可用 Process Explorer 查看进程属性。


21. Linux 实验:观察 fork + exec + wait

创建 fork_exec_wait_demo.c

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
#include <stdio.h>
#include <unistd.h>
#include <sys/wait.h>

int main() {
printf("parent start: pid=%d\n", getpid());

pid_t pid = fork();

if (pid == 0) {
char *argv[] = {"/bin/echo", "hello from child", NULL};
printf("child before exec: pid=%d ppid=%d\n", getpid(), getppid());
execve("/bin/echo", argv, NULL);
perror("execve");
return 1;
} else if (pid > 0) {
int status;
printf("parent waiting child=%d\n", pid);
waitpid(pid, &status, 0);
printf("parent done\n");
} else {
perror("fork");
return 1;
}

return 0;
}

编译运行:

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

观察:

  • 父进程 PID
  • 子进程 PID
  • 子进程 execve 后输出
  • 父进程是否等待子进程结束

22. Linux 实验:用 strace 观察进程相关系统调用

执行:

1
strace -f -e trace=process ./fork_exec_wait_demo

重点观察:

  • execve
  • clone / fork / vfork
  • wait4
  • exit_group

你可能看到类似:

1
2
3
4
5
execve("./fork_exec_wait_demo", ...)
clone(...)
[pid ...] execve("/bin/echo", ...)
[pid ...] exit_group(0)
wait4(...)

解释:

1
2
3
4
第一个 execve 是 shell 启动你的程序
clone/fork 创建子进程
子进程 execve /bin/echo
父进程 wait4 等待子进程

23. Linux 实验:查看命令行和环境变量

启动一个长时间运行的进程:

1
sleep 300

另开终端:

1
ps -eo pid,ppid,args | grep sleep

查看命令行:

1
cat /proc/<pid>/cmdline

注意:

cmdline 中参数之间可能用 \0 分隔,显示时不一定换行。

查看环境变量:

1
tr '\0' '\n' < /proc/<pid>/environ

观察:

  • 命令行参数是什么
  • 环境变量里有什么
  • PATHHOMEPWD 等是否存在

24. Windows 实验:Process Explorer 观察 CreateProcess

在 Windows 下:

  1. 打开 Process Explorer
  2. 打开 cmd.exe
  3. 在 cmd 中执行:
1
notepad.exe

观察:

  • notepad 的父进程是否是 cmd.exe
  • notepad 的 PID
  • notepad 的命令行
  • notepad 加载的 DLL
  • notepad 的当前用户

再从资源管理器双击打开 notepad。

对比:

  • 父进程是否变化
  • 命令行是否变化
  • 当前目录是否变化

25. Windows 实验:Process Monitor 观察进程创建

使用 Process Monitor:

  1. 打开 Process Monitor
  2. 清空当前事件
  3. 设置过滤器:
1
Operation is Process Create
  1. 启动一个程序,例如 notepad
  2. 观察 Process Create 事件

重点看:

  • Parent PID
  • Command line
  • Image Path
  • Current Directory
  • User

Process Monitor 能帮助你看到进程创建时的上下文。

恶意代码分析中,这些字段非常重要。


26. 从逆向工程角度看进程创建

逆向一个程序时,如果它创建子进程,你要关注:

  • 调用了哪个进程创建 API
  • 子进程路径是什么
  • 命令行参数是什么
  • 是否隐藏窗口
  • 是否继承句柄
  • 是否等待子进程
  • 是否重定向标准输入输出
  • 是否通过 shell 执行命令

Linux 常见函数或系统调用:

1
2
3
4
5
6
7
8
fork
vfork
clone
execve
system
popen
wait
waitpid

Windows 常见 API:

1
2
3
4
5
CreateProcessA/W
ShellExecuteA/W
WinExec
CreateProcessAsUser
CreateProcessWithTokenW

这些 API 是逆向分析中的重要行为线索。


27. 从漏洞分析角度看进程创建

进程创建也可能引入漏洞。

常见问题包括:

  • 命令注入
  • 路径劫持
  • 环境变量污染
  • 文件描述符或句柄泄露
  • 子进程继承高权限资源
  • 相对路径执行错误程序
  • 参数拼接不安全

例如危险模式:

1
2
sprintf(cmd, "tar -xf %s", user_input);
system(cmd);

如果 user_input 没有严格处理,就可能导致命令注入。

更安全的思路通常是:

  • 避免 shell 拼接
  • 使用参数数组传参
  • 使用绝对路径
  • 限制环境变量
  • 关闭不必要的继承句柄或文件描述符
  • 最小权限运行

28. 从恶意代码分析角度看进程创建

恶意代码经常利用进程创建实现行为链。

常见例子:

1
宏文档 -> powershell.exe -> 下载器 -> payload.exe
1
恶意程序 -> cmd.exe /c whoami
1
恶意程序 -> rundll32.exe 执行 DLL
1
恶意程序 -> regsvr32.exe 加载脚本

分析时重点看:

  • 父进程是谁
  • 子进程是谁
  • 命令行是什么
  • 程序路径在哪里
  • 是否在临时目录
  • 是否隐藏窗口
  • 是否短时间内创建多个进程
  • 是否调用系统解释器或管理工具

常见 Windows 被滥用程序包括:

1
2
3
4
5
6
7
8
powershell.exe
cmd.exe
wscript.exe
cscript.exe
rundll32.exe
regsvr32.exe
mshta.exe
wmic.exe

这些程序本身是合法工具,但在异常上下文中可能可疑。


29. 进程创建分析的基本问题清单

当你看到一个程序创建了子进程,可以问:

  1. 谁创建了它?
  2. 被创建的程序路径是什么?
  3. 命令行参数是什么?
  4. 当前工作目录是什么?
  5. 运行用户是谁?
  6. 是否继承了句柄或文件描述符?
  7. 是否隐藏窗口?
  8. 父进程是否等待它退出?
  9. 子进程是否继续创建其他进程?
  10. 这个行为是否符合正常业务逻辑?

这些问题适用于:

  • 逆向分析
  • 恶意代码分析
  • 应急响应
  • 日志分析
  • 漏洞影响评估

30. 本节重点总结

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

  1. Linux 中运行新程序常见模型是 fork + exec + wait
  2. fork 创建子进程,父子进程从 fork 后继续执行。
  3. execve 不创建新进程,而是用新程序替换当前进程映像。
  4. execve 成功时不会返回。
  5. wait / waitpid 用于等待和回收子进程。
  6. Windows 通常用 CreateProcess 一次性创建新进程并加载程序。
  7. Linux 和 Windows 的进程创建模型不同,逆向时要按平台理解 API。
  8. 子进程可能继承环境变量、文件描述符、句柄、当前目录等资源。
  9. 命令行参数和当前工作目录对安全分析非常重要。
  10. 恶意代码经常通过创建子进程执行命令、隐藏行为或形成攻击链。

31. 本节课后作业

作业 1:fork + exec + wait 实验

写并运行 fork_exec_wait_demo.c

提交内容:

1
2
3
4
5
6
1. 源代码
2. 程序运行输出
3. 父进程 PID
4. 子进程 PID
5. 子进程 execve 执行了哪个程序
6. 父进程是否等待子进程结束

作业 2:strace 观察进程创建

执行:

1
strace -f -e trace=process ./fork_exec_wait_demo

提交内容:

1
2
3
4
5
1. 你看到的 execve 调用
2. 你看到的 clone/fork/vfork 调用
3. 你看到的 wait4/waitpid 相关调用
4. 哪个系统调用对应子进程加载 /bin/echo
5. strace -f 的作用是什么

作业 3:文件描述符继承实验

写并运行 fd_inherit_demo.c

提交内容:

1
2
3
4
5
1. 程序输出或生成文件内容
2. 父进程写入了什么
3. 子进程写入了什么
4. 为什么子进程能使用父进程打开的 fd
5. 文件描述符继承可能带来什么安全问题

作业 4:命令行和环境变量观察

启动:

1
sleep 300

查看:

1
2
3
ps -eo pid,ppid,args | grep sleep
cat /proc/<pid>/cmdline
tr '\0' '\n' < /proc/<pid>/environ

提交内容:

1
2
3
4
5
1. sleep 的 PID 和 PPID
2. cmdline 内容
3. 环境变量中至少 5 个键值
4. PATH 的值
5. 为什么环境变量会影响程序行为

作业 5:Windows 进程创建观察

如果你有 Windows 环境:

  1. 用 cmd 启动 notepad
  2. 用资源管理器启动 notepad
  3. 用 Process Explorer 对比父进程
  4. 用 Process Monitor 过滤 Process Create

提交内容:

1
2
3
4
5
1. 两种方式下 notepad 的父进程
2. notepad 的命令行
3. notepad 的当前目录
4. Process Create 事件中你看到的字段
5. 为什么命令行和父进程对安全分析重要

32. 自测题

题 1

Linux 中 forkexecve 的区别是什么?

题 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 的层次
隐藏
换装
本文作者:burpow
本文链接:https://youthfulnesszxx.github.io/2026/05/28/第08节-进程创建的Linux-Windows对照/
版权声明:本文采用 CC BY-NC-SA 3.0 CN 协议进行许可