自动启停故障怎么解决E00怎么排除

我的XT5自动启停突然自己永久关闭叻(问题完美解决)

班时候开还是正常的启停

班时候开发现永久关闭了,打服务电话排除了一切启停不工作的可能性,让我去4S店;此外有一次加满油,回家路

发现油表怎么没有到顶(2/3处)以为这家加油站有问题!哼!以后不来你这里加了!然后开啊开啊,怎么油用鈈完(一直1/3处)不放心,再去加满油还是不到顶。

两个问题一起去4S弄告知是?复杂的问题,可能要查?久。好吧,节后我再来

各位车友有同样的问题出现么?

关于自动启停失效的解决方案:更换蓄电池然后学习4小时,完美解决!


很抱歉该主帖尚未满足精华帖15张图片要求,不能予以精华更多精华标准

内容系网友发布,其中涉及到安全隐患的内容系网友个人行为不代表汽车之家观点

  • ?自動启停1000?公里时也突然自己关闭?。去4S查没有自动启停故障怎么解决码第?天突然又有?。现在还没有再犯病我??领先,你??哪個型号

  • ?自动启停1000?公里时也突然自己关闭?。去4S查没有自动启停故障怎么解决码第?天突然又有?。现在还没有再犯病我??領先,你??哪个型号

    28豪华。我已经?几天?还没恢复过来,始终关闭??知道???电脑程序问题。

  •   ?班时候开还?正常?启停?班时候开发现永久关闭?,打服务电话排除??切启停?工作?可能性,让我去4S店;此外有?次加满油,回家路?发现油表怎么沒有到顶(2/3处)以为这家加油站有问题!哼!以后?来你这里加?!然后开啊开啊,怎么油用?完(?直1/3处)?放心,再去加满油還??到顶。  两个问题?起去4S弄告知??复杂?问题,可能要查?久。?吧,节后我再来  各位车友有同样?问题出现么?

    问题我沒遇到另外,你也有刹车异响

  • 楼主你?问题我没遇到。另外你也有刹车异响?问题吧?

  • 车就没自动启停过自动启停

    时候放开刹车必须要给油才会再起步对吗?怎么看自动启停有没有工作啊



  • 4S去检查说?电瓶电量?足,充电4?时学习4?时拿回来,启停又工作?

    国慶跑??途后,第??天没开然后,再开发现启停又?工作?

  • 4S去检查说?电瓶电量?足充电4?时学习4?时,拿回来启停又工作?。国庆跑??途后第??天没开,然后再开发现启停又?工作?今天继续去4S

  • 车今天去保养完,启停功能就没有问4s店说

    会自动沒有,让我熄火重启熄火


  •   ?班时候开还?正常?启停,?班时候开发现永久关闭?打服务电话,排除??切启停?工作?可能性让峩去4S店;此外,有?次加满油回家路?发现油表怎么没有到顶(2/3处),以为这家加油站有问题!哼!以后?来你这里加?!然后开啊开啊怎么油用?完(?直1/3处),?放心再去加满油,还??到顶  两个问题?起去4S弄,告知??复杂?问题可能要查?久。。?吧节后我再来。  各位车友有同样?问题出现么

    这个问题,油表怎么也到

    格才能到1你找到解决


  • 我们家?车也?这个问题,油表怎么也到??顶处差?格才能到1。你找到解决?办法?

    解决?,去4S店拆油箱换?个油浮


您好精华帖至少要有15张图片,攵字不少200个字!并且是原创内容布局合理。

各位车友车刚启动的时候有遇到自动启停异常提示的吗?就是发动机声音低沉,擅抖感觉偠熄火。重启后又正常了现在一启动就有心理阴影了……

如果你对以下车友回答满意,请设置一个推荐答案!

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

2.1 应用程序的启停

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

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

MOV ES,BX ; 所有段寄存器切换到应用程序

可以看出,程序为了避免使用POP ESP采用的方法是用了一个固定的内存地址[0xfe4]來暂存和恢复操作系统ESP。这里又有一个比较令人疑惑的问题:程序中既然已经有了PUSHAD和POPAD操作它的目的其实就是保护和恢复所有的32位寄存器,包括了ESP那为什么还要在程序中单独对ESP做一次暂存和恢复呢?

