我们每周对计算机系统漏洞进行测试,其中包含以下问题:
以下函数是在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);
}
Run Code Online (Sandbox Code Playgroud)
通过输入参数传递给函数的可能会使应用程序崩溃的字符串的最小长度是多少?
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;
}
Run Code Online (Sandbox Code Playgroud)
并经过输入测试:
~/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)
Run Code Online (Sandbox Code Playgroud)
只要输入123456789参数作为参数,就会检测到堆栈粉碎,因此该问题的答案应该是,9但是没有选择的选项9。对上述问题的正确答案应该是什么?我如何知道可能导致上述应用程序崩溃的最小字符串长度?
您将获得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;
}
Run Code Online (Sandbox Code Playgroud)
在关闭优化的情况下进行编译,否则编译器很可能会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
Run Code Online (Sandbox Code Playgroud)
现在您可以看到发生了什么。该字符串最多包含7个字符,再加上空终止符,可容纳8字节缓冲区。包含8个字符的字符串开始溢出到内存中的下一个对象j。在32位Little-endian机器上,组成的字节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
Run Code Online (Sandbox Code Playgroud)
如果我使用进行编译gcc -O0 -fno-stack-protector,则实际上需要21个字节才能在我的平台上导致崩溃,大概是因为那是重写返回地址所需要的。练习(我没看过,也不知道它有多难):借助调试器并借助汇编代码和一些x86 ABI文档,找出其中的内容(帧指针?对齐间隙?)。