使用system()执行存在缓冲区溢出漏洞的程序时没有报错信息

No error message when using system() to execute program with buffer overflow vulnerability

考虑以下存在缓冲区溢出漏洞的程序 (vul.c)。

#include <stdio.h>
#include <string.h>

int main(int argc, char **argv)
{
    char buf[10];
    strcpy(buf, argv[1]);
    printf("%s\n", buf);
    return 0;
}

使用 gcc -o vul vul.c 编译并在 arch linux - linux 4.4.16-1-lts x86-64 上执行的上述程序在使用 ./vul $(perl -e 'print "A"x100') 命令在终端中执行时给出以下输出:

AAAAAAAAAAA...A
Segmentation fault (core dumped)

然后使用 echo $? 命令检查程序状态给出 139 输出。

下面的程序(exp.c)(为了让上面的程序崩溃)

#include <stdlib.h>

int main(void)
{
    printf("%d\n", system("./vul $(perl -e 'print \"A\"x100')"));
    return 0;
}

在同一系统上使用 ./exp 命令执行时使用 gcc -o exp exp.c 编译给出以下输出:

AAAAAAAAAAAA...A
139

我有两个问题:

  1. 为什么 2nd 程序没有产生错误信息?并且,
  2. 我需要使用 -fstack-protector 标志编译程序以在 arch linux 中启用 *** stack smashing detected *** 错误消息,但在 Ubuntu 中不启用。在 Ubuntu 中,可能是 gcc 中默认包含此标志,或者还有其他原因吗?

正如我在评论中指出的那样,system return 是一个具有程序 return 值的整数,通常是错误代码(如果成功则为 0)。

如果您想将错误打印为漂亮的消息,您可以使用 strerror

根据@rht 的评论(请参阅我的下一个编辑)和该评论中引用的问题的答案,returned 值在成功时将为 0,在错误时将为 error | 0x80.要获取原始错误代码,请使用 128 - err_code.

试试这个:

#include <stdlib.h>
#include <errno.h>

int main(void)
{
    int tmp = system("./vul $(perl -e 'print \"A\"x100)");
    if(tmp < 0)
       error("Couldn't run system command");
    else if(tmp >0)
       printf(stderr, "System command returned error: %s", strerror(128 - tmp));
    else
       ; // nothing
    return 0;
}

vul.c 打印(或不打印)错误消息的事实应该与您的 exp.c 程序无关,因为它取决于 vul.c 的编译标志值和任何默认编译器标志 - 事情 exp.c 无法控制。

EDIT(2) - 回应评论。

错误消息 returned 可能不是 errno 值,而是信号 trap 值。

这些有时很难区分,关于如何在不使用 memcmp 反对答案的情况下判断它是哪一个,我没有好的建议。

在这种情况下,您知道 vul.c 永远不会 return 它是 errno 值,这只会给您留下信号陷阱错误,因此您可以使用 strsignal 来打印错误信息。

正如@rht 的评论中所指出的,其中引用了 this question:

Passing tmp to strsignal generates the same error message: "unknown signal 139". The reason is that there is no signal with this signal number. /usr/include/bits/s‌​ignum.h contains all the signals with their signal numbers. Passing tmp-128 to strsignal works.

#include <stdlib.h>
#include <string>

int main(void)
{
    int tmp = system("./vul $(perl -e 'print \"A\"x100)");
    if(tmp < 0)
       error("Couldn't run system command");
    else if(tmp >0)
       printf(stderr, "System command returned error: %s", strsignal(tmp - 128));
    else
       ; // nothing
    return 0;
}

编辑

问题已编辑,因为它的代码被错误复制。我更改了答案以反映该更改。

回答我自己的第 2 个问题。

gcc -Q -v vul.c 命令显示传递给 gcc 的选项。 Ubuntu 中的选项包括 -fstack-protector-strong 标志但不在 arch-linux 中。所以在Ubuntu中,flag默认传给了gcc.

您的 vul.c 和 exp.c 中存在两个问题。

在vul.c,

char buf[10];

10 在这种情况下是不够的,因为 argv[1],即 $(perl -e 'print "A"x100',大于要分配的缓冲区。扩大 buf 大小应该修复分段错误。

exp.c中少了一个单引号,修改如下:

printf("%d\n", system("./vul $(perl -e 'print \"A\"x100')"));

根据我对@Myst 对 "passing tmp-128 to strsignal()" 函数的回答的评论,经过一些试验后我发现它在程序正常退出但返回非 0 状态的情况下不起作用。

以下是我的/usr/include/bits/waitstatus.h内容:

/* If WIFEXITED(STATUS), the low-order 8 bits of the status.  */
#define __WEXITSTATUS(status)   (((status) & 0xff00) >> 8)

/* If WIFSIGNALED(STATUS), the terminating signal.  */
#define __WTERMSIG(status)      ((status) & 0x7f)

/* Nonzero if STATUS indicates normal termination.  */
#define __WIFEXITED(status)     (__WTERMSIG(status) == 0)

/* Nonzero if STATUS indicates termination by a signal.  */
#define __WIFSIGNALED(status) \
  (((signed char) (((status) & 0x7f) + 1) >> 1) > 0)

以上代码表明,程序的退出状态是一个16位的数字,其中高8位是程序返回的状态,如果程序退出则设置其余位some/all因为一个信号,其中7位表示导致程序退出的信号。这就是为什么从 system() 返回的退出状态中减去 128 在上述情况下不起作用的原因。

System()'s source code

由于system()函数也是使用fork()来创建新进程并等待进程终止,因此这里也可以应用同样的方法来检查子进程在父进程中的状态。以下程序对此进行了演示:

#include <stdio.h>
#include <stdlib.h>
#include <sys/wait.h>
#include <string.h>

int main(void)
{
    int status = system("prg_name");
    if (WIFEXITED(status))
        printf("Exited Normally, status = %d\n", WEXITSTATUS(status));
    else if (WIFSIGNALED(status))
        printf("Killed by Signal %d which was %s\n", WTERMSIG(status), strsignal(WTERMSIG(status)));
    return 0;
}