特权级 linux,操作系统-特权级与内核安全示例

Q:通常情况下选择子中的RPL与对应描述符中的DPL相同,那么是否可以取缔RPL?

RPL是保证内核数据安全的关键要素之一;在内核代码中有决定性作用,绝对不能取缔

A.获取操作系统的内核

8d91cd1d7524764c0a927ebcc16aa6db.png

该实验主要通过调用门调用将内核数据进行打印

4f8a61ce0dd3b585101a959e437043f4.png

a47b0b9cb347db15facbbed9a20c0fe3.png

具体的实现代码如下

%include "inc.asm"

org 0x9000

jmp ENTRY_SEGMENT

[section .gdt]

; GDT definition

; 段基址, 段界限, 段属性

GDT_ENTRY : Descriptor 0, 0, 0

CODE32_DESC : Descriptor 0, Code32SegLen - 1, DA_C + DA_32 + DA_DPL3

VIDEO_DESC : Descriptor 0xB8000, 0x07FFF, DA_DRWA + DA_32 + DA_DPL3

DATA32_KERNEL_DESC : Descriptor 0, Data32KernelSegLen - 1, DA_DRW + DA_32 + DA_DPL0

DATA32_USER_DESC : Descriptor 0, Data32UserSegLen - 1, DA_DRW + DA_32 + DA_DPL3

STACK32_KERNEL_DESC : Descriptor 0, TopOfKernelStack32, DA_DRW + DA_32 + DA_DPL0

STACK32_USER_DESC : Descriptor 0, TopOfUserStack32, DA_DRW + DA_32 + DA_DPL3

TSS_DESC : Descriptor 0, TSSLen - 1, DA_386TSS + DA_DPL0

FUNCTION_DESC : Descriptor 0, FunctionSegLen - 1, DA_C + DA_32 + DA_DPL0;内核函数段

; Call Gate

; 选择子, 偏移, 参数个数, 属性

FUNC_GETKERNELDATA_DESC : Gate FunctionSelector, GetKernelData, 0, DA_386CGate + DA_DPL3

; GDT end

GdtLen equ $ - GDT_ENTRY

GdtPtr:

dw GdtLen - 1

dd 0

; GDT Selector

Code32Selector equ (0x0001 << 3) + SA_TIG + SA_RPL3

VideoSelector equ (0x0002 << 3) + SA_TIG + SA_RPL3

KernelData32Selector equ (0x0003 << 3) + SA_TIG + SA_RPL0

UserData32Selector equ (0x0004 << 3) + SA_TIG + SA_RPL3

KernelStack32Selector equ (0x0005 << 3) + SA_TIG + SA_RPL0

UserStack32Selector equ (0x0006 << 3) + SA_TIG + SA_RPL3

TSSSelector equ (0x0007 << 3) + SA_TIG + SA_RPL0

FunctionSelector equ (0x0008 << 3) + SA_TIG + SA_RPL0

; Gate Selector

GetKernelDataSelector equ (0x0009 << 3) + SA_TIG + SA_RPL3

; end of [section .gdt]

TopOfStack16 equ 0x7c00

[section .s16]

[bits 16]

ENTRY_SEGMENT:

mov ax, cs

mov ds, ax

mov es, ax

mov ss, ax

mov sp, TopOfStack16

; initialize GDT for 32 bits code segment

mov esi, CODE32_SEGMENT

mov edi, CODE32_DESC

call InitDescItem

mov esi, DATA32_KERNEL_SEGMENT

mov edi, DATA32_KERNEL_DESC

call InitDescItem

mov esi, DATA32_USER_SEGMENT

mov edi, DATA32_USER_DESC

call InitDescItem

mov esi, STACK32_KERNEL_SEGMENT

mov edi, STACK32_KERNEL_DESC

call InitDescItem

mov esi, STACK32_USER_SEGMENT

mov edi, STACK32_USER_DESC

call InitDescItem

mov esi, FUNCTION_SEGMENT

mov edi, FUNCTION_DESC

call InitDescItem

mov esi, TSS_SEGMENT

mov edi, TSS_DESC

call InitDescItem

