PE-栈与重定位表

栈与重定位表

栈描述的是代码运行过程中,操作系统为调度程序之间相互调用关系,或临时存放操 作数而设置的一种数据结构。重定位表则是在一个PE中的代码被加载到任意一个内存地址 时,用来描述相关操作数地址的变化规律的数据结构。通过重定位技术,代码运行在内存的任意位置时,可以避免因操作数的定位错误而导致失败。

4.1 栈

栈是在程序运行时,操作系 统为调度程序之间相互调用关系或临时存放操作数而设置的一种数据结构。事实上,栈就是 内存的一块域。因为在这块区域中存取数据遵循一定的规则,所以就叫做数据结构。
栈遵循的规则是“先进后出”。可以简单地把栈理解为一个有底的容器,先放进去的东西自然被压在最底下,那么后放进去的东西一定是先被取出。
程序在运行的时候会为系统分配一块内存区作为栈,由栈选择子ss和栈顶指针(esp)来 确定当前栈的大小,CPU则直接操作ebp来存取数据。
压栈、入栈

4.1.1 栈的应用场合

操作系统通过修改esp指令来完成对栈中数据的压栈与出栈。以下场合会用到栈:
1.保存临时的值
2.保存程序现场
3.传递函数参数
4.存放过程中的局部变量

1.保存临时的值

栈可用于在程序中暂时保存寄存器的值,完成某些操作后再恢复
栈可以实现对寄存器的赋值

2.保存程序现场

在调用子程序时,栈可用于保存当前现场。
当指令执行时,会将紧跟在CALL指令后面的下一条指令的地址压入栈,以便于程序在 调用完以后,能正确返回到主程序继续运行。

3.传递函数参数

当调用某个函数的时候,可以使用栈传递参数。

4.存放过程中的局部变量

进入一个过程以后,会定义很多局部变量,而局部变量的存放处也是栈。为局部变量在 栈中申请的内存区域称为缓冲区。当过程结束以后,局部变量将从栈中删除,恢复到进入过程的最初状态。也就是说,局部变量在过程结束以后就自动被释放了,原因是CPU调整了栈的栈顶指针esp。

扫描二维码关注公众号,回复: 14609228 查看本文章

4.1.2 call调用中的栈实例分析

步骤1:将调用函数用到的参数入栈。
步骤2:将call指令的下一条指令入栈,以备返回。
步骤3:保存原始ebp指针。
步骤4:为函数准备栈空间,ebp指向栈中该函数的基地址。
步骤5:为函数中定义的局部变量开辟栈空间(通过调整esp来完成)。
步骤6:运行函数中定义的语句。
步骤7:清理局部变量,恢复原始的ebp指针(通过leave指令完成)。
步骤8:返回步骤2保存的下一条指令处执行,同时清理栈中的函数参数(通过指令retn 8完成)。

栈是从高地址向低地址增长的。

函数一旦被调用,运行开始前应该先将原始的ebp指针压入栈,然后将压入ebp以后 的当前栈顶指针给ebp,这样就等于为局部变量的缓冲区确定了基地址。局部变量由此处开始压入和弹出。

在这里插入图片描述

在这样的栈状态中,被调用的函数开始工作。主要有两个操作:

第一:以esp-x来扩充缓冲区长度,以esp+x来收缩缓冲区长度。
第二:以ebp-x (或ebp+x)来存取局部变量(或参数)的值。

4.1.3 栈溢出

所谓栈溢出,是指由于程序没有考虑栈中定义的局部数据块的大小,而向该数据块写入了过多的数据,从而导致数据越界,覆盖了栈中的已存在的其他数据的技术。以前,认为栈溢出是程序设计错误,由于大家用得多了,便成为了技术。

4.2 代码重定位

代码重定位是把可执行代码从内存的一个地方移动到另外一个地方去,保证该部分代码还能正常执行的一种技术。

4.2.1 重定位的提出

对执行代码从内存的一个地方移动到另外一个地方,所有的字节码均保持不变;如果代 码指令中的某些操作数不跟随若地址发生改变,势必会导致程序运行出错。这里的某些操作数是指那些使用了绝对地址定位的程序指令中的操作数。

