freeBuf
主站

分类

漏洞 工具 极客 Web安全 系统安全 网络安全 无线安全 设备/客户端安全 数据安全 安全管理 企业安全 工控安全

特色

头条 人物志 活动 视频 观点 招聘 报告 资讯 区块链安全 标准与合规 容器安全 公开课

官方公众号企业安全新浪微博

FreeBuf.COM网络安全行业门户,每日发布专业的安全资讯、技术剖析。

FreeBuf+小程序

FreeBuf+小程序

Linux系统安全:Linux系统内存管理的基础知识
2020-07-13 13:10:48

前言:本专题会从Linux内核系统最基本内存管理开始介绍,并且通过实践来深入理解其理论。在学习了基本的理论知识以后,会进入本专题的正文,来介绍实际的攻击。通过详细的理论分析和实践代码让读者深入理解Linux的管理内存的本质以及Linux攻击行文的实现。在以后的开发过程中更轻松的去管理内存避免漏洞的出现。

Linux分段机制

背景

在8086处理器诞生之前,内存寻址方式就是直接访问物理地址。这就是直观的令人能很好理解的访问模式,cpu中的地址值就是我们要访问的内存的地址值。我们称之为cpu的实模式

由于8086处理器想要访问1MB(220)的内存,但是以前处理器只有16位(216=64kB),所以在8086时,设计师将CPU的地址总线升级为20位,但是,问题在于ALU只能处理16位数据,这时就想出了新的地址访问方法,就是分段机制。

分段机制:就是将整个一块内存分成几个段:CS, DS, SS, ES(代码段,数据段,堆栈段,附加段)。然后每个段内空间就变小,就可以对内存进行正常访问了。基本原理就是分段越多,段内需要访问的地址范围就越小。

所以,8086采用分段机制以后,物理地址的访问就变成,首先需要知道要访问的是哪个段,那个段的首地址是多少,然后在这个段内我需要访问的位置离首地址有多远。具体实现:物理地址=(段的首地址<<4) +段内偏移

因为寄存器是16位,而CPU总线为20位,所以从段寄存器中读出16位的段首地址放到CPU20位中的高16位。也就是说段首地址一定是24(16k)的倍数。然后再加上段内偏移就是我们CPU需要访问内存的实际地址。

重要问题:为什么是将段首地址向上移动4位而不是16位(整个段的长度)?

因为这里工程师进行设计的时候,只有20位的数据总线位数和16位的寄存器及ALU位数。没有20-16=4位的寄存器。所以工程师要利用16位合成20位。这里就想到了将一个16位放到20位的高16.将另一个16位放到20位的低16位。然后相加就是我们的寻址地址。这样做出现的问题就是每个段并不是独立的,而是重叠的。

举个例子。假设要访问030H地址的内存空间,可以使用段1的首地址:010H,段偏移是020H,则030H=010H+020H。

也可以使用段2的首地址:00H,段偏移是030H,则030H=000H+030H. 总之要想访问某个物理地址,只要凑出合适的段基地址和段内偏移地址,其和为该物理地址就行了。

因为段是重叠的,不同段可以访问到重叠地址的数据,所以在定义段的时候加上了段长数据来使不同段不重合。

随着时代的发展,出现了80386处理器是一个32位处理器,通常我们所说的CPU位数是指ALU可以处理数据的位数,通用寄存器的位数,数据总线的宽度中最小的一位。ALU和地址总线都是32位的,寻址空间达 4G。也就是说它可以不通过分段机制,直接访问4G的内存空间。但是为了兼容以前的访问模式,也为了方便其自己的内存访问,所以该处理器提供了两种内存访问模式:实模式和保护模式(分段机制)。所以其保留了几个分段寄存器CS,DS,SS,ES。

实模式特点:

  1. CPU刚上电时是出于实模式之下的,CPU访问的地址就是输入的物理。
  2. 实模式的物理地址=16位段基址<< 4 + 16位段偏移量
  3. 实模式下对任意段都具有读写权限。
  4. 实模式下可以访问的内存大小为1M(0x00000-0xfffff)

IA32的内存寻址机制

80386处理器提供的这种具有两种内存访问模式:实模式和保护模式。保留几个16位的分段寄存器的机制就是成为IA32架构。

在 8086 的实模式下,把某一段寄存器左移4位,然后与地址ADDR相加后被直接送到内存总线上,这个相加后的地址就是内存单元的物理地址,而程序中的这个地址就叫逻辑地址(或叫虚地址,就是CPU输出程序员写入的地址)。在IA32的保护模式下,这个逻辑地址不是被直接送到内存总线而是被送到内存管理单元(MMU)。MMU由一个或一组芯片组成,其功能是把逻辑地址映射为物理地址,即进行地址转换,如图所示。

                         