; initialize GDT pointer struct

mov eax, 0

mov ax, ds

shl eax, 4

add eax, GDT_ENTRY

mov dword [GdtPtr + 2], eax

; 1. load GDT

lgdt [GdtPtr]

; 2. close interrupt

cli

; 3. open A20

in al, 0x92

or al, 00000010b

out 0x92, al

; 4. enter protect mode

mov eax, cr0

or eax, 0x01

mov cr0, eax

; 5. load TSS

mov ax, TSSSelector

ltr ax

; 6. jump to 32 bits code

;jmp word Code32Selector : 0

push UserStack32Selector

push TopOfUserStack32

push Code32Selector

push 0

retf

; esi --> code segment label

; edi --> descriptor label

InitDescItem:

push eax

mov eax, 0

mov ax, cs

shl eax, 4

add eax, esi

mov word [edi + 2], ax

shr eax, 16

mov byte [edi + 4], al

mov byte [edi + 7], ah

pop eax

ret

[section .kdat]

[bits 32]

DATA32_KERNEL_SEGMENT:

KDAT db "Kernel Data", 0

KDAT_LEN equ $ - KDAT

KDAT_OFFSET equ KDAT - $$

Data32KernelSegLen equ $ - DATA32_KERNEL_SEGMENT

[section .udat]

[bits 32]

DATA32_USER_SEGMENT:

UDAT times 16 db 0

UDAT_LEN equ $ - UDAT

UDAT_OFFSET equ UDAT - $$

Data32UserSegLen equ $ - DATA32_USER_SEGMENT

[section .tss]

[bits 32]

TSS_SEGMENT:

dd 0

dd TopOfKernelStack32 ; 0

dd KernelStack32Selector ;

dd 0 ; 1

dd 0 ;

dd 0 ; 2

dd 0 ;

times 4 * 18 dd 0

dw 0

dw $ - TSS_SEGMENT + 2

db 0xFF

TSSLen equ $ - TSS_SEGMENT

[section .s32]

[bits 32]

CODE32_SEGMENT:

mov ax, VideoSelector

mov gs, ax

mov ax, UserData32Selector

mov es, ax

mov di, UDAT_OFFSET

call GetKernelDataSelector : 0

mov ax, UserData32Selector ; eip ==> 0x17

mov ds, ax

mov ebp, UDAT_OFFSET

mov bx, 0x0C

mov dh, 12

mov dl, 33

call PrintString

jmp $

; ds:ebp --> string address

; bx --> attribute

; dx --> dh : row, dl : col

PrintString:

push ebp

push eax

push edi

push cx

push dx

print:

mov cl, [ds:ebp]

cmp cl, 0

je end

mov eax, 80

mul dh

add al, dl

shl eax, 1

mov edi, eax

mov ah, bl

mov al, cl

mov [gs:edi], ax

inc ebp

inc dl

jmp print

end:

pop dx

pop cx

pop edi

pop eax

pop ebp

ret

Code32SegLen equ $ - CODE32_SEGMENT

[section .func]

[bits 32]

FUNCTION_SEGMENT:

; es:di --> data buffer

GetKernelDataFunc:

mov ax, KernelData32Selector

mov ds, ax

mov si, KDAT_OFFSET

mov cx, KDAT_LEN

call KMemCpy

retf

; ds:si --> source

; es:di --> destination

; cx --> length

KMemCpy:

cmp si, di

ja btoe

add si, cx

add di, cx

dec si

dec di

jmp etob

btoe:

cmp cx, 0

jz done

mov al, [ds:si]

mov byte [es:di], al

inc si

inc di

dec cx

jmp btoe

etob:

cmp cx, 0

jz done

mov al, [ds:si]

mov byte [es:di], al

dec si

dec di

dec cx

jmp etob

done:

ret

GetKernelData equ GetKernelDataFunc - $$

FunctionSegLen equ $ - FUNCTION_SEGMENT

[section .kgs]

[bits 32]

STACK32_KERNEL_SEGMENT:

times 256 db 0

Stack32KernelSegLen equ $ - STACK32_KERNEL_SEGMENT