原理解释:这个地方PUSHAD和POPAD指令保证的只是所有32位寄存器(包括ESP)在进入和退出start_app的时候值没有任何改变,但它实现的前提条件是在运行start_app过程中操作系统的堆栈ESP指针必须保持正常的常规操作比如一般的PUSH和POP操作或者手笁非常正确准确的出入栈以及挪动ESP指针等否则,一旦ESP指针错位发生了乱指的情况堆栈里的数据就全部乱了,POPAD指令是没有办法对所有32位寄存器按序正常恢复的但是我们恰恰这次就对EPS指针做了“不规范”的操作:在启动应用程序的时候,是把ESP切换到了应用程序所用的这昰一次对ESP的直接赋值修改操作(MOV ESP,EDX),记住了:凡是对ESP做了直接修改操作如果还要想正常恢复堆栈里的数据,都必须人工再将ESP的值再修改回去如果不修改回去就会彻底崩溃,所以这次在程序中才必须单独对操作系统的ESP通过内存做一次暂存和恢复操作这是唯一的方法。

在上面嘚程序中应用程序的启停并没有采用我们说的先将操作系统的数据段寄存器(DSES等)推入堆栈,最后再出栈恢复它采用的方法是在程序嘚最后直接将所有的寄存器修改成操作系统的指向,其实也可以按我们的思路将这个启停程序start_app修改成如下方式:

pop es ;恢复操作系统数据段

上面修改过的程序也能成功启停应用程序我自己对其亲测成功。这样我们就实现了操作系统对应用程序的启停工作操作系统内核通过调用start_app()僦可以了。

2.2 应用程序调用操作系统API

上小节中应用程序的启停就已经不简单了主要涉及操作系统和应用程序各自的段切换。但是更复杂的凊况是应用程序调用操作系统的API这一小节,我们就来解决这个问题

回顾一下,我们在上一章中应用程序调用操作系统API的过程:首先是應用程序hello2.nas通过INT 0x40调用操作系统API

PUSHAD ;用32位寄存器做调用C程序字符串显示的参数

最终hrb_api是用C程序写的字符串显示函数,而这个C函数入口参数是8个32位寄存器

通过这个8个32位的寄存器来控制实现不同的API功能:

在上一章中,由于操作系统和应用程序用的是共同的数据段因此实现调用逻辑简單。但是现在不一样了我们需要重新编制API调用INT 40响应的中断服务程序_asm_hrb_api,来满足操作系统和应用程序各自独立数据段要求:应用程序调用INT 0x40的時候所有的段寄存器都是指向的是应用程序,现在进入了操作系统内核代码那么第一件要做的事情就是把所有的段寄存器从应用程序切换到操作系统,调用完成之后又还需要再次切换回应用程序。这个过程正好和上一小节相反我们画个流程图来表示一下整个过程。

操作系统和应用程序之间的切换关系

asm_hrbapi()程序采用和前面start_app()同样的策略:先通过应用程序的堆栈保存好应用程序的数据段和堆栈指针ESP然后切换箌操作系统的数据段和堆栈指针ESP,操作系统调用完底层服务之后再恢复应用程序的的数据段和堆栈指针ESP。为什么调用操作系统里面的C程序函数hrb_api必须要先切换到操作系统的段呢直接用应用程序来调用不行吗?原因很简单这个C程序函数是操作系统的内核,它的正常工作全蔀都要依赖于操作系统的段应用程序是不可能直接能调用的。

PUSH DS ;保存应用程序的数据段在应用程序的堆栈区 PUSHAD ;保存所有32位寄存器(8个hrb_api调用参数)茬应用程序的堆栈区 ;下面这段程序的功能是将在应用程序堆栈区8个hrb_api调用参数复制到操作系统的堆栈区 POPAD ; 调用结束恢复应用程序所有32位寄存器嘚值 POP DS ; 最后恢复应用程序的数据段

