c字符串漏洞

时间:2019-03-11 07:20:27

标签: c string security

我正在阅读有关C语言中字符串的漏洞的信息,然后遇到了这段代码。谁能给我一个解释为什么发生这种情况?提前致谢。

int main (int argc, char* argv[]) {
  char a[16];
  char b[16];
  char c[32];
  strncpy(a, "0123456789abcdef", sizeof(a));
  strncpy(b, "0123456789abcdef", sizeof(b));
  strncpy(c, a, sizeof(c));

  printf("a = %s\n", a);
  printf("b = %s\n", b);
  printf("c = %s\n", c);
}
  

输出:

a = 0123456789abcdef0123456789abcdef
b = 0123456789abcdef
c = 0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef

2 个答案:

答案 0 :(得分:1)

超出字符串末尾的读取是未定义的行为(UB)。使用UB不能保证代码以一种或另一种方式运行。在不同的系统,编译器,链接器,编译/链接标志上,其行为可能有所不同,具体取决于(看似)不相关的代码以及上述所有代码的版本。

在许多系统上,变量以相反的顺序连续地位于堆栈上。将您的printf替换为:

printf("a (%p) = %s\n", a, a);
printf("b (%p) = %s\n", b, b);
printf("c (%p) = %s\n", c, c);

它将打印数组的地址:

a (0x7fff559adad0) = 0123456789abcdef<F0>ښU<FF>
b (0x7fff559adac0) = 0123456789abcdef0123456789abcdef<F0>ښU<FF>
c (0x7fff559adaa0) = 0123456789abcdef<F0>ښU<FF>

从地址可以明显看出,b的打印输出从地址0x7fff559adac0开始,但一直很好地到达a的地址(从b的开头开始16个字节)。 / p>

还请注意,字符串的末尾有垃圾。只是因为字符串中缺少'\ 0'终止符,并且printf继续读取以下垃圾(UB本身就是这样)。

发生这种情况是因为:

strncpy(a, "0123456789abcdef", sizeof(a));

设置a []使其所有字节等于“ 0123456789abcdef”,而没有空终止符。没有'\ 0'的printf不知道在哪里停止,这将导致UB。

strncpy(b, "0123456789abcdef", sizeof(b));

还设置b []使其所有字节等于“ 0123456789abcdef”,而没有空终止符。同样在这里,任何printf都会导致UB。但是这次,它不是读取随机垃圾,而是读取下一个字符串。

要侮辱人格,就要行

 strncpy(c, a, sizeof(c));

从16字节数组中读取32字节。这也是UB。在您(和我)的系统上,它显示为a,并且后面有很多垃圾。从理论上讲,这可能会使您的程序因访问冲突或分段错误而崩溃。

某些病毒和蠕虫使用此类溢出来读取或写入不应有的数据。

答案 1 :(得分:1)

strncpy是一个危险的函数,因为它仅在剩余空间时才添加空终止。这就是代码中发生的情况,您精确地复制了16个字节

实际上strncpy函数从未打算用于C字符串,而是用于不使用空终止的古老Unix字符串格式。对于大多数目的,应避免使用此功能。特别是,它不是“ strcpy的安全版本”-而是比strcpy更危险的功能,如我们从此处的错误中所见。

解决方案是在复制之前预先检查要复制的尺寸。然后使用strcpy。例如:

char a[16];
const char to_copy[] = "0123456789abcdef";
_Static_assert(sizeof(to_copy) <= sizeof(a), "to_copy is too big");
strcpy(a, to_copy);

要修复当前程序,您需要为空终止符分配空间,如下所示:

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

int main (void)
{
  char a[16+1];
  char b[16+1];
  char c[32+1];
  const char to_copy[] = "0123456789abcdef";
  _Static_assert(sizeof(to_copy) <= sizeof(a), "to_copy is too big");
  _Static_assert(sizeof(to_copy) <= sizeof(b), "to_copy is too big");

  strcpy(a, to_copy);
  strcpy(b, to_copy);
  strcpy(c, a);

  printf("a = %s\n", a);
  printf("b = %s\n", b);
  printf("c = %s\n", c);
}