缓存溢出问题简述

缓存溢出

缓存溢出(Buffer overflow),是指在存在缓存溢出安全漏洞的计算机中,攻击者能够用超出常规长度的字符来填满一个域,一般是内存区地址。这篇文章就是解说简单的缓存溢出问题。文章以x86_32 和 linux 系统平台为蓝本。为了介绍缓存溢出,数据的存储地址、基本的汇编指令、重要的寄存器等内容都要解说。

1.      变量存储


在C语言中,变量属性有非常多中,可是对于缓存溢出问题,我们主要关心的数据的存储位置,或存储空间。因此,这里我们主要关心全局变量,局部变量和静态变量。在C语言中,通常全局变量和静态变量被分配于数据段(data section)中,可是局部变量分配在栈(stack)中。另外,大家清楚,栈空间中还存储CS,IP等一些列与指令地址相关的关键数据,因此,在对局部变量进行数据拷贝的时候,假设拷贝数据块过大,就可能将IP,CS等寄存器存放的数据空间覆盖掉,写入一些非法的数据,当从设IP值时,计算机就跳转到新的非法数据代码空间了。这里有一个简单的C文件,overflow1.c

int data = 0x66666666;

int func1(void)

{

static int sdata = 0x55555555;

int ret = 0;

return ret;

}

int main(int argc, char *argvs[])

{

func1();

return 0;

}

我们首先对它进行编译,然后查看编译后的信息。

# gcc –o overflow1.o –c overflow1.c

# objdump –t overflow1.o

over3.o:     file format elf32-i386

SYMBOL TABLE:

00000000 l    df *ABS*  00000000 over3.c

00000000 l    d  .text  00000000 .text

00000000 l    d  .data  00000000 .data

00000000 l    d  .bss   00000000 .bss

00000004 l     O .data  00000004 sdata.1280

00000000 l    d  .note.GNU-stack        00000000 .note.GNU-stack

00000000 l    d  .comment       00000000 .comment

00000000 g     O .data  00000004 data

00000000 g     F .text  00000012 func1

00000012 g     F .text  0000001e main

从结果中我们非常easy找到data 和 sdata变量的size, section等信息。假设想获取变量存储的地址,能够连接后在运行该命令。可是对于局部ret,恐怕你不easy找到它的存放地址,那么ret的空间在哪里呢? 不要着急,我们继续。

# objdump –d overflow1.o

over3.o:     file format elf32-i386

Disassembly of section .text:

00000000 <func1>:

0:   55                      push   %ebp

1:   89 e5                    mov    %esp,%ebp

3:   83 ec 10                  sub    $0x10,%esp

6:   c7 45 fc 00 00 00 00        movl   $0x0,-0x4(%ebp)

d:   8b 45 fc                  mov    -0x4(%ebp),%eax

10:   c9                       leave

11:   c3                       ret

00000012 <main>:

12:   8d 4c 24 04             lea    0x4(%esp),%ecx

16:   83 e4 f0                and    $0xfffffff0,%esp

19:   ff 71 fc                 pushl  -0x4(%ecx)

1c:   55                      push   %ebp

1d:   89 e5                   mov    %esp,%ebp

1f:   51                      push   %ecx

20:   e8 fc ff ff ff              call   21 <main+0xf>

25:   b8 00 00 00 00           mov    $0x0,%eax

2a:   59                      pop    %ecx

2b:   5d                      pop    %ebp

2c:   8d 61 fc                lea    -0x4(%ecx),%esp

2f:   c3                      ret

从汇编代码里面恐怕还是不明确ret在哪里存储吧?不用操心,我们回头看看C源文件,在func1函数里有一个 int ret = 0 的声明,聪明的你如今是不是找到相应的汇编语句了,你猜对了,就是以下这句话:

6:   c7 45 fc 00 00 00 00        movl   $0x0,-0x4(%ebp)

