一.几个关于指针的小知识点:

1.  malloc是在堆上动态分配内存,返回的是void *,使用时会配合显式/隐式类型转换,用完后需要用free手动释放。

alloca是标准库函数,可以在栈上分配任意字节数量的内存,用完自动释放。

2.指针的优先级较低:

  char (*p)[3],括号中优先级最高,所以p是一个指针,指向一个3个元素的char数组。

  char *p[3],  因为指针优先级较低,所以*与char结合,p代表一个3个元素的数组,每个元素都是一个char *。

3.函数指针:

它的语法类似指向数组的指针,char (*f)(int *),同之前的分析,*f代表f是一个指针,比起char (*p)[3],此时后面的是(int *),所以此指针指向的是一个以int*为参数的函数,返回值类型是char。

  若改成 char *f(int *),则f不再是一个指向函数的指针,而是一个以char*为返回值,参数为int*的函数。

二.缓冲区溢出

先看个例子:

      

左边为c代码,右边为汇编代码,c代码中我们为char数组buf设置了8个字节的缓冲区,虽然在汇编代码中为它在栈上分配了24个字节(还记得吗,在3.7那里讲过什么时候要压栈,这里属于第二种情况,当被保存的局部变量需要被访问到地址时,比如这里的数组,要把这个局部变量压栈,而不能仅仅用寄存器传递),剩下16个字节是不被使用的,而在gets中,由于未指定目标缓冲区大小,当输入超过8个字符时,显然会破坏后续的栈空间,在23个之前不会有太大影响,而一旦超过23个,会破坏返回地址的值,若超过32个字节,会破坏调用函数(即echo)里,调用caller之前存的值((还记得吗,3.7中讲过,返回地址是在存完多余参数,局部变量,被调用者保存寄存器后,由call存入的,可以参考书中3.7节的图3-29来理解)。

具体情况如下图:

给一道很有意思的例题:

这道题里有很多值得注意的点,首先,这儿的返回地址是调用get_line的那个函数压入的,跳转到get_line函数后,get_line先把%rbx压入了栈,这可能是因为后面get_line函数用到%rbx寄存器(还记得3.7讲的被调用者保存寄存器吗,%rbx是其中之一),所以要先压栈保存当前值,此时第二行是01 23 45 67 89 AB CD EF,随后要为局部变量buf数组分配栈空间,分配了0x10=16个字节,所以第4行是%rsp的位置,要注意的是,调用gets后,存入输入的字符串的顺序是先存到%rsp,再到1(%rsp),一直延续的,所以第4行对应存前8个字符,且从后向前存,结果是: 37 36 35 34 33 32 31 30,同理,第三行结果是: 35 34 33 32 31 30 39 38,再向上存,第二行保存的%rbx值被覆盖为: 33 32 31 30 39 38 37 36,最后仍剩2个字符,4和空,所以返回地址对应的第一行会被覆盖为: 00 00 00 00 00 40 00 34。

最后,E题,对malloc的调用应该是malloc(strlen(buf)+1),因为strlen计算长度时并未包含空字符,而且malloc后未检查返回值是否为NULL。

根据上述内容,可以看出缓冲区溢出攻击的原理:在给程序输入的字符串中包含了一些恶意的攻击代码,同时会用一个指向攻击代码的指针覆盖掉返回地址,这样,函数返回时会“迷路”,转而去执行攻击代码。

对抗这种攻击有几种常用方法:

1.栈随机化,即程序开始时,在栈上随机分配一段0-n字节间的随机大小的空间(可用alloca实现),程序不使用这段空间,这样,通过浪费一段空间,可以使程序每次执行时后续的栈位置发生变化。然而这种方式仍有着被破解的可能,攻击者可以在攻击代码前放许多nop指令,这些指令唯一的作用就是指向下一条指令,假设本来栈随机化后栈空间地址的变化范围达到了223个字节,本来要精确地将返回地址改到攻击代码入口的对应的地址需要“精确投放”,即要尝试枚举223种可能,现在攻击代码加上这一堆nop指令,假设达到了28=256个字节,代表只要返回地址指向这些指令中的任何一条,都会导致最后进入攻击代码,因此只要枚举215种可能就行了,因此栈随机化不大保险。

2.栈破坏检测,基本思路是在栈的局部缓冲区插入一个哨兵值,它在程序每次运行时随机产生(比如可以从内存中某个地方取得),在函数返回以及恢复寄存器的值之前,程序会先检测哨兵值是否被改变,若改变了则程序异常终止。

3.限制可执行代码区域,即限制只有保存编译器产生的代码的那部分内存才是可执行的,其他内存区域被限制为只允许读和写。

三.变长栈帧

当声明一个局部变长数组时,编译器无法一开始就确定栈帧的大小,要为之分配多少内存空间,因此需要用变长栈帧。

下面看一个实例,比较难:

左侧两个是相应的C和汇编代码,程序中声明的long *数组,其所占空间大小由传入的n决定,所以栈上至少需要8n个字节容纳这个数组,另外局部变量i由于用到了&i,也要存到栈上。首先%rbp是被调用者保存寄存器,要把%rbp原先的值存入栈,%rbp用来存初始的栈顶位置,它被称为“帧指针”。为什么要特意用一个被调用者保存寄存器来存初始的栈顶位置呢?是因为,在for循环中,i是不停变化的,因此局部变量i在被存到栈中后不能不管它,要在循环过程中修改它的值,而右图中可看到,i存放在初始栈顶向下的8个字节中,因此需要记录下初始栈顶位置以方便定位到i。第4行代码在栈上分配16个字节,其中8个存i,8个没用到。第5行计算得到8n+22,第6行andq $-16,%rax,注意-16补码是0x1111111111111110,即将%rax最后4位置0,也就是把8n+22向下舍入最接近的16的倍数,n是奇数时,结果是8n+8,n是偶数时,结果是8n+16,第7行开辟相应大小的栈空间,开辟后栈指针%rsp的地址由右图中的s1变到下方的s2。第8-10行计算(s2+7)/8 * 8,代表将s2向上舍入到最近的8的倍数,即对应右图中的p。注意左图显示的只是部分汇编代码,它们不是连续的,第11行和12行间有省略号,这里做了一些事情,比如注释中的 i in %rax,在上面的代码中没有操作过,因此这部分操作是被略过去的。13到19行用来完成for循环,没什么可说的。20行的leave指令,等价于 movq %rbp, %rsp  popq %rbp,即恢复寄存器%rbp的值,且栈被释放,恢复回之前的值。

完毕。

CSAPP阅读笔记-变长栈帧,缓冲区溢出攻击-来自第三章3.10的笔记-P192-P204的更多相关文章

  1. CSAPP阅读笔记-栈帧-来自第三章3.7的笔记-P164-P176

    1.基本结构: 如上图所示,是通用的栈帧结构.大致分两块,调用者函数P和被调用者函数Q. 对P来说,要做的工作是把传递参数中多于6个的部分压栈,随后把Q返回时要执行的下一条指令的地址压栈. 对Q来说, ...

  2. CSAPP阅读笔记-汇编语言初探(控制类指令)-来自第三章3.6的笔记-P135-P163

    1.正溢出与负溢出: 首先,一个正数与一个负数相加,不可能溢出,因为结果的绝对值一定小于两个加数的绝对值,既然两个加数能合理表示出来,结果一定也能合理表示出来. 其次,正溢出是由于两个很大的正数相加, ...

  3. CSAPP阅读笔记-gcc常用参数初探-来自第三章3.2的笔记-P113

    gcc是一种C编译器,这次我们根据书上的代码尝试着使用它. 使用之前,先补充前置知识.编译器将源代码转换为可执行代码的流程:首先,预处理器对源代码进行处理,将#define指定的宏进行替换,将#inc ...

  4. CSAPP阅读笔记-struct, union, 数据对齐-来自第三章3.9的笔记-P183-P191

    1.数据对齐 为什么要对齐:通俗点解释就是CPU对数据访问时,每次都是取固定数量的字节数,假如一次取4个字节,若有个int存在0x01-0x04,则一次就能取出,若存在0x03-0x06,则需要分两次 ...

  5. CSAPP阅读笔记-数组分配与访问-来自第三章3.8的笔记-P176-P183

    这一节比较简单,仅记录几个比较重要的点: 1.C语言允许对指针进行运算,计算出的值会根据该指针引用的数据类型大小进行伸缩. 例子: 其中,xE是数组的起始地址.注意,指针运算时,若最终结果为指针,则指 ...

  6. CSAPP阅读笔记-汇编语言初探(算术和逻辑操作类指令)-来自第三章3.5的笔记-P128-P135

    1.算术和逻辑操作类指令分四类:加载有效地址,一元操作,二元操作和移位,如下: 2. leaq指令,类似mov指令,它左侧的数看似是给出一个地址,在内存中从给定的地址取操作数,传给右边的目的地.但其实 ...

  7. CSAPP缓冲区溢出攻击实验(上)

    CSAPP缓冲区溢出攻击实验(上) 下载实验工具.最新的讲义在这. 网上能找到的实验材料有些旧了,有的地方跟最新的handout对不上.只是没有关系,大体上仅仅是程序名(sendstring)或者參数 ...

  8. CSAPP缓冲区溢出攻击实验(下)

    CSAPP缓冲区溢出攻击实验(下) 3.3 Level 2: 爆竹 实验要求 这一个Level的难度陡然提升,我们要让getbuf()返回到bang()而非test(),并且在执行bang()之前将g ...

  9. CSAPP:逆向工程【缓冲区溢出攻击】

    逆向工程[缓冲区溢出攻击] 任务描述 掌握函数调用时的栈帧结构,利用输入缓冲区的溢出漏洞,将攻击代码嵌入当前程序的栈帧中,使程序执行我们所期望的过程. 主要方法 溢出的字符将覆盖栈帧上的数据,会覆盖程 ...

随机推荐

  1. JavaEE互联网轻量级框架整合开发(书籍)阅读笔记(9):通过XML装配Bean

    一.通过XML装配Bean 装配简易值 装配集合 命名空间装配(暂不测试) 二.测试例子 创建一个用户类:UserBean.java package com.xfwl.spring.assem; /* ...

  2. 设计模式11---组合模式(Composite Pattern)

    一.组合模式定义 将对象组合成树形结构以表示“部分-整体”的层次结构,使得用户对单个对象和组合对象的使用具有一致性.Compose objects into tree structures to re ...

  3. XJOI 3605 考完吃糖(DAG图dfs)

    题目描述: 期末考试考完了,分数也出来了,大家准备吃糖庆祝一下,为了鼓励同学们下学期能取得更好的成绩,司马红豆同学让n个同学站成一排,如果某个同学的分数比相邻的一个同学要高,那么他得到的糖果就会比这个 ...

  4. BZOJ 1878 hh的项链(简单莫队)

    Description HH有一串由各种漂亮的贝壳组成的项链.HH相信不同的贝壳会带来好运,所以每次散步 完后,他都会随意取出一 段贝壳,思考它们所表达的含义.HH不断地收集新的贝壳,因此他的项链变得 ...

  5. Java == 和 equals 比较

    在初学Java时,可能会经常碰到下面的代码: 1 String str1 = new String("hello"); 2 String str2 = new String(&qu ...

  6. [转]一次Delete&Insert引发的Mysql死锁

    近日遇到一个比较奇怪的deadlock错误, 错误详情: Deadlock found when trying to get lock; try restarting transaction; nes ...

  7. Spring MVC 基本配制

    WEB.XML 文件中的配制: <?xml version="1.0" encoding="UTF-8"?> <web-app id=&quo ...

  8. Re:从零开始的Spring Security Oauth2(一)

    前言 今天来聊聊一个接口对接的场景,A厂家有一套HTTP接口需要提供给B厂家使用,由于是外网环境,所以需要有一套安全机制保障,这个时候oauth2就可以作为一个方案. 关于oauth2,其实是一个规范 ...

  9. c# 委托与事件的区别

    委托与事件的区别 委托和事件没有可比性,因为委托是数据类型,事件是对象(可以理解为对委托变量的封装.),下面说的是委托的对象(用委托方式实现的事件)和(标准的event方式实现)事件的区别.事件的内部 ...

  10. BZOJ 1562 [NOI2009] 变换序列

    [NOI2009] 变换序列 [题解] 就是有一个序列,每个位置可以填两个数,不可重复,问最小字典序. 显然,可以建一个二分图,判合法就是找完美匹配. 那怎么弄最小字典序呢?有好多种解法,我这里给出了 ...