1. 程式人生 > >第21課 - 特權級與核心安全示例

第21課 - 特權級與核心安全示例

RPL的作用

    RPL必須存在,RPL是保證核心資料安全的關鍵要素之一;     RPL在核心程式碼中有決定性的作用,決不能取締!     本節程式碼示例中PrintString retf之前使用jmp $,否則返回將出現異常

竊取資料示例:計算機中的相似情形

    使用者程式想要訪問獲取作業系統核心中的私密資料!          破壞1使用呼叫門的可升級訪問特性,自定義呼叫門,通過猜測方式獲取其它選擇子和偏移寫入自定義呼叫門引數中,利用猜中的合適選擇子進入核心程式碼。

初步解決方案

    獲取段暫存器中RPL的值     判斷RPL的值是否為SA_RPL0(核心選擇子)         true    檢查通過,可繼續訪問資料         false    特權級較低,觸發異常     程式碼流程     

 

     小技巧
    mov ax, 0                  // 使用0選擇子     mov fs, ax                // 將段暫存器指向0位置處     mov byte [fs: 0], 0        // 往0位置處寫入資料OOPS
    

當前方案問題

    破壞2直接將使用者資料選擇子RPL修改為合適值(0)逃避1中的修補。

解決思路:追蹤真實的請求者

    在棧中獲取函式遠呼叫前CS暫存器的值(請求者)     從之前CS暫存器的值中獲取RPLcr(請求者特權級)     用RPLcr更新到資料緩衝區對應的段暫存器中     使用CheckRPL對段暫存器進行安全檢查     追蹤真實的請求者     

小結

    RPL是保證核心資料安全的關鍵要素之一     核心程式碼可通過 追蹤真實請求者特權級判斷操作合法性     但凡進行函式 遠呼叫,真實請求者的選擇子就會儲存於棧中(CS、IP入棧)             除錯技巧:   使用call指令時會將eip暫存器入棧儲存(遠呼叫還會儲存cs暫存器)                         ndisasm -b 32 -o 0x9000 loader > loader.txt 反彙編檢視call下一條指令相對call的偏移量                         入棧的eip暫存器值等於原有eip值+偏移量                         通過x /6bx ss:esp 檢視棧頂6個位元組的16進位制資料     通過提取真實特權級(RPL能夠保證核心資料安全

程式碼

// loader.asm
%include "inc.asm"                            ; 載入標頭檔案,一些常量、設定函式
 
org 0x9000                                    ; 記憶體載入地址
 
jmp ENTRY_SEGMENT                            ; 跳轉到ENTRY_SEGMENT入口處
 
[section .gdt]                                ; 全域性描述符表,部分段基址未知基址需使用時再調節(InitDescItem)
; GDT definition        8Byte 64bit
;                            "函式名"        段基址        段界限                        段屬性
GDT_ENTRY            :        Descriptor    0,            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            ; 全域性描述符起始地址,先設定為0
 
 
; GDT Selector            2Byte 16bit    TI:全域性、區域性    RPL:請求許可權級別
Code32Selector            equ    (0x0001 << 3) + SA_TIG + SA_RPL3    ; 0x0001==第二個選擇子
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]    ; 真實模式程式碼段(16bit)
[bits 16]        ; 使用16位編譯
ENTRY_SEGMENT:                                ; 16位保護模式入口段
    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        ; 初始化32位程式碼段、資料段、棧段描述符
    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        ; 初始化TSS任務段
    mov edi, TSS_DESC
 
    call InitDescItem
 
    
            ; initialize GDT pointer struct
    mov eax, 0                    ; 程式碼段地址左移4位
    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                    ; 通過0x92埠開啟A20地址線開關
    or al, 00000010b
    out 0x92, al
 
            ; 4. enter protect mode
    mov eax, cr0                ; 設定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        ;jmp dword Code32Selector : 0 ; 使用jmp跳轉到32位程式碼段選擇子的0偏移處
    push TopOfUserStack32
    push Code32Selector
    push 0
    retf                        ; 彈出2個棧,分別給IP、CS暫存器
    
    
