nvicat为什么修改字段的长度保存后为0_计算机自制操作系统(三0):操作系统保护机制...

9784868fa6e6223539db32096c9110e2.png

阅读预警:这章的内容会非常的难,应该是本专栏开播以来最难的一个地方了。无论你到网上任何地方寻找关于操作系统特权级保护的内容都会非常的复杂。我也是花费了很大功夫呕心沥血的层层推导才完成了归纳整理。但是,如果这章的内容都能啃下来,基本上计算机软件类的书按理没有看不懂的了。

在上一章结束的时候,我曾说如果找一个现成的Window或Linux可执行程序,拆除其格式封装后只抽取出其机器代码,把后缀名改成.hrb,然后放在我们的操作系统中启动,程序能不能成功运行呢?答案是不行的,因为我们目前支持的应用程序还只能是最简单的类型:不带任何数据操作的纯代码段可执行程序。这是因为应用程序目前只有单独的代码段,我们没有给应用它分配没有单独的数据段(通过DS和SS等),所以它要做数据访问操作的时候,就会被错误的指向到操作系统的数据段,这样不但应用程序不行正常运行,还会把操作系统搞瘫。所以,本章我们就来解决这个问题,让我们的操作系统支持完整的应用程序,并提供完整的API。要解决这问题,就必须要引入操作系统的一个核心机制:保护机制。

一、实模式问题

16位的工作模式叫实模式,实模式最大的特点就是编程者可以随心所欲:可以对任何内存段地址做操作。普通用户随便一句如下写内存的指令,都足以可能让操作系统崩溃,因为写的内存地址可能是存放的是操作系统内核程序:

MOV  [0x1234],0xff

二、保护模式原理

为了解决实模式的风险问题,计算机便引入了保护模式。虽然我们的操作系统当时是为了支持32位的显卡地址才进入了保护模式的,但其实保护模式更大的作用是为操作系统建立保护机制,这点从“保护”二字就能看出。保护模式的目的是尽最大努力保持操作系统的稳定性。怎么实现保护模式呢?方法是通过段描述符来实现:

0e875ae016c326e950ec97a5332e3f02.png
X86段描述符格式

在段描述符中,主要将有三类字段来协同参与实现保护功能:

1.段限长---LIMIT。该字段规定了每个定义段的长度,比如你要跳转的程序长度超过了的CS定义的段长度或者要访问的数据内存地址超过了DS的定义的段长度,就会报错。

2.段类型---TYPE。在《计算机自制操作系统(二六):多任务调度设计》中,我们知道,每个段都可以定义了可执行、可读写等类型,如果你要在只读的内存段写数据,无疑会报错。

3.描述符特权级---DPL。这个字段除了存在于普通的段描述符中外,还存在于各类门描述符中。该字段用来表示本段的特权级别,显然操作系统段的特权级最高,应用程序段的特权级最低,如果你要在应用程序中对操作系统的段做写入操作,就会被阻止。

三、保护模式实现

本节就将以《30天》书中的案例来说明保护模式具体实现。

(一)无保护模式

开篇就说了,目前我们的操作系统还不支持有内存数据操作的应用程序。那现在我们就来写一个最简单的有数据操作的应用程序crack1.c,看看它的攻击性在哪里。

void HariMain(void)
{
	*((char *) 0x00102600) = 0;
	return;
}

这个程序原理很简单,直接往内存地址0x00102600写成0。这个内存地址是什么呢?我们来看操作系统内存分布图:

f069731e1092c53386850314df2eb9b7.png

显然,在内存中软盘数据的开始地址是1MB,而2600是软盘目录数据的偏移地址,因此对内存地址:0x00102600操作实际就是软盘第一个文件名被修改成了0。操作系统看到文件名是0x00开头的,认为是异常(其内核程序是这么设计的),因此再执行任何dir,type或运行任何应用程序都不会有反应,这样就相当于操作系统被坏了。

(二) 初级保护:为应用程序分配专用段

1.分配专用段作用

上面应用程序搞瘫操作系统,最的问题就是操作系统和应用程序的数据段没有做区分所致,操作系统和应用程序的数据段、堆栈段都混为一团,其实就和实模式的管理方法没有本质区别。所以,为应用程序分配独立的段,是最基本的要求。因此,我们重新对段做如下划分:

7b38d00cfcc2d453fa265b883661008a.png

应用程序的内存空间都是临时申请的,并把这个临时内存空间注册到GDT中就形成了应用程序的代码段和数据段。需要注意的是:代码段的长度是应用程序的实际大小,而数据段的长度由于不好估计,因此这里固定申请为64KB。可以看到,无论是操作系统还是应用程序都只有代码段和数据段,并没有再设置堆栈段。这是因为我们的堆栈寄存器SS一直是和数据类寄存器DSESFSGS保持的同一个值,可以理解为堆栈段就是用的是数据段,那么在同一个程序中,就只需要重新定义ESP栈顶的指针值就可以了,由于这次我们给应用程序申请的数据区长度是64KB,那么我们把ESP栈顶指针设置在这64KB的最顶端最适合了。综上,核心程序如下:

p = (char *) memman_alloc_4k(memman, finfo->size); /*应用程序代码地址*/
q = (char *) memman_alloc_4k(memman, 64 * 1024);   /*应用程序数据地址*/
*((int *) 0xfe8) = (int) p;
file_loadfile(finfo->clustno, finfo->size, p, fat, (char *) (ADR_DISKIMG + 0x003e00));
set_segmdesc(gdt + 1003, finfo->size - 1, (int) p, AR_CODE32_ER);  /*应用程序代码段*/
set_segmdesc(gdt + 1004, 64 * 1024 - 1,   (int) q, AR_DATA32_RW);  /*应用程序数据段*/

start_app(0, 1003 * 8, 64 * 1024, 1004 * 8);   /*64 * 1024为ESP之值*/
memman_free_4k(memman, (int) p, finfo->size);
memman_free_4k(memman, (int) q, 64 * 1024);

这里用start_app()来启动应用程序,对应参数分别为应用程序启动之后新的EIP值和新的CS,ESP和DS(SS)段地址之值。这样,我们再执行应用程序:*((char *) 0x00102600) = 0的时候,它操作的内存区就不再是操作系统数据段了,而是应用程序的数据段,所以不会对操作系统造成破坏,我们达到了初级保护的目的。

可是,我们在实际运行过程中,还是会报错,这次的故障现象不再是操作系统的运行应用程序不会有反应,而是不断的关机重启,说明是发生了系统性的故障。通过仔细推敲,我们发现原因是应用程序数据段的长度固定为64KB所致的,因为应用程序*((char *) 0x00102600) = 0,翻译到汇编指令是:

mov  [0x00102600],0

这条指令默认的数据寄存器是DS:[DS:0x00102600],而我们定义的应用程序数据段长度是64KB,偏移地址0x00102600的长度是>1MB,这样就会发生前面我们介绍的段限长---LIMIT保护错误,会导致处理器异常。我们尝试修改一下,将数据段长度设置为2MB:

q = (char *) memman_alloc_4k(memman, 2048 * 1024);
set_segmdesc(gdt + 1004, 2048 * 1024 - 1,   (int) q, AR_DATA32_RW);
......
start_app(0, 1003 * 8, 2048 * 1024, 1004 * 8);

再次运行应用程序*((char *) 0x00102600) = 0就不会宕机了,而且也能正常使用dir命令以及运行其他应用程序,说明操作系统抵挡住了最低级的攻击,具有初级保护能力。原理实际上是由于应用程序分配了专门的数据段,因此它对内存的写操作就只能被限制在它专用的数据段内,而不会对操作系统的段产生影响

所以现在可以说,我们的操作系统已经可以支持带有数据操作的应用程序了,也即支持完整的应用程序。

2.分配专用段实现

虽然为应用程序分配专用段的作用明显,但是怎么实现还是个问题。最大的困难是由于操作系统和应用程序使用的是不同的代码段和数据段,两个程序之间的切换就会相当的复杂。极端情景是:当应用程序要调用操作系统的API时,调用参数是在应用程序里面的,API实现是在操作系统里面的,那怎么才能实现这个参数传递呢?调用完成之后又怎么回去呢?

2.1 应用程序的启停

我们需要在操作系统里写一个启动应用程序的内核程序start_app(),策略是在应用程序启动之前,通过操作系统的堆栈先保存好操作系统的数据段寄存器(DS和SS等)和操作系统的堆栈指针ESP,再把应用程序的数据段和应用程序的堆栈指针ESP写入相应寄存器,然后开始调用应用程序。应用程序执行完成之后返回之后,操作系统还原之前的各个寄存器值即可。这里为什么不需要将操作系统的代码段CS和EIP入栈保护和恢复呢?这是因为这两个值是通过call和ret等配合调用自动切换的,CPU会自动管理,不需要我们操心。

保存和恢复操作系统普通的段寄存器DS和SS等非常容易,用传统的PUSH和POP即可。但是要保存和恢复操作系统堆栈指针ESP,是不能简单的使用PUSH和POP就行的,关于这点我们在《计算机自制操作系统(二四):开发过程中的痛苦---正确理解编译器也会犯错》中反复强调过:POP ESP 指令不但没有意义,反而它会使程序异常,严重的话计算机会崩溃,应绝对禁用!我们来看书上的程序是怎么解决的:

_start_app:		; void start_app(int eip, int cs, int esp, int ds);
		PUSHAD		; 32位寄存器全部保存
		MOV		EAX,[ESP+36]	; 应用程序EIP
		MOV		ECX,[ESP+4
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值