<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
	<channel>
		<atom:link href="http://gentoo-zh.org/extern.php?action=feed&amp;tid=483&amp;type=rss" rel="self" type="application/rss+xml" />
		<title><![CDATA[Gentoo中文社区 / x86架构：从实模式进入保护模式]]></title>
		<link>http://www.gentoo-zh.org/viewtopic.php?id=483</link>
		<description><![CDATA[x86架构：从实模式进入保护模式 最近发表的帖子。]]></description>
		<lastBuildDate>Wed, 12 Oct 2022 05:00:02 +0000</lastBuildDate>
		<generator>FluxBB</generator>
		<item>
			<title><![CDATA[x86架构：从实模式进入保护模式]]></title>
			<link>http://www.gentoo-zh.org/viewtopic.php?pid=490#p490</link>
			<description><![CDATA[<p>一、先聊聊保护模式的基本概念：为什么叫保护模式？到底保护了啥？是怎么保护的？</p><p> 1、保护模式：要搞定出保护模式的意义，需要理解另一个概念：实模式；实模式下，存在以下重大的安全隐患：</p><p>&#160; &#160; &#160;（1）内存没权限区别：用户程序可以访问任意内存，包括操作系统运行的内存、其他程序运行的内存（个人观点：这是万恶之源。病毒、木马、外挂都是想尽各种办法访问操作系统或其他进程的代码/数据，达到自己的某些特定目的），所有的代码和数据都是能查询和删改的，毫无隐私可言</p><p>&#160; &#160; &#160;（2）用户程序可以随意访问和删除段寄存器，达到和上述同样的效果</p><p>&#160; &#160; &#160;（3）对I/O端口无限制的访问，比如打开文件、发送网络数据包、打印到屏幕或分配内存等；</p><p>&#160; &#160; &#160;（4）可以执行所有指令</p><p>&#160; 2、保护模式保护了啥？ 本质上：</p><p>&#160; &#160; &#160;（1）限制应用程序对内存、寄存器的访问，保护操作系统、其他进程的代码/数据不被恶意访问、甚至删改！</p><p>&#160; &#160; &#160;（2）限制用户程序对某些I/O端口的访问。</p><p>&#160; &#160; &#160;（3）限制用户程序执行某些指令</p><p>&#160; 3、怎么保护的？</p><p>&#160; &#160; &#160; &#160;（1）通过GDT/LDT/IDT表、CS/DS/SS等段寄存器，严格限制用户程序对内存的读写：访问内存前，CS中的CPL先和selector选择子中的RPL对比，数值小的再和GDT表的DPL比，如果数值小于等于，说明是有权限的，才能继续访问对应的内存！（这里多说几句: 64位的windows已经平坦了，进程访问的虚拟地址已经拉通，段寄存器名存实亡；进程之间隔离主要通过CR3，把不同进程同样的虚拟地址映射到不同的物理地址；换句话说，内存保护主要靠页表、MMU等隔离不同进程的物理内存）<br /><span class="postimg"><img src="https://img2020.cnblogs.com/blog/2052730/202102/2052730-20210225113629065-1941658048.png" alt="FluxBB bbcode 测试" /></span> </p><p>附： <br />&#160; CPL：全称current privilege level，存放在代码段寄存器中（cs），代表当前执行程序的特权级；常见的调试器都能查到：一般都是11，即所谓的3环！<br /><span class="postimg"><img src="https://img2020.cnblogs.com/blog/2052730/202102/2052730-20210225121121570-1315433629.png" alt="FluxBB bbcode 测试" /></span> </p><p>&#160; RPL:&#160; 全称request privilege level，请求特权级，存放在段选择子中。注意，不是段寄存器中，不是段寄存器中，不是段寄存器中；它的含义是当前我想以 RPL 这个级别来请求你把这个段选择子置入段寄存器。实际上 RPL 并没有什么用（个人观点，仅供参考），因为请求者任何时刻都可以让 RPL = 0。但是如果请求者是 CPL = 0 的程序，用 RPL = 3 的级别来请求 DPL = 0的数据段，必然会失败；<br />&#160; DPL:&#160; 全称descriptor privilege level，存放在段描述符中，用于表示段的特权级</p><p>&#160; &#160; &#160; &#160;（2）要想访问必须通过操作系统内核，比如打开文件、发送网络数据包、打印到屏幕或分配内存</p><p>&#160; &#160; &#160;</p><p>二、这里简化一下说说要点：</p><p>1、 生成并加载GDT表</p><p>&#160; 实模式下任何进程可以无限制读写任何内存，甚至os的内存，毫无安全性可言；需要对用户进程读写内存的地址做严格限制，衍生出了保护模式；保护模式将内存分成不同的段，段基址、limit、各种属性存放在GDT表；用户程序读写段内存时需要先通过段寄存器的selector在GDT找到段描述符，查看是否有权限、偏移地址是否超过limit等。如果一切ok，可以继续读写段内数据；</p><p>&#160; cs、ds、ss段的描述符：<br /><span class="postimg"><img src="https://img2020.cnblogs.com/blog/2052730/202006/2052730-20200604151403191-277785221.png" alt="FluxBB bbcode 测试" /></span> </p><p>&#160; &#160; 注意点：（1）用户程序运行在3环，是没有权限更改GDT的，所以这种方式完全可以限制3环程序对内存的读写；windows要想改GDT，要么连接windbg，要么写驱动；</p><p>&#160; &#160; &#160; &#160; &#160; &#160; &#160; &#160; &#160; （2）lgdt [cs:gdt_size]，从操作数指向的地址取6字节，高4字节作为gdt的基址，低2字节作为gdt中描述符的个数，如下：<br /><span class="postimg"><img src="https://img2020.cnblogs.com/blog/2052730/202006/2052730-20200604151519462-892260349.png" alt="FluxBB bbcode 测试" /></span> </p><p>&#160; &#160; &#160; 这6字节早在编译时就确定了：<br /><span class="postimg"><img src="https://img2020.cnblogs.com/blog/2052730/202006/2052730-20200604151610240-1896131387.png" alt="FluxBB bbcode 测试" /></span> </p><p>&#160; &#160; &#160;（3）GDT的机制是CPU定的，操作系统负责运维和使用各段的数据 </p><p> </p><p>2、打开A20</p><p>&#160; &#160; &#160;8086下，地址线有20位，寻址范围从0x00000~0xfffff，超过0xfffff的地址会被cpu重新从0x00000开始，相当于丢掉进位（或把地址对0xfffff取模）。80286以后，地址总线扩展到24位，为了访问0x100000~0x10FFEF之间的内存，而不是象8086/8088那样回绕到0，需要开启A20地址线，方法如下：</p><div class="codebox"><pre><code>   mov dx,0x92                        ;南桥ICH芯片内的端口0x92
    in al,dx
    or al,0x02
    out dx,al                        ;打开A20</code></pre></div><p>3、开启保护模式</p><p>&#160; &#160; &#160;CPU提供的控制寄存器CR0~CR3用于控制CPU的运行模式。CR0第一位（0位）是保护模式允许位（Protection Enable，PE），如果把这个位置为1，那么处理器将会进入保护模式，核心代码如下：</p><div class="codebox"><pre><code>    cli                                ;关闭中断，但后面一直没打开
    
    mov eax,cr0
    or eax,0x01
    mov cr0,eax                        ;设置PE位，处理器进入保护模式</code></pre></div><br /><br /><p>&#160; &#160; 注意：要先调用cli把if置0，关闭中断，避免给CR0赋值时被打断；</p><p> </p><p>4、清空段寄存器中的缓存和旧流水线指令</p><p> （1）段寄存器实际上有96位，保护模式下的汇编指令只能操作位于低16位的selector，剩余80位中部分作为缓存（可用sreg命令查看段寄存器dh、dl的值，这些都是描述符的缓存），存储了段基址。只要段不改变，缓存就不会更新；但保护模式下不能直接用实模式的地址（权限不够、位数不对等），需要清空；</p><p>&#160; &#160;（2）cpu和内存的速度差异很大，为了提高效率，cpu会提前预测并执行某些分支指令（幽灵漏洞就是这么来的，细节可参考B站的一个科普视频：https://www.bilibili.com/video/BV1eW411i7ZM?from=search&amp;seid=2138940898008952062），这就是所谓的乱序执行，进入保护模式后这些指令的逻辑结果可能是有问题的，也要马上清除；</p><p>&#160; &#160; &#160;清除各个段寄存器、乱序指令缓存的办法：马上调用jmp指令跳转，让cpu认为当前各种缓存已经失效，核心代码如下：</p><div class="codebox"><pre><code>;保护模式
    jmp 0x0008:flush-$$                ;现在是在16位保护模式下，0x0008依然是段的选择子，而flush则是偏移地址
    [bits 32]
flush:                                ;头部加了vstart=0x7c00,这里变成了0x7c85，所以上面的jmp中的偏移要减去开头的基址，得到偏移    
    mov cx,0x0010                    
    mov ds,cx</code></pre></div><p>5、完整代码：</p><div class="codebox"><pre class="vscroll"><code>;---------------------保护模式主引导扇区程序---------------------
SECTION protectModel vstart=0x7c00 align=16    
    mov ax,0x00 
    mov ss,ax
    mov sp,0x7c00
    
    mov ax,[cs:gdt_base];ax=0x7e00
    mov dx,[cs:gdt_base+0x02];dx=0x0000;
    ;mov ax,[cs:gdt_base+0x7c00];这段已经被加载到0x7c00，所以需要加上；ax=0x7e00
    ;mov dx,[cs:gdt_base+0x7c00+0x02];dx=0x0000;
    mov bx,0x10
    div bx
    
    mov ds,ax                        ;得到base基地址，ds=0x7e0
    mov bx,dx                        ;得到偏移地址，这里是0x0000

;---------------------安装描述符---------------------
    ;描述符0
    mov dword [ebx+0x00],0x00        ;第一个描述符必须是0；这里默认是ds段，也就是gdt_base的基址
    mov dword [ebx+0x04],0x00        ;ebx是gdt表的偏移，ds是gdt的基址
    
    ;描述符1
    mov dword [ebx+0x08],0x7c0001FF
    mov dword [ebx+0x0c],0x00409800    ;基地址0x00007c00,段界限0x001FF,粒度是字节，
                                    ;长度是512字节，在内存中的32位段，特权级为0，只能执行的代码段
    ;描述符2
    mov dword [ebx+0x10],0x8000FFFF    
    mov dword [ebx+0x14],0x0040920B ;基地址0x000B8000,段界限0x0FFFF,粒度是字节，
                                    ;长度是64KB，在内存中的32位段，特权级为0，可以读写的向上拓展的数据段
    ;描述符3                                
    mov dword [ebx+0x18],0x00007A00    
    mov dword [ebx+0x1c],0x00409600 ;基地址0x00000000,段界限0x07A00,粒度是字节，
                                    ;在内存中的32位段，特权级为0，可以读写的向下拓展的栈段
                                    
    mov word [cs:gdt_size],31;写入GDT段界限，4个描述符是32个字节，所以界限就是31
    lgdt [cs:gdt_size]        ;load gdt
    ;mov word [cs:gdt_size+0x7c00],31;写入GDT段界限，4个描述符是32个字节，所以界限就是31
    ;lgdt [cs:gdt_size+0x7c00]        ;load gdt
    
    mov dx,0x92                        ;南桥ICH芯片内的端口0x92
    in al,dx
    or al,0x02
    out dx,al                        ;打开A20
    
    cli                                ;关闭中断，但后面一直没打开
    
    mov eax,cr0
    or eax,0x01
    mov cr0,eax                        ;设置PE位，处理器进入保护模式
    
    ;保护模式
    jmp 0x0008:flush-$$                ;现在是在16位保护模式下，0x0008依然是段的选择子，而flush则是偏移地址
    [bits 32]
flush:                                ;头部加了vstart=0x7c00,这里变成了0x7c85，所以上面的jmp中的偏移要减去开头的基址，得到偏移    
    mov cx,0x0010                    
    mov ds,cx
    
    ;以下在屏幕上显示&quot;Protect mode OK.&quot; 
    mov byte [0x00],&#039;P&#039;  
    mov byte [0x02],&#039;r&#039;
    mov byte [0x04],&#039;o&#039;
    mov byte [0x06],&#039;t&#039;
    mov byte [0x08],&#039;e&#039;
    mov byte [0x0a],&#039;c&#039;
    mov byte [0x0c],&#039;t&#039;
    mov byte [0x0e],&#039; &#039;
    mov byte [0x10],&#039;m&#039;
    mov byte [0x12],&#039;o&#039;
    mov byte [0x14],&#039;d&#039;
    mov byte [0x16],&#039;e&#039;
    mov byte [0x18],&#039; &#039;
    mov byte [0x1a],&#039;O&#039;
    mov byte [0x1c],&#039;K&#039;
         
    ;以下用简单的示例来帮助阐述32位保护模式下的堆栈操作 
     mov cx,00000000000_11_000B         ;加载堆栈段选择子
     mov ss,cx
     mov esp,0x7c00

     mov ebp,esp                        ;保存堆栈指针 
     push byte &#039;.&#039;                      ;压入立即数（字节）
     
     sub ebp,4
     cmp ebp,esp                        ;判断压入立即数时，ESP是否减4 
     jnz ghalt                          
     pop eax
     mov [0x1e],al                      ;显示句点 
      
  ghalt:     
         hlt                                ;已经禁止中断，将不会被唤醒 

;-------------------------------------------------------------------------------
     
    gdt_size    dw 0
    gdt_base    dd 0x00007e00        ;GDT的物理地址,主引导扇区是512个字节，这个地址刚好在主引导扇区之后 
                             
    times 510-($-$$) db 0
                     db 0x55,0xaa</code></pre></div><p>说明：</p><p>（1）整段代码被加载到0x7c00处，所以在开头额外加vstart=0x7c00，让nasm从这个地址开始编译；</p><p>&#160; &#160; 紧接着这4行代码也要更改：去掉0x7c00，因为gdt_base和gdt_size已经相对0x7c00计算偏移</p><p>&#160; mov ax,[cs:gdt_base+0x7c00]<br />&#160; &#160;mov dx,[cs:gdt_base+0x7c00+0x02]</p><p>&#160; mov word [cs:gdt_size+0x7c00],31;写入GDT段界限，4个描述符是32个字节，所以界限就是31<br />&#160; &#160;lgdt [cs:gdt_size+0x7c00]&#160; &#160; &#160;</p><p>（2）flush也是相对0x7c00开始计算偏移的，具体偏移是0x7c85，远超代码段的limit，导致出错异常，又跳回biso启动处执行<br /><span class="postimg"><img src="https://img2020.cnblogs.com/blog/2052730/202006/2052730-20200604145925462-1701187500.png" alt="FluxBB bbcode 测试" /></span> </p><p>解决办法也简单，直接改成jmp 0x0008:flush-$$ 即可，让后面的偏移值相对于段开始的地方，这次对了，如下：<br /><span class="postimg"><img src="https://img2020.cnblogs.com/blog/2052730/202006/2052730-20200604150350218-1371873647.png" alt="FluxBB bbcode 测试" /></span> </p><p>&#160; （3）81行代码：push byte &#039;.&#039;&#160; ，但nasm还是编译成push 0x0000002e, 估计是为了内存对齐<br /><span class="postimg"><img src="https://img2020.cnblogs.com/blog/2052730/202006/2052730-20200604151231152-500169496.png" alt="FluxBB bbcode 测试" /></span> </p><p>&#160; &#160; （4）效果：在频幕上打印一行字<br /><span class="postimg"><img src="https://img2020.cnblogs.com/blog/2052730/202006/2052730-20200604151751530-1737906203.png" alt="FluxBB bbcode 测试" /></span> </p><p> 参考：</p><p>1、https://manybutfinite.com/post/cpu-rings-privilege-and-protection/&#160; cpu运行级别和保护机制</p><p>2、 <a href="https://www.cnblogs.com/Philip-Tell-Truth/p/5211248.html" rel="nofollow">https://www.cnblogs.com/Philip-Tell-Tru … 11248.html</a>&#160; &#160;</p><p>3、x86汇编：从实模式到保护模式</p><p>4、 <a href="https://blog.csdn.net/q1007729991/article/details/52727332" rel="nofollow">https://blog.csdn.net/q1007729991/artic … s/52727332</a>&#160; cpu特权等级</p>]]></description>
			<author><![CDATA[dummy@example.com (batsom)]]></author>
			<pubDate>Wed, 12 Oct 2022 05:00:02 +0000</pubDate>
			<guid>http://www.gentoo-zh.org/viewtopic.php?pid=490#p490</guid>
		</item>
	</channel>
</rss>
