Lecture 09 Machine-Level Programming V:Advanced Topics
Memory Layout

这个内存结构 后面还会学 -- virtual memory address
我们的64位机器 按说能处理64位 但从这个内存layout能看出来 0x0007FFFFFFFFFFF实际上 可以用的只有47位2进制位
但事实上 仅仅是47位 我们也没有这么大的DRAM
栈:在最顶部 从高地址 向 低地址 增长
大多数系统的栈的大小是8MB
在Linux系统上可以通过limit指令来查看

能看到stack size = 8192kb 也就是8mb
这意味着如果使用一个大于8MB的栈指针去访问内存 -- 会产生 segmentation fault 段错误
Test区 往下更下放的地方是程序存放代码的地方 来自于可执行文件 在将Linking会用到这一部分 这个存放可执行程序的区域 叫做 text segment 文本段
Data区 是 存放程序开始时分配的数据的地方 声明的全局变量都在这个数据段中
Heap 用来存放malloc或相关的函数申请的变量 这些变量在程序运行的时候会动态变化 它从一个很小的分配开始 如果每次都通过malloc申请小空间但没有释放 就会向着高地址一直增加
Shared Libraries 也是存放代码的 不过存放的是类似printf 和 malloc 这样的库函数 -- Linking
example


通过反汇编 gdb 查看地址
能发现 小的数组空间分配在了 粉色Data区域这一侧 而 大的数组分配在了Stack限制的地方 如果继续malloc 往中间增长 -- 如果没有空间了malloc会返回0
但是不知道为什么是往中间增长 可能是linux的优化?
Buffer Overflow
缓冲区溢出

当i超过2的时候就会污染d的值 再增长会段错误
在存储字符串的时候 可能会出现很多有关缓冲区溢出的问题
因为我们可能不会事先知道字符串多大 -- 会开很大的空间
引发这个问题的罪魁祸首之一 是 那些存储字符串但不检查边界情况的库函数
例子之一就是gets函数 它做的其实是扫描输入的字符串并寻找代表一行结束的字符

如果我传入的指针是已经分配好大小的缓冲区指针 但是字符串中没有遇到EOF或者\n 就会一直持续下去 就会造成缓冲区溢出
所以现在的编译器会警告你 这是一个不安全的函数 这个函数写于70年代 那个时候还不考虑安全问题
同样的函数还有strcpy函数 两个参数 源地址和目标地址 但是函数复制过程中不知道目标地址的缓冲区有多大
scanf也有这个问题 传递%s 参数 他会读取一个字符串并存储在某个地方 但是它既不知道传递的字符串多长 也不知道目标缓冲区多大
example

23位 和 24位 怎么来的?
从汇编中能看出来

在汇编中能看出来为缓冲区分配了多少空间 -- 0x18 -- 24位
最后一位需要时/0
记住下面call_echo里面标红的这个地址 -- 这是调用返回的地址

23位



是从rsp指针倒着存回去的 30是0的ASCII码
字符串 最后一个 是/0 -- 是null ASCII码为0 刚好占了缓冲区的最后一位
25位的时候

当超过23位的时候 其实就开始破坏返回地址了
24位的时候

破坏地址了 并且调到了一个奇怪的地方哪里 但其实有时候会崩溃 有时候不会崩溃

在我的电脑上事实上是分配了0x10个空间 是 16个字符
但我输到第12位就挂了 不知道为啥
我不知道gcc运行的哪种优化 我干脆直接下载了他的那个文件
然后objdump了


在24位的时候溢出了
Code Injection Attacks

代码注入攻击
就是用任何东西可以填充缓冲区 简单来说 就是 填充的东西是一些代码 并且填充到改变返回地址 -- 本来要返回到P函数的 然后返回到了 填充进去的那些东西的地址 而刚刚好是个代码或可执行文件 -- 就会运行别的东西
怎么免受攻击?
使用更安全的方式来编写代码 比如用fgets来代替gets
fgets有一个参数指示的就是程序最多读取多少字节
除此之外

Randomized stack offsets
栈随机化 地址空间随机化
每次程序运行的时候 它的地址都是变化的
随机分配一块空间 -- 每次程序运行的随机变量是不一样的
每次分配的空间的大小也可能不一样
这里有一个例子

这个例子中有全局变量 有malloc分配到堆区上的变量 有栈上的局部变量 code区上的unless

有些地址不变 有些地址在变化
global变量的地址不变
呃呃我的为什么都在变?
在课程中 global和code 地址是不变的
Stack Canaries
系统会以栈溢出的错误返回 上面的那个例子
gcc Implementation: -fstack-protector


在8位的地方设置了一个值 是从一个特殊的寄存器拿出来的
然后系统做的就是从Canary那里取出值--然后和寄存器里比较 来判断是否溢出
Unions
Union -- 联合体 声明跟 struct很像
但实际上完全不同
Union是使用占用空间最大的域的大小来分配内存
只会使用多值中的一个
