读《Professional Assembly Language》之栈随机化
今年是离开校园的第五年,这五年来我一直在从事应用软件的设计、开发工作,大部分时间是在与高级编程语言、设计模式、业务逻辑打交道。它们大多流于表面,久而久之,与技术底层疏远了,诸如计算机组成原理、汇编语言、编译原理、数据结构以及算法慢慢得生疏,时至今日,我已经弄不懂IA-32里有几类寄存器、间接寻址、词法分析是怎么一回事情了。五年是一个契机,趁着下一个五年开始之际,我计划用三个月至半年时想间,重新学习这些知识,以期达到巩固基础,厚积薄发的目的。
学习过程肯定会有问题,找不出问题的学生不是好学生。因此,我把遇到的问题和解决的方法记录下来,便形成了读书笔记。本篇博文便是我在阅读《Professional Assembly Language》一书时,所作的其中一篇读书笔记。《Professional Assembly Language》,中文译作《汇编语言程序设计》,是我学习汇编语言时选择的工具书,该书对于我这种已经有了高级语言的使用经验,又热衷Linux的人来说非常合适。
本书的第十一章是讲解函数的使用,特别是C风格的函数调用,也就是栈(Stack)在函数调用中的用场。在章节的末尾,讲到了命令行参数,其中涵盖了Linux是如何安排程序的内存空间的。
正如文中所言,每个程序可使用的内存空间起止地址均相同,–当然,这只是Linux玩弄的一个小把戏:虚拟地址空间。
The virtual memory address assigned to the program running in Linux starts at address 0x8048000 and end at address 0xbffffff.
上面这段话,把起止地址说明白了,如图1所示,大致分为两块,低位的块是保存代码和数据,高位的块是程序栈,ESP寄存器保存栈顶地址。
基于上面的文字,很自然,我会想到ESP的值是从0xbffffff开始递减的。但是,事实上并不如此:
The second block in the memory area is the program stack. As mentioned in Chapter 2, “The IA-32 Platform”, the stack grows downward from the end of the memory area. Given this information, you would expect the stack pointer to be set to 0xbffffff each time program starts, but this is not the case. Before the program loads, Linux places a few things into the stack, which is where command-line parameters come in.
依照本章讲解的程序栈结构,栈中要保存环境变量、命令行参数、程序名称等内容,如图2所示。因此,每个程序运行后的ESP寄存器内容应该是不同的。那么问题是,同一个程序多次运行,那么ESP寄存器内容是不是相同的呢?
试验证明,同一个程序每次运行中,ESP寄存器内容亦不相同。这又是为什么呢?了解缓冲区溢出的程序员,应该就能想到问题的答案。
这应该归因于栈随机化,那如何理解栈随机化?在《深入理解计算机系统》一书的第三章,可以找到如下解释:
栈随机化的思想使得栈的位置在程序每次运行时都有变化。因此,即使许多及其都运行同样的代码,它们的栈地址都是不同的。实现的方式是:程序开始时,在栈上分配一段0~n字节之间的随机大小的空间。
上面提到的“随机大小的空间”,程序自身并不使用,但它的作用就是每次执行时后续的栈位置就发生变化,也就是ESP寄存器内容不同。