溢出从懵逼到懵逼的二次方-第一节

yushengji54   ·   发表于 2020-06-21 22:20:59   ·   技术文章

1.1 前言

镜哥坐在我身边,天天汗流浃背的钻研技术,我觉得崇拜的不行,于是和镜哥约定一起钻研二进制方面的东西,共同走向人生巅峰。

介绍的内容基本都来自于《0day安全:软件漏洞分析技术》,再加上一点弟弟个人的理解。不对的地方和需要补充的地方希望诸位哥哥们纠正和完善~(概念 理论警告!!)

1.2 概念介绍

1.2.1 PE文件格式

PE文件格式是Win32平台下可执行文件遵守的数据格式,其中.exe文件和.dll文件就是典型的PE文件。
PE文件也是二进制分析的主要对象。

PE文件格式把可执行文件分成若干个数据节(section),不同的资源被存放在不同的节中。像.text就是数据节

PE文件一般包含如下几段数据节:

  1. .text 由编译器产生,存放着二进制的机器代码,也是我们反汇编和调试的对象。
  2. .data 初始化的数据块,如宏定义、全局变量、静态变量等。
  3. .idata 可执行文件所使用的动态链接库等外来函数与文件的信息。
  4. .rsrc 存放程序的资源,如图标、菜单等。
  5. 除此以外,还可能出现的节包括“.reloc”、“.edata”、“.tls”、“.rdata”等

关于每个数据节的作用,在
https://blog.csdn.net/syflyhua/article/details/9180437
中有详细的表格介绍。

现在大家明确PE文件格式概念就可以了,在这上面不会有过多的难点。主要是弟弟对这个了解的不是很深哈哈哈哈尴尬。

1.2.2 虚拟内存

Windows 的内存可以被分为两个层面:物理内存和虚拟内存。其中,物理内存比较复杂,需要进入 Windows 内核级别 ring0才能看到。通常,在用户模式下,我们用调试器看到的内存地址都是虚拟内存。

计算机上往往同时运行着多个程序,所需的内存需求远远大于计算机的物理内存。那为什么计算机还能同时完美的运行多个程序呢。是虚拟内存的功劳。

虚拟内存通俗点来说,就是系统分配给程序的一大片假地址,让程序相信自己拥有这么多的计算机资源。但是其实,实际占有的内存资源远远达不到“账面”上的这个数字。

举个栗子,Windows的内存管理机制在很大程度上与日常生活中银行所起的金融作用有一定的相似性,我们可以通过一个形象的比方来理解虚拟内存。

  • 进程相当于储户。
  • 内存管理器相当于银行。
  • 物理内存相当于钞票。
  • 虚拟内存相当于存款。
  • 进程可能拥有大片的内存,但使用的往往很少;储户拥有大笔的存款,但实际生活中的开销并没有多少。
  • 进程不使用虚拟内存时,这些内存只是一些地址,是虚拟存在的,是一笔无形的数字财富。
  • 进程使用内存时,内存管理器会为这个虚拟地址映射实际的物理内存地址,虚拟内存地址和最终被映射到的物理内存地址之间没有什么必然联系;储户需要用钱时,银行才会兑换一定的现金给储户,但物理钞票的号码与储户心目中的数字存款之间可能并没有任何联系。
  • 操作系统的实际物理内存空间可以远远小于进程的虚拟内存空间之和,仍能正常调度;银行中的现金准备可以远远小于所有储户的储蓄额总和,仍能正常运转,因为很少会出现所有储户同时要取出全部存款的现象;社会上实际流通的钞票也可以远远小于社会的财富总额。

1.2.3 PE文件与虚拟内存地址之间的映射关系

在实际调试中,我们常常需要把物理地址和虚拟内存地址联系起来,互相比对。

他们两者之间存在一定的映射关系。