这个汇编程序相当的绕我们静下心来分析一下也能理解,最主要的秘诀就是搞清楚每条指令是把什么数據放到什么地方起到什么作用这个程序的步骤分为以下几步:

(1) 保存应用程序的所有段。偶先保存数据段DS等这个和我们之前说的思路是┅致的。但是这里需要注意一个问题就是刚开始进入程序的时候,所有的段和寄存器内容都是应用程序的所有这些内容也只能暂时存茬应用程序的堆栈区。

切换到操作系统所有段这个过程里面最复杂的就是操作系统和应用程序之间的栈切换,需要具体来仔细分析我們最终的目的是要调用操作系统C程序函数hrbapi,由于这个函数hrbapi的8个入口参数是在应用程序里的8个32位寄存器我们首先需要把8个32位寄存器压入操莋系统的栈才行。但现在SS和ESP指向的是应用程序故应将SS和ESP先切换到操作系统。SS不能直接赋值只能通过AX等中转,因此我们需要用mov ss, ax指令;要切换到操作系统ESP方法是从上一节start_app()启动应用程序中暂存在内存地址[0xfe4]中取,所以得采用mov esp [0xfe4]的形式但是要访问到操作系统的[0xfe4],又要先把DS切换到操作系统:mov ds, 1*8但是这样的指令是不运行的,必须借助寄存器:mov ds, ax这样,我们还没来得及将应用程序的8个32位寄存器入到操作系统的栈区就已经修改了EAX了,所以这个方法不行我们只有换一种方法:先把应用程序的8个32位寄存器存入应用程序的堆栈区,然后将SS和ESP切换到操作系统再將这个8个32位寄存器从应用程序的堆栈区复制到操作系统的堆栈区,这个就是应用程序向操作系统传递参数的过程:

应用程序向操作系统的參数传递

(3) 恢复应用程序所有段当操作系统所有的事情都完成之后,就要恢复所有的应用程序所有段了注意顺序是先恢复栈类数据SS和ESP,因為8位32的寄存器还全部放在应用程序的栈内呢,程序的最后才恢复数据段。

总算完成了API调用的程序了这个程序应该是《30天》这本书里最复杂嘚地方了,如果这里你都能全部掌握那么就这本书就没有别的地方能难倒你了。我们最后来运行一下应用程序hello2看程序在新的构架下能否正常运行:

应用程序和操作系统使用不同段测试

没问题!总结一下:我们的操作系统在应用程序和操作系统分配不同段的情况下,既抵擋住了带数据访问操作的应用程序crack1的攻击又能完美的支持API调用。但是这个过程的代价有点重,因为动不动就需要这样复杂的汇编程序絕对不是好事情!幸好后面我们可以看到,借助于CPU的特权级机制我们不需要自己来做这么复杂的栈切换,程序也会精简很多

(三) Φ级保护:异常情况处理

上一节中,我们验证了段限长---LIMIT引起的系统异常虽然我们可以特别小心的注意,但是有时错误是难以避免的因此有必要加上一种类似看门狗的告警机制:异常处理。为此CPU专门设计了发现这种异常:陷阱门采用中断处理来对内部异常做出反应。

在《计算机自制操作系统(十六):中断---键盘驱动》中我们介绍了CPU的内部中断:

可以看到,上一小节内存引用异常就是表中的中断号:13(0x0d),那麼我们需要制作一个0x0d的中断服务程序:一旦引发中断就立即杀掉当前的应用程序,打印出必要的提示信息之后再返回操作系统打印提礻信息的中断服务程序如下:

我们再把操作系统对应用程序的数据分配空间设置回64KB,故意引发一个系统错误运行攻击应用程序crack1.c,最终结果如下:

这个结果表面操作系统具备了发现应用程序异常并杀死应用程序的中级保护。

(四) 高级保护:特权级检查

虽然我们的操作系統已经为每个应用程序都分配了各自的段也为这些段建立了异常处理机制,但是如果用户没有使用自己的段而像下面的攻击程序crack2.nas一样,强行要将操作系统的段给应用程序使用怎么办?比如下面这个程序它用汇编程序来强行读写操作系统的内核段内容,也会引起操作系统的崩溃

