在缓冲区溢出漏洞中,返回地址被覆盖为不正确的地址,但它仍然能够运行。

4
我正在尝试制造缓冲区溢出,以下是我的代码:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>


int check_authentication(char *password) {

   char password_buffer[16];
   int auth_flag = 0;
   strcpy(password_buffer, password);
   if(strcmp(password_buffer, "brillig") == 0)
     auth_flag = 1;
   if(strcmp(password_buffer, "outgrabe") == 0)
     auth_flag = 1;

   return auth_flag; 

}




int main(int argc, char *argv[]) {

   if(argc < 2) {
      printf("Usage: %s <password>\n", argv[0]);
      exit(0);
    }
    if(check_authentication(argv[1])) {
      printf("\n-=-=-=-=-=-=-=-=-=-=-=-=-=-\n");
      printf(" Access Granted.\n");
      printf("-=-=-=-=-=-=-=-=-=-=-=-=-=-\n");
    } 

    else {
      printf("\nAccess Denied.\n");
    }
}

从命令行提供的密码将与“brilling”和“outgrabe”进行比较,如果用户输入与这些任何一个匹配,则将授予访问权限,否则将被拒绝。据我所知,如果提供的密码大于16个字节,则返回地址将被覆盖,但是当我输入“A” 17时,它没有被覆盖。相反,auth_flag被覆盖并为65(十六进制中的0x41,即A)。我无法弄清楚为什么变量被覆盖而不是返回地址。我使用以下命令进行编译:gcc -fno-stack-protector -z execstack -g -o test test.c。希望你们能帮忙解决问题。谢谢!

1
C编译器可以自由地假设在程序执行期间不会发生未定义的行为。它还允许任意重新排序本地变量的地址,前提是这不会改变良好定义的程序的执行。 - EOF
那么你的意思是编译器可以假定应该首先创建 auth_flag,然后是 buffer,这就是它覆盖了 auth_flag 的原因? - Ojs
你可以使用strncpy来避免这个问题。 - Misery
2
根据提问者@Misery的问题,我了解到缓冲区溢出不是一个“问题”,而是一种期望的结果。 - dureuill
@dureuill 我没有那样看过,那是我的错。 - Misery
2个回答

3

正如Sourav Ghosh所说,你的程序会导致未定义的行为。

实际上,这意味着你需要深入了解生成的代码,以查看堆栈的使用方式。

由于这在很大程度上依赖于你的环境(机器、操作系统、编译器版本等),因此我们很难为你调试它。

我的环境中,authenticated标志存储在ebp-12处,而缓冲区存储在ebp-28处。ebp-28被传递给strcmp函数,这意味着溢出将导致authenticated被覆盖。更长的字符串最终会破坏返回地址:“1234567890123456000000000000”会在我的地方触发段错误。

一个推荐的起点可能是编译成汇编语言:

gcc -fno-stack-protector -z execstack -g -S test.c 

生成test.s文件,可以展示您的堆栈情况。如果无法从源代码编译,则可以使用反汇编器(例如objdump和ida pro)查看二进制文件;如果您无法承担这样的设施。请注意,此处明确存在某种形式的堆栈保护。
 movl    %gs:20, %eax
 [...]
 movl    -12(%ebp), %edx
 xorl    %gs:20, %edx
 je      .L5
 call    __stack_chk_fail

如果位于-12(%ebp)的位置不再是%gs:20(例如,您的堆栈已损坏),则会调用__stack_chk_fail

另外,如果真正想要绕过身份验证的攻击者可以控制您的“authenticated”标志的值,他们甚至不会费力去清除返回地址。


你应该使用与原始代码相同的参数。特别是,此代码已启用堆栈保护。 - Jester
修改了我的gcc调用以反映OP的标志。如果有疑问,请使用“objdump”。 - dureuill
你能告诉我如何禁用它吗? - Ojs
哦,我明白了,谢谢。但是你能解释一下如何在不擦除返回地址的情况下控制标志吗?如果不是这样,他们会将多个A作为输入 :) - Ojs
您的堆栈布局大致如下:buffer[16]; authenticated; other_stuff; return address。因此,缓冲区溢出将在任何其他操作之前覆盖已认证标志。 - dureuill

0

一旦输入(password)字符串包含超过15(不是16,需要终止空值)个字符,使用strcpy()将会引发未定义行为

  • 在此之前(除非发生这种情况),您的程序是正常的。
  • 在发生这种情况之后,您无法预测任何事情

因此,在程序显示UB之后,您不能期望它以所期望的方式运行。它可以做任何和每件事情,无论是预期的还是意外的。无法确定它是否覆盖auth_flag

也许值得一提的是,变量的内存分配不一定按照任何特定顺序(出现的顺序)。编译器可以自由地按任何它认为合适的顺序分配内存。


那么你的意思是没有办法使用strcpy()函数并覆盖返回地址? - Ojs
@Ojs,您所说的“覆盖返回地址”是什么意思? - Sourav Ghosh
1
好吧,在一般情况下你无法做出任何预测。但是,如果你使用调试器并为特定的二进制文件创建一个特定的字符串,你就可以在许多情况下预测会发生什么。如果这种预测不成立,缓冲区溢出攻击就不会奏效。 - Eric Renouf
在“覆盖返回地址”中,我指的是更改check_authentication函数中$ebp+4地址。 - Ojs
2
只需使用更长的字符串。它最终会命中返回地址 ;) 请注意,堆栈上可能还有其他东西,例如帧指针或保存的寄存器。 - Jester

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接