将空程序参数向量传递给 execve() 是否合法?
Is it legal to pass a null program argument vector to execve()?
考虑以下 C 代码 (x86_64)
#include <unistd.h>
int main()
{
execve("/bin/ls", 0, 0);
}
我编译为gcc a.c
并执行;我得到一个 SIGABRT
错误
A NULL argv[0] was passed through an exec system call.
Aborted
下一个 运行ning 在 gdb 上,起初我也得到了一个 SIGABRT
,但是我得到了第二个 运行 并且成功了!
Starting program: /bin/ls
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/lib/x86_64-linux-gnu/libthread_db.so.1".
为什么?
我对 /bin/sh
进行了测试,发现它始终适用于 *argv[] = NULL ...
我再次编写了一些可执行文件(不需要任何参数)来测试并发现它们都有效。
所以我猜只有 /bin/sh
或其他 shell 可以将 *argv[] 设置为 NULL,其他文件(如 /bin/ls
)会失败或出现意外行为。
execve()
系统调用的手册页说
The argv
and envp
arrays must each include a null pointer at the end of the array.
如果您的程序不符合这些要求,则无法确定从那时起事情的进展情况。如果它 "works" 对于某些程序,那只是运气不好。
手册页还说
By convention, the first of these strings (i.e., argv[0]
) should contain the filename associated with the file being executed.
该约定相当严格(由 POSIX 强制执行),因此不这样做的程序可被视为有缺陷。如果您要依赖 argv[0]
,那么您的 main()
测试它是否被正确调用可能是个好主意,这样您就可以失败并显示一条很好的错误消息而不是错误,但并非所有程序都这样做.
Linux 专门将 argv=NULL
或 envp=NULL
视为有效的空列表,而不是返回 -EFAULT1。这记录在手册页中:
https://man7.org/linux/man-pages/man2/execve.2.html#NOTES.
这不是由POSIX保证的;其他一些操作系统以这种方式工作,但有些则不然。
注意 1:(或者如果通过 libc 包装器,返回 -1 并设置 errno = EFAULT)。
这在 shellcode 中很常见,因为它已经不可移植,并且它在有效负载中保存了机器代码字节。 (或者在你必须构建的 ROP 攻击的一部分中)。但除此之外,这是一个非常糟糕的主意。 Linux 手册页仅出于可移植性原因强烈建议不要使用它;它实际上保证在 Linux 上工作。 (execve 本身;被调用的程序可能不高兴找到 argc=0
和 argv[0] == NULL
,以及空环境。)
像 /bin/sh
do 这样的实际 shell 如果以这种方式调用就可以工作,这就是为什么 shellcode 不构造 {"/bin/sh", NULL}
可行的另一部分原因char*
的数组并传递指向它的指针。
正如 Toby 解释的那样,/bin/ls
在这种情况下退出并不奇怪。即使您曾使用 execve("/bin/sh", (char*[]){NULL}, (char*[]){NULL});
可移植地使用 execve,您仍然会以 argc=0
完全按照您在 Linux.
下的方式启动程序
TL:DR: 不要这样做,除非它是某些代码大小的一部分或不以正常方式进行的其他 hacky 原因。
考虑以下 C 代码 (x86_64)
#include <unistd.h>
int main()
{
execve("/bin/ls", 0, 0);
}
我编译为gcc a.c
并执行;我得到一个 SIGABRT
错误
A NULL argv[0] was passed through an exec system call. Aborted
下一个 运行ning 在 gdb 上,起初我也得到了一个 SIGABRT
,但是我得到了第二个 运行 并且成功了!
Starting program: /bin/ls [Thread debugging using libthread_db enabled] Using host libthread_db library "/lib/x86_64-linux-gnu/libthread_db.so.1".
为什么?
我对 /bin/sh
进行了测试,发现它始终适用于 *argv[] = NULL ...
我再次编写了一些可执行文件(不需要任何参数)来测试并发现它们都有效。
所以我猜只有 /bin/sh
或其他 shell 可以将 *argv[] 设置为 NULL,其他文件(如 /bin/ls
)会失败或出现意外行为。
execve()
系统调用的手册页说
The
argv
andenvp
arrays must each include a null pointer at the end of the array.
如果您的程序不符合这些要求,则无法确定从那时起事情的进展情况。如果它 "works" 对于某些程序,那只是运气不好。
手册页还说
By convention, the first of these strings (i.e.,
argv[0]
) should contain the filename associated with the file being executed.
该约定相当严格(由 POSIX 强制执行),因此不这样做的程序可被视为有缺陷。如果您要依赖 argv[0]
,那么您的 main()
测试它是否被正确调用可能是个好主意,这样您就可以失败并显示一条很好的错误消息而不是错误,但并非所有程序都这样做.
Linux 专门将 argv=NULL
或 envp=NULL
视为有效的空列表,而不是返回 -EFAULT1。这记录在手册页中:
https://man7.org/linux/man-pages/man2/execve.2.html#NOTES.
这不是由POSIX保证的;其他一些操作系统以这种方式工作,但有些则不然。
注意 1:(或者如果通过 libc 包装器,返回 -1 并设置 errno = EFAULT)。
这在 shellcode 中很常见,因为它已经不可移植,并且它在有效负载中保存了机器代码字节。 (或者在你必须构建的 ROP 攻击的一部分中)。但除此之外,这是一个非常糟糕的主意。 Linux 手册页仅出于可移植性原因强烈建议不要使用它;它实际上保证在 Linux 上工作。 (execve 本身;被调用的程序可能不高兴找到 argc=0
和 argv[0] == NULL
,以及空环境。)
像 /bin/sh
do 这样的实际 shell 如果以这种方式调用就可以工作,这就是为什么 shellcode 不构造 {"/bin/sh", NULL}
可行的另一部分原因char*
的数组并传递指向它的指针。
正如 Toby 解释的那样,/bin/ls
在这种情况下退出并不奇怪。即使您曾使用 execve("/bin/sh", (char*[]){NULL}, (char*[]){NULL});
可移植地使用 execve,您仍然会以 argc=0
完全按照您在 Linux.
TL:DR: 不要这样做,除非它是某些代码大小的一部分或不以正常方式进行的其他 hacky 原因。