; esi    --> code segment label
; edi    --> descriptor label
InitDescItem:                                ; 初始化描述符專案
    push eax
    
    mov eax, 0                        ; 程式碼段地址左移4位
    mov ax, cs
    shl eax, 4                        ; 實地址=段暫存器地址左移4位+偏移地址
    add eax, esi
    mov word [edi + 2], ax            ; 將段基址寫入描述符2個位元組(16位暫存器),低32位的16-31bit(偏移2位元組)
    shr eax, 16                        ; 移除eax實地址中已經寫入段基址的2位元組資料
    mov byte [edi + 4], al            ; 將段基址寫入描述符1個位元組(8位暫存器),高32位的0-7bit(偏移4+0=4位元組)
    mov byte [edi + 7], ah            ; 將段基址寫入描述符1個位元組(8位暫存器),高32位的24-31bit(偏移4+3=7位元組)
 
    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]                    ; TSS任務段104位元組,另可附加額外資訊
[bits 32]
TSS_SEGMENT:
        dd    0                        ; 保留前一個TSS段選擇子,由CPU填寫,高位填0
        dd    TopOfKernelStack32        ; 0特權級棧指標
        dd    KernelStack32Selector    ; 0特權級棧段選擇子,只能用低2個位元組,高位填0
        dd    0                        ; 1特權級
        dd    0                        ;
        dd    0                        ; 2特權級
        dd    0                        ;
        times 4 * 18 dd 0            ; 用於切換暫存器的值,由CPU填寫,共18個dd型別
        dw    0                        ; 最低位為除錯陷阱標誌T,其餘為0
        dw    $ - TSS_SEGMENT + 2        ; I/O Map Base Address
        db    0xFF                    ; 結束標記,屬於額外資訊
        
TSSLen    equ    $ - TSS_SEGMENT
 
 
 
[section .s32]    ; 32位程式碼段
[bits 32]        ; 使用32位編譯
CODE32_SEGMENT:                                ; 32位程式碼段資料
    mov ax, VideoSelector            ; 把視訊段選擇子放到gs全域性段暫存器
    mov gs, ax
 
    mov ax, UserData32Selector        ; 設定資料段地址
    mov es, ax
    
    mov di, UDAT_OFFSET
    
    call GetKernelDataSelector : 0    ; 使用呼叫門模擬惡意操作,拷貝核心資料至使用者空間
    
    mov ax, UserData32Selector        ; 入棧eip ==> eip+當前指令相對上面一條指令的偏移(0x10+7=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             ; 總偏移字元數,al太小超過255會溢位(3*80+16==3行16列)
    push dx                 ; 備份dx(dh),供迴圈呼叫避免丟失行號
    mov dh, 0               ; dx高位置0,確保dx=dl
    add ax, dx              ; 總偏移字元數(65536個字元)
    pop dx
    shl eax, 1                ; 每個字元佔2位元組
    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
    jmp $
    retf
    
Code32SegLen    equ $ - CODE32_SEGMENT
 
 
[section .func]
[bits 32]
FUNCTION_SEGMENT:
 
;es:di --> data buffer
GetKernelDataFunc:            ; 呼叫門,利用此呼叫門進入核心拷貝資料
    mov cx, [esp + 4]        ; 重寫RPL避免使用高許可權選擇子進入核心程式
    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            ; 檢查RPL避免猜中選擇子後進入核心,ax==原有RPL
    
    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            ; 如果是RPL3則立即退出,否則進入異常
    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:                            ; 32位棧段定義
    times 256 * 4 db 0
 
Stack32KernelSegLen equ $ - STACK32_KERNEL_SEGMENT
TopOfKernelStack32 equ Stack32KernelSegLen - 1
 
[section .usg]
[bits 32]
STACK32_USER_SEGMENT:                            ; 32位棧段定義
    times 256 * 4 db 0
 
Stack32UserSegLen equ $ - STACK32_USER_SEGMENT
TopOfUserStack32 equ Stack32UserSegLen - 1