首先明确几个重要概念:

  1. 1)文件偏移地址(File Offset
  2. 数据在 PE文件中的地址叫文件偏移地址,个人认为叫做文件地址更加准确。这是文件在磁盘上存放时相对于文件开头的偏移。
  3. 2)装载基址(Image Base
  4. PE 装入内存时的基地址。默认情况下,EXE 文件在内存中的基地址是0x00400000DLL文件是0x10000000。这些位置可以通过修改编译选项更改。
  5. 3)虚拟内存地址(Virtual AddressVA
  6. PE 文件中的指令被装入内存后的地址。
  7. 4)相对虚拟地址(Relative Virtual AddressRVA)相对虚拟地址是内存地址相对于映射基址的偏移量。

虚拟内存地址、映射基址、相对虚拟内存地址三者之间有如下关系。

  1. VA= Image Base+ RVA


如图所示,PE文件的结构和程序放置到内存中的状态具有很大的一致性。
我的理解是,左边PE文件的结构是程序在静止、未运行下的状态。而右边是动态的,是程序运行时的状态。
所以结构具有很大的一致性。

1.2.4 栈

栈是一种数据结构,是为了让数据管理起来更加清晰,使用起来更加高效。
大部分栈就像一个罐装的薯片包装。

你只能从一侧取用数据(薯片)和放入数据(薯片),上面的数据(薯片)先被取出(吃掉),下面的数据(薯片)最后取出(吃掉)。

栈有两端,顶端称为栈顶,底端称为栈底

栈有两种操作,把数据放入栈的操作,称为出栈,就是一个放入数据的过程。
另一个把栈顶端的数据取出的操作,称为出栈。(因为栈只能对位于最上面的数据进行操作)

1.2.5 函数调用和栈

  1. int func_B(int arg_B1, int arg_B2)
  2. {
  3. int var_B1, var_B2;
  4. var_B1=arg_B1+arg_B2;
  5. var_B2=arg_B1-arg_B2;
  6. return var_B1*var_B2;
  7. }
  8. int func_A(int arg_A1, int arg_A2)
  9. {
  10. int var_A;
  11. var_A = func_B(arg_A1,arg_A2) + arg_A1 ;
  12. return var_A;
  13. }
  14. int main(int argc, char **argv, char **envp)
  15. {
  16. int var_main;
  17. var_main=func_A(4,3);
  18. return var_main;
  19. }

可以看到,上面那段C语言代码中存在三个函数 main函数func_A以及func_B
当程序开始执行时,首先从main函数开始执行,所以先是main函数入栈,进入栈的底部,当在main函数中执行到func_A时,func_A入栈了,所以func_A的栈帧在main函数的上方,执行func_A的时候,又调用到了func_B,于是func_B入栈。

如图,先执行先入栈,后执行会后入栈。越靠近栈顶,函数就越先执行。

不过图中可以看到,相邻的栈帧之间会存在局部变量和返回地址,假设func_A函数中某段数据超出边界,覆盖到了下面的变量和返回地址。那等该函数执行完出栈之后想要返回main函数时,返回地址已经不是main函数的地址,那程序执行就会出现问题。假设我们认真构造一下返回地址的那几个字节,指向某些恶意的代码,那也就形成了溢出攻击。这就是最简单形式的溢出攻击。

1.3 尾声

这个帖子删删写写也写了好多次,中途镜哥也过来指导了好多次,谢谢镜哥~这次帖子只是介绍了溢出所需的一些基本概念,可能会有些枯燥。但是“地基”要打的牢固,高楼才会稳。地址的计算和栈的概念是后面我们理解溢出的基础。

弟弟的帖子有什么错误也希望哥哥们指出,谢谢大家。

打赏我,让我更有动力~

1 条回复   |  直到 2020-7-5 | 1253 次浏览

无心法师
发表于 2020-7-5

怎么没有金币奖励…

评论列表

  • 加载数据中...

编写评论内容
登录后才可发表内容
返回顶部 投诉反馈

© 2016 - 2024 掌控者 All Rights Reserved.