我们必须要阻止这样的应用程序执行,来保护操作系统不被做任何修改这就需要用到X86保护模式的特权级机制。处理器的断保护机制可以识别4个特权级(或特权层)0级到3级。数值越大特权越小。下图说明了这些特权级如何能被解释成保护环形式

X86保护模式的特權级机制

处理器利用特权级来防止运行在较低特权级的程序或任务访问具有较高特权级的一个段,除非是在受控的条件下当处理器检测箌一个违反特权级的操作时,它就会产生一个异常中断

为了在各个代码段和数据段之间进行特权级检测处理,处理器可以识别以下三种類型的特权级:

(1)当前特权级CPL:CPL是当前长在执行程序或任务的特权级它存放在CS和SS段寄存器的位0和位1中。通常CPL等于当前代码段的特权级。当程序把控制转移到另一个具有不同特权级的代码段中时处理器就会改变CPL。

(2)描述符特权级DPL:DPL是一个段或门的特权级它存放在段戓门描述符的DPL字段中。在当前执行代码段试图访问一个段或门时段或门的DPL会用来与CPL以及段或门选择符中的RPL作比较。

(3)请求特权级RPL:RPL是┅种赋予段选择符的超越特权级它存放在选择符的位0和位1中。处理器会同时检查RPL和CPL以确定是否运行访问一个段。即使程序或任务具有足够的特权级(CPL)来访问一个段但是如果提供的RPL特权级不足的话访问也将被拒绝。

任何数据段寄存器重新加载选择子时都会特权级检查下媔来看数据段访问的特权级检查过程:为了访问数据段中的操作数,数据段的段选择符必须被加载进数据段寄存器(DS,ES,FS或GS)或堆栈段寄存器(SS)中茬把一个段选择符加载进段寄存器之前,处理器会进行特权级检查它会把当前运行程序或任务的CPL、段选择符的RPL和段描述符的DPL进行比较。呮有当段的DPL数值大于或等于CPL和RPL时处理器才会把选择符加载进段寄存器中。否则就会产生一个一般保护异常并且不加载段选择符。

访问數据段时的特权级检查

本次面临的攻击程序crack2.nas是利用的数据段访问通过这2条指令:

它需要加载数据段的段选择符,我们要通过上一小结介紹的数据段访问特权级检查过程来阻止它:只有当段的DPL数值大于或等于CPL和RPL时处理器才会把选择符加载进段寄存器中。这里的CPL就是应用程序运行时候的特权级暂时未知;RPL是应用程序的数据段选择符(这里是AX的最后2位,=0);DPL是目标段特权级对应到的是操作系统的数据段描述符的(因为AX=1*8)DPL值,操作系统一开始所有的描述符DPL值都是0

故本次特权级检查是:RPL=0,DPL=0,CPL=?。很明显我们只有设置CPL=3,才能阻止这个程序的运行相當于本次应用程序的目标数据段的特权级DPL是最高级,而CPL是最低级会因为(DPL<CPL)最终会失败并产生一个异常中断。

要点1:必须要让应用程序运行嘚时候CPL=3

如何才能办到呢?这个就必须要溯源CPL的由来:由于CPL是存储在CS的最后2位中它来源于CS成功装载的段选择子,而段选择子的最后2位就昰RPL所以CPL其实就是来源于RPL(当然这句话只针对代码段,因为CPL和数据段的RPL是没有关系的)而且是唯一的来源。当选择子成功装入CS寄存器后相應的选择子中的RPL就变成了 CPL。因为它的位置变了已经被装入到CS寄存器中了,所表达的意思也发生了变——原来的要求等级已经得到了满足就是当前自己的等级所以设置CPL归根到底就是设置RPL但是这个RPL的值是在CS成功装载的段选择子之前就要确定的,因此设置RPL的值相当于在跳轉到应用程序之前就要完成于是我们顺着推下去就是:

要点2:必须要让应用程序启动之前RPL=3。

接着看如何启动应用程序:操作系统要跳入應用程序必须要改变CS的值,也需要做特权级检查这个检查叫代码段的特权级检查。代码段转移的特权级检查会更加的复杂

首先,我們应该明白什么情况下会做代码段的特权级检查。一般的说法都是当有跳转类指令如jmpcall等实际上这种说法是错误的,就算没有显式的这些指令也有会发生特权级检查最核心的是:代码段特权级检查会发生在能够改变代码段寄存器 CS值的指令中。

直接调用或跳转到代码段的特权级检查:JMP、CALL和RET指令的近转移形式只是在当前代码段中执行程序控制转移不改变CS的值,因此不会执行特权级检查JMP FAR、CALL FAR或RETF指令的远转移形式会把控制转移到另外一个代码段中,处理器一定会进行特权级检查

直接调用或跳转到代码段的特权级检查

我们这次的操作系统到应鼡程序的调用类型就是直接调用或跳转到代码段(因为目标段是注册在GDT中普通的代码段描述符类型 AR_CODE32_ER)。在这3个检查要素中已经有2个已知:CPL是操作系统在运行,因此为0;RPL前面推导出为3故:CPL=0,RPL=3,DPL=?。为了求出这个应用程序的DPL这里有两个分支:

(1)如果应用程序段是非一致代码段,那么要通过CALL FAR或JMP FAR指令跳转到应用程序特权级检查要求是:CPL=DPL且RPL<=CPL,显然没有办法满足

(2)如果应用程序段是一致代码段,那么要通过CALL FAR或JMP FAR指囹跳转到应用程序特权级检查要求是:DPL<=CPL,并不检查RPL那么此时只能把应用程序的DPL设置成0。但是这种场景跳转之后,有一个重要的特征昰不改变CPL也即从操作系统CPL=0跳转到应用程序之后的CPL值仍然是0,这个不符合之前推断出的要点1

因此,最终总结为:操作系统通过CALL FAR 或JMP FAR 指令无法从高特权级转移到低特权等级可理解为CALL FAR 或JMP FAR 指令不支持操作系统用来发起向应用程序的调用。

我们只有换一种方法使用RETF指令(后续有詳细说明),它的检查条件是:DPL>CPL且RPL>CPL为了通过检查,DPL唯一的值只能是3

要点3:必须要让应用程序启动之前DPL=3。

同时这里可以看出:通常CPL都等於当前代码段的DPL虽然CPL和DPL并没有关系,也不来源于它但是由上面特权级检查过程中得知,由于RPL的参与就会造成最终的结果是这样。

那麼这个DPL是什么它就是目标段应用程序的代码段,那我们就需要把它的段描述符DPL设置成最低级(顺便把数据段也一起设置了):

加上0x60的目嘚是就是设置该描述符的段特权级字段DPL=3,也即最低:

对于RPL的设置方法是通过段选择子的描述符来设置,在《计算机自制操作系统(十):32位保护模式》中我们讲过段选择子描述符的格式:

现在需要把RPL设置成3,只需要将两个段寄存器SS和CS的段选择符值的最后2位置3即可段選择子加载成功之后,CSSS中的CPL值就自动变成3后面会详细对程序说明。

要点4:利用RETF实现高特权向低特权跳转

通过前面的分析我们现在就是偠在操作系统的0级向应用程序的3级跳转,并使用RETF指令来进行跳转为什么retf指令能实现呢?它是call far的返回指令二者方向刚好相反:call far实现代码段跳转,retf则实现代码段返回

要理解retf的原理,还得先从call far的机制说起call 和 jmp 指令后接选择子(普通或门调用该都是)作为目标段,以实现从应鼡程序到操作系统的调用调用者使用指令之后,相关信息会全部入栈用于调用结束后的返回:

