我们每周对计算机系统漏洞进行一次测试,有以下问题:
下面的函数是在32位x86系统上运行的程序的一部分;编译器不改变堆栈上变量的顺序。
void function(char *input) {
int i = 1;
char buffer[8];
int j = 2;
strcpy(buffer,input);
printf("%x %x %s\n",i,j,buffer);
}通过可能使应用程序崩溃的输入参数传递给函数的字符串的最小长度是多少? ( a)10 b)11 c)12 d)13
我编写了一个main函数来调用void function(...,并使用gcc -m32 test.c -o test编译程序,因为我在64位计算机上。以下是主要职能:
int main(int argc, char *argv[]) {
function(argv[1]);
return 1;
}并通过投入进行测试:
~/Dir:./test 1234567
1 2 1234567
~/Dir:./test 12345678
1 2 12345678
~/Dir:./test 123456789
1 2 123456789
*** stack smashing detected ***: <unknown> terminated
Aborted (core dumped)当我输入123456789作为参数时,就会检测到堆栈分解,所以这个问题的答案应该是9,但是没有选择9的选项。对上述问题的正确答案应该是什么?如何知道可以使上述应用程序崩溃的字符串的最小长度?
发布于 2019-08-25 12:39:12
您将得到9个字符的“堆栈粉碎检测”,因为您的编译器确实会对堆栈中的变量进行重新排序。GCC是这样做的,即使在-O0也是如此。为了防止这种情况,将变量放入结构中。
#include <stdio.h>
#include <string.h>
struct variables {
int i;
char buffer[8];
int j;
};
void function(char *input) {
struct variables s;
s.i = 1;
s.j = 2;
strcpy(s.buffer, input);
printf("%x %x %s\n", s.i, s.j, s.buffer);
}
int main(int argc, char *argv[]) {
function(argv[1]);
return 0;
}在关闭优化的情况下对其进行编译,否则编译器很可能会优化s本身。
$ ./a.out 1234567
1 2 1234567
$ ./a.out 12345678
1 0 12345678
$ ./a.out 123456789
1 39 123456789
$ ./a.out 1234567890
1 3039 1234567890
$ ./a.out 1234567890a
1 613039 1234567890a
$ ./a.out 1234567890ab
1 62613039 1234567890ab
$ ./a.out 1234567890abc
1 62613039 1234567890abc
*** stack smashing detected ***: <unknown> terminated
[2] 6086 abort (core dumped) ./a.out 1234567890abc现在你可以看到发生了什么。该字符串最多有7个字符,加上空终止符,适合于8字节缓冲区.有了8个字符,字符串开始溢出到内存中的下一个东西,即j。在32位的小终端机器上,组成j的字节具有{0x02、0x00、0x00、0x00}的值。在8到11个字符的情况下,字符串逐步接管j。
在12个字符时,空终止符覆盖s后内存中的任何内容。在我的测试中,内存中的这个字节碰巧值为0,所以没有什么比覆盖j更糟糕的了。在13个字符时,字符串c的最后一个字符覆盖该字节,该字节被堆栈保护所检测,因为该字节实际上是堆栈金丝雀的一部分。
在我的构建中,造成崩溃所需的字符数是13个。然而,这是因为在j之后碰巧有一个空字节。考虑到练习的假设,可能导致应用程序崩溃所需的字符数是12。此时,strcpy调用写入函数的本地存储,这可能是一个未映射的地址。
对于可视参考,这是strcpy调用之前的内存内容:
+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+
| 01 | 00 | 00 | 00 | ?? | ?? | ?? | ?? | ?? | ?? | ?? | ?? | 02 | 00 | 00 | 00 | 00 | ?? |
+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+
^-i ^-buffer ^-j ^-stack canary如果我用gcc -O0 -fno-stack-protector编译,那么在我的平台上造成崩溃需要花费21个字节,大概是因为覆盖返回地址所需的时间。练习(我没有看过,也不知道它有多难):使用调试器,借助汇编代码和一些x86 ABI文档,找出其中的内容(帧指针?对齐间隙?)
发布于 2019-08-25 11:55:35
这个问题是在假设编译器没有重新排序堆栈上的变量,并且没有利用未定义的行为来执行某些优化的假设下工作的,但是您的测试程序就是这样做的。它很可能将数组放置在堆栈的最高地址,这样就可以减少造成崩溃所需的字符。
在这个问题的约束下,如果一个int是4个字节,答案将是12。字符9-12将写入其中一个int变量的字节,而字符串的终止空字节将被写入该字节之后的一个字节,可能会写入函数的返回地址。
https://stackoverflow.com/questions/57645610
复制相似问题