TopOfKernelStack32 equ Stack32KernelSegLen - 1

[section .ugs]

[bits 32]

STACK32_USER_SEGMENT:

times 256 db 0c

Stack32UserSegLen equ $ - STACK32_USER_SEGMENT

TopOfUserStack32 equ Stack32UserSegLen - 1

实现结果

0b4b91fa775834ab80975d5eba6a5581.png

从结果中可以看到内核数据被用户进行了拷贝,导致内核数据不安全

B.对上述出现的错误进行改进

初步的解决方法-提出一个检查函数

1.获取段寄存器中RPL的值

2.判断RPL的值是否为SA_RPL0;true则为检查通过,可以继续访问数据,如果为false,特权级较低,出发异常

具体实现是在[section .func] [bits 32]中定义一个检查函数,并在拷贝之前将其进行调用,对RPL的值进行判断

代码如下

[section .func]

[bits 32]

FUNCTION_SEGMENT:

; es:di --> data buffer

GetKernelDataFunc:

mov cx, [esp + 4]

and cx, 0x0003

mov ax, es

and ax, 0xFFFC

or ax, cx

mov es, ax

mov ax, KernelData32Selector

mov ds, ax

mov si, KDAT_OFFSET

mov cx, KDAT_LEN

call KMemCpy

retf

; ds:si --> source

; es:di --> destination

; cx --> length

KMemCpy:

mov ax, es

call CheckRPL;进行调用

cmp si, di

ja btoe

add si, cx

add di, cx

dec si

dec di

jmp etob

btoe:

cmp cx, 0

jz done

mov al, [ds:si]

mov byte [es:di], al

inc si

inc di

dec cx

jmp btoe

etob:

cmp cx, 0

jz done

mov al, [ds:si]

mov byte [es:di], al

dec si

dec di

dec cx

jmp etob

done:

ret

; ax --> selector value

;检查函数

CheckRPL:

and ax, 0x0003;进行与操作

cmp ax, SA_RPL0;进行比较操作

jz valid;进行判断跳转

mov ax, 0

mov fs, ax

mov byte [fs:0], 0;触发异常

代码运行结果

5e41cb0b23b83fb079cb0bcfc22704b5.png

从运行结果可以看出,代码没有对内核数据进行拷贝,同时检查函数起到作用, 对RPL的值进行判断,并触发异常进行打印。但是对用户的选择子RPL的值进行修改伪造,将其改为0,又可以将内核数据进行拷贝。

C.用户程序可以通过伪造的选择子中的RPL的值,从而绕开安全检查的机制,在这里需要提出新的解决方案

1.在栈中获取函数远调用前CS寄存器的值(请求者)

2.从之前CS寄存器的值获取RPL

3.用RPL更新到数据缓冲区对应的段寄存器中

4.实验检查函数CheckRPL对段寄存器进行安全检查

f9b7bc68a68890a24d5d4079437a661c.png

代码

%include "inc.asm"

org 0x9000

jmp ENTRY_SEGMENT

[section .gdt]

; GDT definition

; 段基址, 段界限, 段属性

GDT_ENTRY : Descriptor 0, 0, 0

CODE32_DESC : Descriptor 0, Code32SegLen - 1, DA_C + DA_32 + DA_DPL3

VIDEO_DESC : Descriptor 0xB8000, 0x07FFF, DA_DRWA + DA_32 + DA_DPL3

DATA32_KERNEL_DESC : Descriptor 0, Data32KernelSegLen - 1, DA_DRW + DA_32 + DA_DPL0

DATA32_USER_DESC : Descriptor 0, Data32UserSegLen - 1, DA_DRW + DA_32 + DA_DPL3

STACK32_KERNEL_DESC : Descriptor 0, TopOfKernelStack32, DA_DRW + DA_32 + DA_DPL0

STACK32_USER_DESC : Descriptor 0, TopOfUserStack32, DA_DRW + DA_32 + DA_DPL3

TSS_DESC : Descriptor 0, TSSLen - 1, DA_386TSS + DA_DPL0

FUNCTION_DESC : Descriptor 0, FunctionSegLen - 1, DA_C + DA_32 + DA_DPL0;内核函数段

