我正在尝试缓冲区溢出并尝试使用fgets的某个输入覆盖堆栈的返回地址
这是代码:
void foo()
{
fprintf(stderr, "You did it.\n");
}
void bar()
{
char buf[20];
puts("Input:");
fgets(buf, 24, stdin);
printf("Your input:.\n", strlen(buf));
}
int main(int argc, char **argv)
{
bar();
return 0;
}
在正常执行时,程序只返回您的输入。我希望它在不修改代码的情况下输出foo()。
我的想法是输入20 buf
来溢出'A'
的缓冲区。这会起作用并导致分段错误。
我的下一个想法是找出foo()
\x4006cd
的地址,并将其附加到20 'A'
。
根据我的理解,这应该覆盖堆栈的返回地址并使其跳转到foo
。但它只会导致段错误。
我做错了什么?
更新:汇编程序转储 主
Dump of assembler code for function main:
0x000000000040073b <+0>: push %rbp
0x000000000040073c <+1>: mov %rsp,%rbp
0x000000000040073f <+4>: sub $0x10,%rsp
0x0000000000400743 <+8>: mov %edi,-0x4(%rbp)
0x0000000000400746 <+11>: mov %rsi,-0x10(%rbp)
0x000000000040074a <+15>: mov $0x0,%eax
0x000000000040074f <+20>: callq 0x4006f1 <bar>
0x0000000000400754 <+25>: mov $0x0,%eax
0x0000000000400759 <+30>: leaveq
0x000000000040075a <+31>: retq
End of assembler dump.
FOO
Dump of assembler code for function foo:
0x00000000004006cd <+0>: push %rbp
0x00000000004006ce <+1>: mov %rsp,%rbp
0x00000000004006d1 <+4>: mov 0x200990(%rip),%rax # 0x601068 <stderr@@GLIBC_2.2.5>
0x00000000004006d8 <+11>: mov %rax,%rcx
0x00000000004006db <+14>: mov $0x15,%edx
0x00000000004006e0 <+19>: mov $0x1,%esi
0x00000000004006e5 <+24>: mov $0x400804,%edi
0x00000000004006ea <+29>: callq 0x4005d0 <fwrite@plt>
0x00000000004006ef <+34>: pop %rbp
0x00000000004006f0 <+35>: retq
End of assembler dump.
栏:
Dump of assembler code for function bar:
0x00000000004006f1 <+0>: push %rbp
0x00000000004006f2 <+1>: mov %rsp,%rbp
0x00000000004006f5 <+4>: sub $0x20,%rsp
0x00000000004006f9 <+8>: mov $0x40081a,%edi
0x00000000004006fe <+13>: callq 0x400570 <puts@plt>
0x0000000000400703 <+18>: mov 0x200956(%rip),%rdx # 0x601060 <stdin@@GLIBC_2.2.5>
0x000000000040070a <+25>: lea -0x20(%rbp),%rax
0x000000000040070e <+29>: mov $0x18,%esi
0x0000000000400713 <+34>: mov %rax,%rdi
0x0000000000400716 <+37>: callq 0x4005b0 <fgets@plt>
0x000000000040071b <+42>: lea -0x20(%rbp),%rax
0x000000000040071f <+46>: mov %rax,%rdi
0x0000000000400722 <+49>: callq 0x400580 <strlen@plt>
0x0000000000400727 <+54>: mov %rax,%rsi
0x000000000040072a <+57>: mov $0x400821,%edi
0x000000000040072f <+62>: mov $0x0,%eax
0x0000000000400734 <+67>: callq 0x400590 <printf@plt>
0x0000000000400739 <+72>: leaveq
0x000000000040073a <+73>: retq
End of assembler dump.
答案 0 :(得分:3)
你没有计算内存对齐。我改变了代码,以便更容易找到正确的位置。
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
int **x;
int z;
void foo()
{
fprintf(stderr, "You did it.\n");
}
void bar()
{
char buf[2];
//puts("Input:");
//fgets(buf, 70, stdin);
x = (int**) buf;
for(z=0;z<8;z++)
printf("%d X=%x\n", z, *(x+z));
*(x+3) = foo;
printf("Your input: %d %s\n", strlen(buf), buf);
}
int main(int argc, char **argv)
{
printf("Foo: %x\n", foo);
printf("Main: %x\n", main);
bar();
return 0;
}
使用较小的缓冲区,在我的示例中为2,我从缓冲区的开头找到了24字节的返回地址(x + 3,对于8字节指针; 64位,无调试,没有优化...)。这个位置可以根据缓冲区大小,体系结构等而改变。在这个例子中,我设法将bar的返回地址更改为foo。无论如何,你会在foo返回时得到一个分段错误,因为它没有正确设置为返回main。
我将x和z添加为全局变量,以便不更改bar的堆栈大小。代码将显示一个类似指针的数组,从buf [0]开始。在我的情况下,我在主要位置3找到了地址。这就是为什么最终代码有*(x + 3)= foo。正如我所说,这个位置可能会根据编译选项,机器等而改变。要找到正确的位置,请在地址列表中找到main的地址(在调用条之前打印)。
重要的是要注意我在main中说地址,而不是main的地址,因为返回地址设置为调用bar之后的行而不是main的开头。所以,就我而言,它是0x4006af而不是0x400668。
在你的例子中,有20个字节的缓冲区,据我所知,它被对齐到32个字节(0x20)。
如果你想对fgets做同样的事情,你必须弄清楚如何输入foo的地址,但是如果你正在运行x86 / x64机器,记得把它添加到little enddian中。您可以更改代码以显示每个字节的值字节,因此您可以按正确的顺序获取它们并使用ALT +编号键入它们。请记住,您在按住ALT时键入的数字是十进制数字。有些终端不会友好处理0x00。
我的输出如下:
$ gcc test.c -o test
test.c: In function ‘bar’:
test.c:21: warning: assignment from incompatible pointer type
$ ./test
Foo: 400594
Main: 400668
0 X=9560e9f0
1 X=95821188
2 X=889350f0
3 X=4006af
4 X=889351d8
5 X=0
6 X=0
7 X=95a1ed1d
Your input: 5 ▒▒`▒9
You did it.
Segmentation fault
答案 1 :(得分:2)
void bar() { char buf[20]; puts("Input:"); fgets(buf, 24, stdin); printf("Your input:.\n", strlen(buf)); }
...这有效并导致分段错误...
编译器可能正在用更安全的变体替换fgets
,其中包括检查目标缓冲区大小。如果检查失败,则prgram无条件地调用abort()
。
在这种特殊情况下,您应该使用-U_FORTIFY_SOURCE
或-D_FORTIFY_SOURCE=0
编译该程序。