MMU地址转换MMU是一种硬件电路,它包含两个部件,一个是分段部件,一个是分页部件,在此,我们把它们分别叫做分段机制和分页机制,以利于从逻辑的角度来理解硬件的实现机制。分段机制把一个逻辑地址转换为线性地址;接着,分页机制把一个线性地址转换为物理地址。

三种地址转换关系IA32中有六个16位段寄存器:CS, DS, SS, ES,FS, GS.跟8086的段寄存器不同的是,这些寄存器存放的不再是某个段的基地址,而是某个段的选择符(Selector)。

分段机制的实现

1.GDT与GDTR的出现

随着CPU和操作系统的发展,人们对内存访问的要求越来越高,不仅仅要求能够访问到内存,还要求要对内存进行访问权限,段的大小等属性的设置与查看等。所以80386处理器与8086处理器的分段模式并非完全相同的,而是进行了相当大的改进,把分段目的从能访问到更大内存转变成了对内存的访问能进行控制,实现段保护,所以成为保护模式。

但是16位的段寄存器还是存储的信息是完全不够的,所以CPU规定操作系统必须提供一个表,这个表中存储了每个段的的段描述符(段描述符中包括了:段的基地址,段的界限,段的保护属性)。这张表就是我们称的GDT表。所以,我们就减轻段寄存器的工作,只需要其找到这张表上的对应的段描述符既可。所以,每个CPU一定有一个GDT表,同时为了找到这张表,CPU提供一个专门的寄存器来存储这张表的地址,成为GDTR寄存器。

2.段描述符(GDT表项)的解析

段描述符是一个8字节的数据结构。

段描述符

3.实例分析

1>实验性分析

段描述符实例分析

这个是gdt表的一部分,我们可以看到,第2段和第6段分别是存放64位下的内核代码和用户代码的段信息。其中保存了段大小,段的内存地址以及访问权限等信息。

不过,通过分析,在linux中,不同段的基地址都相同,为0.代表linux只是使用了分段机制的权限保护功能,而没有使用其访问更大内存的功能。

2>源码性分析

GDTR寄存器的值是GDT表的内存地址,是需要linux内核写入的。而GDT表示属于per cpu变量中gdt_page中存储的。per cpu变量是每个CPU独有的变量,是linux系统分配给cpu的。

Linux分页机制

分页的硬件支持

一个逻辑地址经过分段机制转换为一个线性地址之后,便需要分页单元将线性地址转换为实际的物理地址。

从80386开始,所有的80x86处理器都支持分页。是否开启分页通过设置cr0寄存器的PG标志来决定,当PG为0时,表示不开启分页,此时线性地址呗解释为物理地址。

分页机制管理的对象是固定大小的存储块,称之为(page)。分页机制把整个线性地址空间及整个物理地址空间都看成由页组成,在线性地址空间中的任何一页,可以映射为物理地址空间中的任何一页(我们把物理空间中的一页叫做一个页面或页框(page frame))。

80386中每一页的大小都为4KB,每一页的起始地址都能被4K整除12位全为0)。因此,80386把4G的线性地址空间,划分为1M个页面。

线性地址映射到物理地址的数据结构称为页表,页表存放在主存中,由内核进行适当的初始化。

线性地址转换

一个线性地址被分页机制解析为三部分:目录项(高10位),页表(中间10位),页内偏移量(最低12位)

                         线性地址寻址流程

  • cr3寄存器:存储页目录基地址
  • 目录项:存储在页目录中的偏移量
  • 页表(中间10位):存储在页表内存中的偏移量
  • 页内偏移量:存储在页内的偏移量
  • 页目录:每一项都是一张页表的基地址
  • 页表:每一项都是一个页
  • 页:存储数据的地方,每一个页对应一个数据块。

在linux中使用了4级分页模型

Linux系统中4级分页过程

 

  • 页全局目录:pgd
  • 页上级目录:pud
  • 页中间目录:pmd
  • 页表:pt

总结

本文介绍了x86架构CPU中MMU中重要得硬件机制。这是我们研究操作系统安全的基础。下面的我们会继续对Linux中的内存管理进行理论分析以及实验探究。为我们的攻击打下基础。

# 系统安全 # 系统安全 # linux安全 # 操作系统 # Linux内核 # X86CPU
本文为 独立观点,未经允许不得转载,授权请联系FreeBuf客服小蜜蜂,微信:freebee2022
被以下专辑收录,发现更多精彩内容
+ 收入我的专辑
+ 加入我的收藏
相关推荐
  • 0 文章数
  • 0 关注者