也就是说,ret的空间分配在-0x4%(ebp)指向的空间中。从

1:   89 e5                    mov    %esp,%ebp

你会发现ebp存放的是esp的内容,也就说-0x4%(ebp)指向的是栈空间地址,而ret就存放在哪里。

如今明确了变量的存放空间了,以下我们要继续解说关于栈中其他的信息。

2.      重要指令和寄存器


为了更好的了解缓存溢出,就要了解与其相关的指令和寄存器。我在这部分内容中会解说这些信息。

IP,CS,ebp,esp是我们要讲的寄存器。CS:IP指向将要运行的指令的存储地址。一般的函数跳转指令和函数调用指令就是通过改动CS:IP的值来达到跳转目的。当指令段发生改变时,CS寄存器的数值才改变,在同一个指令段中,通常仅仅改变IP的数值就能够了。我们今天介绍的默认仅仅是通过改动IP的值而达到跳转目的。Esp寄存器存放的当前栈顶地址,而ebp作为一个备份寄存器,保存着进入新函数后esp的值。

在重要的指令中,主要有push, pop, call, ret, leave. Push 和 pop是一对栈操作指令,push完毕入栈操作,将数据写入栈中,并更新esp的内容,而pop指令与其相反,它将数据从栈中取出,并更新esp的内容。 当中 call指令是函数调用指令,它主要完毕指令计数器寄存器(IP) 的入栈操作(为了简单期间,这里不考虑CS入栈问题,有兴趣的同学能够去查看引用文献),相似于指令 “push ip”。而ret指令与call指令相反,是一个函数返回指令,将IP的值从栈中弹出,相似”pop ip”。Leave也能够看成一个符合指令,它相似于 “mov ebp, esp; pop ebp” 两个指令的效果。

了解了上面的基本知识,我们来分析一下汇编代码。为了清楚期间,我们将overflow1.o 进行连接,然后查看连接后的重要汇编代码片段。

# gcc –o overflow1 overflow.o

# objdump –d overflow1

…..

08048324 <func1>:

8048324:       55                      push   %ebp

8048325:       89 e5                    mov    %esp,%ebp

8048327:       83 ec 10                 sub    $0x10,%esp

804832a:       c7 45 fc 00 00 00 00        movl   $0x0,-0x4(%ebp)

8048331:       8b 45 fc                  mov    -0x4(%ebp),%eax

8048334:       c9                       leave

8048335:       c3                       ret

08048336 <main>:

8048336:       8d 4c 24 04             lea    0x4(%esp),%ecx

804833a:       83 e4 f0                and    $0xfffffff0,%esp

804833d:       ff 71 fc                pushl  -0x4(%ecx)

8048340:       55                      push   %ebp

8048341:       89 e5                   mov    %esp,%ebp

8048343:       51                      push   %ecx

8048344:       e8 db ff ff ff          call   8048324 <func1>

8048349:       b8 00 00 00 00          mov    $0x0,%eax

804834e:       59                      pop    %ecx

804834f:       5d                      pop    %ebp

8048350:       8d 61 fc                lea    -0x4(%ecx),%esp

….

从对func1函数開始调用開始,我们跟踪栈里面的内容。当call指令运行后,计算机会跳到func1函数处继续运行,如今就将这些指令合并:

8048344:       e8 db ff ff ff          call   8048324 <func1>

8048324:       55                      push   %ebp

8048325:       89 e5                    mov    %esp,%ebp

8048327:       83 ec 10                 sub    $0x10,%esp

804832a:       c7 45 fc 00 00 00 00        movl   $0x0,-0x4(%ebp)

运行后的结果是什么呢?

第一条指令是将IP压栈;

第二条指令将ebp压栈

第三条是将esp的值保存到ebp中

第四条指令更新esp的值,向前16bytes。

第五条指令给ret赋初值0,而且能够确定ret的地址是%ebp - 4