; Call Gate

; 选择子, 偏移, 参数个数, 属性

FUNC_GETKERNELDATA_DESC : Gate FunctionSelector, GetKernelData, 0, DA_386CGate + DA_DPL3

; GDT end

GdtLen equ $ - GDT_ENTRY

GdtPtr:

dw GdtLen - 1

dd 0

; GDT Selector

Code32Selector equ (0x0001 << 3) + SA_TIG + SA_RPL3

VideoSelector equ (0x0002 << 3) + SA_TIG + SA_RPL3

KernelData32Selector equ (0x0003 << 3) + SA_TIG + SA_RPL0

UserData32Selector equ (0x0004 << 3) + SA_TIG + SA_RPL3

KernelStack32Selector equ (0x0005 << 3) + SA_TIG + SA_RPL0

UserStack32Selector equ (0x0006 << 3) + SA_TIG + SA_RPL3

TSSSelector equ (0x0007 << 3) + SA_TIG + SA_RPL0

FunctionSelector equ (0x0008 << 3) + SA_TIG + SA_RPL0

; Gate Selector

GetKernelDataSelector equ (0x0009 << 3) + SA_TIG + SA_RPL3

; end of [section .gdt]

TopOfStack16 equ 0x7c00

[section .s16]

[bits 16]

ENTRY_SEGMENT:

mov ax, cs

mov ds, ax

mov es, ax

mov ss, ax

mov sp, TopOfStack16

; initialize GDT for 32 bits code segment

mov esi, CODE32_SEGMENT

mov edi, CODE32_DESC

call InitDescItem

mov esi, DATA32_KERNEL_SEGMENT

mov edi, DATA32_KERNEL_DESC

call InitDescItem

mov esi, DATA32_USER_SEGMENT

mov edi, DATA32_USER_DESC

call InitDescItem

mov esi, STACK32_KERNEL_SEGMENT

mov edi, STACK32_KERNEL_DESC

call InitDescItem

mov esi, STACK32_USER_SEGMENT

mov edi, STACK32_USER_DESC

call InitDescItem

mov esi, FUNCTION_SEGMENT

mov edi, FUNCTION_DESC

call InitDescItem

mov esi, TSS_SEGMENT

mov edi, TSS_DESC

call InitDescItem

; initialize GDT pointer struct

mov eax, 0

mov ax, ds

shl eax, 4

add eax, GDT_ENTRY

mov dword [GdtPtr + 2], eax

; 1. load GDT

lgdt [GdtPtr]

; 2. close interrupt

cli

; 3. open A20

in al, 0x92

or al, 00000010b

out 0x92, al

; 4. enter protect mode

mov eax, cr0

or eax, 0x01

mov cr0, eax

; 5. load TSS

mov ax, TSSSelector

ltr ax

; 6. jump to 32 bits code

;jmp word Code32Selector : 0

push UserStack32Selector

push TopOfUserStack32

push Code32Selector

push 0

retf

; esi --> code segment label

; edi --> descriptor label

InitDescItem:

push eax

mov eax, 0

mov ax, cs

shl eax, 4

add eax, esi

mov word [edi + 2], ax

shr eax, 16

mov byte [edi + 4], al

mov byte [edi + 7], ah

pop eax

ret

[section .kdat]

[bits 32]

DATA32_KERNEL_SEGMENT:

KDAT db "Kernel Data", 0

KDAT_LEN equ $ - KDAT

KDAT_OFFSET equ KDAT - $$

Data32KernelSegLen equ $ - DATA32_KERNEL_SEGMENT

[section .udat]

[bits 32]

DATA32_USER_SEGMENT:

UDAT times 16 db 0

UDAT_LEN equ $ - UDAT

UDAT_OFFSET equ UDAT - $$

Data32UserSegLen equ $ - DATA32_USER_SEGMENT

[section .tss]

[bits 32]

TSS_SEGMENT:

dd 0

dd TopOfKernelStack32 ; 0

dd KernelStack32Selector ;

dd 0 ; 1

dd 0 ;

dd 0 ; 2

