所以,我试图在这段代码上执行缓冲区溢出,目的是将变量target
更改为'Y'。问题是,我似乎无法让缓冲区溢出到足以产生运行时错误。谁能帮助我理解为什么?
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
enum {SIZE = 50};
char target = 'Z';
char name[SIZE];
FILE *f;
void read(char *s) {
char buffer[SIZE];
int i = 0;
int c;
for (;;)
{
c = getchar();
if ((c == EOF) || (c == '\n'))
break;
buffer[i] = c;
i++;
}
buffer[i] = '\0';
for (i = 0; i < SIZE; i++)
s[i] = buffer[i];
}
int main(void)
{
read(name);
if (strcmp(name, "ABCD") == 0)
target = 'Y';
printf("%s\n", name);
printf("%c\n", target);
exit(0);
}
答案 0 :(得分:1)
如果您尝试使用更改的返回地址执行此操作,则需要使用所需的特定地址覆盖它。在这种情况下,您可以使用代码target = 'Y';
根据堆栈的组织方式,您可以直接从read()
中删除返回地址,这将使您返回到调用它的第一行main
之后。如果做不到这一点,你就有机会破坏i
,这会让你随意写入内存。编写这些字节时需要小心,因为它会影响索引。
我将假装这是在ARM上完成的,因为我更熟悉它们的堆栈布局和调用约定。所以,你的堆栈看起来像这样:
STACKPTR-60: buffer[0]
| | |
STACKPTR-11: buffer[49]
STACKPTR-10: padding
STACKPTR -9: padding
STACKPTR -8: i (bits 7..0)
| | |
STACKPTR -5: i (bits 31..24)
STACKPTR -4: c (bits 7..0)
| | |
STACKPTR -1: c (bits 31..24)
STACKPTR +0: link register (return address) (bits 7..0)
| | |
STACKPTR +3: link register (return address) (bits 31..24)
因此,使用此堆栈布局,当您溢出buffer
时,您将首先填充两个填充字节。之后,填写i
的最低有效字节。在这种情况下,您可以跳过一个巨大的溢出,只需修改索引以指向堆叠链接寄存器。您希望i=60
开始写入返回地址,但是在您编写它之后,它将递增,因此您真的想要i=59
,然后它将递增到i=60
。此时,您可以将所需的退货地址从最不重要到最重要,然后是EOF
或\n
。
非常重要的是要注意这一切都非常具体,特定于编译器。编译器设置将确定int
的大小以及是否填充堆栈以对齐变量,以及变量在堆栈中的显示顺序。该体系结构将确定您的堆栈帧的外观。
答案 1 :(得分:0)
试试这个&#39;不崩溃&#39;您的代码版本,它会覆盖name
数组的边界,以获得适当大小的输入字符串:
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
enum {SIZE = 50};
char target_1 = 'Z';
char name[SIZE] = "Help";
char target_2 = 'Z';
static void read(char *s)
{
char buffer[2*SIZE];
int i = 0;
int c;
while ((c = getchar()) != EOF && c != '\n')
buffer[i++] = c;
buffer[i] = '\0';
for (int j = 0; j < i; j++)
s[j] = buffer[j];
}
int main(void)
{
printf("Data-1: %c %s %c\n", target_1, name, target_2);
read(name);
printf("Data-2: %c %s %c\n", target_1, name, target_2);
return(0);
}
示例运行:
$ for len in 48 49 50 51 52 53 54 ; do echo $len; perl -e "print 'a' x $len" | ./bo; done
48
Data-1: Z Help Z
Data-2: Z aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa Z
49
Data-1: Z Help Z
Data-2: Z aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa Z
50
Data-1: Z Help Z
Data-2: Z aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaZ Z
51
Data-1: Z Help Z
Data-2: a aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa Z
52
Data-1: Z Help Z
Data-2: a aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa Z
53
Data-1: Z Help Z
Data-2: a aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa Z
54
Data-1: Z Help Z
Data-2: a aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa Z
$
您可以看到target_1
的内存地址高于name
,并且它会被大小为52或更大的输入覆盖。
当我说'崩溃&#39;时,我的意思是,对于长度足以溢出name
缓冲区但不会溢出的输入行,它不会崩溃buffer
缓冲区&#39; (大致长度为50到100)。使用较长的输入字符串,程序会因分段错误而崩溃,但有趣的是它在打印第二行输出后崩溃,而不是从read()
返回时崩溃。
140
Data-1: Z Help Z
Data-2: a aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa Z
Segmentation fault: 11
在Mac OS X 10.9.2 Mavericks上测试,使用GCC 4.9.0。