全局变量的地址包含在机器码中,而操作数 为局部变量或函数参数的指令中均不包含绝对地址。

如果在代码编写中使用重定位技术,你就可以将代码随意地部署到内存中,而不影响程序的运行。

4.2.2 实现代码重定位的方法

代码重定位的关键问题是代码中使用了大量的绝对地址。

对代码重新定位,就可以实现在一个内存进程中合理地插入附加代码,实现代码的有效 拼接,而不会因为插入导致数据或代码移位而造成内存访问错误。
同时,代码的重定位也可以帮助我们将自己设计的具有独立功能模块的代码,注册到另 外一个运行的程序内存空间,并作为这个运行的程序的一个子线程,来达到隐藏自己的目的。 通过这种方法运行的程序,别人无法使用任务管理器査看到它,也无法使用第三方的工具找到它。他们所能了解到的只是正常程序多了一个线程,多占用了内存,仅此而已。

4.2.3 重定位编程

https://www.pianshen.com/article/5691728749/

4.3 PE头文件中的重定位表

为了方便开发人员编程,通常允许代码中存在重定位信息。重定位信息是在编译的时候,由 编译器生成并被保留在可执行文件屮。当程序执行前,操作系统会根据这些重定位信息对代码予以修正,复杂的操作由编译器和操作系统代替程序完成。开发人员在编写程序的时候就 可以随意地使用那些涉及直接寻址的指令了。

4.3.1 重定位表定位

重定位表为数据目录中注册的数据类型之一,其描述信息处于数据目录的第6个目录项中。

重定位表所在地址
重定位表数据大小

4.3.2 重定位表项 IMAGE_BASE_RELOCATION

与导入表类似,重定位表指针指向的位置是一个数组,而不像导出表一样只有一个结构。
这个数组的每一项都是如下结构:

 IMAGE_BASE_RELOCATION STRUCT 
           Virtual Address //dd  重定位内存页的起始RVA
           SizeOfBlock	  //dd  重定位块的长度
 IMAGE_BASE_RELOCATION ENDS

71.IMAGE_BASE_RELOCATION.VirtualAddress
+0000h,双字。重定位块RVA。由于直接寻址指令较多,所以在一些PE文件中,存在 大量的需要修正的重定位地址。按照常规计算,每个地址占4字节,如果有n个重定位项, 那么需要总的空间为4n字节。重新审视直接寻址中的地址发现,在一页中的所有地址只需要12位因为Win32页面大小为1000h,也就是4096字节,即2的12次方。而这12位只需要用一个字就能表达出来。如果有n个重定位项,则只需要2&n个地址4字节的页面起 始RVA+4字节的本页的重定位项个数。将以上两种情况的表达式分别是:
Sum0=4n
Sum1=2n+4+4

很明显,当有大量的重定位地址时,Sum0远大于Sum1。事实上,为了节约存储空间,重定位表的存储方式选择第二种方式。字段IMAGE_BASE_RELOCATION.VirtualAddress就 是表达式Sum1中的第一个4,也就是页面的起始RVA。

72.IMAGE_BASE_RELOCATION.SizeOfBlock
+0004h,双字。重定位块中重定位表项数量。该字段是表达式Sum1里的第二个4,描述 的是该页面中所有的重定位表的项数。
数组和数组之间并不是相邻的。
在这里插入图片描述

在实际的PE文件中,我们只能看到0和3这两种情况,也就是说这一项要么是对齐用的,要么是需要全部修正的。

4.3.3 重定位表的结构

所有的重定位块最终以一个VirtualAddress字段为0的IMAGE_BASE_RELOCATION结构作为结束。

现在可以解释:为什么我们看到的PE文件的可执行代码一般都是从1000h开始, 而不是从PE的基地址开始了。如果从PE的基地址开始,也就是页面的起始地址为0000h, 在定义重定位表块时第一个VirtualAddress的值就是0 ;按照对重定位块的定义,到了这里就是所有重定位项的结束。这个解释和事实有出入,所以,在装载可执行代码时都是从1000h开始。
在这里插入图片描述

猜你喜欢

转载自blog.csdn.net/weixin_51732593/article/details/122023016