dd 0 ;

times 4 * 18 dd 0

dw 0

dw $ - TSS_SEGMENT + 2

db 0xFF

TSSLen equ $ - TSS_SEGMENT

[section .s32]

[bits 32]

CODE32_SEGMENT:

mov ax, VideoSelector

mov gs, ax

mov ax, UserData32Selector

mov es, ax

mov di, UDAT_OFFSET

call GetKernelDataSelector : 0

mov ax, UserData32Selector ; eip ==> 0x17

mov ds, ax

mov ebp, UDAT_OFFSET

mov bx, 0x0C

mov dh, 12

mov dl, 33

call PrintString

jmp $

; ds:ebp --> string address

; bx --> attribute

; dx --> dh : row, dl : col

PrintString:

push ebp

push eax

push edi

push cx

push dx

print:

mov cl, [ds:ebp]

cmp cl, 0

je end

mov eax, 80

mul dh

add al, dl

shl eax, 1

mov edi, eax

mov ah, bl

mov al, cl

mov [gs:edi], ax

inc ebp

inc dl

jmp print

end:

pop dx

pop cx

pop edi

pop eax

pop ebp

ret

Code32SegLen equ $ - CODE32_SEGMENT

[section .func]

[bits 32]

FUNCTION_SEGMENT:

; es:di --> data buffer

GetKernelDataFunc:

mov cx, [esp + 4]

and cx, 0x0003

mov ax, es

and ax, 0xFFFC

or ax, cx

mov es, ax

mov ax, KernelData32Selector

mov ds, ax

mov si, KDAT_OFFSET

mov cx, KDAT_LEN

call KMemCpy

retf

; ds:si --> source

; es:di --> destination

; cx --> length

KMemCpy:

mov ax, es

call CheckRPL;进行调用

cmp si, di

ja btoe

add si, cx

add di, cx

dec si

dec di

jmp etob

btoe:

cmp cx, 0

jz done

mov al, [ds:si]

mov byte [es:di], al

inc si

inc di

dec cx

jmp btoe

etob:

cmp cx, 0

jz done

mov al, [ds:si]

mov byte [es:di], al

dec si

dec di

dec cx

jmp etob

done:

ret

; ax --> selector value

;检查函数

CheckRPL:

and ax, 0x0003;进行与操作

cmp ax, SA_RPL0;进行比较操作

jz valid;进行判断跳转

mov ax, 0

mov fs, ax

mov byte [fs:0], 0;触发异常

valid:

ret

GetKernelData equ GetKernelDataFunc - $$

FunctionSegLen equ $ - FUNCTION_SEGMENT

[section .kgs]

[bits 32]

STACK32_KERNEL_SEGMENT:

times 256 db 0

Stack32KernelSegLen equ $ - STACK32_KERNEL_SEGMENT

TopOfKernelStack32 equ Stack32KernelSegLen - 1

[section .ugs]

[bits 32]

STACK32_USER_SEGMENT:

times 256 db 0

Stack32UserSegLen equ $ - STACK32_USER_SEGMENT

TopOfUserStack32 equ Stack32UserSegLen - 1

首先通过反编译在call GetKernelDataSelector : 0跳转处设置断点,接下来对通用寄存器eip寄存器进行查看,由92a8到92af之间的差值为7,可以推断mov ax, UserData32Selector ; eip ==> 0x17,该处是返回地址。同时0x17会入栈,cs也会入栈,对寄存器进行查看,此时cs寄存器为0x0b,接下来对栈顶的6个字节进行查看,发现0x17与cs寄存器都入栈,再继续操作,触发异常,过程如图所示

c194a6e47a72a091b75a69c65ccaebe7.png

96eebf874e5b4bc93a96036546eced35.png

d9c14105460224d0418085a323a83e97.png

e092c07b218af858041f0f8b16b9bb83.png

小结

1.RPL是保证内核数据安全的关键要素之一

2.内核代码可通过追踪真实请求者特权级判断操作合法性

3.进行函数远调用时,真实请求者的选择子就会存储于栈中

4.通过提取真实特权级能够保证内核数据安全

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值