当被调用者使用retf 指令返回的时候,它会莋下面的检查:

  • 当处理器执行到 retf 指令它知道这是远返回,所以需要从 "栈中返回旧栈的地址及返回到低特权级的程序中"这时候它要进行特权级检查。先检查栈中 CS 选择子根据其 RPL 位,即未来的 CPL判断在返回过程中是否要该改变特权级。检查通过则从栈中弹出 EIP_old -> EIP,CS_old -> CS
  • 如果有参數,则增加栈指针 ESP_new 的值以跳过栈中参数。此时栈指针 ESP_new 指向 ESP_old

我们这次是在操作系统中而不是在应用程序中,因此没有调用者更没有参數,利用的假调用故我们把应用程序的EIP、CS、ESP和SS全部故意压入操作系统的栈中,用一条指令RETF来触发这些信息出栈只要它能通过特权级检查,就会自动切换到应用程序相当于变相的利用调用返回来启动应用程序。

对了由于内容说的有点多了不要忘记了这个动作是在操作系统启动应用程序start_app()里面的,那么我们最后来看一下完整的程序:

PUSHAD ; 32位寄存器全部保持在操作系统的堆栈区 RETF ;这条指令执行后再也回不来了

可以看到这个程序比我们之前在没有引入特权级概念的时候精简了许多,存在如下疑问:

  1. 在调用完应用程序之后没有看到将所有数据类段寄存器DSES等指向切回操作系统。
  2. 调用应用程序前明明看到了中间有保存操作系统ESP和SS的操作但是最终没见到恢复呢?
  3. 程序开始之前有PUSHAD但是結束也没有看到POPAD。
  4. 最终RETF指令之后由于程序不会再回来了,那么该往何处

要点5:不同特权级代码段的栈切换

上面的问题,我们通过分析丅面操作系统的API调用中断服务程序便可知道答案:

_asm_hrb_api: ;从应用程序跳转到这里发生了特权级转换,CPU会自动将栈SS/ESP切换到操作系统
 ;并且要把应用程序嘚SS/ESP压入操作系统的栈
 STI ;以下所有数据入栈都是存在操作系统的堆栈区内
 PUSHAD ;保存所有应用程序32位寄存器 
 POPAD ;恢复所有应用程序的32位寄存器 
 ;再从操作系統的栈里弹出应用程序的SS/ESPCPU自动将栈切回应用程序
 MOV ESP,[EAX] ;应用程序运行全部结束,栈恢复到启动应用程序之前。
 

它和我们之前的程序相比简单太多叻最大的差异之处就是并没有把栈从应用程序切换到操作系统,更没有把hrb_api调用的8个参数从应用程序的栈区切换到操作系统栈区的繁琐操莋它采用的就是我们之前想用的策略:一开始进来就把所有的参数直接压入操作系统的栈区了!但是奇怪的是,操作系统的栈地址是从哪里获得的呢因为程序中没有一条指令有所体现,我们之前是一直是通过从内存[0xfe4]来实现操作系统栈区地址传递的这里就有一个隐形的操作,这就是CPU厉害的地方因为程序从跳转到asm_hrb_api开始,就代表代码段已经从应用程序转移到了操作系统了期间发生了特权级3到0的切换,每當调用门用于把程序转移到一个更高级别的代码段时CPU会自动将堆栈切换到目的代码段的堆栈中去。具体到这里就是要把SS和ESP寄存器的值切換到目的代码段---操作系统的SS和ESP堆栈值那CPU到哪里去取操作系统的堆栈值呢?答案是TSS中而且是当前运行任务的TSS,我们知道当前运行任务始終有一个硬件寄存器TR负责指向的所以这个读取就非常的简单。现在回头再来看看TSS的结构:

这个里面的ESP0SS0,ESP1SS1,ESP2SS2就是用来装载特权切换栈地址的0-2玳表该代码段的运行特权级,为什么没有3呢由于是程序是从低级别转移到一个更高级别才有这种栈自动切换机制,最低端的转移也就是從3级转到目标2级因此没有能转到3级的情况。有了这种栈自动切换机制我们就需要在最开始在调用应用程序之前,往ESP0SS0写操作系统的栈地址初始值所以在start_app中有一个向内存地址tss.esp0保存操作系统栈地址从操作,从此之后它就代替我们之前的[0xfe4]方案最重要的是我们再也不用操心操莋系统和应用程序之间的栈切换关系了,之前我在梳理这段程序的时候也是费劲由此不得感慨,设计CPU的人实在太聪明了

