第21課 - 特權級與核心安全示例
阿新 • • 發佈:2018-12-02
RPL的作用
RPL必須存在,RPL是保證核心資料安全的關鍵要素之一; RPL在核心程式碼中有決定性的作用,決不能取締! 本節程式碼示例中PrintString retf之前使用jmp $,否則返回將出現異常竊取資料示例:計算機中的相似情形
使用者程式想要訪問獲取作業系統核心中的私密資料! 破壞1: 使用呼叫門的可升級訪問特性,自定義呼叫門,通過猜測方式獲取其它選擇子和偏移寫入自定義呼叫門引數中,利用猜中的合適選擇子進入核心程式碼。初步解決方案
獲取段暫存器中RPL的值 判斷RPL的值是否為SA_RPL0(核心選擇子) true 檢查通過,可繼續訪問資料 false 特權級較低,觸發異常 程式碼流程小技巧
當前方案問題
破壞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