Windows 和 Linux 路径查找的差异

Windows 和 Linux 在查找命令路径的手段是完全不一样的,比如在 cmd 中执行“notepad”时,Windows 的搜索路径顺序是:当前目录 -> PATH 环境变量的目录列表。

实验1,一段 Process Monitor 捕获的记录:

18:34:23.7807382	cmd.exe	5628	CreateFile	C:\Users\lu4nx	SUCCESS	Desired Access: Read Data/List Directory, Synchronize, Disposition: Open, Options: Directory, Synchronous IO Non-Alert, Attributes: n/a, ShareMode: Read, Write, Delete, AllocationSize: n/a, OpenResult: Opened
(1) 18:34:23.7808239	cmd.exe	5628	QueryDirectory	C:\Users\lu4nx\notepad.*	NO SUCH FILE	FileInformationClass: FileFullDirectoryInformation, Filter: notepad.*
18:34:23.7809035	cmd.exe	5628	CloseFile	C:\Users\lu4nx	SUCCESS
18:34:23.7810305	cmd.exe	5628	CreateFile	C:\Windows\System32	SUCCESS	Desired Access: Read Data/List Directory, Synchronize, Disposition: Open, Options: Directory, Synchronous IO Non-Alert, Attributes: n/a, ShareMode: Read, Write, Delete, AllocationSize: n/a, OpenResult: Opened
18:34:23.7810729	cmd.exe	5628	QueryDirectory	C:\Windows\System32\notepad.*	SUCCESS	FileInformationClass: FileFullDirectoryInformation, Filter: notepad.*, 2: notepad.exe
18:34:23.7811148	cmd.exe	5628	CloseFile	C:\Windows\System32	SUCCESS
18:34:23.7812160	cmd.exe	5628	CreateFile	C:\Windows\System32	SUCCESS	Desired Access: Read Data/List Directory, Synchronize, Disposition: Open, Options: Directory, Synchronous IO Non-Alert, Attributes: n/a, ShareMode: Read, Write, Delete, AllocationSize: n/a, OpenResult: Opened
18:34:23.7812530	cmd.exe	5628	QueryDirectory	C:\Windows\System32\notepad.COM	NO SUCH FILE	FileInformationClass: FileFullDirectoryInformation, Filter: notepad.COM
18:34:23.7813641	cmd.exe	5628	CloseFile	C:\Windows\System32	SUCCESS
18:34:23.7814800	cmd.exe	5628	CreateFile	C:\Windows\System32	SUCCESS	Desired Access: Read Data/List Directory, Synchronize, Disposition: Open, Options: Directory, Synchronous IO Non-Alert, Attributes: n/a, ShareMode: Read, Write, Delete, AllocationSize: n/a, OpenResult: Opened
(2) 18:34:23.7815201	cmd.exe	5628	QueryDirectory	C:\Windows\System32\notepad.EXE	SUCCESS	FileInformationClass: FileFullDirectoryInformation, Filter: notepad.EXE, 2: notepad.exe

当前 cmd 工作目录在 C:\Users\lu4nx,执行 notepad:

  1. 由于没带扩展名,Windows 会自动匹配扩展名:
  2. 位置(1)中,先在当前目录用通配符(notepad.*)匹配,未匹配上,跳过;
  3. 在 C:\Windows\System32 中用通配符匹配,匹配到 C:\Windows\System32\notepad.EXE(见位置(2))。

如果当前目录中用通配符匹配上了呢?实验2,当前目录存在文件名 notepad.png 时:

18:58:00.8207071	cmd.exe	5628	QueryDirectory	C:\Users\lu4nx\notepad.*	SUCCESS	FileInformationClass: FileFullDirectoryInformation, Filter: notepad.*, 2: notepad.png
18:58:00.8207533	cmd.exe	5628	CloseFile	C:\Users\lu4nx	SUCCESS
18:58:00.8208713	cmd.exe	5628	CreateFile	C:\Users\lu4nx	SUCCESS	Desired Access: Read Data/List Directory, Synchronize, Disposition: Open, Options: Directory, Synchronous IO Non-Alert, Attributes: n/a, ShareMode: Read, Write, Delete, AllocationSize: n/a, OpenResult: Opened
18:58:00.8209130	cmd.exe	5628	QueryDirectory	C:\Users\lu4nx\notepad.COM	NO SUCH FILE	FileInformationClass: FileFullDirectoryInformation, Filter: notepad.COM
18:58:00.8209460	cmd.exe	5628	CloseFile	C:\Users\lu4nx	SUCCESS
18:58:00.8210505	cmd.exe	5628	CreateFile	C:\Users\lu4nx	SUCCESS	Desired Access: Read Data/List Directory, Synchronize, Disposition: Open, Options: Directory, Synchronous IO Non-Alert, Attributes: n/a, ShareMode: Read, Write, Delete, AllocationSize: n/a, OpenResult: Opened
18:58:00.8210882	cmd.exe	5628	QueryDirectory	C:\Users\lu4nx\notepad.EXE	NO SUCH FILE	FileInformationClass: FileFullDirectoryInformation, Filter: notepad.EXE
18:58:00.8211163	cmd.exe	5628	CloseFile	C:\Users\lu4nx	SUCCESS
18:58:00.8212405	cmd.exe	5628	CreateFile	C:\Users\lu4nx	SUCCESS	Desired Access: Read Data/List Directory, Synchronize, Disposition: Open, Options: Directory, Synchronous IO Non-Alert, Attributes: n/a, ShareMode: Read, Write, Delete, AllocationSize: n/a, OpenResult: Opened
18:58:00.8212768	cmd.exe	5628	QueryDirectory	C:\Users\lu4nx\notepad.BAT	NO SUCH FILE	FileInformationClass: FileFullDirectoryInformation, Filter: notepad.BAT
18:58:00.8213042	cmd.exe	5628	CloseFile	C:\Users\lu4nx	SUCCESS
18:58:00.8213997	cmd.exe	5628	CreateFile	C:\Users\lu4nx	SUCCESS	Desired Access: Read Data/List Directory, Synchronize, Disposition: Open, Options: Directory, Synchronous IO Non-Alert, Attributes: n/a, ShareMode: Read, Write, Delete, AllocationSize: n/a, OpenResult: Opened
18:58:00.8214374	cmd.exe	5628	QueryDirectory	C:\Users\lu4nx\notepad.CMD	NO SUCH FILE	FileInformationClass: FileFullDirectoryInformation, Filter: notepad.CMD
18:58:00.8214819	cmd.exe	5628	CloseFile	C:\Users\lu4nx	SUCCESS
18:58:00.8216048	cmd.exe	5628	CreateFile	C:\Users\lu4nx	SUCCESS	Desired Access: Read Data/List Directory, Synchronize, Disposition: Open, Options: Directory, Synchronous IO Non-Alert, Attributes: n/a, ShareMode: Read, Write, Delete, AllocationSize: n/a, OpenResult: Opened
18:58:00.8216800	cmd.exe	5628	QueryDirectory	C:\Users\lu4nx\notepad.VBS	NO SUCH FILE	FileInformationClass: FileFullDirectoryInformation, Filter: notepad.VBS
18:58:00.8217083	cmd.exe	5628	CloseFile	C:\Users\lu4nx	SUCCESS
18:58:00.8218071	cmd.exe	5628	CreateFile	C:\Users\lu4nx	SUCCESS	Desired Access: Read Data/List Directory, Synchronize, Disposition: Open, Options: Directory, Synchronous IO Non-Alert, Attributes: n/a, ShareMode: Read, Write, Delete, AllocationSize: n/a, OpenResult: Opened
18:58:00.8218426	cmd.exe	5628	QueryDirectory	C:\Users\lu4nx\notepad.VBE	NO SUCH FILE	FileInformationClass: FileFullDirectoryInformation, Filter: notepad.VBE
18:58:00.8218694	cmd.exe	5628	CloseFile	C:\Users\lu4nx	SUCCESS
18:58:00.8219637	cmd.exe	5628	CreateFile	C:\Users\lu4nx	SUCCESS	Desired Access: Read Data/List Directory, Synchronize, Disposition: Open, Options: Directory, Synchronous IO Non-Alert, Attributes: n/a, ShareMode: Read, Write, Delete, AllocationSize: n/a, OpenResult: Opened
18:58:00.8219986	cmd.exe	5628	QueryDirectory	C:\Users\lu4nx\notepad.JS	NO SUCH FILE	FileInformationClass: FileFullDirectoryInformation, Filter: notepad.JS
18:58:00.8220247	cmd.exe	5628	CloseFile	C:\Users\lu4nx	SUCCESS
18:58:00.8221176	cmd.exe	5628	CreateFile	C:\Users\lu4nx	SUCCESS	Desired Access: Read Data/List Directory, Synchronize, Disposition: Open, Options: Directory, Synchronous IO Non-Alert, Attributes: n/a, ShareMode: Read, Write, Delete, AllocationSize: n/a, OpenResult: Opened
18:58:00.8221520	cmd.exe	5628	QueryDirectory	C:\Users\lu4nx\notepad.JSE	NO SUCH FILE	FileInformationClass: FileFullDirectoryInformation, Filter: notepad.JSE
18:58:00.8221786	cmd.exe	5628	CloseFile	C:\Users\lu4nx	SUCCESS
18:58:00.8222737	cmd.exe	5628	CreateFile	C:\Users\lu4nx	SUCCESS	Desired Access: Read Data/List Directory, Synchronize, Disposition: Open, Options: Directory, Synchronous IO Non-Alert, Attributes: n/a, ShareMode: Read, Write, Delete, AllocationSize: n/a, OpenResult: Opened
18:58:00.8223078	cmd.exe	5628	QueryDirectory	C:\Users\lu4nx\notepad.WSF	NO SUCH FILE	FileInformationClass: FileFullDirectoryInformation, Filter: notepad.WSF
18:58:00.8223335	cmd.exe	5628	CloseFile	C:\Users\lu4nx	SUCCESS
18:58:00.8224279	cmd.exe	5628	CreateFile	C:\Users\lu4nx	SUCCESS	Desired Access: Read Data/List Directory, Synchronize, Disposition: Open, Options: Directory, Synchronous IO Non-Alert, Attributes: n/a, ShareMode: Read, Write, Delete, AllocationSize: n/a, OpenResult: Opened
18:58:00.8224623	cmd.exe	5628	QueryDirectory	C:\Users\lu4nx\notepad.WSH	NO SUCH FILE	FileInformationClass: FileFullDirectoryInformation, Filter: notepad.WSH
18:58:00.8224890	cmd.exe	5628	CloseFile	C:\Users\lu4nx	SUCCESS
18:58:00.8225823	cmd.exe	5628	CreateFile	C:\Users\lu4nx	SUCCESS	Desired Access: Read Data/List Directory, Synchronize, Disposition: Open, Options: Directory, Synchronous IO Non-Alert, Attributes: n/a, ShareMode: Read, Write, Delete, AllocationSize: n/a, OpenResult: Opened
18:58:00.8226454	cmd.exe	5628	QueryDirectory	C:\Users\lu4nx\notepad.MSC	NO SUCH FILE	FileInformationClass: FileFullDirectoryInformation, Filter: notepad.MSC
18:58:00.8226775	cmd.exe	5628	CloseFile	C:\Users\lu4nx	SUCCESS