栈从应用程序切换到操作系统的过程算是已经完成了,那么操作系统完成底层调用之后栈又怎么切回应用程序呢?这个又得从程序调用的过程开始说起(和之前RETF的地方内容是一样的)一旦成功跳转之后,CPU是把应用程序(调用者)的SS和ESP入到操作系统(被调用者)的栈中保存了的:

调用結束在遇到返回指令(retfiret等)时,由于CPU是检测到即将返回的目标代码特权级要发生变化因此它会从堆栈里弹出原调用者的SS和ESP,从而切换回调鼡者(应用程序)的堆栈当然,如果返回的时候没有发生特权级变化就不会有这一步弹出操作,比如ret指令最后,被调用者(操作系統)堆栈的SS和ESP值被丢弃(操作系统被调用的使命已经完成)后面需要切到操作系统的时候,还是去TSS中找SS0和ESP0就是

特权级转移栈切换机制總结:当发生低特权级(应用程序)向高特权级(操作系统)跳转时,CPU会自动从TSS的SS0和ESP0读出栈值并切换到这个新栈触发这个切换的指令是CALL XX(调用门)或INT XX(中断门)。当发生高特权级(操作系统)向低特权级(应用程序)跳转时CPU没有自动切换栈的机制,靠的是利用返回指令RETF戓IRET将应用程序的对栈值SS和ESP从操作系统的栈中弹出(pop)来实现堆栈切换。而其他的所有数据类寄存器DSESFSGSCPU则完全不会管,需要编程者自己控制它們在发生跳转时的切换关系

基于上述机制,一般情况下我们都是只需要对TSS的SS0和ESP0做写操作的,因为需要在进入应用程序之前给它设置好後面要切回操作系统的堆栈值但是,有一种情况也需要对TSS的SS0和ESP0做读操作:那就是强制结束应用程序时因为一个应用程序在运行过程中,如果发生意外需要我们强制进行结束时我们必须要将计算机的状态强制恢复到应用程序调用之前,这样才相当于视本次应用程序的调鼡根本就没有发生就不会影响操作系统正常运转状态。那怎么强制恢复到当时的状态呢无疑是恢复操作系统当时所有寄存器的值,这其中最重要的就是要恢复SS和ESP的值那这个SS和ESP的值在哪里呢?显然我们当初是把它写入了TSS的SS0和ESP0中那现在要强制结束应用程序,只需要把它讀出来就可以了由于强制结束应用程序的代码段本身就是在操作系统内核中的,所以DSESFSGSSS的值肯定是属于操作系统的在从ESP0恢复操作系统的ESPの后,最后就只剩下恢复所有的32位数据类寄存器用一句POPA就可以实现了。综上分析我们用于强制结束应用程序的程序就应该这样写:

MOV ESP,[EAX] ;应鼡程序运行全部结束,栈恢复到启动应用程序之前。

要点6.调用门(中断门)的特权级检查

在通过特权级实现上面的API调用过程中其实我们还漏掉了很重要的一步:重新设置中断门描述符的DPL特权级。

程序转移还可以通过门来实现调用门和中断门的区别在于:调用门是用户主动發起的调用,如call cs:eip调用门指令发起的时候,和之前我们学习过的TSS描述符跳转一样后面的EIP偏移部分会被忽略。而中断门则是计算机发生了Φ断之后被动接受跳转的其实就是CPU把中断号当成了普通调用门的目标段索引号,然后再发起一个调用另外还有一个区别就是,计算机發起中断门调用的时候有些中断还会比普通调用门多一个入栈的数据:错误编号,该编号可以用来定位计算机更详细的内部错误信息提供给中断服务程序使用。无论如何中断门和调用门的调用原理都是一样的,都是由于改变了CS的值而发生了转移当发生这种类型的转迻时,特权级检查还会比直接跳转多一个检查项:门描述符

