2.7 控制与数据的结合

发布时间 2023-05-25 09:54:34作者: C~A

理解指针

每一个指针都对应一个类型。这个类型表明该指针指向哪一类对象。如果对象类型为T,那么指针类型为T*,特殊的void *类型代表通用指针。

每一个指针都有一个值。这个值时某个指定类型的对象的地址。

指针用‘&’运算符创建。这个运算符可以应用到任何lvalue类的C表达式上,lvalue意指可以出现在赋值语句左边的表达式。这样的例子包括变量以及结构、联合和数组的元素。我们已经看到,因为leaq指令是设计用来计算内存引用的地址的,&运算符的机器代码实现常常用这条指令来计算表达式的值。

*操作符用于间接引用指针。其结果是一个值,它的类型与该指针的类型一致。间接引用是用内存引用来实现的,要么是存储到一个指定的地址,要么是从指定的地址读取。

数组与指针紧密联系。一个数组的名字可以像一个指针变量一样引用(但是不能修改)。数组引用(例如a[3])与指针运算和间接引用(例如*(a+ 3))有一样的效果。数组引用和指针运算都需要用对象大小对偏移量进行伸缩。当我们写表达式p+i,这里指针p的值为p,得到的地址计算为p+L·i,这里L是与p相关联的数据类型的大小。

将指针从一种类型强制转换成另一种类型,只改变它的类型,而不改变它的值。强制类型转换的一个效果是改变指针运算的伸缩。

指针也可以指向函数。例如,如果我们有一个函数,用下面这个原型定义:

int fun(int x, int *p);

然后,我们可以声明一个指针fp,将它赋值为这个函数,代码如下:

int (*fp)(int, int *);

fp=fun;

然后用这个指针来调用这个函数:

int y=1;

int result =fp(3, &y);

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

内存越界引用与缓冲区溢出

对越界的数组元素的写操作会破坏存储在栈中的状态信息,当程序试图重新加载寄存器或执行ret指令时,就会出现严重的错误。如果访问越界了,返回指针的值以及更多的保存状态就会被破坏,那么程序就可能返回到意想不到的地址。

 对抗缓冲区溢出攻击

1.栈随机化。为了在系统中插入攻击代码,攻击者要插入这段代码的指针,而产生这个指针需要知道这段代码放置的栈地址。栈随机化的思想使得栈的位置在程序每次运行时都有变化。因此,即使许多机器都运行同样代码,他们的栈地址是不同的。实现的方式是,在程序开始时在栈上分配一段随机大小的空间,程序不使用这段空间,但会导致程序每次执行时后续栈位置发生变化。

2.栈破坏检测。思想是在栈帧中任何局部缓冲区与栈状态直接存储一个哨兵值,在程序每次运行时随机产生并标志为可读,在恢复寄存器状态和函数返回之前,程序检查这个哨兵值是否改变,如果改变程序将异常终止。

3.限制可执行代码区域。只有保存编译器产生的代码那部分内存才需要是可执行的,消除攻击者向系统插入可执行代码的能力