notepad.* 匹配成功之后,会补充后缀名来逐个判断,而这些后缀名都是固定的,如果手上有 XP 的源码,可以参考 base/cmd/cdata.c:

TCHAR PathExtDefaultStr[] = TEXT(".COM;.EXE;.BAT;.CMD;.VBS;.JS;.WS");

对于想深入寻找路径机制的,可以挂调试器调试 cmd.exe 的 SearchForExecutable 函数,也可以看泄露源码的 base/cmd/cext.c 中的该函数的具体实现。

Linux 中就没这么复杂了,比如执行 emacs 命令,shell 直接从 PATH 环境变量设定的路径逐个检查即可,具体实现也可以参考 bash 的源码 findcmd.c:

search_for_command (pathname, flags)
     const char *pathname;
     int flags;
{
  char *hashed_file, *command, *pathlist;
  int temp_path, st;
  SHELL_VAR *path;
  /* ... 省略 ...*/

  path = find_variable_tempenv ("PATH");
  temp_path = path && tempvar_p (path);

  /* ... 省略 ...*/
}

两个系统在命令查找上有着截然不同的实现方式,Windows 优先查找当前目录、依赖扩展名和环境变量;而 Linux 只依赖环境变量,在未理解两系统差异的情况下容易写出不安全的代码,比如为了实现跨平台,有这么一段代码:

import os

# ... 省略 ...

os.system("update")

本意是利用系统自身机制,可以在 Linux 中执行 update 命令,在 Windows 中执行 update.exe;但因为 Windows 中当前目录的优先级高于 PATH 环境变量,而执行目录又是不可控的,容易导致恶意劫持,比如当前目录中恰好有一个 update.cmd 的恶意文件,update.cmd 会优先被执行。

最近 Go 语言就修复了一个类似漏洞(CVE-2021-3115,补丁参考:https://github.com/golang/go/commit/46e2e2e9d99925bbf724b12693c6d3e27a95d6a0) ,Windows 中 go(如 go get) 在调用 cgo 时没有验证 cgo 的路径,会导致命令劫持、远程 RCE。