如何增強 Linux 核心中的訪問控制安全
背景
前段時間,我們的專案組在幫客戶解決一些作業系統安全領域的問題,涉及到windows,Linux,macOS三大作業系統平臺。無論什麼作業系統,本質上都是一個軟體,任何軟體在一開始設計的時候,都不能百分之百的滿足人們的需求,所以作業系統也是一樣,為了儘可能的滿足人們需求,不得不提供一些供人們定製作業系統的機制。當然除了官方提供的一些機制,也有一些黑魔法,這些黑魔法不被推薦使用,但是有時候面對具體的業務場景,可以作為一個參考的思路。
Linux中常見的攔截過濾
本文著重介紹Linux平臺上常見的攔截:
- 使用者態動態庫攔截。
- 核心態系統呼叫攔截。
- 堆疊式檔案系統攔截。
- inline hook攔截。
- LSM(Linux Security Modules)
動態庫劫持
Linux上的動態庫劫持主要是基於LD_ PRELOAD環境變數,這個環境變數的主要作用是改變動態庫的載入順序,讓使用者有選擇的載入不同動態庫中的相同函式。但是使用不當就會引起嚴重的安全問題,我們可以通過它在主程式和動態連線庫中載入別的動態函式,這就給我們提供了一個機會,向別人的程式注入惡意的程式碼。
假設有以下使用者名稱密碼驗證的函式:
#include <stdio.h> #include <string.h> #include <stdlib.h> int main(int argc, char **argv) { char passwd[] = "password"; if (argc < 2) { printf("Invalid argc!\n"); return; } if (!strcmp(passwd, argv[1])) { printf("Correct Password!\n"); return; } printf("Invalid Password!\n"); }
我們再寫一段hookStrcmp的程式,讓這個比較永遠正確。
#include <stdio.h> int strcmp(const char *s1, const char *s2) { /* 永遠返回0,表示兩個字串相等 */ return 0; }
依次執行以下命令,就會使我們的hook程式先執行。
gcc -Wall -fPIC -shared -o hookStrcmp.so hookStrcmp.c export LD_PRELOAD=”./hookStrcmp.so
結果會發現,我們自己寫的strcmp函式優先被呼叫了。這是一個最簡單的劫持 ,但是如果劫持了類似於geteuid/getuid/getgid,讓其返回0,就相當於暴露了root許可權。所以為了安全起見,一般將LD_ PRELOAD環境變數禁用掉。
Linux系統呼叫劫持
最近發現在4.4.0的核心中有513多個系統呼叫(很多都沒用過),系統呼叫劫持的目的是改變系統中原有的系統呼叫,用我們自己的程式替換原有的系統呼叫。Linux核心中所有的系統呼叫都是放在一個叫做sys_ call _table的核心陣列中,陣列的值就表示這個系統呼叫服務程式的入口地址。整個系統呼叫的流程如下:
當使用者態發起一個系統呼叫時,會通過80軟中斷進入到syscall hander,進而進入全域性的系統呼叫表sys_ call _table去查詢具體的系統呼叫,那麼如果我們將這個陣列中的地址改成我們自己的程式地址,就可以實現系統呼叫劫持。但是核心為了安全,對這種操作做了一些限制:
- sys_ call _table的符號沒有匯出,不能直接獲取。
- sys_ call _table所在的記憶體頁是隻讀屬性的,無法直接進行修改。
對於以上兩個問題,解決方案如下(方法不止一種):
- 獲取sys call table的地址 :grep sys _ call _table /boot/System.map-uname -r
- 控制頁表只讀屬性是由CR0暫存器的WP位控制的,只要將這個位清零就可以對只讀頁表進行修改。
/* make the page writable */ int make_rw(unsigned long address) { unsigned int level; pte_t *pte = lookup_address(address, &level);//查詢虛擬地址所在的頁表地址 pte->pte |= _PAGE_RW;//設定頁表讀寫屬性 return 0; }
/* make the page write protected */ int make_ro(unsigned long address) { unsigned int level; pte_t *pte = lookup_address(address, &level); pte->pte &= ~_PAGE_RW;//設定只讀屬性 return 0; }
開始替換系統呼叫
本文實現的是對 ls這個命令對應的系統呼叫,系統呼叫號是 _ NR _getdents。
static int syscall_init_module(void) { orig_getdents = sys_call_table[__NR_getdents]; make_rw((unsigned long)sys_call_table); //修改頁屬性 sys_call_table[__NR_getdents] = (unsigned long *)hacked_getdents; //設定新的系統呼叫地址 make_ro((unsigned long)sys_call_table); return 0; }
恢復原狀
static void syscall_cleanup_module(void) { printk(KERN_ALERT "Module syscall unloaded.\n"); make_rw((unsigned long)sys_call_table); sys_call_table[__NR_getdents] = (unsigned long *)orig_getdents; make_ro((unsigned long)sys_call_table); }
使用Makefile編譯,insmod插入核心模組後,再執行ls時,就會進入到我們的系統呼叫,我們可以在hook程式碼中刪掉某些檔案,ls就不會顯示這些檔案,但是這些檔案還是存在的。
堆疊式檔案系統
Linux通過vfs虛擬檔案系統來統一抽象具體的磁碟檔案系統,從上到下的IO棧形成了一個堆疊式。通過對核心原始碼的分析,以一次讀操作為例,從上到下所執行的流程如下:
核心中採用了很多c語言形式的物件導向,也就是函式指標的形式,例如read是vfs提供使用者的介面,具體底下呼叫的是ext2的read操作。我們只要實現VFS提供的各種介面,就可以實現一個堆疊式檔案系統。Linux核心中已經整合了一些堆疊式檔案系統,例如Ubuntu在安裝時會提醒你是否需要加密home目錄,其實就是一個堆疊式的加密檔案系統(eCryptfs),原理如下:
實現了一個堆疊式檔案系統,相當於所有的讀寫操作都會進入到我們的檔案系統,可以拿到所有的資料,就可以進行做一些攔截過濾。
以下是我實現的一個最簡單的堆疊式檔案系統,實現了最簡單的開啟、讀寫檔案,麻雀雖小但五臟俱全。
https://github.com/wangzhangjun/wzjfs
inline hook
我們知道核心中的函式不可能把所有功能都在這個函式中全部實現,它必定要呼叫它的下層函式。如果這個下層函式可以得到我們想要的過濾資訊內容,就可以把下層函式在上層函式中的offset替換成新的函式的offset,這樣上層函式呼叫下層函式時,就會跳到新的函式中,在新的函式中做過濾和劫持內容的工作。所以從原理上來說,inline hook可以想hook哪裡就hook哪裡。
inline hook 有兩個重要的問題:
- 如何定位hook點。
- 如何注入hook函式入口。
對於第一個問題:
需要有一點的核心原始碼經驗,比如說對於read操作,原始碼如下:
在這裡當發起read系統呼叫後,就會進入到sys read,在sys read中會呼叫vfs read函式,在vfs read的引數中正好有我們需要過濾的資訊,那麼就可以把vfs_ read當做一個hook點。
對於第二個問題:
如何Hook?這裡介紹兩種方式:
第一種方式:直接進行二進位制替換,將call指令的運算元替換為hook函式的地址。
第二種方式:Linux核心提供的kprobes機制。
其原理是在hook點注入int 3(x86)的機器碼,讓cpu執行到這裡的時候會觸發sig trap訊號,然後將使用者自定義的hook函式注入到sig trap的回撥函式中,達到觸發hook函式的目的。這個其實也是偵錯程式的原理。
LSM
LSM是Linux Secrity Module的簡稱,即linux安全模組。是一種通用的Linux安全框架,具有效率高,簡單易用等特點。原理如下:
LSM在核心中做了以下工作:
- 在特定的核心資料結構中加入安全域。
- 在核心原始碼中不同的關鍵點插入對安全鉤子函式的呼叫。
- 加入一個通用的安全系統呼叫。
- 提供了函式允許核心模組註冊為安全模組或者登出。
- 將capabilities邏輯的大部分移植為一個可選的安全模組,具有可擴充套件性。
適用場景
對於以上幾種Hook方式,有其不同的應用場景。
- 動態庫劫持不太完全,劫持的資訊有可能滿足不了我們的需求,還有可能別人在你之前劫持了,一旦禁用LD_ PRELOAD就失效了。
- 系統呼叫劫持,劫持的資訊有可能滿足不了我們的需求,例如不能獲取struct file結構體,不能獲取檔案的絕對路徑等。
- 堆疊式檔案系統,依賴於Mount,可能需要重啟系統。
- inline hook,靈活性高,隨意Hook,即時生效無需重啟,但是在不同核心版本之間通用性差,一旦某些函式發生了變化,Hook失效。
- LSM,在早期的核心中,只能允許一個LSM核心模組載入,例如載入了SELinux,就不能載入其他的LSM模組,在最新的核心版本中不存在這個問題。
總結
篇幅有限,本文只是介紹了Linux上的攔截技術,後續有機會可以一起探討windows和macOS上的攔截技術。事實上類似的審計HOOK放到任何一個系統中都是剛需,不只是kernel,我們可以看到越來越多的vm和runtime甚至包括很多web元件、前端應用都提供了更靈活的hook方式,這是透明化和實時性兩個安全大趨勢下最常見的解決方案。
相關文章
- 基於linux下的selinux強制訪問控制Linux
- linux安全篇:禁止頻繁訪問的ip訪問nginxLinuxNginx
- linux 核心安全增強 — stack canaryLinux
- 洞見RSA 2021|如何設計安全的控制系統遠端訪問
- linux 核心安全增強(一)— stack canaryLinux
- 類的訪問控制
- 資料安全合規需要從基於角色的訪問控制邁向基於屬性的訪問控制
- SpringBoot框架整合SpringSecurity實現安全訪問控制Spring Boot框架Gse
- Swift 中的訪問控制Swift
- Flask——訪問控制Flask
- Mongodb訪問控制MongoDB
- 如何在Mac上訪問任務控制Mac
- linux tomcat 開通443 (用https安全訪問)LinuxTomcatHTTP
- 如何在Linux中如何限制對su命令的訪問Linux
- Swift的訪問控制講解Swift
- CDN 訪問控制的那些事
- Nginx 對訪問量的控制Nginx
- 從mimikatz學習Windows安全之訪問控制模型(二)Windows模型
- 從mimikatz學習Windows安全之訪問控制模型(一)Windows模型
- openGauss 訪問控制模型模型
- ABAC訪問控制模型模型
- jCasbin: 強大的訪問控制、許可權管理框架,支援 ACL, RBAC, ABAC框架
- Linux核心中斷Linux
- C++ 訪問說明符詳解:封裝資料,控制訪問,提升安全性C++封裝
- 幽默:資料技術本身真的能控制訪問安全? - ardalis
- 如何檢視Linux 當前訪問ipLinux
- 訪問控制中斷的風險
- DigiCert和Thales如何增強Hyperledger的網路安全性
- IOS - ACL (訪問控制列表)iOS
- 006.Nginx訪問控制Nginx
- HTTP之訪問控制「CORS」HTTPCORS
- Vue前端訪問控制方案Vue前端
- Ubuntu 增加埠訪問控制Ubuntu
- weblogic控制檯訪問慢問題Web
- 如何訪問Windows 10隱藏的一個控制皮膚功能Windows
- 如何安全地訪問網際網路
- Linux之facl----設定檔案訪問控制列表(詳解)Linux
- RBAC-基於角色的訪問控制