本帖最后由 wholesome 于 2020-4-14 00:15 编辑
0x0b溢出实验背后的那些事 溢出实验学习了一段时间,基本原理还是懂了!但是吧,一直让我头疼的就是溢出实验背后的那些专业词:什么ELF、PE,又或者什么可执行文件、目标文件。。。。。。 所以今天打算具体了解,以便在今后的学习中,遇到这些名词不会懵。(反正总是要接触的) 所有程序员最开始接触的第一个代码就是输出hello world!今天我们也依旧以这个例子来探究这背后的奥秘: 接下来我们使用gcc命令编译这个源代码: 可以看见我们已经成功运行。 但是最初写这个代码只是为了打印出字符串,并没有关注这后面的原理。现在已经计算机专业接近两年了,所以有必要提升一下自己,扩展一些知识面。 当我们使用gcc编译我们的源代码时,我们看到的一气呵成其实对于计算机来说,历经了不少的过程:预处理、编译、汇编、链接。 1、预处理 gcc -E hello.c -o hello.i(-E表示只进行预处理) hello.i文件内容很多,在文件的内容最后我们才看见我们写的一些源代码: 那么预处理源代码处理的什么呢? (1)将头文件(#include<stdio.h>)用其内容来替换该头文件名。 不过,有时候我们也会看到使用双引号的头文件#include"stdio.h",那么使用一对尖括号和双引号有什么区别呢? 使用一堆尖括号时,预处理器直接到存放编译器所提供的标准头文件的目录(通常是include子目录)中寻找文件;如果文件名是用一对引号括起来的,则预处理器首先在当前目录中查找文件,如果找不到,再按操作系统的path命令设置的自动搜索路径进行查找,最后才到存放编译器所提供的标准头文件的目录中查找。 (2)去掉“//”和“/* */”的注释 (3)将所有“#define”删除,并且展开所有的宏定义。 (4)处理“#if”、“#ifdef”、“#elif”、“#else”、“#endif”等条件预编译指令。 2、编译:将预处理完的hello.i文件经过一系列词法分析、语法分析、语义分析及优化后生成的相应汇编文件。 gcc -S hello.i -o hello.s 这个时候我们似乎能看懂一些了,里面有许多的汇编指令。 所以,编译这个过程简言之就是检查语法,生成汇编。 3、汇编 (最开始蛮疑惑的,上一个步骤不是已经生成汇编了吗) 原来此处的汇编过程是将hello.s生成机器可以执行的指令,每一个汇编语句都对应着一条机器指令。这就是我们之前提取shellcode机器码时所看到的。 gcc -c hello.s -o hello.o 当然我们也可以直接从源代码输出hello.o: gcc -c hello.c -o hello.o 乱码了。。。。。。
到目前为止,我们经历了预处理、编译、汇编三个过程,此时我们得到的hello.o就是目标文件。 4、链接 将目标文件与库函数进行链接后,我们得到的就是可执行文件。 链接又可分为静态链接与动态链接 (1)静态链接是指把要调用的函数或者过程链接到可执行文件中,成为可执行文件的一部分。程序运行的时候就可以直接使用,以便达到执行速度快的优点。但是静态链接会占用很大的空间。(在gcc溢出实验编译源文件时,我们会使用-static参数确保可执行文件是静态链接) (2)动态链接则是将所调用函数的描述信息(往往是一些重定位信息)拷贝到应用程序的可执行文件中去。所以只有当我们的应用程序被装入内存开始运行时,才在应用程序与相应的DLL之间建立链接关系。即,可执行文件会根据所要执行的DLL中的函数时以及链接产生的重定位信息,Windows才转去执行DLL中相应的函数代码。 ---------------------------- 上面提到目标文件和可执行文件,那么我们又来围绕这两个展开讨论。 在没有经过链接的文件称为目标文件,一旦链接就是可执行文件了。 其实,这两者的格式是很类似的,即目标文件的存储结构是按照可执行文件的格式存储的,只是没有经过链接,一些符号和地址也就没有进行调整。 在本文最开始的hello.c文件中,操作环境是在Linux平台,此时的可执行文件是ELF文件格式: 在windows平台下,可执行文件就是PE文件格式。 那么可执行文件(目标文件)的格式是什么呢? 首先明确,可执行文件(目标文件)是以不同“节”的形式存储不同信息,有时候成为“段” 对上面文件稍作修改: 上图大致表示了可执行文件(目标文件)存在的主要几个段段,不过存储顺序是: file_header:包含整个文件的文件属性。 .text .data .bss(不占磁盘空间) 其中,值得一提的是,当我们把源文件编译成目标文件后,又习惯分成两种段:程序指令(.text)和程序数据(.data /.bss)。为什么这么做呢?主要有以下三点: (1)在于赋予程序指令段的权限是可读写的,而程序数据段是只读的,这样起到了一定的程序安全(在缓冲区溢出实验深有体会) (2)提高对CPU的缓存命中率 (3)节省内存 (不过最后一点挺复杂的,有兴趣的自己百度哟) 在Linux里面我们可以使用objdump命令查看目标文件内容: 重新编译(不链接)一下(-m32表示编译成32位,-c只编译不链接): gcc -c -m32 hello.c -o hello.o 然后再使用objdump工具查看目标文件的结构和内容 objdump -h hello.o(-h表示将ELF文件的各个段的基本信息打印出来) 从上面可以捕获到的信息是段的长度和段所在的位置。 我们也可以使用size获取长度: size hello.o 其中dec表示三个段总长度(十进制),而hex是十六进制。 此外,我们也可以使用readelf 命令来查看ELF: readelf -h hello.o
参考链接: https://blog.csdn.net/weixin_41143631/article/details/81221777 https://www.cnblogs.com/cyyljw/p/10949660.html 《程序员的自我修养》 |