因此,我们得到当前栈的值

IP                  0x88-0x8B      ;;High address

Ebp->   oldEBP           0x84-0x87      ;; ebp = 0x84

ret                 0x80-0x83

Nil                 0x7c-0x7f

Nil                 0x78-0x7b

Esp->   Nil                 0x74-0x77      ;; esp = 0x74

….                 ….                 ;;low address

而且ebp会作为备份寄存器保留老的esp寄存器的值,当函数返回时,还原esp和ebp,以及IP。缓存溢出就是在还原之前首先将栈中IP的值改动成其余的数值,从而是CPU跳转到一个错误地址或无效地址。

假设按照上面栈的地址,那么ret变量的地址应该是0x80, 而老的IP数据的存储地址应该是0x88,假设在向ret进行数据拷贝时,数据过长,将会覆盖oldebp和IP的地址,从而导致程序在返回时,将错误的IP值弹出到指令计数器中,CPU将会跳转到该错误地址进行代码运行。以下提供了两个案例程序,给大家參考。

Examples

Overflow2中是一个典型的内存溢出,作者通过向一个局部变量数组中写入过长数据,使程序无条件跳转的my_func() 一个非法函数中。

#include <stdio.h>

#include <string.h>

char strs[32] = {0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x8, /

0xc4, 0x83, 0x04, 0x08, 0xc4, 0x83, 0x04, 0x08, /

0xc4, 0x83, 0x04, 0x08, 0xc4, 0x83, 0x04, 0x08} ;

/*my_func的地址是0x080483c4*/

int my_func(void)

{

printf("in My Func!/n");

return 87;

}

int print(void)

{

int tmp = 0x33;

int ret = 0x22;

char str[4];

char *data;

strncpy(str, strs, 24);

return ret;

}

int main(int argc, char *argvs[])

{

int ret = print();

printf("ret = %x/n", ret);

return 0;

}

Overflow3.c 是一个不通过函数调用,强制跳转到my_func()函数,并成功返回到主函数。

#include <stdio.h>

#include <string.h>

int my_func(void)

{

printf("in My Func!/n");

return 87;

}

int print(void)

{

int ret = 0x22;

int str[4];

asm (                           /

"mov 0(%%ebp), %%ebx;   /

mov %%eax, 0(%%ebp);   /

push %%eax;            /

sub $4, %%ebp;         /

mov %%ebx, 0(%%ebp)"   /

:               /

: "a"(my_func));

return ret;

}

int main(int argc, char *argvs[])

{

int ret = print();

printf("ret = %x/n", ret);

return 0;

}

Reference

缓存溢出问题简述

时间: 2024-10-15 09:40:36

缓存溢出问题简述的相关文章

关于这周工作中遇到的关于缓存问题的记录

序:本周在工作中遇到了一些麻烦,解决过程比较曲折和辛苦,特此记录,留作经验供以后参考 发现问题:周一上班的时候,运营打电话来说,我们上个月做的一个活动感觉数据不对,商家过来投诉了.结果我数据库一查,数据还真有问题!这次的活动采用的是页面上使用缓存系统显示活动数值(总金额),同时在后台记录详细的每条活动数据的办法.每次用户发生业务行为的时候都会在后台的缓存的总金额上增加,同时记录这次行为发生的金额数.结果我周一把数据库的记录加一起来一算,发现和页面上缓存的总金额竟然差了将近一半! 解决的过程: 1

MyBatis学习--查询缓存

简介 以前在使用Hibernate的时候知道其有一级缓存和二级缓存,限制ORM框架的发展都是互相吸收其他框架的优点,在Hibernate中也有一级缓存和二级缓存,用于减轻数据压力,提高数据库性能. mybaits提供一级缓存和二级缓存结构如下图: 可以看出一级缓存是sqlSession级别的,而二级缓存是Mapper级别的,同一个Mapper中的多个sqlSession可以共享缓存数据. 一级缓存是SqlSession级别的缓存.在操作数据库时需要构造 sqlSession对象,在对象中有一个数

