将博客的一篇文章转过来了,论坛小透明一直没敢发过帖子,大佬太多了,如果有错误,还请指正orz
0x00 seccomp沙箱机制
seccomp 是 Linux 内核提供的一种应用程序沙箱机制,seccomp 通过只允许应用程序调用 exit(), sigreturn(), read() 和 write() 四种系统调用来达到沙箱的效果。如果应用程序调用了除了这四种之外的系统调用, kernel 会向进程发送 SIGKILL 信号。
seccomp 很难在实际中得到推广,因为限制实在是太多了,Linus 本人也对它的应用持怀疑的态度,直到出现了 seccomp-bpf。seccomp-bpf 是 seccomp 的一个扩展,它可以通过配置来允许应用程序调用其他的系统调用。chrome 中第一个应用 seccomp-bpf 的场景是把 Flash 放到了沙箱里运行(实在是不放心),后续也把 render 的过程放到了沙箱里。
0x01 BPF (Berkeley Packets Filter)
BPF是类Unix系统上针对数据链路层的原生接口,提供数据链路层封包的收发,BPF也支持封包过滤,其过滤规则在linux中应用到了很多地方。xt_bpf对netfilter,cls_bpf在内核的qdisk层,SECCOMP-BPF,以及一系列其他地方例如:team driver、PTP code等BPF都被用到。
BPF定义了一个伪机器。这个伪机器可以执行代码,包含一个32位的累加器A,一个32位的索引寄存器X,一个16 x 32位的内存和一个隐含的程序计数器,具有有赋值、算术、跳转指令。
一条指令由一个定义好的结构体sock_filter表示,形式如下:
与真正的机器代码很相似,若干个这样的结构体组成的结构体数组,就成为BPF的指令序列。
为了方便编写规则,BPF的设计者定义了两个指令宏来完成规则的编写(/usr/include/linux/bpf_common.h)
而BPF的过滤规则就是由这两个指令宏组成的指令序列完成的,这个序列是一个结构体数组,下面就是一个过滤execve系统调用的过滤规则:
这两个指令宏展开后,其实也都是赋了值的sock_filter结构体。他只是封装了一下,方便使用。
BPF_STMT和BPF_JUMP的操作指令由以下组成:(操作数为第二个参数)
0x02 prctl函数调用
prctl就是在c程序中可以使用BPF过滤规则操作进程的一个函数调用。函数原型如下
option有很多,这里我只关注PR_SET_NO_NEW_PRIVS(38)和PR_SET_SECCOMP
PR_SET_NO_NEW_PRIVS(38) (since Linux 3.5)
PR_SET_NO_NEW_PRIVS的第二个参数若设置为1,那么程序线程将不能通过执行execve系统调用来获得提权,该选项只对execve这个系统调用有效。意思就是若你使用syscall(59,'/bin/sh',null,null)或system("/bin/sh")(内部还是系统调用execve)获得的线程shell,用户组依然是之前的用户组,且不能获得更高权限。
PR_SET_SECCOMP(22)
如果参数2为SECCOMP_MODE_STRICT(1),则只允许调用read,write,_exit(not exit_group),sigreturn这几个syscall.如果参数2为SECCOMP_MODE_FILTER(2),则为过滤模式,其中对syscall的限制通过参数3的结构体,来自定义过滤规则。
&prog形式如下:
这个filter就是指向包含struct sock_filter的结构体数组指针,比如上述的struct sock_filter filter[]。
通过使用ptrcl禁用execve系统调用
0x03 seccomp库函数(和2019ByteCTF VIP复现无关)
这个库可以提供一些函数实现prctl类似的效果,库中封装了一些函数,可以不用了解BPF规则而实现过滤。
但是在c程序中使用它,需要装一些库文件
通过使用该库的函数实现禁用execve系统调用
scmp_filter_ctx是过滤器的结构体
seccomp_init对结构体进行初始化,若参数为SCMP_ACT_ALLOW,则过滤为黑名单模式;若为SCMP_ACT_KILL,则为白名单模式,即没有匹配到规则的系统调用都会杀死进程,默认不允许所有的syscall。
def_action为
seccomp_rule_add是添加一条规则
arg_cnt表明是否需要对对应系统调用的参数做出限制以及指示做出限制的个数,如果仅仅需要允许或者禁止所有某个系统调用,arg_cnt直接传入0即可,seccomp_rule_add(ctx, SCMP_ACT_KILL, SCMP_SYS(execve), 0)即禁用execve,不管其参数如何。
如果考虑到更高的自定义,需要先去了解一下具体系统调用的参数情况,然后再利用SCMP_AX及SCMP_CMP_XX类的宏定义做一些过滤。以read为例,read函数原型
限制从标准输入stdin读入的字节数不能为100。
seccomp_load是应用过滤,seccomp_reset是解除过滤。
0x04 2019ByteCTF VIP
4.edit存在堆溢出,可以覆盖任意大小数据,但是内容不可控
6.become vip函数存在栈溢出,buf大小为0x20个字节
但是可以输入0x50字节数据
该函数中存在seccomp的系统调用过滤
v1就是上面说的sock_fprog结构体,那么v2就是指向BPF结构体filter的指针
查看下面的赋值
规则条数为11个,filter指针指向v4,buf可以溢出到v4
思路
覆盖BPF过滤规则,让open的返回值为0,那么在edit功能中,read(fd, a1, a2),就变成了read(0, a1, a2),从而堆溢出内容可控,然后就常规的getshell思路。
问题与解决
open函数使用的是openat系统调用
使用seccomp-tools生成规则,一条规则是8个字节,可以溢出48个字节,根据seccomp-tools使用说明,编写规则
规则如下:
生成16进制字符串
在后续的利用中,可以正常向堆中写入可控的数据,但是无法getshell,报错:
检查system调用参数是对的,在huai的帮助下,怀疑可能是system函数调用了open函数,因为过滤规则影响了open的正常使用。写一个只有system("/bin/sh")的c程序,查看调用过程发现有调用openat的过程。
重新编写过滤规则,限制打开的文件,进行过滤,不影响system的正常使用。
可以看到在openat的原型中,第二个参数是要打开的文件名字符串指针,所以要限制第二个参数,程序中0x40207e存放的是/dev/urandom的地址。规则如下
生成十六进制后覆盖规则,可以正常getshell。
EXP
0x05 REFERENCE
[招生]科锐逆向工程师培训(2024年11月15日实地,远程教学同时开班, 第51期)
最后于 2020-3-21 17:07
被okchenshuo编辑
,原因: 上传附近