文章目录

写在前面

新法颁布,很多也不敢发了,就偶尔写一篇有意思的东西吧

命令执行底层分析

注意本篇是基于windows平台进行的分析

环境这方面就不多说了VisualStudio、Vscode、php-src源码编译

插件用了c\c++、CodeRunner,环境很简单不想多说,直接干活

故事从一个​​system('whoami');​​说起,你明天都输入这些函数真的就不好奇干了些什么吗

分析前先给大家看看函数调用栈,很清晰

[PHP底层]命令执行底层分析_php


回到正题

[PHP底层]命令执行底层分析_句柄_02


通过全局搜索,我们定位到了​​ext\standard\exec.c​​第263行

[PHP底层]命令执行底层分析_windows_03


这时候顺便还发现​​exec​​​、​​system​​​、​​passthru​​​调用的是同一个函数​​php_exec_ex​

[PHP底层]命令执行底层分析_php_04


静态调试有限,直接打上断点动态调试,首先是对cmd参数进行初始化处理

[PHP底层]命令执行底层分析_字符串_05


这是一个宏定义函数,实际上调用​​zend_parse_arg_string​​实际上是对执行的命令检查是否为空并且赋值

[PHP底层]命令执行底层分析_windows_06


接下来,看到这句话我似乎明白了当初的低版本命令执行等骚姿势为什么高版本不行了

[PHP底层]命令执行底层分析_vscode_07


接下来调用​​php_exec​

[PHP底层]命令执行底层分析_windows_08


发现函数内部会首先调用​​VCWD_POPEN()​​函数去处理cmd指令,可以看到​​VCWD_POPEN()​​函数调用会通过相应的平台去执行,不赘述

[PHP底层]命令执行底层分析_字符串_09


可以看到通过宏定义,实际上调用了​​virtual_popen​

[PHP底层]命令执行底层分析_vscode_10

而​​virtual_popen​​​的核心是​​popen_ex​​,对于不同平台实现也不同

[PHP底层]命令执行底层分析_php_11

[PHP底层]命令执行底层分析_windows_12


继续看看它干了些什么

[PHP底层]命令执行底层分析_php_13


这里面有个比较关键的分配空间的操作,为什么加一个​​/c​​你细品

[PHP底层]命令执行底层分析_句柄_14


实际上最终cmd的调用是​​cmd.exe /c "whoami"​​​是不是和​​bash -c​​很像呢,从这里可以看出

[PHP底层]命令执行底层分析_vscode_15


进行类型转换

[PHP底层]命令执行底层分析_php_16

到这里也就会发现system命令执行函数底层都会调用系统终端cmd.exe来执行传入的指令参数,既然要调用cmd,肯定就要启动相应的进程

这里通过Windows系统API来启动cmd.exe进程,通过搜索引擎了解发现

在 Windows 平台上,创建进程有 WinExec,system,_spawn/_wspawn,CreateProcess,ShellExecute 等多种途径,但基本上还是由 CreateProcess 家族封装的。

本文章著作权归作者所有,任何形式的转载都请注明出处。

[PHP底层]命令执行底层分析_字符串_17


看看这个函数的函数声明,对照上面参数看一看就ok

WINBASEAPI
BOOL
WINAPI
CreateProcessW(
LPCWSTR lpApplicationName, //指向一个NULL结尾的,新进程的可执行文件的名称
LPWSTR lpCommandLine, //指向一个NULL结尾的,传给新进程的命令行字符串
LPSECURITY_ATTRIBUTES lpProcessAttributes,
//指向一个SECURITY_ATTRIBUTES结构体,分配给新的进程对象
//SECURITY_ATTRIBUTES结构可以决定是否返回的句柄可以被子进程继承(bInheritHandle )。如果lpProcessAttributes参数为空(NULL),那么句柄不能被继承。
//SECURITY_ATTRIBUTES结构的lpSecurityDescriptor成员可以指定新进程的安全描述符,如果参数为空,新进程使用默认的安全描述符。
LPSECURITY_ATTRIBUTES lpThreadAttributes,
//指向一个SECURITY_ATTRIBUTES结构体,分配给新的线程对象
BOOL bInheritHandles,
//标识新进程是否可以从调用进程处继承所有可继承的句柄。被继承的句柄与原进程拥有完全相同的值和访问权限。
DWORD dwCreationFlags,
//标识了影响新进程创建方式的标志,多个标志按位或进行组合
LPVOID lpEnvironment,
//指向一块内存,其中包含新进程要使用的环境字符串。如果此参数为空,新进程继承父进程的一组环境字符串。
LPCWSTR lpCurrentDirectory,
//指向一个以NULL结尾的字符串,用来设置新进程的当前驱动器和目录,这个字符串必须是一个包含驱动器名的绝对路径。如果这个参数为空,新进程将使用与父进程相同的驱动器和目录。
LPSTARTUPINFOW lpStartupInfo,
//指向一个用于决定新进程的主窗体如何显示的STARTUPINFO结构体。
LPPROCESS_INFORMATION lpProcessInformation
//指向一个用来接收新进程的识别信息的PROCESS_INFORMATION结构体。
);

[PHP底层]命令执行底层分析_句柄_18

因此它最终将进程运行的结果信息以流的形式返回,也就完成了PHP命令执行函数的整个调用过程

[PHP底层]命令执行底层分析_windows_19


后面就是对这个返回的文件流的操作就不分析了