题目名:smallbox解题数:52题目描述:你能从这个小盒子里逃出来吗?知识点:沙箱绕过(只允许ptrace)ptrace 是 Linux 系统中一个非常重要的系统调用,它允许一个进程观察和控制另一个进程的执行,并可以检查和修改被跟踪进程的内存和寄存器。这个系统调用是实现调试器(如 gdb)和Hook的核心机制,函数原型:逆向分析非常简单,找到main函数:程序先通过mmap函数申请地址0xDEADC0DE000LL处的0x1000大小的空间,并赋予rwx权限。然后执行fork函数创建子进程,并让子进程进入while(1);死循环。父进程继续执行,读取我们的输入到0xDEADC0DE000LL内存,加载沙箱,最后将我们的输入作为汇编指令执行。我们使用seccomp-tools工具查看程序的沙箱保护情况:程序只允许我们执行ptrace系统调用,这其实给我们提示了这道题目的解题方法。我们输入的汇编指令需要多次通过ptrace系统调用拿下Shell:通过上述操作,子进程最终会执行我们写入的shellcode,具体步骤如下所示。通过汇编指令获取子进程的pid。通过fork函数获取的子进程pid会被存储在变量pid(rbp-0xC)中,我们将其存储到rbx寄存器中:可以使用gdb调试,查看rbx寄存器的内容是否被正确修改:可以看到,rbx寄存器的值成功被修改为子进程的pid。使用PTRACE_ATTACH附加子进程。对应的汇编代码:对应的脚本:可以gdb调试查看是否附加成功:返回值被存储在rax寄存器,为0代表附加成功。通过PTRACE_POKETEXT将shellcode写入到子进程。在64位系统中,每次只能写入8字节的数据。因此,我们需要先将shellcode转换为8字节为一组的数据。具体做法如下所示:然后,我们分3次写入到子进程的内存空间中,对应的汇编代码:对应的脚本:由于子进程已经被我们父进程通过ptrace系统调用附加,我们无法再通过gdb进行附加到子进程调试。**一切皆文件(Everything is a file)**是 Linux 操作系统最核心、最优雅的设计理念之一。为了确保正确写入shellcode到子进程的内存空间中,我们可以直接查看/proc/[pid]/mem文件:先通过gdb调试父进程,执行完所有写入的汇编后,找到rbx寄存器保存的子进程pid:然后使用dd命令查看子进程的内存空间:可以发现,我们已经成功将shellcode写入到子进程的内存空间中。通过PTRACE_GETREGS获取子进程的寄存器的值。需要注意的是,第四个参数是主进程的内存空间地址,即存储regs的内存位置。对应的汇编代码:对应的脚本:获取到的结构为(x86_64架构下):同样,我们可以动态调试查看是否执行成功。若成功,rax寄存器返回值为0,并且[rbp-0x200]存储了子进程的user_regs_struct。修改RIP寄存器指向我们的shellcode。根据user_regs_struct结构体,&user_regs_struct + 0x80位置存储了子进程的RIP指针的值。我们使用汇编指令将其修改为写入的shellcode的地址:通过PTRACE_SETREGS将修改后的寄存器的值写入子进程。最后,将修改后的RIP指针的值写回到子进程中,方法同PTRACE_GETREGS,不再赘述。对应的汇编:对应的脚本:同样,我们可以动态调试查看是否执行成功。若成功,rax寄存器返回值为0。通过PTRACE_DETACH让子进程脱离附加状态,继续自主运行。对应的汇编代码:对应的脚本:使用gdb逐行调试后,发现子进程成功执行shell:但是,打远程靶机会发现打不通。经过反复调试排查发现,在本地没有gdb调试的情况下,也无法拿到shell权限。原因如下:解决方法:在ptrace_attch的后面利用循环增加延时操作:在汇编代码的末尾增加jmp $让主进程进入死循环,避免子进程随着主进程结束。#include <sys/ptrace.h>
---
来源: 看雪论坛
原文链接: https://bbs.kanxue.com/thread-287850.htm
[原创]NepCTF 2025 Pwn赛题smallbox解析:如何靠一条ptrace指令逃出沙箱?
149 浏览
3 回复
感谢分享~~~写的挺详细。后续同一场比赛的WP,比较简单的题目合在一起发更容易评优秀/精华。
winmt
感谢分享~~~写的挺详细。后续同一场比赛的WP,比较简单的题目合在一起发更容易评优秀/精华。
感谢,下次比赛整理成一篇发。
感谢分享~~~写的挺详细。后续同一场比赛的WP,比较简单的题目合在一起发更容易评优秀/精华。
感谢,下次比赛整理成一篇发。
能私聊吗