问题:
使用调用门如何实现不同特权级代码之间的跳转(如:从高特权级到低特权级)?
不幸的事实:
调用门只支持从低特权级到高特权级执行
无法利用调用门从高特权级到低特权级执行
从高特权级的代码段通过return far可以返回到低特权级的代码段执行。这时return far是一个跳转指令,完成从高特权级到低特权级的跳转,这正是我们想要的。
return的本质是做跳转的,而不是我们根深蒂固的做返回的。只是最常用的方式是做返回使用。
思路整理:
调用门的特权级跳转:
1、通过远调用(call far),低特权级 -> 高特权级
2、通过远返回(retf),高特权级 -> 低特权级
retf的本质就是恢复cs和eip的值,因此,我们需要首先将cs和eip的值放入栈中。
需要提前知道的事实:
x86处理器对于不同的特权级需要使用不同的栈
每一个特权级对应一个私有的栈(最多四个栈)
特权级跳转变化之前必须指定好相应的栈
解决方案(高特权级 -> 低特权级)
1、指定目标栈段选择子(push)
2、指定栈顶指针位置(push)
3、指定目标代码段选择子(push)
4、指定目标代码段偏移(push)
5、跳转(retf)
实验1:
1 %include "inc.asm" 2 3 org 0x9000 4 5 jmp ENTRY_SEGMENT 6 7 [section .gdt] 8 ; GDT definition 9 ; 段基址, 段界限, 段属性 10 GDT_ENTRY : Descriptor 0, 0, 0 11 CODE32_DESC : Descriptor 0, Code32SegLen - 1, DA_C + DA_32 + DA_DPL3 12 VIDEO_DESC : Descriptor 0xB8000, 0x07FFF, DA_DRWA + DA_32 + DA_DPL3 13 DATA32_DESC : Descriptor 0, Data32SegLen - 1, DA_DR + DA_32 + DA_DPL3 14 STACK32_DESC : Descriptor 0, TopOfStack16, DA_DRW + DA_32 + DA_DPL3 15 ; GDT end 16 17 GdtLen equ $ - GDT_ENTRY 18 19 GdtPtr: 20 dw GdtLen - 1 21 dd 0 22 23 24 ; GDT Selector 25 26 Code32Selector equ (0x0001 << 3) + SA_TIG + SA_RPL3 27 VideoSelector equ (0x0002 << 3) + SA_TIG + SA_RPL3 28 Data32Selector equ (0x0003 << 3) + SA_TIG + SA_RPL3 29 Stack32Selector equ (0x0004 << 3) + SA_TIG + SA_RPL3 30 31 ; end of [section .gdt] 32 33 TopOfStack16 equ 0x7c00 34 35 [section .s16] 36 [bits 16] 37 ENTRY_SEGMENT: 38 mov ax, cs 39 mov ds, ax 40 mov es, ax 41 mov ss, ax 42 mov sp, TopOfStack16 43 44 ; initialize GDT for 32 bits code segment 45 mov esi, CODE32_SEGMENT 46 mov edi, CODE32_DESC 47 48 call InitDescItem 49 50 mov esi, DATA32_SEGMENT 51 mov edi, DATA32_DESC 52 53 call InitDescItem 54 55 mov esi, STACK32_SEGMENT 56 mov edi, STACK32_DESC 57 58 call InitDescItem 59 60 ; initialize GDT pointer struct 61 mov eax, 0 62 mov ax, ds 63 shl eax, 4 64 add eax, GDT_ENTRY 65 mov dword [GdtPtr + 2], eax 66 67 ; 1. load GDT 68 lgdt [GdtPtr] 69 70 ; 2. close interrupt 71 cli 72 73 ; 3. open A20 74 in al, 0x92 75 or al, 00000010b 76 out 0x92, al 77 78 ; 4. enter protect mode 79 mov eax, cr0 80 or eax, 0x01 81 mov cr0, eax 82 83 ; 5. jump to 32 bits code 84 ; jmp dword Code32Selector : 0 85 push Stack32Selector ; mu biao zhan duan xuan ze zi 86 push TopOfStack32 ; zhan ding zhi zhen wei zhi 87 push Code32Selector ; mu biao dai ma duan xuan ze zi 88 push 0 ; mu bioa dai ma duan pian yi 89 retf 90 91 92 93 ; esi --> code segment label 94 ; edi --> descriptor label 95 InitDescItem: 96 push eax 97 98 mov eax, 0 99 mov ax, cs 100 shl eax, 4 101 add eax, esi 102 mov word [edi + 2], ax 103 shr eax, 16 104 mov byte [edi + 4], al 105 mov byte [edi + 7], ah 106 107 pop eax 108 109 ret 110 111 [section .dat] 112 [bits 32] 113 DATA32_SEGMENT: 114 DTOS db "D.T.OS!",0 115 DTOS_OFFSET equ DTOS - $$ 116 117 Data32SegLen equ $ - DATA32_SEGMENT 118 119 120 [section .s32] 121 [bits 32] 122 CODE32_SEGMENT: 123 mov ax, VideoSelector 124 mov gs, ax 125 126 mov ax, Data32Selector 127 mov ds, ax 128 129 mov ax, Stack32Selector 130 mov ss, ax 131 132 mov ax, Data32Selector 133 mov ds, ax 134 135 mov ebp, DTOS_OFFSET 136 mov bx, 0x0C 137 mov dh, 12 138 mov dl, 33 139 140 call PrintString 141 142 jmp $ 143 144 145 ; ds:ebp --> string address 146 ; bx --> attribute 147 ; dx --> dh : row, dl : col 148 PrintString: 149 push ebp 150 push eax 151 push edi 152 push cx 153 push dx 154 155 print: 156 mov cl, [ds:ebp] 157 cmp cl, 0 158 je end 159 mov eax, 80 160 mul dh 161 add al, dl 162 shl eax, 1 163 mov edi, eax 164 mov ah, bl 165 mov al, cl 166 mov [gs:edi], ax 167 inc ebp 168 inc dl 169 jmp print 170 171 end: 172 pop dx 173 pop cx 174 pop edi 175 pop eax 176 pop ebp 177 178 ret 179 180 Code32SegLen equ $ - CODE32_SEGMENT 181 182 [section .gs] 183 [bits 32] 184 STACK32_SEGMENT: 185 times 1024 * 4 db 0 186 187 Stack32SegLen equ $ - STACK32_SEGMENT 188 TopOfStack32 equ Stack32SegLen - 1
11-14行我们给每一个段加上了特权级DA_DPL3,同时26-29行也必须加上DA_RPL3。85-89是我们新添加的代码,运行结果如下:
成功的从高特权级跳到了低特权级。
将程序改成下面的样子可以得到同样的结果:
1 %include "inc.asm" 2 3 org 0x9000 4 5 jmp ENTRY_SEGMENT 6 7 [section .gdt] 8 ; GDT definition 9 ; 段基址, 段界限, 段属性 10 GDT_ENTRY : Descriptor 0, 0, 0 11 CODE32_DESC : Descriptor 0, Code32SegLen - 1, DA_C + DA_32 + DA_DPL0 12 VIDEO_DESC : Descriptor 0xB8000, 0x07FFF, DA_DRWA + DA_32 + DA_DPL0 13 DATA32_DESC : Descriptor 0, Data32SegLen - 1, DA_DR + DA_32 + DA_DPL0 14 STACK32_DESC : Descriptor 0, TopOfStack16, DA_DRW + DA_32 + DA_DPL0 15 ; GDT end 16 17 GdtLen equ $ - GDT_ENTRY 18 19 GdtPtr: 20 dw GdtLen - 1 21 dd 0 22 23 24 ; GDT Selector 25 26 Code32Selector equ (0x0001 << 3) + SA_TIG + SA_RPL0 27 VideoSelector equ (0x0002 << 3) + SA_TIG + SA_RPL0 28 Data32Selector equ (0x0003 << 3) + SA_TIG + SA_RPL0 29 Stack32Selector equ (0x0004 << 3) + SA_TIG + SA_RPL0 30 31 ; end of [section .gdt] 32 33 TopOfStack16 equ 0x7c00 34 35 [section .s16] 36 [bits 16] 37 ENTRY_SEGMENT: 38 mov ax, cs 39 mov ds, ax 40 mov es, ax 41 mov ss, ax 42 mov sp, TopOfStack16 43 44 ; initialize GDT for 32 bits code segment 45 mov esi, CODE32_SEGMENT 46 mov edi, CODE32_DESC 47 48 call InitDescItem 49 50 mov esi, DATA32_SEGMENT 51 mov edi, DATA32_DESC 52 53 call InitDescItem 54 55 mov esi, STACK32_SEGMENT 56 mov edi, STACK32_DESC 57 58 call InitDescItem 59 60 ; initialize GDT pointer struct 61 mov eax, 0 62 mov ax, ds 63 shl eax, 4 64 add eax, GDT_ENTRY 65 mov dword [GdtPtr + 2], eax 66 67 ; 1. load GDT 68 lgdt [GdtPtr] 69 70 ; 2. close interrupt 71 cli 72 73 ; 3. open A20 74 in al, 0x92 75 or al, 00000010b 76 out 0x92, al 77 78 ; 4. enter protect mode 79 mov eax, cr0 80 or eax, 0x01 81 mov cr0, eax 82 83 ; 5. jump to 32 bits code 84 ; jmp dword Code32Selector : 0 85 ; push Stack32Selector ; mu biao zhan duan xuan ze zi 86 ; push TopOfStack32 ; zhan ding zhi zhen wei zhi 87 push Code32Selector ; mu biao dai ma duan xuan ze zi 88 push 0 ; mu bioa dai ma duan pian yi 89 retf 90 91 92 93 ; esi --> code segment label 94 ; edi --> descriptor label 95 InitDescItem: 96 push eax 97 98 mov eax, 0 99 mov ax, cs 100 shl eax, 4 101 add eax, esi 102 mov word [edi + 2], ax 103 shr eax, 16 104 mov byte [edi + 4], al 105 mov byte [edi + 7], ah 106 107 pop eax 108 109 ret 110 111 [section .dat] 112 [bits 32] 113 DATA32_SEGMENT: 114 DTOS db "D.T.OS!",0 115 DTOS_OFFSET equ DTOS - $$ 116 117 Data32SegLen equ $ - DATA32_SEGMENT 118 119 120 [section .s32] 121 [bits 32] 122 CODE32_SEGMENT: 123 mov ax, VideoSelector 124 mov gs, ax 125 126 mov ax, Data32Selector 127 mov ds, ax 128 129 mov ax, Stack32Selector 130 mov ss, ax 131 132 mov ax, Data32Selector 133 mov ds, ax 134 135 mov ebp, DTOS_OFFSET 136 mov bx, 0x0C 137 mov dh, 12 138 mov dl, 33 139 140 call PrintString 141 142 jmp $ 143 144 145 ; ds:ebp --> string address 146 ; bx --> attribute 147 ; dx --> dh : row, dl : col 148 PrintString: 149 push ebp 150 push eax 151 push edi 152 push cx 153 push dx 154 155 print: 156 mov cl, [ds:ebp] 157 cmp cl, 0 158 je end 159 mov eax, 80 160 mul dh 161 add al, dl 162 shl eax, 1 163 mov edi, eax 164 mov ah, bl 165 mov al, cl 166 mov [gs:edi], ax 167 inc ebp 168 inc dl 169 jmp print 170 171 end: 172 pop dx 173 pop cx 174 pop edi 175 pop eax 176 pop ebp 177 178 ret 179 180 Code32SegLen equ $ - CODE32_SEGMENT 181 182 [section .gs] 183 [bits 32] 184 STACK32_SEGMENT: 185 times 1024 * 4 db 0 186 187 Stack32SegLen equ $ - STACK32_SEGMENT 188 TopOfStack32 equ Stack32SegLen - 1
这个实验告诉我们:
1、retf就是一个跳转指令,87-89行的代码与84行是等价的。
2、在相同的特权级之间跳转时不需要栈发生变化的。
3、特权级改变时一定要指定栈,要不然程序就会发生崩溃
单步实验:
首先用ndisasm -o 0x9000 loader > loader.txt进行反汇编,找到retf的断点位置0x90A6。
启动bochs开始执行。
运行到0x90A6时结果如下:
可以看到此时cs寄存器的最后两位是0,这正是默认的特权级,继续执行。
执行了retf过后,结果如下:
此时cs的最后一个字节为b,可以算出最后两位为11,确实跳转到了特权级为3的代码段了。
小结:
调用门只支持从低特权级跳转到高特权级
利用远返回(retf)可以从高特权级转移到低特权级
x86处理器每一个特权级对应一个私有的栈
特权级跳转变化之前必须指定好相应的栈