字符串终止漏洞
仔细考虑这一点,使用strncpy()
可能是最常见的方式(我能想到的),它可能会产生空终止错误。由于通常人们认为缓冲区的长度不包括\0
. 所以你会看到类似下面的内容:
strncpy(a, "0123456789abcdef", sizeof(a));
假设a
用字符串初始化不会以空值终止char a[16]
。a
那么为什么这是一个问题呢?在记忆中,你现在有类似的东西:
30 31 32 33 34 35 36 37 38 39 61 62 63 64 65 66
e0 f3 3f 5a 9f 1c ff 94 49 8a 9e f5 3a 5b 64 8e
如果没有空终止符,标准字符串函数将不知道缓冲区的长度。例如,strlen(a)
将继续计数,直到达到一个0x00
字节。那是什么时候,谁知道呢?但是每当它找到它时,它会返回一个比你的缓冲区大得多的长度;假设是 78。让我们看一个例子:
int main(int argc, char **argv) {
char a[16];
strncpy(a, "0123456789abcdef", sizeof(a));
... lots of code passes, functions are called...
... we finally come back to array a ...
do_something_with_a(a);
}
void do_something_with_a(char *a) {
int a_len = 0;
char new_array[16];
// Don't know what the length of the 'a' string is, but it's a string so lets use strlen()!
a_len = strlen(a);
// Gonna munge the 'a' string, so lets copy it first into new_array
strncpy(new_array, a, a_len);
}
您现在刚刚将 78 个字节写入一个仅分配了 16 个字节的变量。
缓冲区溢出
当写入缓冲区的数据多于分配给该缓冲区的数据时,就会发生缓冲区溢出。这对于字符串没有什么不同,除了许多string.h
函数依赖这个空字节来表示字符串的结束。正如我们在上面看到的。
在示例中,我们将 78 个字节写入一个仅分配 16 个字节的缓冲区。不仅如此,它还是一个局部变量。这意味着缓冲区已在堆栈上分配。现在那些最后写入的 66 个字节,它们只是覆盖了堆栈的 66 个字节。
如果您在该缓冲区的末尾写入足够多的数据,您将覆盖另一个局部变量a_len
(如果您以后使用它也不好),任何保存在堆栈上的堆栈帧指针,然后是函数的返回地址。现在你真的走了,把事情搞砸了。因为现在返回地址是完全错误的。当到达终点时do_something_with_a()
,就会发生不好的事情。
现在我们可以在上面的例子中再添加一个。
void do_something_with_a(char *a, char *new_a) {
int a_len = 0;
char new_array[16];
// Don't know what the length of the 'a' string is, but it's a string so
// lets use strlen()!
a_len = strlen(a);
//
// By the way, copying anything based on a length that's not what you
// initialized the array with is horrible horrible coding. But it's
// just an example.
//
// Gonna munge the 'a' string, so lets copy it first into new_array
strncpy(new_array, a, a_len);
// 'a_len' was on the stack, that we just blew away by writing 66 extra
// bytes to the 'new_array' buffer. So now the first 4 bytes after 16
// has now been written into a_len. This can still be interpreted as
// a signed int. So if you use the example memory, a_len is now 0xe0f33f5a
//
// ... did some more munging ...
//
// Now I want to return the new munged string in the *new_a variable
strncpy(new_a, new_array, a_len);
// Everything burns
}
我认为我的评论几乎可以解释一切。但最后,您现在已经将大量数据写入了一个数组,很可能认为您只写入了 16 个字节。根据此漏洞的表现方式,这可能会导致通过远程代码执行进行利用。
这是一个非常人为的糟糕编码示例,但是如果您在使用内存和复制数据时不小心,您会看到事情会如何迅速升级。大多数情况下,漏洞不会这么明显。对于大型程序,您有很多事情要做,以至于该漏洞可能不容易被发现,并且可能由代码多个函数调用触发。
有关缓冲区溢出如何工作的更多信息。
在任何人提到它之前,为了简单起见,我在引用内存时忽略了字节序
延伸阅读
漏洞
通用弱点枚举 (CWE) 条目的 完整描述
安全编码字符串演示文稿(PDF 自动下载)
匹兹堡大学 - 安全编码 C/C++:字符串漏洞 (PDF)