这篇文章很早就写完了字数有点多之前懒得抄到博客上去。最近回头再看相当于温习一遍也有了很多新的收获。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 39prctl函数原型int prctl(int option,unsigned long argv2,unsigned long argv3,unsigned long argv4unsigned 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的时候表示允许的系统调用有readwriteexit和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下是ebxexc,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跳转操作可以和JGEJGTJEQJSET一起表示有条件的跳转和BPF_JA一起表示没有条件的跳转。#includestdio.h #includefcntl.h #includeunistd.h #includestddef.h #includelinux/seccomp.h #includelinux/filter.h #includesys/prctl.h #includelinux/bpf.h //off和imm都是有符号类型编码信息定义在内核头文件linux/bpf.h #includesys/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), // 比较是否为59execve 的系统调用号是就跳过下一行如果不是就执行下一行第三个参数表示执行正确的指令跳转第四个参数表示执行错误的指令跳转 BPF_JUMP(BPF_JMP|BPF_JGE, 0, 1, 0), // BPF_STMP(BPF_RETBPF_K,SECCOMP_RET_KILL), // 杀死一个进程 // BPF_STMP(BPF_RETBPF_K,SECCOMP_RET_TRACE), // 父进程追踪子进程具体没太搞清楚 BPF_STMT(BPF_RETBPF_K,SECCOMP_RET_ERRNO), // 异常处理 BPF_STMT(BPF_RETBPF_K,SECCOMP_RET_ALLOW), // 这里表示系统调用如果正常允许系统调用 }; struct sock_fprog prog{ .lensizeof(filter)/sizeof(sock_filter[0]), .filterfilter, }; 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_RETBPF_K,SECCOMP_RET_ERRNO)的异常处理。其实如果我们要直接杀死这个进程的话BPF_STMP(BPF_RETBPF_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 ./可执行文件名.bpfasm运用这个模块我们可以写一个asm脚本然后执行seccomp-tools asm [asm脚本名]4例题pwnable.tw —— orworw是一种方法在系统调用被严格禁用的情况下代表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即可这里写一个汇编脚本。然后用pwntools写一个脚本吧shellcode发送过去就好了自己写一遍啊2333有借鉴过其他师傅的博客这里一并列出linux沙箱之seccompseccomp学习笔记关于seccomp沙箱中的bpf规则写在最后prctl函数是最原始的建立沙箱规则的函数。后面出现的seccomp.h对prctl函数进行了封装有了seccomp_init()seccomp_rule_add()seccomp_load()函数来进行沙箱规则的设置。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 (ANDd 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导入规则之后添加的规则才能执行函数原型如下从prctl函数开始学习沙箱规则 - Riv4ille - 博客园