mybatis 一级缓存和二级缓存

1.默认是会话期内 一级session缓存 2.二级缓存: 引入二级缓存的jar, 配置 ehcache.xml, mapper.xml引入缓存<cache type="org.mybatis.caches.ehcache.EhcacheCache"></cache> 如下mapper: <?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE mapper PU

Spring+SpringMVC+MyBatis深入学习及搭建(八)——MyBatis查询缓存

1.什么是查询缓存 mybatis提供查询缓存,用于减轻数据库压力,提高数据库性能. mybatis提供一级缓存和二级缓存. 一级缓存是SqlSession级别的缓存.在操作数据库时需要构造sqlSession对象,在对象中有一个数据结构(HashMap)用于存储缓存数据.不同的sqlSession之间的缓存数据区域(HashMap)是互相不影响的. 二级缓存是mapper级别的缓存,多个sqlSession去操作同一个Mapper的sql语句,多个sqlSession可以共用二级缓存,二级缓存

nginx 反向代理、缓存

lvs+keepalive+nginx(realserver)两台+tomcat(后端服务器),nginx的配置文件nginx.conf如下 user  nobody nobody; worker_processes 12; error_log /var/log/nginx/error.log crit;(取消记录错误日志) #error_log  /var/log/nginx/debug.log  debug_http; #error_log  logs/error.log; #error_l

关于Linux TCP接收缓存以及接收窗口的一个细节解析

关于TCP的接收缓存以及通告窗口,一般而言懂TCP的都能说出个大概,但是涉及到细节的话可能理解就不那么深入了.由于我最近的工作与TCP有关,顺便又想起了很久之前遇到的一个问题:明明在接收端有8192字节的接收缓存,为什么收了不到8000字节的数据就ZeroWindow了呢?当时我的解决方案是直接扩大接收缓存完事,然后就没有然后了.后来深挖了一下细节,发现了很多曾经不知道的东西,如今对TCP的理解想必又深入了一些,趁着国庆假期顺便就把很多想法整理成一篇文章了. 0.network buffer &

Mybatis学习记录(四)--高级查询和缓存

这些都是连贯的学习笔记,所以有的地方因为之前都说过,所以也就没怎么写详细了,看不太明白的可以看看之前的笔记. 一.高级查询 高级查询主要是一对一查询,一对多查询,多对多查询 1.一对一查询 有用户和订单两个表,用户对订单是1对1查询.也就是订单中有一个外键是指向用户的. 先创建实体类: User.java public class User { private int id; private String username; private String password; private St

Mybatis(五) 延迟加载和缓存机制(一级二级缓存)

踏踏实实踏踏实实,开开心心,开心是一天不开心也是一天,路漫漫其修远兮. --WH 一.延迟加载 延迟加载就是懒加载,先去查询主表信息,如果用到从表的数据的话,再去查询从表的信息,也就是如果没用到从表的数据的话,就不查询从表的信息.所以这就是突出了懒这个特点.真是懒啊. Mybatis中resultMap可以实现延迟加载 1.1.查询订单信息,延迟加载用户信息,一对一关系. 1.1.1.开启延迟加载 全局配置文件中,settings标签用来设置全局常量的,这里就用到了. 1 <settings>

MyBatis入门第2天--高级映射与查询缓存

文档版本 开发工具 测试平台 工程名字 日期 作者 备注 V1.0 2016.06.28 lutianfei none mybatis框架执行过程: 1.配置mybatis的配置文件,SqlMapConfig.xml(名称不固定) 2.通过配置文件,加载mybatis运行环境,创建SqlSessionFactory会话工厂 SqlSessionFactory在实际使用时按单例方式. 3.通过SqlSessionFactory创建SqlSession SqlSession是一个面向用户接口(提供操