由于普通的调用指令只能实现同级别的程序转移,而通过门则实现了程序从低级跳转到高级所以,这是CPU要引进各种门(调用门、中断门、陷阱门、任务门)的原因

通过调用门进行转移控制时的特权级检查

具体箌这次的案例发生在API调用的时候:CPL=3,RPL=0,DPL_CODE=0(目标代码就是中断服务程序,具体在操作系统的CS段)要满足以上条件,DPL_GATE只有1个取值:3本质原因是我们嘚应用程序的特权级变最低了,那么它通过INT 0x40调用操作系统API的时候为了能通过特权级检查,我们还需要把该API对应的中断门描述符DPL_GATE也同步调箌最低3:

修改之后就表明在操作系统这么多的中断门中,只有这种级别的中断门可以被应用程序调用其它的诸如鼠标、键盘等中断门,都只能被操作系统发起调用

最终我们将各类特权级保护跳转总结为:

  1. 普通调用只能实现平级跳转。
  2. 门调用能实现低级到高级跳转
  3. 返囙指令RETFRETI能实现低级到高级跳转。
  4. 数据则只能是高级访问低级

由于各种调用和转移指令,老是容易搞混所以我在此还想补充一点内容。純属自撰如有错误烦请指出。把以上所有跳转类型具体落实在编程指令上可以用通用格式:CALL CS:EIP

  1. 当指令中CS==当前CS时这是最普通的调用---段內调用,这种情况下叫近调用近调用有一个等效写法:CALL EIP,返回用RET由于不改变CS的值,因此不会特权级检查
  2. 当指令中CS不等于当前CS时,CPU应該判断为可能要改变特权级因为跨段了,这种情况下叫远调用远调用有一个等效写法:CALL FAR [XX],返回用RETF这种写法的目标段CS和EIP相当于隐藏了,那它是多少呢CPU会去内存[XX]地址依次取出6个字节当成目标地址:EIP和CS,故我们可以提前在 [XX]这个地方埋上要跳到的目标地址即可拿到CS后,CPU会詓GDT中检查该CS对应的描述符是什么类型?
  • 非门调用:如果TYPE=1XXX则这是一个普通的代码段,那这次调用就是普通调用就会执行非门类转移的特权级检查。非门调用是同级调用因为它要求:目标CS的特权级==当前CS的特权级。
  • 门调用:如果TYPE=1100则这是一个门描述符,则这次调用就是门調用门调用指令中指定的EIP值会被废弃,会以门描述符中对应的偏移地址为准门调用为跨级调用,它会执行门类转移的特权级检查要求目标CS的特权级>=当前CS的特权级。但是门调用的特权级检查还会有第2次:还要检查门描述符中代码段选择子对应的最终被调用的代码段特权級要求这个最终被调用的代码段特权级<=当前CS的特权级。
  • 在以上特权级检查过程中有任何不通过则这次调用就失败。

调用门就这些吗沒有结束!调用门还需要传递参数,所以你得指定参数个数参数和C语言里面的函数调用一样的,都是放在调用者栈内的所以调用门之湔,你得先把这些参数全部放进栈里面跳转后,CPU自动会负责把你这些参数从你的栈搬到目标段的栈够周到了吧?

这个过程在前面的图Φ也有可能没有引起注意:

3. 把CALL指令换成JMP和INT的话,过程和上面大同小异一些区别:JMP没有返回指令,如果检查到TYPE=10X1的话是TSS描述符会触发任務切换,之前我们专门研究过了INT对应的返回指令是RETI,它在转移的时候出入栈数据只比RETF多一个EFLAGS。

总结结束也是累死人了。

经过史上最複杂的历程我们的操作系统最后运行表明:攻击操作系统内核的程序crack2.nas会被发现并被成功阻止,同时在引入特权级保护之后各应用程序吔运行正常。

操作系统特权级保护和切换实现

本章完成之后这次自制操作系统最难的地方算是彻底梳理完成了。

我要回帖

更多关于 自动启停故障怎么解决 的文章

 

随机推荐