0%

栈溢出绕过canary

总结绕过canary保护机制的几种方法

canary原意为金丝雀,以前下井工人通常会带一只金丝雀,当瓦斯气体浓度超标后金丝雀会死亡,以此起到预警作用,linux里防止栈溢出的canary机制也是类似
canary是一个介于栈基址ebp和变量之间的数值,程序运行结束时会检查canary的值是否正确,错误则会终止程序运行并打印错误信息
具体在栈中的结构如下

变量1
变量2
canary
ebp
eip

(canary不一定与与基址相邻)
因此如果在开启canary的程序中进行栈溢出必定会污染canary的值,在面对有canary保护的程序时必须想办法绕过或利用canary机制
依然从那个简单的例子开始

test.c

分别用 不使用canary和使用canary保护机制编译一下

1
2
gcc -m32 -fno-stack-protector test.c -o test1
gcc -m32 -fstack-protector test.c -o test2

反编译一下看看两种编译方式的区别

1
2
objdump -d test1
objdump -d test2

后者在函数开始时:

1
2
3
mov    %gs:0x14,%ecx  ;将gs:0x14的值给ecx
mov %ecx,-0xc(%ebp) ;将ecx中的值放在ebp-0xc的位置
xor %ecx,%ecx ;清空ecx

函数快要结束时:

1
2
3
4
mov    -0xc(%ebp),%eax  ;从ebp-0xc位置取值给eax
xor %gs:0x14,%eax ;将eax与gs:0x14异或
je 120a <vul+0x4d> ;如果为0程序正常结束,这里120a就是下一步的地址
call 12c0 <__stack_chk_fail_local> ;不为0这说明canary值被修改,调用__stack_chk_fail函数处理(终止程序)

在有canary机制的程序里溢出看下结果

成功监测到了栈溢出,终止了程序

绕过canary机制
多进程程序canary爆破
每次运行程序时,canary的值都是不一样的,因此爆破canary看似不太可能,但在特定情况下还是可行的。
在程序使用多个子进程时,子进程的崩溃不会影响到父进程,但同一个程序里的canary是一样的,所以爆破就有了可行性

canary_brute.c

1
gcc -m32 -fstack-protector -no-pie canary_brute.c -o canary_brute.

程序会反复fork子进程调用vul函数,所以爆破canary存在可行性(主进程不会因栈溢出触发保护函数而退出)。
在32位程序中canary占4字节,且为了防止canary的值被连带泄露,canary的最高位为’\x00’。

栈的结构如下

爆破脚本

实战练习
2017 湖湘杯Pwn100
做法还是爆破canary,爆破出来后结合之前ret2libc中的内容,直接上脚本了

主动触发栈溢出保护
如果程序结束时程序检查canary的值被修改会调到_stack_chk_fail,再看下_stack_chk_fail的源码

1
2
3
4
5
6
7
8
9
10
11
void __attribute__ ((noreturn)) __stack_chk_fail (void)
{
__fortify_fail ("stack smashing detected");
}
void __attribute__ ((noreturn)) internal_function __fortify_fail (const char *msg)
{
/* The loop is added only to keep gcc happy. */
while (1)
__libc_message (2, "*** %s ***: %s terminated\n",
msg, __libc_argv[0] ?: "<unknown>");
}

程序终止并把argv[0]中的东西打印出来,SSP leak(Stack Smashing Protect leak)技术就是利用canary机制,将我们需要的内容放入argv[0]中,当栈溢出并最终打印argv[0]时,我们所需的内容便会打印出来,虽然很巧妙,但是局限性也很大,只能读取程序中特定的内容,更不要说getshell了
实战练习
jarvisoj smashes

劫持_stack_chk_fail

顾名思义,劫持_stack_chk_fail函数,即覆写_stack_chk_fail函数的在got表上的地址,也是故意触发canary机制。

部分内容参考资料ichunqiu月刊第六期