将空程序参数向量传递给 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=NULLenvp=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=0argv[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 原因。