深入理解计算机系统——第三章—3.10在机器级程序中将控制与数据结合起来

3.10 在机器级程序中将控制与数据结合起来

3.10.1 理解指针

指针以一种统一方式,对不同数据结构中的元素产生引用。
1) 每个指针都对应一个类型。(指针类型不是机器代码中的一部分;只是C语言提供的一种抽象,帮助程序员避免寻址错误。)
2) 每个指针都有一个值。这个值是某个指定类型的对象的地址。特殊的 NULL(0)值代表该指针没有指向任何地方。
3) 指针用‘&’运算符创建
4) 操作符是用来间接引用指针
5) 数组与指针紧密联系。一个数组的名字可以像指针变量一样引用(但是不能修改)。如,a[3]等价于*(a+3)
数组引用和指针运算都需要用对象大小对偏移量进行伸缩。
6) 将指针从一种类型强制转换成另一种类型,只改变它的类型,而不改变它的值。强制类型转换的一个效果是改变指针运算的伸缩。
例如:

char *p = 'c' ;
(int *)p + 7 的结果为 p + 28;
(int *)(p + 7) 的结果为 p + 7。

(强制类型转换的优先级高于加法)
7) 指针也可以指向函数。这提供了一个很强大的存储和像代码传递引用的功能,这些引用可以被程序的某个其他部分调用。
例如:

int fun(int x, int *p);
/** 声明指针 fp ,将它赋值为这个函数 */
int (*fp)(int, int *);
fp = fun;
/** 函数调用 */
int y = 1;
int result = fp(3, &y);

函数指针的值是该函数机器代码表示中第一条指令的地址。

3.10.2 应用:使用 GDB 调试器

先运行OBJDUMP 来获得程序的反汇编版本。如下命令行来启动 GDB:
linux> gdb prog
通常的方法是在程序感兴趣的地方附近设置断点。

命令 效果
quit 退出GDB
run 运行程序(在此给出命令行参数)
kill 停止程序
break sth 在函数sth入口处设置断点
break *0x400540 在地址0x400540处设置断点
delete 1 删除断点1
delete 删除所有断点
stepi 执行1条指令
stepi 4 执行4条指令
nexti 类似stepi,但以函数调用为单位
continue 继续执行
finish 运行到当前函数返回
disas sth 反汇编函数sth
disas 0x400540 反汇编0x400540附近的函数
disas 0x400540.0x40054d 反汇编指定范围内的代码
print /x $rip 以十六进制输出程序计数器(%rip)的值
print $rip 以十进制输出程序计数器(%rip)的值
print /t $rip 以二进制输出程序计数器(%rip)的值
print 0x100 输出0x100的十进制
print /x 555 输出555的十六进制
print /x ($rsp + 8) 以十六进制输出%rsp + 8
print *(long *) 0x7fff ffff e818 输出位于地址0x7fff ffff e818的长整数
print *(long *) (%rsp + 8) 输出位于地址%rsp + 8的长整数
x/2g 0x7fffffffe818 检查从地址0x7fffffffe818开始的双字(8字节)
x/20b sth 检查函数sth的前20个字节
info frame 有当前栈帧的信息
info registers 所有寄存器的值
help 获取有关GDB的信息

339  GDBGDB\blue {图3-39} \; GDB命令示例。说明了一些GDB支持机器级程序调试的方式

3.10.3 内存越界引用和缓冲区溢出

缓冲区溢出(buffer overflow。在栈中分配某个字符数组来保存字符串,但是字符串的长度超出了为数组分配的空间。
示例:
这里写图片描述
echo对应的汇编:
这里写图片描述
越界会破坏的信息:
这里写图片描述
这里写图片描述
缓冲区溢出的一个更加致命的使用就是让程序执行它本来不愿意执行的函数。这是一种最常见的通过计算机网络攻击系统安全的方法。

3.10.4 对抗缓冲区溢出攻击

1. 栈随机化

为了在系统中插入攻击代码,攻击者既要插入代码,也要插入指向这段代码的指针,这个指针也是攻击字符串的一部分。产生这个指针需要知道这个字符串放置的栈地址。
栈随机化的思想使得栈的位置在程序每次运行时都有变化。这类技术称为地址空间布局随机化Address-Space Layout Randomization),简称ASLR
通常攻击者使用空操作雪橇(nop sled,使程序”滑过“目标序列,即在实际攻击代码前插入一段很长的nop(读作“no op”no operation的缩写)指令。
示例:
nm0,  2mn \geq m \geq 0,\; 2^m 个字节的nop sled能破解2n2^n的栈随机化需要枚举 2nm2^{n-m} 次。

2. 栈破坏检测

计算机的第二道防线是能够检测到何时栈已经被破坏。
GCC提供一种栈保护者机制,来检测缓冲区越界。其思想是在栈帧中任何局部缓冲区与栈状态之间存储一个特殊的金丝雀(canary,也称为哨兵值(guard value,是在程序每次运行时随机产生的。
在这里插入图片描述
echo函数示例:
这里写图片描述
在这里插入图片描述
在这里插入图片描述
程序第三行通过段地址%fs:40从内存读入金丝雀的值,保存在栈中;
程序第十一行取出该值与原地址的值作比较,不相等则栈异常。
tips:
容易越界的参数尽可能放置在栈底,以保护其他参数。
示例:
在这里插入图片描述
在这里插入图片描述
图(b)中,参数v比数组参数buf更靠近栈顶,vbuf缓冲越界不会破坏v

3. 限制可执行代码区域

最后一招是消除攻击者向系统插入可执行代码的能力。
随机化、栈保护和限制哪部分内存可以存储可执行代码——是用于最小化程序缓冲区溢出攻击漏洞三种最常见的机制。

3.10.5 支持变长栈帧

前面所讲的各种函数的机器级代码,都有一个共同点,即编译器能够预先确定需要为栈帧分配多少空间。
下面示例为局部存储是变长的。
这里写图片描述
这里写图片描述
%rbp称为帧指针(frame pointer)有时称为基址帧(base pointer);
leave指令将栈帧指针恢复到它之前的值(第20行)。等价于:

movq %rbp, %rsp    ;Set stack pointer to begining of frame
popq %rbp          ;Restore saved %rbp and set stack ptr to end of caller's frame

重点习题:
在这里插入图片描述
答案:
在这里插入图片描述

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章